1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 21:02:41 +01:00

Make temporary token storage/schema more flexible

Summary:
Ref T10603. This makes minor updates to temporary tokens:

  - Rename `objectPHID` (which is sometimes used to store some other kind of identifier instead of a PHID) to `tokenResource` (i.e., which resource does this token permit access to?).
  - Add a `userPHID` column. For LFS tokens and some other types of tokens, I want to bind the token to both a resource (like a repository) and a user.
  - Add a `properties` column. This makes tokens more flexible and supports custom behavior (like scoping LFS tokens even more tightly).

Test Plan:
- Ran `bin/storage upgrade -f`, got a clean upgrade.
- Viewed one-time tokens.
- Revoked one token.
- Revoked all tokens.
- Performed a one-time login.
- Performed a password reset.
- Added an MFA token.
- Removed an MFA token.
- Used a file token to view a file.
- Verified file token was removed after viewing file.
- Linked my account to an OAuth1 account (Twitter).

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T10603

Differential Revision: https://secure.phabricator.com/D15478
This commit is contained in:
epriestley 2016-03-16 05:17:47 -07:00
parent 8e3ea4e034
commit a837c3d73e
14 changed files with 79 additions and 49 deletions

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken
CHANGE objectPHID tokenResource VARBINARY(64) NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken
ADD userPHID VARBINARY(64);

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_auth.auth_temporarytoken
SET properties = '{}' WHERE properties = '';

View file

@ -132,7 +132,7 @@ final class PhabricatorAuthOneTimeLoginController
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken()) id(new PhabricatorAuthTemporaryToken())
->setObjectPHID($target_user->getPHID()) ->setTokenResource($target_user->getPHID())
->setTokenType($password_type) ->setTokenType($password_type)
->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::digest($key)) ->setTokenCode(PhabricatorHash::digest($key))

View file

@ -11,7 +11,7 @@ final class PhabricatorAuthRevokeTokenController
$query = id(new PhabricatorAuthTemporaryTokenQuery()) $query = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($viewer) ->setViewer($viewer)
->withObjectPHIDs(array($viewer->getPHID())); ->withTokenResources(array($viewer->getPHID()));
if (!$is_all) { if (!$is_all) {
$query->withIDs(array($id)); $query->withIDs(array($id));
} }

View file

@ -635,7 +635,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken()) id(new PhabricatorAuthTemporaryToken())
->setObjectPHID($user->getPHID()) ->setTokenResource($user->getPHID())
->setTokenType($onetime_type) ->setTokenType($onetime_type)
->setTokenExpires(time() + phutil_units('1 day in seconds')) ->setTokenExpires(time() + phutil_units('1 day in seconds'))
->setTokenCode($key_hash) ->setTokenCode($key_hash)
@ -679,7 +679,7 @@ final class PhabricatorAuthSessionEngine extends Phobject {
return id(new PhabricatorAuthTemporaryTokenQuery()) return id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($user) ->setViewer($user)
->withObjectPHIDs(array($user->getPHID())) ->withTokenResources(array($user->getPHID()))
->withTokenTypes(array($onetime_type)) ->withTokenTypes(array($onetime_type))
->withTokenCodes(array($key_hash)) ->withTokenCodes(array($key_hash))
->withExpired(false) ->withExpired(false)

View file

@ -36,7 +36,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$temporary_token = id(new PhabricatorAuthTemporaryTokenQuery()) $temporary_token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($user) ->setViewer($user)
->withObjectPHIDs(array($user->getPHID())) ->withTokenResources(array($user->getPHID()))
->withTokenTypes(array(self::TEMPORARY_TOKEN_TYPE)) ->withTokenTypes(array(self::TEMPORARY_TOKEN_TYPE))
->withExpired(false) ->withExpired(false)
->withTokenCodes(array(PhabricatorHash::digest($key))) ->withTokenCodes(array(PhabricatorHash::digest($key)))
@ -55,7 +55,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
id(new PhabricatorAuthTemporaryToken()) id(new PhabricatorAuthTemporaryToken())
->setObjectPHID($user->getPHID()) ->setTokenResource($user->getPHID())
->setTokenType(self::TEMPORARY_TOKEN_TYPE) ->setTokenType(self::TEMPORARY_TOKEN_TYPE)
->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::digest($key)) ->setTokenCode(PhabricatorHash::digest($key))

View file

