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:
parent
8e3ea4e034
commit
a837c3d73e
14 changed files with 79 additions and 49 deletions
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken
|
||||||
|
CHANGE objectPHID tokenResource VARBINARY(64) NOT NULL;
|
2
resources/sql/autopatches/20160316.lfs.02.token.user.sql
Normal file
2
resources/sql/autopatches/20160316.lfs.02.token.user.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken
|
||||||
|
ADD userPHID VARBINARY(64);
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE {$NAMESPACE}_auth.auth_temporarytoken
|
||||||
|
ADD properties LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
||||||
|
UPDATE {$NAMESPACE}_auth.auth_temporarytoken
|
||||||
|
SET properties = '{}' WHERE properties = '';
|
|
@ -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))
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 )----------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue