1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-19 20:10:55 +01:00

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
This commit is contained in:
epriestley 2014-08-04 12:04:13 -07:00
parent e8d272b0da
commit 30f6405a86
5 changed files with 211 additions and 1 deletions

View file

@ -1192,6 +1192,7 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'PhabricatorAuthProviderConfigTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorAuthProviderConfigTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorAuthRegisterController' => 'PhabricatorAuthController',
'PhabricatorAuthRevokeTokenController' => 'PhabricatorAuthController',
'PhabricatorAuthSession' => array(
'PhabricatorAuthDAO',
'PhabricatorPolicyInterface',
@ -4999,6 +5002,7 @@ phutil_register_library_map(array(
'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSessions' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelTokens' => 'PhabricatorSettingsPanel',
'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckAphlict' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckAuth' => 'PhabricatorSetupCheck',

View file

@ -103,6 +103,8 @@ final class PhabricatorAuthApplication extends PhabricatorApplication {
=> 'PhabricatorAuthConfirmLinkController',
'session/terminate/(?P<id>[^/]+)/'
=> 'PhabricatorAuthTerminateSessionController',
'token/revoke/(?P<id>[^/]+)/'
=> 'PhabricatorAuthRevokeTokenController',
'session/downgrade/'
=> 'PhabricatorAuthDowngradeSessionController',
'multifactor/'

View file

@ -0,0 +1,78 @@
<?php
final class PhabricatorAuthRevokeTokenController
extends PhabricatorAuthController {
private $id;
public function willProcessRequest(array $data) {
$this->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);
}
}

View file

@ -17,6 +17,33 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
) + 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 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
return null;
}
}

View file

@ -0,0 +1,100 @@
<?php
final class PhabricatorSettingsPanelTokens
extends PhabricatorSettingsPanel {
public function getPanelKey() {
return 'tokens';
}
public function getPanelName() {
return pht('Temporary Tokens');
}
public function getPanelGroup() {
return pht('Sessions and Logs');
}
public function isEnabled() {
return true;
}
public function processRequest(AphrontRequest $request) {
$viewer = $request->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;
}
}