Fix Access Token reissue with
Rotate Refresh Token policy active If user configured his/her oauth2 client with "Rotate Refresh Token Policy" active once emmited access token re issuing that newly access token create was invalid. Change-Id: I037c8b561dc5b720c71ccba7d8e2a081fb9783e5
This commit is contained in:
parent
d83b763c2c
commit
07294e97ac
|
@ -30,7 +30,7 @@ class RefreshToken extends BaseModelEloquent {
|
|||
|
||||
private $friendly_scopes;
|
||||
|
||||
protected $fillable = array('value', 'from_ip', 'lifetime','scope','audience','void','created_at','updated_at','client_id');
|
||||
protected $fillable = ['value', 'from_ip', 'lifetime','scope','audience','void','created_at','updated_at','client_id'];
|
||||
|
||||
public function access_tokens()
|
||||
{
|
||||
|
|
|
@ -23,7 +23,6 @@ use jwt\JWTClaim;
|
|||
use OAuth2\Models\AccessToken;
|
||||
use Models\OAuth2\AccessToken as AccessTokenDB;
|
||||
use Models\OAuth2\RefreshToken as RefreshTokenDB;
|
||||
use Models\OAuth2\ResourceServer;
|
||||
use OAuth2\Builders\IdTokenBuilder;
|
||||
use OAuth2\Exceptions\AbsentClientException;
|
||||
use OAuth2\Exceptions\AbsentCurrentUserException;
|
||||
|
@ -415,21 +414,10 @@ final class TokenService implements ITokenService
|
|||
)
|
||||
);
|
||||
|
||||
$cache_service = $this->cache_service;
|
||||
$client_service = $this->client_service;
|
||||
$auth_service = $this->auth_service;
|
||||
$client_repository = $this->client_repository;
|
||||
$access_token_repository = $this->access_token_repository;
|
||||
|
||||
return $this->tx_service->transaction(function () use (
|
||||
$auth_code,
|
||||
$redirect_uri,
|
||||
$access_token,
|
||||
$cache_service,
|
||||
$client_service,
|
||||
$auth_service,
|
||||
$client_repository,
|
||||
$access_token_repository
|
||||
$access_token
|
||||
) {
|
||||
|
||||
$value = $access_token->getValue();
|
||||
|
@ -437,8 +425,8 @@ final class TokenService implements ITokenService
|
|||
//oauth2 client id
|
||||
$client_id = $access_token->getClientId();
|
||||
$user_id = $access_token->getUserId();
|
||||
$client = $client_repository->getClientById($client_id);
|
||||
$user = $auth_service->getUserById($user_id);
|
||||
$client = $this->client_repository->getClientById($client_id);
|
||||
$user = $this->auth_service->getUserById($user_id);
|
||||
|
||||
// TODO; move to a factory
|
||||
|
||||
|
@ -458,7 +446,7 @@ final class TokenService implements ITokenService
|
|||
|
||||
$access_token_db->user()->associate($user);
|
||||
|
||||
$access_token_repository->add($access_token_db);
|
||||
$this->access_token_repository->add($access_token_db);
|
||||
|
||||
//check if use refresh tokens...
|
||||
Log::debug
|
||||
|
@ -507,9 +495,9 @@ final class TokenService implements ITokenService
|
|||
|
||||
$this->storesAccessTokenOnCache($access_token);
|
||||
//stores brand new access token hash value on a set by client id...
|
||||
$cache_service->addMemberSet($client_id . TokenService::ClientAccessTokenPrefixList, $hashed_value);
|
||||
$this->cache_service->addMemberSet($client_id . TokenService::ClientAccessTokenPrefixList, $hashed_value);
|
||||
|
||||
$cache_service->incCounter
|
||||
$this->cache_service->incCounter
|
||||
(
|
||||
$client_id . TokenService::ClientAccessTokensQty,
|
||||
TokenService::ClientAccessTokensQtyLifetime
|
||||
|
@ -715,11 +703,26 @@ final class TokenService implements ITokenService
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccessToken $access_token
|
||||
* @return bool
|
||||
*/
|
||||
private function clearAccessTokenOnCache(AccessToken $access_token){
|
||||
$value = $access_token->getValue();
|
||||
$hashed_value = Hash::compute('sha256', $value);
|
||||
|
||||
if ($this->cache_service->exists($hashed_value)) {
|
||||
$this->cache_service->delete($hashed_value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccessToken $access_token
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
public function storesAccessTokenOnCache(AccessToken $access_token)
|
||||
private function storesAccessTokenOnCache(AccessToken $access_token)
|
||||
{
|
||||
//stores in REDIS
|
||||
|
||||
|
@ -751,11 +754,24 @@ final class TokenService implements ITokenService
|
|||
], intval($access_token->getLifetime()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccessTokenDB $access_token
|
||||
* @return bool
|
||||
*/
|
||||
private function clearAccessTokenDBOnCache(AccessTokenDB $access_token){
|
||||
|
||||
if ($this->cache_service->exists($access_token->value)) {
|
||||
$this->cache_service->delete($access_token->value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccessTokenDB $access_token
|
||||
* @throws InvalidAccessTokenException
|
||||
*/
|
||||
public function storeAccessTokenDBOnCache(AccessTokenDB $access_token)
|
||||
private function storeAccessTokenDBOnCache(AccessTokenDB $access_token)
|
||||
{
|
||||
//stores in Cache
|
||||
|
||||
|
@ -796,18 +812,10 @@ final class TokenService implements ITokenService
|
|||
*/
|
||||
public function getAccessToken($value, $is_hashed = false)
|
||||
{
|
||||
$cache_service = $this->cache_service;
|
||||
$lock_manager_service = $this->lock_manager_service;
|
||||
$configuration_service = $this->configuration_service;
|
||||
$access_token_repository = $this->access_token_repository;
|
||||
|
||||
return $this->tx_service->transaction(function () use (
|
||||
$value,
|
||||
$is_hashed,
|
||||
$cache_service,
|
||||
$lock_manager_service,
|
||||
$access_token_repository,
|
||||
$configuration_service
|
||||
$is_hashed
|
||||
) {
|
||||
//hash the given value, bc tokens values are stored hashed on DB
|
||||
$hashed_value = !$is_hashed ? Hash::compute('sha256', $value) : $value;
|
||||
|
@ -816,11 +824,11 @@ final class TokenService implements ITokenService
|
|||
try
|
||||
{
|
||||
// check cache ...
|
||||
if (!$cache_service->exists($hashed_value))
|
||||
if (!$this->cache_service->exists($hashed_value))
|
||||
{
|
||||
$lock_manager_service->lock('lock.get.accesstoken.' . $hashed_value, function() use($value, $hashed_value, $access_token_repository){
|
||||
$this->lock_manager_service->lock('lock.get.accesstoken.' . $hashed_value, function() use($value, $hashed_value){
|
||||
// check on DB...
|
||||
$access_token_db = $access_token_repository->getByValue($hashed_value);
|
||||
$access_token_db = $this->access_token_repository->getByValue($hashed_value);
|
||||
if (is_null($access_token_db))
|
||||
{
|
||||
if($this->isAccessTokenRevoked($hashed_value))
|
||||
|
@ -847,8 +855,7 @@ final class TokenService implements ITokenService
|
|||
});
|
||||
}
|
||||
|
||||
$cache_values = $cache_service->getHash($hashed_value, array
|
||||
(
|
||||
$cache_values = $this->cache_service->getHash($hashed_value,[
|
||||
'user_id',
|
||||
'client_id',
|
||||
'scope',
|
||||
|
@ -858,7 +865,7 @@ final class TokenService implements ITokenService
|
|||
'from_ip',
|
||||
'audience',
|
||||
'refresh_token'
|
||||
));
|
||||
]);
|
||||
|
||||
// reload auth code ...
|
||||
$auth_code = AuthorizationCode::load
|
||||
|
@ -870,7 +877,7 @@ final class TokenService implements ITokenService
|
|||
$cache_values['audience'],
|
||||
null,
|
||||
null,
|
||||
$configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime'),
|
||||
$this->configuration_service->getConfigValue('OAuth2.AuthorizationCode.Lifetime'),
|
||||
$cache_values['from_ip'],
|
||||
$access_type = OAuth2Protocol::OAuth2Protocol_AccessType_Online,
|
||||
$approval_prompt = OAuth2Protocol::OAuth2Protocol_Approval_Prompt_Auto,
|
||||
|
@ -887,6 +894,7 @@ final class TokenService implements ITokenService
|
|||
$cache_values['issued'],
|
||||
$cache_values['lifetime']
|
||||
);
|
||||
|
||||
$refresh_token_value = $cache_values['refresh_token'];
|
||||
|
||||
if (!empty($refresh_token_value)) {
|
||||
|
@ -925,9 +933,10 @@ final class TokenService implements ITokenService
|
|||
/**
|
||||
* Creates a new refresh token and associate it with given access token
|
||||
* @param AccessToken $access_token
|
||||
* @param boolean $refresh_cache
|
||||
* @return RefreshToken
|
||||
*/
|
||||
public function createRefreshToken(AccessToken &$access_token)
|
||||
public function createRefreshToken(AccessToken &$access_token, $refresh_cache = false)
|
||||
{
|
||||
$refresh_token = $this->refresh_token_generator->generate(
|
||||
RefreshToken::create(
|
||||
|
@ -936,28 +945,18 @@ final class TokenService implements ITokenService
|
|||
)
|
||||
);
|
||||
|
||||
$client_repository = $this->client_repository;
|
||||
$auth_service = $this->auth_service;
|
||||
$cache_service = $this->cache_service;
|
||||
$access_token_repository = $this->access_token_repository;
|
||||
$refresh_token_repository = $this->refresh_token_repository;
|
||||
|
||||
return $this->tx_service->transaction(function () use (
|
||||
$refresh_token,
|
||||
$access_token,
|
||||
$client_repository,
|
||||
$auth_service,
|
||||
$cache_service,
|
||||
$access_token_repository,
|
||||
$refresh_token_repository
|
||||
$refresh_cache
|
||||
) {
|
||||
$value = $refresh_token->getValue();
|
||||
//hash the given value, bc tokens values are stored hashed on DB
|
||||
$hashed_value = Hash::compute('sha256', $value);
|
||||
$client_id = $refresh_token->getClientId();
|
||||
$user_id = $refresh_token->getUserId();
|
||||
$client = $client_repository->getClientById($client_id);
|
||||
$user = $auth_service->getUserById($user_id);
|
||||
$client = $this->client_repository->getClientById($client_id);
|
||||
$user = $this->auth_service->getUserById($user_id);
|
||||
|
||||
// todo: move to a factory
|
||||
$refresh_token_db = new RefreshTokenDB (
|
||||
|
@ -972,16 +971,23 @@ final class TokenService implements ITokenService
|
|||
|
||||
$refresh_token_db->client()->associate($client);
|
||||
$refresh_token_db->user()->associate($user);
|
||||
$refresh_token_repository->add($refresh_token_db);
|
||||
$this->refresh_token_repository->add($refresh_token_db);
|
||||
//associate current access token to refresh token on DB
|
||||
$access_token_db = $this->access_token_repository->getByValue(Hash::compute('sha256', $access_token->getValue()));
|
||||
$access_token_db->refresh_token()->associate($refresh_token_db);
|
||||
|
||||
$access_token_repository->add($access_token_db);
|
||||
$this->access_token_repository->add($access_token_db);
|
||||
|
||||
$access_token->setRefreshToken($refresh_token);
|
||||
// bc refresh token could change
|
||||
if($refresh_cache) {
|
||||
if($this->clearAccessTokenOnCache($access_token))
|
||||
$this->storesAccessTokenOnCache($access_token);
|
||||
if($this->clearAccessTokenDBOnCache($access_token_db))
|
||||
$this->storeAccessTokenDBOnCache($access_token_db);
|
||||
}
|
||||
|
||||
$cache_service->incCounter
|
||||
$this->cache_service->incCounter
|
||||
(
|
||||
$client_id . TokenService::ClientRefreshTokensQty,
|
||||
TokenService::ClientRefreshTokensQtyLifetime
|
||||
|
|
|
@ -159,9 +159,8 @@ final class RefreshBearerTokenGrantType extends AbstractGrantType
|
|||
);
|
||||
}
|
||||
|
||||
$access_token = $this->token_service->createAccessTokenFromRefreshToken($refresh_token, $scope);
|
||||
|
||||
$new_refresh_token = null;
|
||||
$access_token = $this->token_service->createAccessTokenFromRefreshToken($refresh_token, $scope);
|
||||
/*
|
||||
* the authorization server could employ refresh token
|
||||
* rotation in which a new refresh token is issued with every access
|
||||
|
@ -174,7 +173,7 @@ final class RefreshBearerTokenGrantType extends AbstractGrantType
|
|||
if ($this->current_client->useRotateRefreshTokenPolicy())
|
||||
{
|
||||
$this->token_service->invalidateRefreshToken($refresh_token_value);
|
||||
$new_refresh_token = $this->token_service->createRefreshToken($access_token);
|
||||
$new_refresh_token = $this->token_service->createRefreshToken($access_token, true);
|
||||
}
|
||||
|
||||
$response = new OAuth2AccessTokenResponse
|
||||
|
|
|
@ -43,7 +43,7 @@ class RefreshToken extends Token {
|
|||
}
|
||||
|
||||
public static function create(AccessToken $access_token, $lifetime = 0){
|
||||
$instance = new self();
|
||||
$instance = new self();
|
||||
$instance->scope = $access_token->getScope();
|
||||
$instance->user_id = $access_token->getUserId();
|
||||
$instance->client_id = $access_token->getClientId();
|
||||
|
|
|
@ -123,9 +123,10 @@ interface ITokenService {
|
|||
/**
|
||||
* Creates a new refresh token and associate it with given access token
|
||||
* @param AccessToken $access_token
|
||||
* @param boolean $refresh_cache
|
||||
* @return RefreshToken
|
||||
*/
|
||||
public function createRefreshToken(AccessToken &$access_token);
|
||||
public function createRefreshToken(AccessToken &$access_token, $refresh_cache = false);
|
||||
|
||||
/**
|
||||
* Get a refresh token by its value
|
||||
|
@ -137,7 +138,6 @@ interface ITokenService {
|
|||
*/
|
||||
public function getRefreshToken($value, $is_hashed = false);
|
||||
|
||||
|
||||
/**
|
||||
* Revokes all related tokens to a specific auth code
|
||||
* @param $auth_code Authorization Code
|
||||
|
@ -181,7 +181,6 @@ interface ITokenService {
|
|||
*/
|
||||
public function invalidateRefreshToken($value, $is_hashed = false);
|
||||
|
||||
|
||||
/**
|
||||
* Revokes a give refresh token and all related access tokens
|
||||
* @param $value
|
||||
|
|
|
@ -1,5 +1,16 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Copyright 2016 OpenStack Foundation
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
**/
|
||||
use Auth\User;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use OAuth2\OAuth2Protocol;
|
||||
|
@ -11,7 +22,7 @@ use Illuminate\Support\Facades\Session;
|
|||
* Class OAuth2ProtocolTest
|
||||
* Test Suite for OAuth2 Protocol
|
||||
*/
|
||||
class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
||||
final class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
||||
{
|
||||
|
||||
private $current_realm;
|
||||
|
@ -104,7 +115,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get Auth Code Test
|
||||
*/
|
||||
|
@ -365,7 +375,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
);
|
||||
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
array(),
|
||||
|
@ -518,7 +527,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
);
|
||||
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
array(),
|
||||
|
@ -616,7 +624,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
$output = array();
|
||||
parse_str($query, $output);
|
||||
|
||||
|
||||
//do get auth token...
|
||||
$params = array(
|
||||
'code' => $output['code'],
|
||||
|
@ -624,7 +631,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_AuthCode,
|
||||
);
|
||||
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@token",
|
||||
$params,
|
||||
array(),
|
||||
|
@ -645,7 +651,6 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
$this->assertTrue(!empty($access_token));
|
||||
$this->assertTrue(!empty($refresh_token));
|
||||
|
||||
|
||||
$params = array(
|
||||
'refresh_token' => $refresh_token,
|
||||
'grant_type' => OAuth2Protocol::OAuth2Protocol_GrantType_RefreshToken,
|
||||
|
@ -667,12 +672,28 @@ class OAuth2ProtocolTest extends OpenStackIDBaseTest
|
|||
$response = json_decode($content);
|
||||
|
||||
//get new access token and new refresh token...
|
||||
$new_access_token = $response->access_token;
|
||||
$new_access_token = $response->access_token;
|
||||
$new_refresh_token = $response->refresh_token;
|
||||
|
||||
$this->assertTrue(!empty($new_access_token));
|
||||
$this->assertTrue(!empty($new_refresh_token));
|
||||
|
||||
|
||||
//do token validation ....
|
||||
$params = array(
|
||||
'token' => $new_access_token,
|
||||
);
|
||||
|
||||
$response = $this->action("POST", "OAuth2\OAuth2ProviderController@introspection",
|
||||
$params,
|
||||
array(),
|
||||
array(),
|
||||
array(),
|
||||
// Symfony interally prefixes headers with "HTTP", so
|
||||
array("HTTP_Authorization" => " Basic " . base64_encode($client_id . ':' . $client_secret)));
|
||||
|
||||
$this->assertResponseStatus(200);
|
||||
|
||||
} catch (Exception $ex) {
|
||||
throw $ex;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue