@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Allow MFA providers to be deprecated or disabled

Summary: Ref T13222. Providers can now be deprecated (existing factors still work, but users can't add new factors for the provider) or disabled (factors stop working, also can't add new ones).

Test Plan:
- Enabled, deprecated, and disabled some providers.
- Viewed provider detail, provider list.
- Viewed MFA settings list.
- Verified that I'm prompted for enabled + deprecated only at gates.
- Tried to disable final provider, got an error.
- Hit the MFA setup gate by enabling "Require MFA" with no providers, got a more useful message.
- Immediately forced a user to the "MFA Setup Gate" by disabling their only active provider with another provider enabled ("We no longer support TOTP, you HAVE to finish Duo enrollment to continue starting Monday.").

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D20031

+406 -34
+4
src/__phutil_library_map__.php
··· 2234 2234 'PhabricatorAuthFactorProviderListController' => 'applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php', 2235 2235 'PhabricatorAuthFactorProviderNameTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderNameTransaction.php', 2236 2236 'PhabricatorAuthFactorProviderQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderQuery.php', 2237 + 'PhabricatorAuthFactorProviderStatus' => 'applications/auth/constants/PhabricatorAuthFactorProviderStatus.php', 2238 + 'PhabricatorAuthFactorProviderStatusTransaction' => 'applications/auth/xaction/PhabricatorAuthFactorProviderStatusTransaction.php', 2237 2239 'PhabricatorAuthFactorProviderTransaction' => 'applications/auth/storage/PhabricatorAuthFactorProviderTransaction.php', 2238 2240 'PhabricatorAuthFactorProviderTransactionQuery' => 'applications/auth/query/PhabricatorAuthFactorProviderTransactionQuery.php', 2239 2241 'PhabricatorAuthFactorProviderTransactionType' => 'applications/auth/xaction/PhabricatorAuthFactorProviderTransactionType.php', ··· 7961 7963 'PhabricatorAuthFactorProviderListController' => 'PhabricatorAuthProviderController', 7962 7964 'PhabricatorAuthFactorProviderNameTransaction' => 'PhabricatorAuthFactorProviderTransactionType', 7963 7965 'PhabricatorAuthFactorProviderQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 7966 + 'PhabricatorAuthFactorProviderStatus' => 'Phobject', 7967 + 'PhabricatorAuthFactorProviderStatusTransaction' => 'PhabricatorAuthFactorProviderTransactionType', 7964 7968 'PhabricatorAuthFactorProviderTransaction' => 'PhabricatorModularTransaction', 7965 7969 'PhabricatorAuthFactorProviderTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 7966 7970 'PhabricatorAuthFactorProviderTransactionType' => 'PhabricatorModularTransactionType',
+103
src/applications/auth/constants/PhabricatorAuthFactorProviderStatus.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthFactorProviderStatus 4 + extends Phobject { 5 + 6 + private $key; 7 + private $spec = array(); 8 + 9 + const STATUS_ACTIVE = 'active'; 10 + const STATUS_DEPRECATED = 'deprecated'; 11 + const STATUS_DISABLED = 'disabled'; 12 + 13 + public static function newForStatus($status) { 14 + $result = new self(); 15 + 16 + $result->key = $status; 17 + $result->spec = self::newSpecification($status); 18 + 19 + return $result; 20 + } 21 + 22 + public function getName() { 23 + return idx($this->spec, 'name', $this->key); 24 + } 25 + 26 + public function getStatusHeaderIcon() { 27 + return idx($this->spec, 'header.icon'); 28 + } 29 + 30 + public function getStatusHeaderColor() { 31 + return idx($this->spec, 'header.color'); 32 + } 33 + 34 + public function isActive() { 35 + return ($this->key === self::STATUS_ACTIVE); 36 + } 37 + 38 + public function getListIcon() { 39 + return idx($this->spec, 'list.icon'); 40 + } 41 + 42 + public function getListColor() { 43 + return idx($this->spec, 'list.color'); 44 + } 45 + 46 + public function getFactorIcon() { 47 + return idx($this->spec, 'factor.icon'); 48 + } 49 + 50 + public function getFactorColor() { 51 + return idx($this->spec, 'factor.color'); 52 + } 53 + 54 + public function getOrder() { 55 + return idx($this->spec, 'order', 0); 56 + } 57 + 58 + public static function getMap() { 59 + $specs = self::newSpecifications(); 60 + return ipull($specs, 'name'); 61 + } 62 + 63 + private static function newSpecification($key) { 64 + $specs = self::newSpecifications(); 65 + return idx($specs, $key, array()); 66 + } 67 + 68 + private static function newSpecifications() { 69 + return array( 70 + self::STATUS_ACTIVE => array( 71 + 'name' => pht('Active'), 72 + 'header.icon' => 'fa-check', 73 + 'header.color' => null, 74 + 'list.icon' => null, 75 + 'list.color' => null, 76 + 'factor.icon' => 'fa-check', 77 + 'factor.color' => 'green', 78 + 'order' => 1, 79 + ), 80 + self::STATUS_DEPRECATED => array( 81 + 'name' => pht('Deprecated'), 82 + 'header.icon' => 'fa-ban', 83 + 'header.color' => 'indigo', 84 + 'list.icon' => 'fa-ban', 85 + 'list.color' => 'indigo', 86 + 'factor.icon' => 'fa-ban', 87 + 'factor.color' => 'indigo', 88 + 'order' => 2, 89 + ), 90 + self::STATUS_DISABLED => array( 91 + 'name' => pht('Disabled'), 92 + 'header.icon' => 'fa-times', 93 + 'header.color' => 'red', 94 + 'list.icon' => 'fa-times', 95 + 'list.color' => 'red', 96 + 'factor.icon' => 'fa-times', 97 + 'factor.color' => 'grey', 98 + 'order' => 3, 99 + ), 100 + ); 101 + } 102 + 103 + }
+35 -1
src/applications/auth/controller/PhabricatorAuthNeedsMultiFactorController.php
··· 197 197 ->addCancelButton('/', pht('Continue')); 198 198 } 199 199 200 + $views = array(); 201 + 200 202 $messages = array(); 201 203 202 204 $messages[] = pht( ··· 210 212 ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 211 213 ->setErrors($messages); 212 214 213 - return $view; 215 + $views[] = $view; 216 + 217 + 218 + $providers = id(new PhabricatorAuthFactorProviderQuery()) 219 + ->setViewer($viewer) 220 + ->withStatuses( 221 + array( 222 + PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE, 223 + )) 224 + ->execute(); 225 + if (!$providers) { 226 + $messages = array(); 227 + 228 + $required_key = 'security.require-multi-factor-auth'; 229 + 230 + $messages[] = pht( 231 + 'This install has the configuration option "%s" enabled, but does '. 232 + 'not have any active multifactor providers configured. This means '. 233 + 'you are required to add MFA, but are also prevented from doing so. '. 234 + 'An administrator must disable "%s" or enable an MFA provider to '. 235 + 'allow you to continue.', 236 + $required_key, 237 + $required_key); 238 + 239 + $view = id(new PHUIInfoView()) 240 + ->setTitle(pht('Multi-Factor Authentication is Misconfigured')) 241 + ->setSeverity(PHUIInfoView::SEVERITY_ERROR) 242 + ->setErrors($messages); 243 + 244 + $views[] = $view; 245 + } 246 + 247 + return $views; 214 248 } 215 249 216 250 }
+10
src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderListController.php
··· 20 20 ->setHeader($provider->getDisplayName()) 21 21 ->setHref($provider->getURI()); 22 22 23 + $status = $provider->newStatus(); 24 + 25 + $icon = $status->getListIcon(); 26 + $color = $status->getListColor(); 27 + if ($icon !== null) { 28 + $item->setStatusIcon("{$icon} {$color}", $status->getName()); 29 + } 30 + 31 + $item->setDisabled(!$status->isActive()); 32 + 23 33 $list->addItem($item); 24 34 } 25 35
+9
src/applications/auth/controller/mfa/PhabricatorAuthFactorProviderViewController.php
··· 58 58 ->setHeader($provider->getDisplayName()) 59 59 ->setPolicyObject($provider); 60 60 61 + $status = $provider->newStatus(); 62 + 63 + $header_icon = $status->getStatusHeaderIcon(); 64 + $header_color = $status->getStatusHeaderColor(); 65 + $header_name = $status->getName(); 66 + if ($header_icon !== null) { 67 + $view->setStatus($header_icon, $header_color, $header_name); 68 + } 69 + 61 70 return $view; 62 71 } 63 72
+10
src/applications/auth/editor/PhabricatorAuthFactorProviderEditEngine.php
··· 95 95 protected function buildCustomEditFields($object) { 96 96 $factor_name = $object->getFactor()->getFactorName(); 97 97 98 + $status_map = PhabricatorAuthFactorProviderStatus::getMap(); 99 + 98 100 return array( 99 101 id(new PhabricatorStaticEditField()) 100 102 ->setKey('displayType') ··· 109 111 ->setDescription(pht('Display name for the MFA provider.')) 110 112 ->setValue($object->getName()) 111 113 ->setPlaceholder($factor_name), 114 + id(new PhabricatorSelectEditField()) 115 + ->setKey('status') 116 + ->setTransactionType( 117 + PhabricatorAuthFactorProviderStatusTransaction::TRANSACTIONTYPE) 118 + ->setLabel(pht('Status')) 119 + ->setDescription(pht('Status of the MFA provider.')) 120 + ->setValue($object->getStatus()) 121 + ->setOptions($status_map), 112 122 ); 113 123 } 114 124
+12 -1
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 473 473 $factors = id(new PhabricatorAuthFactorConfigQuery()) 474 474 ->setViewer($viewer) 475 475 ->withUserPHIDs(array($viewer->getPHID())) 476 - ->setOrderVector(array('-id')) 476 + ->withFactorProviderStatuses( 477 + array( 478 + PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE, 479 + PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED, 480 + )) 477 481 ->execute(); 482 + 483 + // Sort factors in the same order that they appear in on the Settings 484 + // panel. This means that administrators changing provider statuses may 485 + // change the order of prompts for users, but the alternative is that the 486 + // Settings panel order disagrees with the prompt order, which seems more 487 + // disruptive. 488 + $factors = msort($factors, 'newSortVector'); 478 489 479 490 // If the account has no associated multi-factor auth, just issue a token 480 491 // without putting the session into high security mode. This is generally
+6 -2
src/applications/auth/factor/PhabricatorAuthFactor.php
··· 54 54 return null; 55 55 } 56 56 57 - public function canCreateNewConfiguration(PhabricatorUser $user) { 57 + public function canCreateNewConfiguration( 58 + PhabricatorAuthFactorProvider $provider, 59 + PhabricatorUser $user) { 58 60 return true; 59 61 } 60 62 61 - public function getConfigurationCreateDescription(PhabricatorUser $user) { 63 + public function getConfigurationCreateDescription( 64 + PhabricatorAuthFactorProvider $provider, 65 + PhabricatorUser $user) { 62 66 return null; 63 67 } 64 68
+7 -2
src/applications/auth/factor/PhabricatorSMSAuthFactor.php
··· 67 67 return $messages; 68 68 } 69 69 70 - public function canCreateNewConfiguration(PhabricatorUser $user) { 70 + public function canCreateNewConfiguration( 71 + PhabricatorAuthFactorProvider $provider, 72 + PhabricatorUser $user) { 73 + 71 74 if (!$this->loadUserContactNumber($user)) { 72 75 return false; 73 76 } ··· 75 78 return true; 76 79 } 77 80 78 - public function getConfigurationCreateDescription(PhabricatorUser $user) { 81 + public function getConfigurationCreateDescription( 82 + PhabricatorAuthFactorProvider $provider, 83 + PhabricatorUser $user) { 79 84 80 85 $messages = array(); 81 86
+34 -4
src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php
··· 7 7 private $phids; 8 8 private $userPHIDs; 9 9 private $factorProviderPHIDs; 10 + private $factorProviderStatuses; 10 11 11 12 public function withIDs(array $ids) { 12 13 $this->ids = $ids; ··· 28 29 return $this; 29 30 } 30 31 32 + public function withFactorProviderStatuses(array $statuses) { 33 + $this->factorProviderStatuses = $statuses; 34 + return $this; 35 + } 36 + 31 37 public function newResultObject() { 32 38 return new PhabricatorAuthFactorConfig(); 33 39 } ··· 42 48 if ($this->ids !== null) { 43 49 $where[] = qsprintf( 44 50 $conn, 45 - 'id IN (%Ld)', 51 + 'config.id IN (%Ld)', 46 52 $this->ids); 47 53 } 48 54 49 55 if ($this->phids !== null) { 50 56 $where[] = qsprintf( 51 57 $conn, 52 - 'phid IN (%Ls)', 58 + 'config.phid IN (%Ls)', 53 59 $this->phids); 54 60 } 55 61 56 62 if ($this->userPHIDs !== null) { 57 63 $where[] = qsprintf( 58 64 $conn, 59 - 'userPHID IN (%Ls)', 65 + 'config.userPHID IN (%Ls)', 60 66 $this->userPHIDs); 61 67 } 62 68 63 69 if ($this->factorProviderPHIDs !== null) { 64 70 $where[] = qsprintf( 65 71 $conn, 66 - 'factorProviderPHID IN (%Ls)', 72 + 'config.factorProviderPHID IN (%Ls)', 67 73 $this->factorProviderPHIDs); 68 74 } 69 75 76 + if ($this->factorProviderStatuses !== null) { 77 + $where[] = qsprintf( 78 + $conn, 79 + 'provider.status IN (%Ls)', 80 + $this->factorProviderStatuses); 81 + } 82 + 70 83 return $where; 84 + } 85 + 86 + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { 87 + $joins = parent::buildJoinClauseParts($conn); 88 + 89 + if ($this->factorProviderStatuses !== null) { 90 + $joins[] = qsprintf( 91 + $conn, 92 + 'JOIN %R provider ON config.factorProviderPHID = provider.phid', 93 + new PhabricatorAuthFactorProvider()); 94 + } 95 + 96 + return $joins; 71 97 } 72 98 73 99 protected function willFilterPage(array $configs) { ··· 92 118 } 93 119 94 120 return $configs; 121 + } 122 + 123 + protected function getPrimaryTableAlias() { 124 + return 'config'; 95 125 } 96 126 97 127 public function getQueryApplicationClass() {
+6
src/applications/auth/storage/PhabricatorAuthFactorConfig.php
··· 80 80 return $this; 81 81 } 82 82 83 + public function newSortVector() { 84 + return id(new PhutilSortVector()) 85 + ->addInt($this->getFactorProvider()->newStatus()->getOrder()) 86 + ->addInt($this->getID()); 87 + } 88 + 83 89 84 90 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 85 91
+15 -5
src/applications/auth/storage/PhabricatorAuthFactorProvider.php
··· 14 14 15 15 private $factor = self::ATTACHABLE; 16 16 17 - const STATUS_ACTIVE = 'active'; 18 - const STATUS_DEPRECATED = 'deprecated'; 19 - const STATUS_DISABLED = 'disabled'; 20 - 21 17 public static function initializeNewProvider(PhabricatorAuthFactor $factor) { 22 18 return id(new self()) 23 19 ->setProviderFactorKey($factor->getFactorKey()) 24 20 ->attachFactor($factor) 25 - ->setStatus(self::STATUS_ACTIVE); 21 + ->setStatus(PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE); 26 22 } 27 23 28 24 protected function getConfiguration() { ··· 116 112 public function getEnrollButtonText(PhabricatorUser $user) { 117 113 return $this->getFactor()->getEnrollButtonText($this, $user); 118 114 } 115 + 116 + public function newStatus() { 117 + $status_key = $this->getStatus(); 118 + return PhabricatorAuthFactorProviderStatus::newForStatus($status_key); 119 + } 120 + 121 + public function canCreateNewConfiguration(PhabricatorUser $user) { 122 + return $this->getFactor()->canCreateNewConfiguration($this, $user); 123 + } 124 + 125 + public function getConfigurationCreateDescription(PhabricatorUser $user) { 126 + return $this->getFactor()->getConfigurationCreateDescription($this, $user); 127 + } 128 + 119 129 120 130 /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 121 131
+103
src/applications/auth/xaction/PhabricatorAuthFactorProviderStatusTransaction.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthFactorProviderStatusTransaction 4 + extends PhabricatorAuthFactorProviderTransactionType { 5 + 6 + const TRANSACTIONTYPE = 'status'; 7 + 8 + public function generateOldValue($object) { 9 + return $object->getStatus(); 10 + } 11 + 12 + public function applyInternalEffects($object, $value) { 13 + $object->setStatus($value); 14 + } 15 + 16 + public function getTitle() { 17 + $old = $this->getOldValue(); 18 + $new = $this->getNewValue(); 19 + 20 + $old_display = PhabricatorAuthFactorProviderStatus::newForStatus($old) 21 + ->getName(); 22 + $new_display = PhabricatorAuthFactorProviderStatus::newForStatus($new) 23 + ->getName(); 24 + 25 + return pht( 26 + '%s changed the status of this provider from %s to %s.', 27 + $this->renderAuthor(), 28 + $this->renderValue($old_display), 29 + $this->renderValue($new_display)); 30 + } 31 + 32 + public function validateTransactions($object, array $xactions) { 33 + $errors = array(); 34 + $actor = $this->getActor(); 35 + 36 + $map = PhabricatorAuthFactorProviderStatus::getMap(); 37 + foreach ($xactions as $xaction) { 38 + $new_value = $xaction->getNewValue(); 39 + 40 + if (!isset($map[$new_value])) { 41 + $errors[] = $this->newInvalidError( 42 + pht( 43 + 'Status "%s" is invalid. Valid statuses are: %s.', 44 + $new_value, 45 + implode(', ', array_keys($map))), 46 + $xaction); 47 + continue; 48 + } 49 + 50 + $require_key = 'security.require-multi-factor-auth'; 51 + $require_mfa = PhabricatorEnv::getEnvConfig($require_key); 52 + 53 + if ($require_mfa) { 54 + $status_active = PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE; 55 + if ($new_value !== $status_active) { 56 + $active_providers = id(new PhabricatorAuthFactorProviderQuery()) 57 + ->setViewer($actor) 58 + ->withStatuses( 59 + array( 60 + $status_active, 61 + )) 62 + ->execute(); 63 + $active_providers = mpull($active_providers, null, 'getID'); 64 + unset($active_providers[$object->getID()]); 65 + 66 + if (!$active_providers) { 67 + $errors[] = $this->newInvalidError( 68 + pht( 69 + 'You can not deprecate or disable the last active MFA '. 70 + 'provider while "%s" is enabled, because new users would '. 71 + 'be unable to enroll in MFA. Disable the MFA requirement '. 72 + 'in Config, or create or enable another MFA provider first.', 73 + $require_key)); 74 + continue; 75 + } 76 + } 77 + } 78 + } 79 + 80 + return $errors; 81 + } 82 + 83 + public function didCommitTransaction($object, $value) { 84 + $status = PhabricatorAuthFactorProviderStatus::newForStatus($value); 85 + 86 + // If a provider has undergone a status change, reset the MFA enrollment 87 + // cache for all users. This may immediately force a lot of users to redo 88 + // MFA enrollment. 89 + 90 + // We could be more surgical about this: we only really need to affect 91 + // users who had a factor under the provider, and only really need to 92 + // do anything if a provider was disabled. This is just a little simpler. 93 + 94 + $table = new PhabricatorUser(); 95 + $conn = $table->establishConnection('w'); 96 + 97 + queryfx( 98 + $conn, 99 + 'UPDATE %R SET isEnrolledInMultiFactor = 0', 100 + $table); 101 + } 102 + 103 + }
+9 -3
src/applications/people/storage/PhabricatorUser.php
··· 908 908 * @task factors 909 909 */ 910 910 public function updateMultiFactorEnrollment() { 911 - $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere( 912 - 'userPHID = %s', 913 - $this->getPHID()); 911 + $factors = id(new PhabricatorAuthFactorConfigQuery()) 912 + ->setViewer($this) 913 + ->withUserPHIDs(array($this->getPHID())) 914 + ->withFactorProviderStatuses( 915 + array( 916 + PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE, 917 + PhabricatorAuthFactorProviderStatus::STATUS_DEPRECATED, 918 + )) 919 + ->execute(); 914 920 915 921 $enrolled = count($factors) ? 1 : 0; 916 922 if ($enrolled !== $this->isEnrolledInMultiFactor) {
+43 -16
src/applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php
··· 53 53 $factors = id(new PhabricatorAuthFactorConfigQuery()) 54 54 ->setViewer($viewer) 55 55 ->withUserPHIDs(array($user->getPHID())) 56 - ->setOrderVector(array('-id')) 57 56 ->execute(); 57 + $factors = msort($factors, 'newSortVector'); 58 58 59 59 $rows = array(); 60 60 $rowc = array(); ··· 69 69 $rowc[] = null; 70 70 } 71 71 72 + $status = $provider->newStatus(); 73 + $status_icon = $status->getFactorIcon(); 74 + $status_color = $status->getFactorColor(); 75 + 76 + $icon = id(new PHUIIconView()) 77 + ->setIcon("{$status_icon} {$status_color}") 78 + ->setTooltip(pht('Provider: %s', $status->getName())); 79 + 72 80 $rows[] = array( 81 + $icon, 73 82 javelin_tag( 74 83 'a', 75 84 array( ··· 95 104 pht("You haven't added any authentication factors to your account yet.")); 96 105 $table->setHeaders( 97 106 array( 107 + null, 98 108 pht('Name'), 99 109 pht('Type'), 100 110 pht('Created'), 101 - '', 111 + null, 102 112 )); 103 113 $table->setColumnClasses( 104 114 array( 115 + null, 105 116 'wide pri', 106 - '', 117 + null, 107 118 'right', 108 119 'action', 109 120 )); 110 121 $table->setRowClasses($rowc); 111 122 $table->setDeviceVisibility( 112 123 array( 124 + true, 113 125 true, 114 126 false, 115 127 false, ··· 129 141 $add_color = PHUIButtonView::GREY; 130 142 } 131 143 144 + $can_add = (bool)$this->loadActiveMFAProviders(); 145 + 132 146 $buttons[] = id(new PHUIButtonView()) 133 147 ->setTag('a') 134 148 ->setIcon('fa-plus') 135 149 ->setText(pht('Add Auth Factor')) 136 150 ->setHref($this->getPanelURI('?new=true')) 137 151 ->setWorkflow(true) 152 + ->setDisabled(!$can_add) 138 153 ->setColor($add_color); 139 154 140 155 $buttons[] = id(new PHUIButtonView()) ··· 155 170 156 171 // Check that we have providers before we send the user through the MFA 157 172 // gate, so you don't authenticate and then immediately get roadblocked. 158 - $providers = id(new PhabricatorAuthFactorProviderQuery()) 159 - ->setViewer($viewer) 160 - ->withStatuses(array(PhabricatorAuthFactorProvider::STATUS_ACTIVE)) 161 - ->execute(); 173 + $providers = $this->loadActiveMFAProviders(); 174 + 162 175 if (!$providers) { 163 176 return $this->newDialog() 164 177 ->setTitle(pht('No MFA Providers')) 165 178 ->appendParagraph( 166 179 pht( 167 - 'There are no active MFA providers. At least one active provider '. 168 - 'must be available to add new MFA factors.')) 180 + 'This install does not have any active MFA providers configured. '. 181 + 'At least one provider must be configured and active before you '. 182 + 'can add new MFA factors.')) 169 183 ->addCancelButton($cancel_uri); 170 184 } 171 - $providers = mpull($providers, null, 'getPHID'); 172 - $proivders = msortv($providers, 'newSortVector'); 173 185 174 186 $token = id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( 175 187 $viewer, ··· 184 196 185 197 // Only let the user continue creating a factor for a given provider if 186 198 // they actually pass the provider's checks. 187 - $selected_factor = $selected_provider->getFactor(); 188 - if (!$selected_factor->canCreateNewConfiguration($viewer)) { 199 + if (!$selected_provider->canCreateNewConfiguration($viewer)) { 189 200 $selected_provider = null; 190 201 } 191 202 } ··· 200 211 $provider_uri = id(new PhutilURI($this->getPanelURI())) 201 212 ->setQueryParam('providerPHID', $provider_phid); 202 213 203 - $factor = $provider->getFactor(); 204 - $is_enabled = $factor->canCreateNewConfiguration($viewer); 214 + $is_enabled = $provider->canCreateNewConfiguration($viewer); 205 215 206 216 $item = id(new PHUIObjectItemView()) 207 217 ->setHeader($provider->getDisplayName()) ··· 216 226 $item->setDisabled(true); 217 227 } 218 228 219 - $create_description = $factor->getConfigurationCreateDescription( 229 + $create_description = $provider->getConfigurationCreateDescription( 220 230 $viewer); 221 231 if ($create_description) { 222 232 $item->appendChild($create_description); ··· 422 432 423 433 return id(new AphrontDialogResponse()) 424 434 ->setDialog($dialog); 435 + } 436 + 437 + private function loadActiveMFAProviders() { 438 + $viewer = $this->getViewer(); 439 + 440 + $providers = id(new PhabricatorAuthFactorProviderQuery()) 441 + ->setViewer($viewer) 442 + ->withStatuses( 443 + array( 444 + PhabricatorAuthFactorProviderStatus::STATUS_ACTIVE, 445 + )) 446 + ->execute(); 447 + 448 + $providers = mpull($providers, null, 'getPHID'); 449 + $providers = msortv($providers, 'newSortVector'); 450 + 451 + return $providers; 425 452 } 426 453 427 454