@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 administrators to get a list of users who don't have MFA configured

Summary:
Fixes T12400. Adds a "Has MFA" filter to People so you can figure out who you need to harass before turning on "require MFA".

When you run this as a non-admin, you don't currently actually hit the exception: the query just doesn't work. I think this is probably okay, but if we add more of these it might be better to make the "this didn't work" more explicit since it could be confusing in some weird edge cases (like, an administrator sending a non-administrator a link which they expect will show the non-administrator some interesting query results, but they actually just get no constraint). The exception is more of a fail-safe in case we make application changes in the future and don't remember this weird special case.

Test Plan:
- As an administrator and non-administrator, used People and Conduit to query MFA, no-MFA, and don't-care-about-MFA. These queries worked for an admin and didn't work for a non-admin.
- Viewed the list as an administrator, saw MFA users annotated.
- Viewed config help, clicked link as an admin, ended up in the right place.

{F4093033}

{F4093034}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T12400

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

+85 -18
+2
src/__phutil_library_map__.php
··· 3763 3763 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', 3764 3764 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 3765 3765 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', 3766 + 'PhabricatorSearchConstraintException' => 'applications/search/exception/PhabricatorSearchConstraintException.php', 3766 3767 'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php', 3767 3768 'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php', 3768 3769 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', ··· 9072 9073 'PhabricatorSearchBaseController' => 'PhabricatorController', 9073 9074 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 9074 9075 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', 9076 + 'PhabricatorSearchConstraintException' => 'Exception', 9075 9077 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 9076 9078 'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField', 9077 9079 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
+16 -7
src/applications/config/option/PhabricatorSecurityConfigOptions.php
··· 66 66 , 67 67 PhabricatorEnv::getDoclink('Configuring Encryption'))); 68 68 69 + $require_mfa_description = $this->deformat(pht(<<<EOTEXT 70 + By default, Phabricator allows users to add multi-factor authentication to 71 + their accounts, but does not require it. By enabling this option, you can 72 + force all users to add at least one authentication factor before they can use 73 + their accounts. 74 + 75 + Administrators can query a list of users who do not have MFA configured in 76 + {nav People}: 77 + 78 + - **[[ %s | %s ]]** 79 + EOTEXT 80 + , 81 + '/people/?mfa=false', 82 + pht('List of Users Without MFA'))); 83 + 69 84 return array( 70 85 $this->newOption('security.alternate-file-domain', 'string', null) 71 86 ->setLocked(true) ··· 132 147 ->setLocked(true) 133 148 ->setSummary( 134 149 pht('Require all users to configure multi-factor authentication.')) 135 - ->setDescription( 136 - pht( 137 - 'By default, Phabricator allows users to add multi-factor '. 138 - 'authentication to their accounts, but does not require it. '. 139 - 'By enabling this option, you can force all users to add '. 140 - 'at least one authentication factor before they can use their '. 141 - 'accounts.')) 150 + ->setDescription($require_mfa_description) 142 151 ->setBoolOptions( 143 152 array( 144 153 pht('Multi-Factor Required'),
+13
src/applications/people/query/PhabricatorPeopleQuery.php
··· 18 18 private $nameLike; 19 19 private $nameTokens; 20 20 private $namePrefixes; 21 + private $isEnrolledInMultiFactor; 21 22 22 23 private $needPrimaryEmail; 23 24 private $needProfile; ··· 97 98 98 99 public function withNamePrefixes(array $prefixes) { 99 100 $this->namePrefixes = $prefixes; 101 + return $this; 102 + } 103 + 104 + public function withIsEnrolledInMultiFactor($enrolled) { 105 + $this->isEnrolledInMultiFactor = $enrolled; 100 106 return $this; 101 107 } 102 108 ··· 335 341 'user.username LIKE %~ OR user.realname LIKE %~', 336 342 $this->nameLike, 337 343 $this->nameLike); 344 + } 345 + 346 + if ($this->isEnrolledInMultiFactor !== null) { 347 + $where[] = qsprintf( 348 + $conn, 349 + 'user.isEnrolledInMultiFactor = %d', 350 + (int)$this->isEnrolledInMultiFactor); 338 351 } 339 352 340 353 return $where;
+48 -11
src/applications/people/query/PhabricatorPeopleSearchEngine.php
··· 18 18 } 19 19 20 20 protected function buildCustomSearchFields() { 21 - return array( 21 + $fields = array( 22 22 id(new PhabricatorSearchStringListField()) 23 23 ->setLabel(pht('Usernames')) 24 24 ->setKey('usernames') ··· 84 84 pht( 85 85 'Pass true to find only users awaiting administrative approval, '. 86 86 'or false to omit these users.')), 87 - id(new PhabricatorSearchDateField()) 88 - ->setKey('createdStart') 89 - ->setLabel(pht('Joined After')) 87 + ); 88 + 89 + $viewer = $this->requireViewer(); 90 + if ($viewer->getIsAdmin()) { 91 + $fields[] = id(new PhabricatorSearchThreeStateField()) 92 + ->setLabel(pht('Has MFA')) 93 + ->setKey('mfa') 94 + ->setOptions( 95 + pht('(Show All)'), 96 + pht('Show Only Users With MFA'), 97 + pht('Hide Users With MFA')) 90 98 ->setDescription( 91 - pht('Find user accounts created after a given time.')), 92 - id(new PhabricatorSearchDateField()) 93 - ->setKey('createdEnd') 94 - ->setLabel(pht('Joined Before')) 95 - ->setDescription( 96 - pht('Find user accounts created before a given time.')), 99 + pht( 100 + 'Pass true to find only users who are enrolled in MFA, or false '. 101 + 'to omit these users.')); 102 + } 103 + 104 + $fields[] = id(new PhabricatorSearchDateField()) 105 + ->setKey('createdStart') 106 + ->setLabel(pht('Joined After')) 107 + ->setDescription( 108 + pht('Find user accounts created after a given time.')); 109 + 110 + $fields[] = id(new PhabricatorSearchDateField()) 111 + ->setKey('createdEnd') 112 + ->setLabel(pht('Joined Before')) 113 + ->setDescription( 114 + pht('Find user accounts created before a given time.')); 97 115 98 - ); 116 + return $fields; 99 117 } 100 118 101 119 protected function getDefaultFieldOrder() { ··· 149 167 150 168 if ($map['needsApproval'] !== null) { 151 169 $query->withIsApproved(!$map['needsApproval']); 170 + } 171 + 172 + if (idx($map, 'mfa') !== null) { 173 + $viewer = $this->requireViewer(); 174 + if (!$viewer->getIsAdmin()) { 175 + throw new PhabricatorSearchConstraintException( 176 + pht( 177 + 'The "Has MFA" query constraint may only be used by '. 178 + 'administrators, to prevent attackers from using it to target '. 179 + 'weak accounts.')); 180 + } 181 + 182 + $query->withIsEnrolledInMultiFactor($map['mfa']); 152 183 } 153 184 154 185 if ($map['createdStart']) { ··· 252 283 253 284 if ($user->getIsMailingList()) { 254 285 $item->addIcon('fa-envelope-o', pht('Mailing List')); 286 + } 287 + 288 + if ($viewer->getIsAdmin()) { 289 + if ($user->getIsEnrolledInMultiFactor()) { 290 + $item->addIcon('fa-lock', pht('Has MFA')); 291 + } 255 292 } 256 293 257 294 if ($viewer->getIsAdmin()) {
+2
src/applications/search/controller/PhabricatorApplicationSearchController.php
··· 331 331 'query parameters and correct errors.'); 332 332 } catch (PhutilSearchQueryCompilerSyntaxException $ex) { 333 333 $exec_errors[] = $ex->getMessage(); 334 + } catch (PhabricatorSearchConstraintException $ex) { 335 + $exec_errors[] = $ex->getMessage(); 334 336 } 335 337 336 338 // The engine may have encountered additional errors during rendering;
+4
src/applications/search/exception/PhabricatorSearchConstraintException.php
··· 1 + <?php 2 + 3 + final class PhabricatorSearchConstraintException 4 + extends Exception {}