@ -221,7 +221,7 @@ abstract class PhabricatorOAuth1AuthProvider
// Wipe out an existing token, if one exists. // Wipe out an existing token, if one exists.
$token = id(new PhabricatorAuthTemporaryTokenQuery()) $token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withObjectPHIDs(array($key)) ->withTokenResources(array($key))
->withTokenTypes(array($type)) ->withTokenTypes(array($type))
->executeOne(); ->executeOne();
if ($token) { if ($token) {
@ -230,7 +230,7 @@ abstract class PhabricatorOAuth1AuthProvider
// Save the new secret. // Save the new secret.
id(new PhabricatorAuthTemporaryToken()) id(new PhabricatorAuthTemporaryToken())
->setObjectPHID($key) ->setTokenResource($key)
->setTokenType($type) ->setTokenType($type)
->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode($secret) ->setTokenCode($secret)
@ -243,7 +243,7 @@ abstract class PhabricatorOAuth1AuthProvider
$token = id(new PhabricatorAuthTemporaryTokenQuery()) $token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withObjectPHIDs(array($key)) ->withTokenResources(array($key))
->withTokenTypes(array($type)) ->withTokenTypes(array($type))
->withExpired(false) ->withExpired(false)
->executeOne(); ->executeOne();

View file

@ -4,8 +4,9 @@ final class PhabricatorAuthTemporaryTokenQuery
extends PhabricatorCursorPagedPolicyAwareQuery { extends PhabricatorCursorPagedPolicyAwareQuery {
private $ids; private $ids;
private $objectPHIDs; private $tokenResources;
private $tokenTypes; private $tokenTypes;
private $userPHIDs;
private $expired; private $expired;
private $tokenCodes; private $tokenCodes;
@ -14,8 +15,8 @@ final class PhabricatorAuthTemporaryTokenQuery
return $this; return $this;
} }
public function withObjectPHIDs(array $object_phids) { public function withTokenResources(array $resources) {
$this->objectPHIDs = $object_phids; $this->tokenResources = $resources;
return $this; return $this;
} }
@ -34,41 +35,39 @@ final class PhabricatorAuthTemporaryTokenQuery
return $this; return $this;
} }
protected function loadPage() { public function withUserPHIDs(array $phids) {
$table = new PhabricatorAuthTemporaryToken(); $this->userPHIDs = $phids;
$conn_r = $table->establishConnection('r'); return $this;
$data = queryfx_all(
$conn_r,
'SELECT * FROM %T %Q %Q %Q',
$table->getTableName(),
$this->buildWhereClause($conn_r),
$this->buildOrderClause($conn_r),
$this->buildLimitClause($conn_r));
return $table->loadAllFromArray($data);
} }
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { public function newResultObject() {
$where = array(); return new PhabricatorAuthTemporaryToken();
}
protected function loadPage() {
return $this->loadStandardPage($this->newResultObject());
}
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
$where = parent::buildWhereClauseParts($conn);
if ($this->ids !== null) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'id IN (%Ld)', 'id IN (%Ld)',
$this->ids); $this->ids);
} }
if ($this->objectPHIDs !== null) { if ($this->tokenResources !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'objectPHID IN (%Ls)', 'tokenResource IN (%Ls)',
$this->objectPHIDs); $this->tokenResources);
} }
if ($this->tokenTypes !== null) { if ($this->tokenTypes !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'tokenType IN (%Ls)', 'tokenType IN (%Ls)',
$this->tokenTypes); $this->tokenTypes);
} }
@ -76,12 +75,12 @@ final class PhabricatorAuthTemporaryTokenQuery
if ($this->expired !== null) { if ($this->expired !== null) {
if ($this->expired) { if ($this->expired) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'tokenExpires <= %d', 'tokenExpires <= %d',
time()); time());
} else { } else {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'tokenExpires > %d', 'tokenExpires > %d',
time()); time());
} }
@ -89,14 +88,19 @@ final class PhabricatorAuthTemporaryTokenQuery
if ($this->tokenCodes !== null) { if ($this->tokenCodes !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn_r, $conn,
'tokenCode IN (%Ls)', 'tokenCode IN (%Ls)',
$this->tokenCodes); $this->tokenCodes);
} }
$where[] = $this->buildPagingClause($conn_r); if ($this->userPHIDs !== null) {
$where[] = qsprintf(
$conn,
'userPHID IN (%Ls)',
$this->userPHIDs);
}
return $this->formatWhereClause($where); return $where;
} }
public function getQueryApplicationClass() { public function getQueryApplicationClass() {

View file

@ -3,30 +3,39 @@
final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
implements PhabricatorPolicyInterface { implements PhabricatorPolicyInterface {
// TODO: OAuth1 stores a client identifier here, which is not a real PHID. // NOTE: This is usually a PHID, but may be some other kind of resource
// At some point, we should rename this column to be a little more generic. // identifier for some token types.
protected $objectPHID; protected $tokenResource;
protected $tokenType; protected $tokenType;
protected $tokenExpires; protected $tokenExpires;
protected $tokenCode; protected $tokenCode;
protected $userPHID;
protected $properties;
protected function getConfiguration() { protected function getConfiguration() {
return array( return array(
self::CONFIG_TIMESTAMPS => false, self::CONFIG_TIMESTAMPS => false,
self::CONFIG_SERIALIZATION => array(
'properties' => self::SERIALIZATION_JSON,
),
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'tokenResource' => 'phid',
'tokenType' => 'text64', 'tokenType' => 'text64',
'tokenExpires' => 'epoch', 'tokenExpires' => 'epoch',
'tokenCode' => 'text64', 'tokenCode' => 'text64',
'userPHID' => 'phid?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_token' => array( 'key_token' => array(
'columns' => array('objectPHID', 'tokenType', 'tokenCode'), 'columns' => array('tokenResource', 'tokenType', 'tokenCode'),
'unique' => true, 'unique' => true,
), ),
'key_expires' => array( 'key_expires' => array(
'columns' => array('tokenExpires'), 'columns' => array('tokenExpires'),
), ),
'key_user' => array(
'columns' => array('userPHID'),
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
@ -73,12 +82,12 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
public static function revokeTokens( public static function revokeTokens(
PhabricatorUser $viewer, PhabricatorUser $viewer,
array $object_phids, array $token_resources,
array $token_types) { array $token_types) {
$tokens = id(new PhabricatorAuthTemporaryTokenQuery()) $tokens = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($viewer) ->setViewer($viewer)
->withObjectPHIDs($object_phids) ->withTokenResources($token_resources)
->withTokenTypes($token_types) ->withTokenTypes($token_types)
->withExpired(false) ->withExpired(false)
->execute(); ->execute();
@ -88,6 +97,15 @@ final class PhabricatorAuthTemporaryToken extends PhabricatorAuthDAO
} }
} }
public function getTemporaryTokenProperty($key, $default = null) {
return idx($this->properties, $key, $default);
}
public function setTemporaryTokenProperty($key, $value) {
$this->properties[$key] = $value;
return $this;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -1123,7 +1123,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
// Save the new secret. // Save the new secret.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$token = id(new PhabricatorAuthTemporaryToken()) $token = id(new PhabricatorAuthTemporaryToken())
->setObjectPHID($this->getPHID()) ->setTokenResource($this->getPHID())
->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE) ->setTokenType(self::ONETIME_TEMPORARY_TOKEN_TYPE)
->setTokenExpires(time() + phutil_units('1 hour in seconds')) ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
->setTokenCode(PhabricatorHash::digest($key)) ->setTokenCode(PhabricatorHash::digest($key))
@ -1136,7 +1136,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
public function validateOneTimeToken($token_code) { public function validateOneTimeToken($token_code) {
$token = id(new PhabricatorAuthTemporaryTokenQuery()) $token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withObjectPHIDs(array($this->getPHID())) ->withTokenResources(array($this->getPHID()))
->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE)) ->withTokenTypes(array(self::ONETIME_TEMPORARY_TOKEN_TYPE))
->withExpired(false) ->withExpired(false)
->withTokenCodes(array(PhabricatorHash::digest($token_code))) ->withTokenCodes(array(PhabricatorHash::digest($token_code)))

View file

@ -46,7 +46,7 @@ final class PhabricatorPasswordSettingsPanel extends PhabricatorSettingsPanel {
if ($key) { if ($key) {
$token = id(new PhabricatorAuthTemporaryTokenQuery()) $token = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($user) ->setViewer($user)
->withObjectPHIDs(array($user->getPHID())) ->withTokenResources(array($user->getPHID()))
->withTokenTypes(array($password_type)) ->withTokenTypes(array($password_type))
->withTokenCodes(array(PhabricatorHash::digest($key))) ->withTokenCodes(array(PhabricatorHash::digest($key)))
->withExpired(false) ->withExpired(false)

View file

@ -23,7 +23,7 @@ final class PhabricatorTokensSettingsPanel extends PhabricatorSettingsPanel {
$tokens = id(new PhabricatorAuthTemporaryTokenQuery()) $tokens = id(new PhabricatorAuthTemporaryTokenQuery())
->setViewer($viewer) ->setViewer($viewer)
->withObjectPHIDs(array($viewer->getPHID())) ->withTokenResources(array($viewer->getPHID()))
->execute(); ->execute();
$rows = array(); $rows = array();