From 535cfa3ebebe7cde6c8043969471a67c6380b4a6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 30 Apr 2014 14:30:00 -0700 Subject: [PATCH] Add `bin/auth list-factors` and `bin/auth strip` to remove multi-factor auth Summary: Ref T4398. The major goals here is to let administrators strip auth factors in two cases: - A user lost their phone and needs access restored to their account; or - an install previously used an API-based factor like SMS, but want to stop supporting it (this isn't possible today). Test Plan: - Used `bin/auth list-factors` to show installed factors. - Used `bin/auth strip` with various mixtures of flags to selectively choose and strip factors from accounts. - Also ran `bin/auth refresh` to verify refreshing OAuth tokens works (small `OAuth` vs `OAuth2` tweak). Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T4398 Differential Revision: https://secure.phabricator.com/D8909 --- src/__phutil_library_map__.php | 4 + ...catorAuthManagementListFactorsWorkflow.php | 28 +++ ...abricatorAuthManagementRefreshWorkflow.php | 4 +- ...PhabricatorAuthManagementStripWorkflow.php | 164 ++++++++++++++++++ 4 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php create mode 100644 src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cdff33a79f..b27b445e7f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1219,8 +1219,10 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'applications/auth/controller/config/PhabricatorAuthListController.php', 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', + 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', + 'PhabricatorAuthManagementStripWorkflow' => 'applications/auth/management/PhabricatorAuthManagementStripWorkflow.php', 'PhabricatorAuthManagementWorkflow' => 'applications/auth/management/PhabricatorAuthManagementWorkflow.php', 'PhabricatorAuthNeedsApprovalController' => 'applications/auth/controller/PhabricatorAuthNeedsApprovalController.php', 'PhabricatorAuthNewController' => 'applications/auth/controller/config/PhabricatorAuthNewController.php', @@ -3982,8 +3984,10 @@ phutil_register_library_map(array( 'PhabricatorAuthListController' => 'PhabricatorAuthProviderConfigController', 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementStripWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorAuthNeedsApprovalController' => 'PhabricatorAuthController', 'PhabricatorAuthNewController' => 'PhabricatorAuthProviderConfigController', diff --git a/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php new file mode 100644 index 0000000000..1367335cd2 --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php @@ -0,0 +1,28 @@ +setName('list-factors') + ->setExamples('**list-factors**') + ->setSynopsis(pht('List available multi-factor authentication factors.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $factors = PhabricatorAuthFactor::getAllFactors(); + + $console = PhutilConsole::getConsole(); + foreach ($factors as $factor) { + $console->writeOut( + "%s\t%s\n", + $factor->getFactorKey(), + $factor->getFactorName()); + } + + return 0; + } + +} diff --git a/src/applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php index 86cb03a21f..97e02193cb 100644 --- a/src/applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php +++ b/src/applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php @@ -96,10 +96,10 @@ final class PhabricatorAuthManagementRefreshWorkflow } $provider = $providers[$key]; - if (!($provider instanceof PhabricatorAuthProviderOAuth)) { + if (!($provider instanceof PhabricatorAuthProviderOAuth2)) { $console->writeOut( "> %s\n", - pht("Skipping, provider is not an OAuth provider.")); + pht("Skipping, provider is not an OAuth2 provider.")); continue; } diff --git a/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php new file mode 100644 index 0000000000..809ba3cc95 --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php @@ -0,0 +1,164 @@ +setName('strip') + ->setExamples('**strip** [--user username] [--type type]') + ->setSynopsis( + pht( + 'Remove multi-factor authentication from an account.')) + ->setArguments( + array( + array( + 'name' => 'user', + 'param' => 'username', + 'repeat' => true, + 'help' => pht('Strip factors from specified users.'), + ), + array( + 'name' => 'all-users', + 'help' => pht('Strip factors from all users.'), + ), + array( + 'name' => 'type', + 'param' => 'factortype', + 'repeat' => true, + 'help' => pht('Strip a specific factor type.'), + ), + array( + 'name' => 'all-types', + 'help' => pht('Strip all factors, regardless of type.'), + ), + array( + 'name' => 'force', + 'help' => pht('Strip factors without prompting.'), + ), + array( + 'name' => 'dry-run', + 'help' => pht('Show factors, but do not strip them.'), + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $usernames = $args->getArg('user'); + $all_users = $args->getArg('all-users'); + + if ($usernames && $all_users) { + throw new PhutilArgumentUsageException( + pht( + 'Specify either specific users with --user, or all users with '. + '--all-users, but not both.')); + } else if (!$usernames && !$all_users) { + throw new PhutilArgumentUsageException( + pht( + 'Use --user to specify which user to strip factors from, or '. + '--all-users to strip factors from all users.')); + } else if ($usernames) { + $users = id(new PhabricatorPeopleQuery()) + ->setViewer($this->getViewer()) + ->withUsernames($usernames) + ->execute(); + + $users_by_username = mpull($users, null, 'getUsername'); + foreach ($usernames as $username) { + if (empty($users_by_username[$username])) { + throw new PhutilArgumentUsageException( + pht( + 'No user exists with username "%s".', + $username)); + } + } + } else { + $users = null; + } + + $types = $args->getArg('type'); + $all_types = $args->getArg('all-types'); + if ($types && $all_types) { + throw new PhutilArgumentUsageException( + pht( + 'Specify either specific factors with --type, or all factors with '. + '--all-types, but not both.')); + } else if (!$types && !$all_types) { + throw new PhutilArgumentUsageException( + pht( + 'Use --type to specify which factor to strip, or --all-types to '. + 'strip all factors. Use `auth list-factors` to show the available '. + 'factor types.')); + } + + if ($users && $types) { + $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere( + 'userPHID IN (%Ls) AND factorKey IN (%Ls)', + mpull($users, 'getPHID'), + $types); + } else if ($users) { + $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere( + 'userPHID IN (%Ls)', + mpull($users, 'getPHID')); + } else if ($types) { + $factors = id(new PhabricatorAuthFactorConfig())->loadAllWhere( + 'factorKey IN (%Ls)', + $types); + } else { + $factors = id(new PhabricatorAuthFactorConfig())->loadAll(); + } + + if (!$factors) { + throw new PhutilArgumentUsageException( + pht('There are no matching factors to strip.')); + } + + $handles = id(new PhabricatorHandleQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(mpull($factors, 'getUserPHID')) + ->execute(); + + $console = PhutilConsole::getConsole(); + + $console->writeOut("%s\n\n", pht("These auth factors will be stripped:")); + + foreach ($factors as $factor) { + $impl = $factor->getImplementation(); + $console->writeOut( + " %s\t%s\t%s\n", + $handles[$factor->getUserPHID()]->getName(), + $factor->getFactorKey(), + ($impl + ? $impl->getFactorName() + : '?')); + } + + $is_dry_run = $args->getArg('dry-run'); + if ($is_dry_run) { + $console->writeOut( + "\n%s\n", + pht('End of dry run.')); + + return 0; + } + + $force = $args->getArg('force'); + if (!$force) { + if (!$console->confirm(pht('Strip these authentication factors?'))) { + throw new PhutilArgumentUsageException( + pht('User aborted the workflow.')); + } + } + + $console->writeOut("%s\n", pht('Stripping authentication factors...')); + + foreach ($factors as $factor) { + $factor->delete(); + } + + $console->writeOut("%s\n", pht('Done.')); + + return 0; + } + +}