diff --git a/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php b/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php --- a/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php +++ b/src/applications/auth/management/PhabricatorAuthManagementRevokeWorkflow.php @@ -7,7 +7,8 @@ $this ->setName('revoke') ->setExamples( - "**revoke** --type __type__ --from __user__\n". + "**revoke** --list\n". + "**revoke** --type __type__ --from __@user__\n". "**revoke** --everything --everywhere") ->setSynopsis( pht( @@ -16,15 +17,20 @@ array( array( 'name' => 'from', - 'param' => 'user', + 'param' => 'object', 'help' => pht( - 'Revoke credentials for the specified user.'), + 'Revoke credentials for the specified object. To revoke '. + 'credentials for a user, use "@username".'), ), array( 'name' => 'type', 'param' => 'type', + 'help' => pht('Revoke credentials of the given type.'), + ), + array( + 'name' => 'list', 'help' => pht( - 'Revoke credentials of the given type.'), + 'List information about available credential revokers.'), ), array( 'name' => 'everything', @@ -42,18 +48,29 @@ } public function execute(PhutilArgumentParser $args) { - $viewer = PhabricatorUser::getOmnipotentUser(); + $viewer = $this->getViewer(); $all_types = PhabricatorAuthRevoker::getAllRevokers(); $is_force = $args->getArg('force'); + // The "--list" flag is compatible with revoker selection flags like + // "--type" to filter the list, but not compatible with target selection + // flags like "--from". + $is_list = $args->getArg('list'); + $type = $args->getArg('type'); $is_everything = $args->getArg('everything'); if (!strlen($type) && !$is_everything) { - throw new PhutilArgumentUsageException( - pht( - 'Specify the credential type to revoke with "--type" or specify '. - '"--everything".')); + if ($is_list) { + // By default, "bin/revoke --list" implies "--everything". + $types = $all_types; + } else { + throw new PhutilArgumentUsageException( + pht( + 'Specify the credential type to revoke with "--type" or specify '. + '"--everything". Use "--list" to list available credential '. + 'types.')); + } } else if (strlen($type) && $is_everything) { throw new PhutilArgumentUsageException( pht( @@ -75,6 +92,32 @@ $is_everywhere = $args->getArg('everywhere'); $from = $args->getArg('from'); + + if ($is_list) { + if (strlen($from) || $is_everywhere) { + throw new PhutilArgumentUsageException( + pht( + 'You can not "--list" and revoke credentials (with "--from" or '. + '"--everywhere") in the same operation.')); + } + } + + if ($is_list) { + $last_key = last_key($types); + foreach ($types as $key => $type) { + echo tsprintf( + "**%s** (%s)\n\n", + $type->getRevokerKey(), + $type->getRevokerName()); + + id(new PhutilConsoleBlock()) + ->addParagraph(tsprintf('%B', $type->getRevokerDescription())) + ->draw(); + } + + return 0; + } + $target = null; if (!strlen($from) && !$is_everywhere) { throw new PhutilArgumentUsageException( @@ -133,6 +176,13 @@ 'Destroyed %s credential(s) of type "%s".', new PhutilNumber($count), $type->getRevokerKey())); + + $guidance = $type->getRevokerNextSteps(); + if ($guidance !== null) { + echo tsprintf( + "%s\n", + $guidance); + } } echo tsprintf( diff --git a/src/applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php b/src/applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php --- a/src/applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php +++ b/src/applications/auth/revoker/PhabricatorAuthConduitTokenRevoker.php @@ -5,6 +5,19 @@ const REVOKERKEY = 'conduit'; + public function getRevokerName() { + return pht('Conduit API Tokens'); + } + + public function getRevokerDescription() { + return pht( + "Revokes all Conduit API tokens used to access the API.\n\n". + "Users will need to use `arc install-certificate` to install new ". + "API tokens before `arc` commands will work. Bots and scripts which ". + "access the API will need to have new tokens generated and ". + "installed."); + } + public function revokeAllCredentials() { $table = id(new PhabricatorConduitToken()); $conn = $table->establishConnection('w'); diff --git a/src/applications/auth/revoker/PhabricatorAuthPasswordRevoker.php b/src/applications/auth/revoker/PhabricatorAuthPasswordRevoker.php --- a/src/applications/auth/revoker/PhabricatorAuthPasswordRevoker.php +++ b/src/applications/auth/revoker/PhabricatorAuthPasswordRevoker.php @@ -5,6 +5,35 @@ const REVOKERKEY = 'password'; + public function getRevokerName() { + return pht('Passwords'); + } + + public function getRevokerDescription() { + return pht( + "Revokes all stored passwords.\n\n". + "Account passwords and VCS passwords (used to access repositories ". + "over HTTP) will both be revoked. Passwords for any third party ". + "applications which use shared password infrastructure will also ". + "be revoked.\n\n". + "Users will need to reset account passwords, possibly by using the ". + "\"Forgot Password?\" link on the login page. They will also need ". + "to reset VCS passwords.\n\n". + "Passwords are revoked, not just removed. Users will be unable to ". + "select the passwords they used previously and must choose new, ". + "unique passwords.\n\n". + "Revoking passwords will not terminate outstanding login sessions. ". + "Use the \"session\" revoker in conjunction with this revoker to force ". + "users to login again."); + } + + public function getRevokerNextSteps() { + return pht( + 'NOTE: Revoking passwords does not terminate existing sessions which '. + 'were established using the old passwords. To terminate existing '. + 'sessions, run the "session" revoker now.'); + } + public function revokeAllCredentials() { $query = new PhabricatorAuthPasswordQuery(); return $this->revokeWithQuery($query); diff --git a/src/applications/auth/revoker/PhabricatorAuthRevoker.php b/src/applications/auth/revoker/PhabricatorAuthRevoker.php --- a/src/applications/auth/revoker/PhabricatorAuthRevoker.php +++ b/src/applications/auth/revoker/PhabricatorAuthRevoker.php @@ -8,6 +8,13 @@ abstract public function revokeAllCredentials(); abstract public function revokeCredentialsFrom($object); + abstract public function getRevokerName(); + abstract public function getRevokerDescription(); + + public function getRevokerNextSteps() { + return null; + } + public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; diff --git a/src/applications/auth/revoker/PhabricatorAuthSSHRevoker.php b/src/applications/auth/revoker/PhabricatorAuthSSHRevoker.php --- a/src/applications/auth/revoker/PhabricatorAuthSSHRevoker.php +++ b/src/applications/auth/revoker/PhabricatorAuthSSHRevoker.php @@ -5,6 +5,18 @@ const REVOKERKEY = 'ssh'; + public function getRevokerName() { + return pht('SSH Keys'); + } + + public function getRevokerDescription() { + return pht( + "Revokes all SSH public keys.\n\n". + "SSH public keys are revoked, not just removed. Users will need to ". + "generate and upload new, unique keys before they can access ". + "repositories or other services over SSH."); + } + public function revokeAllCredentials() { $query = new PhabricatorAuthSSHKeyQuery(); return $this->revokeWithQuery($query); diff --git a/src/applications/auth/revoker/PhabricatorAuthSessionRevoker.php b/src/applications/auth/revoker/PhabricatorAuthSessionRevoker.php --- a/src/applications/auth/revoker/PhabricatorAuthSessionRevoker.php +++ b/src/applications/auth/revoker/PhabricatorAuthSessionRevoker.php @@ -5,6 +5,16 @@ const REVOKERKEY = 'session'; + public function getRevokerName() { + return pht('Sessions'); + } + + public function getRevokerDescription() { + return pht( + "Revokes all active login sessions.\n\n". + "Affected users will be logged out and need to log in again."); + } + public function revokeAllCredentials() { $table = new PhabricatorAuthSession(); $conn = $table->establishConnection('w'); diff --git a/src/applications/auth/revoker/PhabricatorAuthTemporaryTokenRevoker.php b/src/applications/auth/revoker/PhabricatorAuthTemporaryTokenRevoker.php --- a/src/applications/auth/revoker/PhabricatorAuthTemporaryTokenRevoker.php +++ b/src/applications/auth/revoker/PhabricatorAuthTemporaryTokenRevoker.php @@ -5,6 +5,19 @@ const REVOKERKEY = 'temporary'; + public function getRevokerName() { + return pht('Temporary Tokens'); + } + + public function getRevokerDescription() { + return pht( + "Revokes temporary authentication tokens.\n\n". + "Temporary tokens are used in password reset mail, welcome mail, and ". + "by some other systems like Git LFS. Revoking temporary tokens will ". + "invalidate existing links in password reset and invite mail that ". + "was sent before the revocation occurred."); + } + public function revokeAllCredentials() { $table = new PhabricatorAuthTemporaryToken(); $conn = $table->establishConnection('w');