@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add an explicit temporary token management page to Settings

Summary:
Ref T5506. This makes it easier to understand and manage temporary tokens.

Eventually this could be more user-friendly, since it's relatively difficult to understand what this screen means. My short-term goal is just to make the next change easier to implement and test.

The next diff will close a small security weakness: if you change your email address, password reset links which were sent to the old address are still valid. Although an attacker would need substantial access to exploit this (essentially, it would just make it easier for them to re-compromise an already compromised account), it's a bit surprising. In the next diff, email address changes will invalidate outstanding password reset links.

Test Plan:
- Viewed outstanding tokens.
- Added tokens to the list by making "Forgot your password?" requests.
- Revoked tokens individually.
- Revoked all tokens.
- Tried to use a revoked token.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T5506

Differential Revision: https://secure.phabricator.com/D10133

+211 -1
+4
src/__phutil_library_map__.php
··· 1192 1192 'PhabricatorAuthProviderConfigTransaction' => 'applications/auth/storage/PhabricatorAuthProviderConfigTransaction.php', 1193 1193 'PhabricatorAuthProviderConfigTransactionQuery' => 'applications/auth/query/PhabricatorAuthProviderConfigTransactionQuery.php', 1194 1194 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 1195 + 'PhabricatorAuthRevokeTokenController' => 'applications/auth/controller/PhabricatorAuthRevokeTokenController.php', 1195 1196 'PhabricatorAuthSession' => 'applications/auth/storage/PhabricatorAuthSession.php', 1196 1197 'PhabricatorAuthSessionEngine' => 'applications/auth/engine/PhabricatorAuthSessionEngine.php', 1197 1198 'PhabricatorAuthSessionGarbageCollector' => 'applications/auth/garbagecollector/PhabricatorAuthSessionGarbageCollector.php', ··· 2146 2147 'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php', 2147 2148 'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php', 2148 2149 'PhabricatorSettingsPanelSessions' => 'applications/settings/panel/PhabricatorSettingsPanelSessions.php', 2150 + 'PhabricatorSettingsPanelTokens' => 'applications/settings/panel/PhabricatorSettingsPanelTokens.php', 2149 2151 'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php', 2150 2152 'PhabricatorSetupCheckAPC' => 'applications/config/check/PhabricatorSetupCheckAPC.php', 2151 2153 'PhabricatorSetupCheckAphlict' => 'applications/notification/setup/PhabricatorSetupCheckAphlict.php', ··· 3986 3988 'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction', 3987 3989 'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 3988 3990 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', 3991 + 'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController', 3989 3992 'PhabricatorAuthSession' => array( 3990 3993 'PhabricatorAuthDAO', 3991 3994 'PhabricatorPolicyInterface', ··· 4999 5002 'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel', 5000 5003 'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel', 5001 5004 'PhabricatorSettingsPanelSessions' => 'PhabricatorSettingsPanel', 5005 + 'PhabricatorSettingsPanelTokens' => 'PhabricatorSettingsPanel', 5002 5006 'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck', 5003 5007 'PhabricatorSetupCheckAphlict' => 'PhabricatorSetupCheck', 5004 5008 'PhabricatorSetupCheckAuth' => 'PhabricatorSetupCheck',
+2
src/applications/auth/application/PhabricatorAuthApplication.php
··· 103 103 => 'PhabricatorAuthConfirmLinkController', 104 104 'session/terminate/(?P<id>[^/]+)/' 105 105 => 'PhabricatorAuthTerminateSessionController', 106 + 'token/revoke/(?P<id>[^/]+)/' 107 + => 'PhabricatorAuthRevokeTokenController', 106 108 'session/downgrade/' 107 109 => 'PhabricatorAuthDowngradeSessionController', 108 110 'multifactor/'
+78
src/applications/auth/controller/PhabricatorAuthRevokeTokenController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthRevokeTokenController 4 + extends PhabricatorAuthController { 5 + 6 + private $id; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->id = $data['id']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $is_all = ($this->id === 'all'); 17 + 18 + $query = id(new PhabricatorAuthTemporaryTokenQuery()) 19 + ->setViewer($viewer) 20 + ->withObjectPHIDs(array($viewer->getPHID())); 21 + if (!$is_all) { 22 + $query->withIDs(array($this->id)); 23 + } 24 + 25 + $tokens = $query->execute(); 26 + foreach ($tokens as $key => $token) { 27 + if (!$token->isRevocable()) { 28 + // Don't revoke unrevocable tokens. 29 + unset($tokens[$key]); 30 + } 31 + } 32 + 33 + $panel_uri = '/settings/panel/tokens/'; 34 + 35 + if (!$tokens) { 36 + return $this->newDialog() 37 + ->setTitle(pht('No Matching Tokens')) 38 + ->appendParagraph( 39 + pht('There are no matching tokens to revoke.')) 40 + ->appendParagraph( 41 + pht( 42 + '(Some types of token can not be revoked, and you can not revoke '. 43 + 'tokens which have already expired.)')) 44 + ->addCancelButton($panel_uri); 45 + } 46 + 47 + if ($request->isDialogFormPost()) { 48 + foreach ($tokens as $token) { 49 + $token->setTokenExpires(PhabricatorTime::getNow() - 1)->save(); 50 + } 51 + return id(new AphrontRedirectResponse())->setURI($panel_uri); 52 + } 53 + 54 + if ($is_all) { 55 + $title = pht('Revoke Tokens?'); 56 + $short = pht('Revoke Tokens'); 57 + $body = pht( 58 + 'Really revoke all tokens? Among other temporary authorizations, '. 59 + 'this will disable any outstanding password reset or account '. 60 + 'recovery links.'); 61 + } else { 62 + $title = pht('Revoke Token?'); 63 + $short = pht('Revoke Token'); 64 + $body = pht( 65 + 'Really revoke this token? Any temporary authorization it enables '. 66 + 'will be disabled.'); 67 + } 68 + 69 + return $this->newDialog() 70 + ->setTitle($title) 71 + ->setShortTitle($short) 72 + ->appendParagraph($body) 73 + ->addSubmitButton(pht('Revoke')) 74 + ->addCancelButton($panel_uri); 75 + } 76 + 77 + 78 + }
+27 -1
src/applications/auth/storage/PhabricatorAuthTemporaryToken.php
··· 17 17 ) + parent::getConfiguration(); 18 18 } 19 19 20 + public function getTokenReadableTypeName() { 21 + // Eventually, it would be nice to let applications implement token types 22 + // so we can put this in modular subclasses. 23 + switch ($this->tokenType) { 24 + case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: 25 + return pht('One-Time Login Token'); 26 + case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: 27 + return pht('Password Reset Token'); 28 + } 29 + 30 + return $this->tokenType; 31 + } 32 + 33 + public function isRevocable() { 34 + if ($this->tokenExpires < time()) { 35 + return false; 36 + } 37 + 38 + switch ($this->tokenType) { 39 + case PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE: 40 + case PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE: 41 + return true; 42 + } 43 + 44 + return false; 45 + } 46 + 20 47 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 21 48 22 49 ··· 39 66 public function describeAutomaticCapability($capability) { 40 67 return null; 41 68 } 42 - 43 69 44 70 }
+100
src/applications/settings/panel/PhabricatorSettingsPanelTokens.php
··· 1 + <?php 2 + 3 + final class PhabricatorSettingsPanelTokens 4 + extends PhabricatorSettingsPanel { 5 + 6 + public function getPanelKey() { 7 + return 'tokens'; 8 + } 9 + 10 + public function getPanelName() { 11 + return pht('Temporary Tokens'); 12 + } 13 + 14 + public function getPanelGroup() { 15 + return pht('Sessions and Logs'); 16 + } 17 + 18 + public function isEnabled() { 19 + return true; 20 + } 21 + 22 + public function processRequest(AphrontRequest $request) { 23 + $viewer = $request->getUser(); 24 + 25 + $tokens = id(new PhabricatorAuthTemporaryTokenQuery()) 26 + ->setViewer($viewer) 27 + ->withObjectPHIDs(array($viewer->getPHID())) 28 + ->execute(); 29 + 30 + $rows = array(); 31 + foreach ($tokens as $token) { 32 + 33 + if ($token->isRevocable()) { 34 + $button = javelin_tag( 35 + 'a', 36 + array( 37 + 'href' => '/auth/token/revoke/'.$token->getID().'/', 38 + 'class' => 'small grey button', 39 + 'sigil' => 'workflow', 40 + ), 41 + pht('Revoke')); 42 + } else { 43 + $button = javelin_tag( 44 + 'a', 45 + array( 46 + 'class' => 'small grey button disabled', 47 + ), 48 + pht('Revoke')); 49 + } 50 + 51 + if ($token->getTokenExpires() >= time()) { 52 + $expiry = phabricator_datetime($token->getTokenExpires(), $viewer); 53 + } else { 54 + $expiry = pht('Expired'); 55 + } 56 + 57 + $rows[] = array( 58 + $token->getTokenReadableTypeName(), 59 + $expiry, 60 + $button, 61 + ); 62 + } 63 + 64 + $table = new AphrontTableView($rows); 65 + $table->setNoDataString(pht("You don't have any active tokens.")); 66 + $table->setHeaders( 67 + array( 68 + pht('Type'), 69 + pht('Expires'), 70 + pht(''), 71 + )); 72 + $table->setColumnClasses( 73 + array( 74 + 'wide', 75 + 'right', 76 + 'action', 77 + )); 78 + 79 + 80 + $terminate_icon = id(new PHUIIconView()) 81 + ->setIconFont('fa-exclamation-triangle'); 82 + $terminate_button = id(new PHUIButtonView()) 83 + ->setText(pht('Revoke All')) 84 + ->setHref('/auth/token/revoke/all/') 85 + ->setTag('a') 86 + ->setWorkflow(true) 87 + ->setIcon($terminate_icon); 88 + 89 + $header = id(new PHUIHeaderView()) 90 + ->setHeader(pht('Temporary Tokens')) 91 + ->addActionLink($terminate_button); 92 + 93 + $panel = id(new PHUIObjectBoxView()) 94 + ->setHeader($header) 95 + ->appendChild($table); 96 + 97 + return $panel; 98 + } 99 + 100 + }