From d6d3ad6f80d90d68355e97f80896c94f709865c4 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 15 Mar 2017 11:44:27 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 2 + .../PhabricatorSecurityConfigOptions.php | 23 ++++--- .../people/query/PhabricatorPeopleQuery.php | 13 ++++ .../query/PhabricatorPeopleSearchEngine.php | 61 +++++++++++++++---- ...PhabricatorApplicationSearchController.php | 2 + .../PhabricatorSearchConstraintException.php | 4 ++ 6 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 src/applications/search/exception/PhabricatorSearchConstraintException.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 59e2feb7d0..cfd11d630f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3763,6 +3763,7 @@ phutil_register_library_map(array( 'PhabricatorSearchBaseController' => 'applications/search/controller/PhabricatorSearchBaseController.php', 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', + 'PhabricatorSearchConstraintException' => 'applications/search/exception/PhabricatorSearchConstraintException.php', 'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php', 'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php', 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', @@ -9072,6 +9073,7 @@ phutil_register_library_map(array( 'PhabricatorSearchBaseController' => 'PhabricatorController', 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', + 'PhabricatorSearchConstraintException' => 'Exception', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', diff --git a/src/applications/config/option/PhabricatorSecurityConfigOptions.php b/src/applications/config/option/PhabricatorSecurityConfigOptions.php index 4d4da4bd02..47fd1bb6f2 100644 --- a/src/applications/config/option/PhabricatorSecurityConfigOptions.php +++ b/src/applications/config/option/PhabricatorSecurityConfigOptions.php @@ -66,6 +66,21 @@ EOTEXT , PhabricatorEnv::getDoclink('Configuring Encryption'))); + $require_mfa_description = $this->deformat(pht(<<newOption('security.alternate-file-domain', 'string', null) ->setLocked(true) @@ -132,13 +147,7 @@ EOTEXT ->setLocked(true) ->setSummary( pht('Require all users to configure multi-factor authentication.')) - ->setDescription( - pht( - 'By default, Phabricator allows users to add multi-factor '. - 'authentication to their accounts, but does not require it. '. - 'By enabling this option, you can force all users to add '. - 'at least one authentication factor before they can use their '. - 'accounts.')) + ->setDescription($require_mfa_description) ->setBoolOptions( array( pht('Multi-Factor Required'), diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php index b6791a7be2..6cb2922083 100644 --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -18,6 +18,7 @@ final class PhabricatorPeopleQuery private $nameLike; private $nameTokens; private $namePrefixes; + private $isEnrolledInMultiFactor; private $needPrimaryEmail; private $needProfile; @@ -100,6 +101,11 @@ final class PhabricatorPeopleQuery return $this; } + public function withIsEnrolledInMultiFactor($enrolled) { + $this->isEnrolledInMultiFactor = $enrolled; + return $this; + } + public function needPrimaryEmail($need) { $this->needPrimaryEmail = $need; return $this; @@ -337,6 +343,13 @@ final class PhabricatorPeopleQuery $this->nameLike); } + if ($this->isEnrolledInMultiFactor !== null) { + $where[] = qsprintf( + $conn, + 'user.isEnrolledInMultiFactor = %d', + (int)$this->isEnrolledInMultiFactor); + } + return $where; } diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php index ce448b0bc9..e800a8ee1a 100644 --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -18,7 +18,7 @@ final class PhabricatorPeopleSearchEngine } protected function buildCustomSearchFields() { - return array( + $fields = array( id(new PhabricatorSearchStringListField()) ->setLabel(pht('Usernames')) ->setKey('usernames') @@ -84,18 +84,36 @@ final class PhabricatorPeopleSearchEngine pht( 'Pass true to find only users awaiting administrative approval, '. 'or false to omit these users.')), - id(new PhabricatorSearchDateField()) - ->setKey('createdStart') - ->setLabel(pht('Joined After')) - ->setDescription( - pht('Find user accounts created after a given time.')), - id(new PhabricatorSearchDateField()) - ->setKey('createdEnd') - ->setLabel(pht('Joined Before')) - ->setDescription( - pht('Find user accounts created before a given time.')), - ); + + $viewer = $this->requireViewer(); + if ($viewer->getIsAdmin()) { + $fields[] = id(new PhabricatorSearchThreeStateField()) + ->setLabel(pht('Has MFA')) + ->setKey('mfa') + ->setOptions( + pht('(Show All)'), + pht('Show Only Users With MFA'), + pht('Hide Users With MFA')) + ->setDescription( + pht( + 'Pass true to find only users who are enrolled in MFA, or false '. + 'to omit these users.')); + } + + $fields[] = id(new PhabricatorSearchDateField()) + ->setKey('createdStart') + ->setLabel(pht('Joined After')) + ->setDescription( + pht('Find user accounts created after a given time.')); + + $fields[] = id(new PhabricatorSearchDateField()) + ->setKey('createdEnd') + ->setLabel(pht('Joined Before')) + ->setDescription( + pht('Find user accounts created before a given time.')); + + return $fields; } protected function getDefaultFieldOrder() { @@ -151,6 +169,19 @@ final class PhabricatorPeopleSearchEngine $query->withIsApproved(!$map['needsApproval']); } + if (idx($map, 'mfa') !== null) { + $viewer = $this->requireViewer(); + if (!$viewer->getIsAdmin()) { + throw new PhabricatorSearchConstraintException( + pht( + 'The "Has MFA" query constraint may only be used by '. + 'administrators, to prevent attackers from using it to target '. + 'weak accounts.')); + } + + $query->withIsEnrolledInMultiFactor($map['mfa']); + } + if ($map['createdStart']) { $query->withDateCreatedAfter($map['createdStart']); } @@ -254,6 +285,12 @@ final class PhabricatorPeopleSearchEngine $item->addIcon('fa-envelope-o', pht('Mailing List')); } + if ($viewer->getIsAdmin()) { + if ($user->getIsEnrolledInMultiFactor()) { + $item->addIcon('fa-lock', pht('Has MFA')); + } + } + if ($viewer->getIsAdmin()) { $user_id = $user->getID(); if ($is_approval) { diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php index c7c2b432b4..faca9991ea 100644 --- a/src/applications/search/controller/PhabricatorApplicationSearchController.php +++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php @@ -331,6 +331,8 @@ final class PhabricatorApplicationSearchController 'query parameters and correct errors.'); } catch (PhutilSearchQueryCompilerSyntaxException $ex) { $exec_errors[] = $ex->getMessage(); + } catch (PhabricatorSearchConstraintException $ex) { + $exec_errors[] = $ex->getMessage(); } // The engine may have encountered additional errors during rendering; diff --git a/src/applications/search/exception/PhabricatorSearchConstraintException.php b/src/applications/search/exception/PhabricatorSearchConstraintException.php new file mode 100644 index 0000000000..7d674518e1 --- /dev/null +++ b/src/applications/search/exception/PhabricatorSearchConstraintException.php @@ -0,0 +1,4 @@ +