Begin cleaning up OAuth scope handling
Summary: Ref T7303. OAuth scope handling never got fully modernized and is a bit of a mess. Also introduce implicit "ALWAYS" and "NEVER" scopes. Always give tokens access to meta-methods like `conduit.getcapabilities` and `conduit.query`. These do not expose user information. Test Plan: - Used a token to call `user.whoami`. - Used a token to call `conduit.query`. - Used a token to try to call `user.query`, got rebuffed. Reviewers: chad Reviewed By: chad Maniphest Tasks: T7303 Differential Revision: https://secure.phabricator.com/D15593
This commit is contained in:
@@ -29,7 +29,6 @@
|
||||
final class PhabricatorOAuthServer extends Phobject {
|
||||
|
||||
const AUTHORIZATION_CODE_TIMEOUT = 300;
|
||||
const ACCESS_TOKEN_TIMEOUT = 3600;
|
||||
|
||||
private $user;
|
||||
private $client;
|
||||
@@ -158,39 +157,35 @@ final class PhabricatorOAuthServer extends Phobject {
|
||||
/**
|
||||
* @task token
|
||||
*/
|
||||
public function validateAccessToken(
|
||||
PhabricatorOAuthServerAccessToken $token,
|
||||
$required_scope) {
|
||||
public function authorizeToken(
|
||||
PhabricatorOAuthServerAccessToken $token) {
|
||||
|
||||
$created_time = $token->getDateCreated();
|
||||
$must_be_used_by = $created_time + self::ACCESS_TOKEN_TIMEOUT;
|
||||
$expired = time() > $must_be_used_by;
|
||||
$authorization = id(new PhabricatorOAuthClientAuthorization())
|
||||
->loadOneWhere(
|
||||
'userPHID = %s AND clientPHID = %s',
|
||||
$token->getUserPHID(),
|
||||
$token->getClientPHID());
|
||||
$user_phid = $token->getUserPHID();
|
||||
$client_phid = $token->getClientPHID();
|
||||
|
||||
$authorization = id(new PhabricatorOAuthClientAuthorizationQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withUserPHIDs(array($user_phid))
|
||||
->withClientPHIDs(array($client_phid))
|
||||
->executeOne();
|
||||
if (!$authorization) {
|
||||
return false;
|
||||
}
|
||||
$token_scope = $authorization->getScope();
|
||||
if (!isset($token_scope[$required_scope])) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
$valid = true;
|
||||
if ($expired) {
|
||||
$valid = false;
|
||||
// check if the scope includes "offline_access", which makes the
|
||||
// token valid despite being expired
|
||||
if (isset(
|
||||
$token_scope[PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS])) {
|
||||
$valid = true;
|
||||
// TODO: This should probably be reworked; expiration should be an
|
||||
// exclusive property of the token. For now, this logic reads: tokens for
|
||||
// authorizations with "offline_access" never expire.
|
||||
|
||||
$is_expired = $token->isExpired();
|
||||
if ($is_expired) {
|
||||
$offline_access = PhabricatorOAuthServerScope::SCOPE_OFFLINE_ACCESS;
|
||||
$authorization_scope = $authorization->getScope();
|
||||
if (empty($authorization_scope[$offline_access])) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $valid;
|
||||
return $authorization;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,13 +4,7 @@ final class PhabricatorOAuthServerScope extends Phobject {
|
||||
|
||||
const SCOPE_OFFLINE_ACCESS = 'offline_access';
|
||||
const SCOPE_WHOAMI = 'whoami';
|
||||
const SCOPE_NOT_ACCESSIBLE = 'not_accessible';
|
||||
|
||||
/*
|
||||
* Note this does not contain SCOPE_NOT_ACCESSIBLE which is magic
|
||||
* used to simplify code for data that is not currently accessible
|
||||
* via OAuth.
|
||||
*/
|
||||
public static function getScopesDict() {
|
||||
return array(
|
||||
self::SCOPE_OFFLINE_ACCESS => 1,
|
||||
|
||||
@@ -144,7 +144,7 @@ final class PhabricatorOAuthServerTokenController
|
||||
$result = array(
|
||||
'access_token' => $access_token->getToken(),
|
||||
'token_type' => 'Bearer',
|
||||
'expires_in' => PhabricatorOAuthServer::ACCESS_TOKEN_TIMEOUT,
|
||||
'expires_in' => $access_token->getExpiresDuration(),
|
||||
);
|
||||
return $response->setContent($result);
|
||||
} catch (Exception $e) {
|
||||
|
||||
@@ -7,7 +7,7 @@ final class PhabricatorOAuthClientAuthorizationQuery
|
||||
private $userPHIDs;
|
||||
private $clientPHIDs;
|
||||
|
||||
public function witHPHIDs(array $phids) {
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
@@ -22,19 +22,12 @@ final class PhabricatorOAuthClientAuthorizationQuery
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new PhabricatorOAuthClientAuthorization();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new PhabricatorOAuthClientAuthorization();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T auth %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
return $table->loadAllFromArray($data);
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $authorizations) {
|
||||
@@ -49,43 +42,44 @@ final class PhabricatorOAuthClientAuthorizationQuery
|
||||
|
||||
foreach ($authorizations as $key => $authorization) {
|
||||
$client = idx($clients, $authorization->getClientPHID());
|
||||
|
||||
if (!$client) {
|
||||
$this->didRejectResult($authorization);
|
||||
unset($authorizations[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$authorization->attachClient($client);
|
||||
}
|
||||
|
||||
return $authorizations;
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->phids) {
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'phid IN (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->userPHIDs) {
|
||||
if ($this->userPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'userPHID IN (%Ls)',
|
||||
$this->userPHIDs);
|
||||
}
|
||||
|
||||
if ($this->clientPHIDs) {
|
||||
if ($this->clientPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'clientPHID IN (%Ls)',
|
||||
$this->clientPHIDs);
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
|
||||
@@ -22,4 +22,18 @@ final class PhabricatorOAuthServerAccessToken
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public function isExpired() {
|
||||
$now = PhabricatorTime::getNow();
|
||||
$expires_epoch = $this->getExpiresEpoch();
|
||||
return ($now > $expires_epoch);
|
||||
}
|
||||
|
||||
public function getExpiresEpoch() {
|
||||
return $this->getDateCreated() + 3600;
|
||||
}
|
||||
|
||||
public function getExpiresDuration() {
|
||||
return PhabricatorTime::getNow() - $this->getExpiresEpoch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user