Skip to content

Commit

Permalink
Merge pull request #1189 from dnhb/master
Browse files Browse the repository at this point in the history
Add switch to prevent revoking of refresh tokens.
  • Loading branch information
Sephster authored Jun 3, 2021
2 parents 9bfb699 + d0cf492 commit 8c33b52
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 7 deletions.
16 changes: 16 additions & 0 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ class AuthorizationServer implements EmitterAwareInterface
*/
private $defaultScope = '';

/**
* @var bool
*/
private $revokeRefreshTokens = true;

/**
* New server instance.
*
Expand Down Expand Up @@ -136,6 +141,7 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc
$grantType->setPrivateKey($this->privateKey);
$grantType->setEmitter($this->getEmitter());
$grantType->setEncryptionKey($this->encryptionKey);
$grantType->setRevokeRefreshTokens($this->revokeRefreshTokens);

$this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType;
$this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL;
Expand Down Expand Up @@ -233,4 +239,14 @@ public function setDefaultScope($defaultScope)
{
$this->defaultScope = $defaultScope;
}

/**
* Sets wether to revoke refresh tokens or not (for all grant types).
*
* @param bool $revokeRefreshTokens
*/
public function setRevokeRefreshTokens(bool $revokeRefreshTokens): void
{
$this->revokeRefreshTokens = $revokeRefreshTokens;
}
}
17 changes: 15 additions & 2 deletions src/Grant/AbstractGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
protected $defaultScope;

/**
* @var bool
*/
protected $revokeRefreshTokens;

/**
* @param ClientRepositoryInterface $clientRepository
*/
Expand Down Expand Up @@ -167,6 +172,14 @@ public function setDefaultScope($scope)
$this->defaultScope = $scope;
}

/**
* @param bool $revokeRefreshTokens
*/
public function setRevokeRefreshTokens(bool $revokeRefreshTokens)
{
$this->revokeRefreshTokens = $revokeRefreshTokens;
}

/**
* Validate the client.
*
Expand All @@ -178,7 +191,7 @@ public function setDefaultScope($scope)
*/
protected function validateClient(ServerRequestInterface $request)
{
list($clientId, $clientSecret) = $this->getClientCredentials($request);
[$clientId, $clientSecret] = $this->getClientCredentials($request);

if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
Expand Down Expand Up @@ -239,7 +252,7 @@ protected function getClientEntityOrFail($clientId, ServerRequestInterface $requ
*/
protected function getClientCredentials(ServerRequestInterface $request)
{
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
[$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request);

$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);

Expand Down
14 changes: 9 additions & 5 deletions src/Grant/RefreshTokenGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,23 @@ public function respondToAccessTokenRequest(

// Expire old tokens
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
if ($this->revokeRefreshTokens) {
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
}

// Issue and persist new access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
$this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken));
$responseType->setAccessToken($accessToken);

// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken);
if ($this->revokeRefreshTokens) {
$refreshToken = $this->issueRefreshToken($accessToken);

if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));
$responseType->setRefreshToken($refreshToken);
if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));
$responseType->setRefreshToken($refreshToken);
}
}

return $responseType;
Expand Down
117 changes: 117 additions & 0 deletions tests/Grant/RefreshTokenGrantTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use LeagueTests\Stubs\RefreshTokenEntity;
use LeagueTests\Stubs\ScopeEntity;
use LeagueTests\Stubs\StubResponseType;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;

class RefreshTokenGrantTest extends TestCase
Expand Down Expand Up @@ -68,6 +69,7 @@ public function testRespondToRequest()
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setRevokeRefreshTokens(true);

$oldRefreshToken = $this->cryptStub->doEncrypt(
\json_encode(
Expand Down Expand Up @@ -181,6 +183,7 @@ public function testRespondToReducedScopes()
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setRevokeRefreshTokens(true);

$oldRefreshToken = $this->cryptStub->doEncrypt(
\json_encode(
Expand Down Expand Up @@ -467,4 +470,118 @@ public function testRespondToRequestRevokedToken()

$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}

public function testRevokedRefreshToken()
{
$refreshTokenId = 'foo';

$client = new ClientEntity();
$client->setIdentifier('foo');
$client->setRedirectUri('http://foo/bar');

$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);

$scopeEntity = new ScopeEntity();
$scopeEntity->setIdentifier('foo');

$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);

$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf();

$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
$refreshTokenRepositoryMock->method('isRefreshTokenRevoked')
->will($this->onConsecutiveCalls(false, true));
$refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken')->with($this->equalTo($refreshTokenId));

$oldRefreshToken = $this->cryptStub->doEncrypt(
\json_encode(
[
'client_id' => 'foo',
'refresh_token_id' => $refreshTokenId,
'access_token_id' => 'abcdef',
'scopes' => ['foo'],
'user_id' => 123,
'expire_time' => \time() + 3600,
]
)
);

$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
'scope' => ['foo'],
]);

$grant = new RefreshTokenGrant($refreshTokenRepositoryMock);
$grant->setClientRepository($clientRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setRevokeRefreshTokens(true);
$grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M'));

Assert::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId));
}

public function testUnrevokedRefreshToken()
{
$refreshTokenId = 'foo';

$client = new ClientEntity();
$client->setIdentifier('foo');
$client->setRedirectUri('http://foo/bar');

$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);

$scopeEntity = new ScopeEntity();
$scopeEntity->setIdentifier('foo');

$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);

$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf();

$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
$refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(false);
$refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken');

$oldRefreshToken = $this->cryptStub->doEncrypt(
\json_encode(
[
'client_id' => 'foo',
'refresh_token_id' => $refreshTokenId,
'access_token_id' => 'abcdef',
'scopes' => ['foo'],
'user_id' => 123,
'expire_time' => \time() + 3600,
]
)
);

$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
'scope' => ['foo'],
]);

$grant = new RefreshTokenGrant($refreshTokenRepositoryMock);
$grant->setClientRepository($clientRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M'));

Assert::assertFalse($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId));
}
}

0 comments on commit 8c33b52

Please sign in to comment.