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 @@ -1192,6 +1192,7 @@ 'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php', 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', + 'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php', 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', 'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php', 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', @@ -2146,6 +2147,7 @@ 'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php', 'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php', 'PhabricatorSettingsPanelSessions' => 'applications/settings/panel/PhabricatorSettingsPanelSessions.php', + 'PhabricatorSettingsPanelTokens' => 'applications/settings/panel/PhabricatorSettingsPanelTokens.php', 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 'PhabricatorSetupCheckAPC' => 'applications/config/check/PhabricatorSetupCheckAPC.php', 'PhabricatorSetupCheckAphlict' => 'applications/notification/setup/PhabricatorSetupCheckAphlict.php', @@ -3986,6 +3988,7 @@ 'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', + 'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController', 'PhabricatorAuthSession' => array( 'PhabricatorAuthDAO', 'PhabricatorPolicyInterface', @@ -4999,6 +5002,7 @@ 'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel', 'PhabricatorSettingsPanelSessions' => 'PhabricatorSettingsPanel', + 'PhabricatorSettingsPanelTokens' => 'PhabricatorSettingsPanel', 'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck', 'PhabricatorSetupCheckAphlict' => 'PhabricatorSetupCheck', 'PhabricatorSetupCheckAuth' => 'PhabricatorSetupCheck', diff --git a/src/applications/auth/application/PhabricatorAuthApplication.php b/src/applications/auth/application/PhabricatorAuthApplication.php --- a/src/applications/auth/application/PhabricatorAuthApplication.php +++ b/src/applications/auth/application/PhabricatorAuthApplication.php @@ -103,6 +103,8 @@ => 'PhabricatorAuthConfirmLinkController', 'session/terminate/(?P[^/]+)/' => 'PhabricatorAuthTerminateSessionController', + 'token/revoke/(?P[^/]+)/' + => 'PhabricatorAuthRevokeTokenController', 'session/downgrade/' => 'PhabricatorAuthDowngradeSessionController', 'multifactor/' diff --git a/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php new file mode 100644 --- /dev/null +++ b/src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php @@ -0,0 +1,78 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $viewer = $request->getUser(); + + $is_all = ($this->id === 'all'); + + $query = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($viewer->getPHID())); + if (!$is_all) { + $query->withIDs(array($this->id)); + } + + $tokens = $query->execute(); + foreach ($tokens as $key => $token) { + if (!$token->isRevocable()) { + // Don't revoke unrevocable tokens. + unset($tokens[$key]); + } + } + + $panel_uri = '/settings/panel/tokens/'; + + if (!$tokens) { + return $this->newDialog() + ->setTitle(pht('No Matching Tokens')) + ->appendParagraph( + pht('There are no matching tokens to revoke.')) + ->appendParagraph( + pht( + '(Some types of token can not be revoked, and you can not revoke '. + 'tokens which have already expired.)')) + ->addCancelButton($panel_uri); + } + + if ($request->isDialogFormPost()) { + foreach ($tokens as $token) { + $token->setTokenExpires(PhabricatorTime::getNow() - 1)->save(); + } + return id(new AphrontRedirectResponse())->setURI($panel_uri); + } + + if ($is_all) { + $title = pht('Revoke Tokens?'); + $short = pht('Revoke Tokens'); + $body = pht( + 'Really revoke all tokens? Among other temporary authorizations, '. + 'this will disable any outstanding password reset or account '. + 'recovery links.'); + } else { + $title = pht('Revoke Token?'); + $short = pht('Revoke Token'); + $body = pht( + 'Really revoke this token? Any temporary authorization it enables '. + 'will be disabled.'); + } + + return $this->newDialog() + ->setTitle($title) + ->setShortTitle($short) + ->appendParagraph($body) + ->addSubmitButton(pht('Revoke')) + ->addCancelButton($panel_uri); + } + + +} diff --git a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php --- a/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php +++ b/src/applications/auth/storage/PhabricatorAuthTemporaryToken.php @@ -17,6 +17,33 @@ ) + parent::getConfiguration(); } + public function getTokenReadableTypeName() { + // Eventually, it would be nice to let applications implement token types + // so we can put this in modular subclasses. + switch ($this->tokenType) { + case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: + return pht('One-Time Login Token'); + case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: + return pht('Password Reset Token'); + } + + return $this->tokenType; + } + + public function isRevocable() { + if ($this->tokenExpires < time()) { + return false; + } + + switch ($this->tokenType) { + case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: + case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: + return true; + } + + return false; + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -40,5 +67,4 @@ return null; } - } diff --git a/src/applications/settings/panel/PhabricatorSettingsPanelTokens.php b/src/applications/settings/panel/PhabricatorSettingsPanelTokens.php new file mode 100644 --- /dev/null +++ b/src/applications/settings/panel/PhabricatorSettingsPanelTokens.php @@ -0,0 +1,100 @@ +getUser(); + + $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) + ->setViewer($viewer) + ->withObjectPHIDs(array($viewer->getPHID())) + ->execute(); + + $rows = array(); + foreach ($tokens as $token) { + + if ($token->isRevocable()) { + $button = javelin_tag( + 'a', + array( + 'href' => '/auth/token/revoke/'.$token->getID().'/', + 'class' => 'small grey button', + 'sigil' => 'workflow', + ), + pht('Revoke')); + } else { + $button = javelin_tag( + 'a', + array( + 'class' => 'small grey button disabled', + ), + pht('Revoke')); + } + + if ($token->getTokenExpires() >= time()) { + $expiry = phabricator_datetime($token->getTokenExpires(), $viewer); + } else { + $expiry = pht('Expired'); + } + + $rows[] = array( + $token->getTokenReadableTypeName(), + $expiry, + $button, + ); + } + + $table = new AphrontTableView($rows); + $table->setNoDataString(pht("You don't have any active tokens.")); + $table->setHeaders( + array( + pht('Type'), + pht('Expires'), + pht(''), + )); + $table->setColumnClasses( + array( + 'wide', + 'right', + 'action', + )); + + + $terminate_icon = id(new PHUIIconView()) + ->setIconFont('fa-exclamation-triangle'); + $terminate_button = id(new PHUIButtonView()) + ->setText(pht('Revoke All')) + ->setHref('/auth/token/revoke/all/') + ->setTag('a') + ->setWorkflow(true) + ->setIcon($terminate_icon); + + $header = id(new PHUIHeaderView()) + ->setHeader(pht('Temporary Tokens')) + ->addActionLink($terminate_button); + + $panel = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->appendChild($table); + + return $panel; + } + +}