diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2254,6 +2254,7 @@ 'PhabricatorAuthManagementCachePKCS8Workflow' => 'applications/auth/management/PhabricatorAuthManagementCachePKCS8Workflow.php', 'PhabricatorAuthManagementLDAPWorkflow' => 'applications/auth/management/PhabricatorAuthManagementLDAPWorkflow.php', 'PhabricatorAuthManagementListFactorsWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php', + 'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php', 'PhabricatorAuthManagementRecoverWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRecoverWorkflow.php', 'PhabricatorAuthManagementRefreshWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRefreshWorkflow.php', 'PhabricatorAuthManagementRevokeWorkflow' => 'applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php', @@ -7892,6 +7893,7 @@ 'PhabricatorAuthFactorConfig' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', ), 'PhabricatorAuthFactorConfigQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorAuthFactorProvider' => array( @@ -7948,6 +7950,7 @@ 'PhabricatorAuthManagementCachePKCS8Workflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementLDAPWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementListFactorsWorkflow' => 'PhabricatorAuthManagementWorkflow', + 'PhabricatorAuthManagementListMFAProvidersWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRecoverWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRefreshWorkflow' => 'PhabricatorAuthManagementWorkflow', 'PhabricatorAuthManagementRevokeWorkflow' => 'PhabricatorAuthManagementWorkflow', diff --git a/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php --- a/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php +++ b/src/applications/auth/management/PhabricatorAuthManagementListFactorsWorkflow.php @@ -14,9 +14,8 @@ public function execute(PhutilArgumentParser $args) { $factors = PhabricatorAuthFactor::getAllFactors(); - $console = PhutilConsole::getConsole(); foreach ($factors as $factor) { - $console->writeOut( + echo tsprintf( "%s\t%s\n", $factor->getFactorKey(), $factor->getFactorName()); diff --git a/src/applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/management/PhabricatorAuthManagementListMFAProvidersWorkflow.php @@ -0,0 +1,33 @@ +setName('list-mfa-providers') + ->setExamples('**list-mfa-providerrs**') + ->setSynopsis( + pht( + 'List available multi-factor authentication providers.')) + ->setArguments(array()); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $providers = id(new PhabricatorAuthFactorProviderQuery()) + ->setViewer($viewer) + ->execute(); + + foreach ($providers as $provider) { + echo tsprintf( + "%s\t%s\n", + $provider->getPHID(), + $provider->getDisplayName()); + } + + return 0; + } + +} diff --git a/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php --- a/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php +++ b/src/applications/auth/management/PhabricatorAuthManagementStripWorkflow.php @@ -24,12 +24,22 @@ 'name' => 'type', 'param' => 'factortype', 'repeat' => true, - 'help' => pht('Strip a specific factor type.'), + 'help' => pht( + 'Strip a specific factor type. Use `bin/auth list-factors` for '. + 'a list of factor types.'), ), array( 'name' => 'all-types', 'help' => pht('Strip all factors, regardless of type.'), ), + array( + 'name' => 'provider', + 'param' => 'phid', + 'repeat' => true, + 'help' => pht( + 'Strip factors for a specific provider. Use '. + '`bin/auth list-mfa-providers` for a list of providers.'), + ), array( 'name' => 'force', 'help' => pht('Strip factors without prompting.'), @@ -42,6 +52,8 @@ } public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + $usernames = $args->getArg('user'); $all_users = $args->getArg('all-users'); @@ -55,10 +67,8 @@ } else if (!$usernames && !$all_users) { throw new PhutilArgumentUsageException( pht( - 'Use %s to specify which user to strip factors from, or '. - '%s to strip factors from all users.', - '--user', - '--all-users')); + '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()) @@ -79,37 +89,83 @@ } $types = $args->getArg('type'); + $provider_phids = $args->getArg('provider'); $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) { + 'Specify either specific factors with "--type", or all factors with '. + '"--all-types", but not both.')); + } else if ($provider_phids && $all_types) { + throw new PhutilArgumentUsageException( + pht( + 'Specify either specific factors with "--provider", or all factors '. + 'with "--all-types", but not both.')); + } else if (!$types && !$all_types && !$provider_phids) { 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.')); + 'Use "--type " or "--provider " to specify which '. + 'factors to strip, or "--all-types" to strip all factors. '. + 'Use `bin/auth list-factors` to show the available factor types '. + 'or `bin/auth list-mfa-providers` to show available providers.')); } - 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); + $type_map = PhabricatorAuthFactor::getAllFactors(); + + if ($types) { + foreach ($types as $type) { + if (!isset($type_map[$type])) { + throw new PhutilArgumentUsageException( + pht( + 'Factor type "%s" is unknown. Use `bin/auth list-factors` to '. + 'get a list of known factor types.', + $type)); + } + } + } + + $provider_query = id(new PhabricatorAuthFactorProviderQuery()) + ->setViewer($viewer); + + if ($provider_phids) { + $provider_query->withPHIDs($provider_phids); + } + + if ($types) { + $provider_query->withProviderFactorKeys($types); + } + + $providers = $provider_query->execute(); + $providers = mpull($providers, null, 'getPHID'); + + if ($provider_phids) { + foreach ($provider_phids as $provider_phid) { + if (!isset($providers[$provider_phid])) { + throw new PhutilArgumentUsageException( + pht( + 'No provider with PHID "%s" exists. '. + 'Use `bin/auth list-mfa-providers` to list providers.', + $provider_phid)); + } + } } else { - $factors = id(new PhabricatorAuthFactorConfig())->loadAll(); + if (!$providers) { + throw new PhutilArgumentUsageException( + pht( + 'There are no configured multi-factor providers.')); + } + } + + $factor_query = id(new PhabricatorAuthFactorConfigQuery()) + ->setViewer($viewer) + ->withFactorProviderPHIDs(array_keys($providers)); + + if ($users) { + $factor_query->withUserPHIDs(mpull($users, 'getPHID')); } + $factors = $factor_query->execute(); + if (!$factors) { throw new PhutilArgumentUsageException( pht('There are no matching factors to strip.')); @@ -125,14 +181,13 @@ $console->writeOut("%s\n\n", pht('These auth factors will be stripped:')); foreach ($factors as $factor) { - $impl = $factor->getImplementation(); - $console->writeOut( + $provider = $factor->getFactorProvider(); + + echo tsprintf( " %s\t%s\t%s\n", $handles[$factor->getUserPHID()]->getName(), - $factor->getFactorKey(), - ($impl - ? $impl->getFactorName() - : '?')); + $provider->getProviderFactorKey(), + $provider->getDisplayName()); } $is_dry_run = $args->getArg('dry-run'); @@ -154,17 +209,9 @@ $console->writeOut("%s\n", pht('Stripping authentication factors...')); + $engine = new PhabricatorDestructionEngine(); foreach ($factors as $factor) { - $user = id(new PhabricatorPeopleQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs(array($factor->getUserPHID())) - ->executeOne(); - - $factor->delete(); - - if ($user) { - $user->updateMultiFactorEnrollment(); - } + $engine->destroyObject($factor); } $console->writeOut("%s\n", pht('Done.')); diff --git a/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php b/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php --- a/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php +++ b/src/applications/auth/query/PhabricatorAuthFactorConfigQuery.php @@ -6,6 +6,7 @@ private $ids; private $phids; private $userPHIDs; + private $factorProviderPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -22,6 +23,11 @@ return $this; } + public function withFactorProviderPHIDs(array $provider_phids) { + $this->factorProviderPHIDs = $provider_phids; + return $this; + } + public function newResultObject() { return new PhabricatorAuthFactorConfig(); } @@ -54,6 +60,13 @@ $this->userPHIDs); } + if ($this->factorProviderPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'factorProviderPHID IN (%Ls)', + $this->factorProviderPHIDs); + } + return $where; } diff --git a/src/applications/auth/storage/PhabricatorAuthFactorConfig.php b/src/applications/auth/storage/PhabricatorAuthFactorConfig.php --- a/src/applications/auth/storage/PhabricatorAuthFactorConfig.php +++ b/src/applications/auth/storage/PhabricatorAuthFactorConfig.php @@ -1,8 +1,11 @@ setViewer($engine->getViewer()) + ->withPHIDs(array($this->getUserPHID())) + ->executeOne(); + + $this->delete(); + + if ($user) { + $user->updateMultiFactorEnrollment(); + } + } + } diff --git a/src/docs/user/userguide/multi_factor_auth.diviner b/src/docs/user/userguide/multi_factor_auth.diviner --- a/src/docs/user/userguide/multi_factor_auth.diviner +++ b/src/docs/user/userguide/multi_factor_auth.diviner @@ -126,9 +126,28 @@ arguments. This command can selectively strip types of factors. You can use -`bin/auth list-factors` for a list of available factor types. +`bin/auth list-factors` to get a list of available factor types. ```lang=console # Show supported factor types. phabricator/ $ ./bin/auth list-factors ``` + +Once you've identified the factor types you want to strip, you can strip them +using the `--type` flag to specify one or more factor types: + +```lang=console +# Strip all SMS and TOTP factors for a user. +phabricator/ $ ./bin/auth strip --user --type sms --type totp +``` + +The `bin/auth strip` command can also selectively strip factors for certain +providers. This is more granular than stripping all factors of a given type. +You can use `bin/auth list-mfa-providers` to get a list of providers. + +Once you have a provider PHID, use `--provider` to select factors to strip: + +```lang=console +# Strip all factors for a particular provider. +phabricator/ $ ./bin/auth strip --user --provider +```