Skip to content

Commit

Permalink
RefreshTokenGrant: add option whether to revoke refreshed access tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
josiasmontag committed Sep 29, 2023
1 parent ab7714d commit 0faa481
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/AuthorizationServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ class AuthorizationServer implements EmitterAwareInterface
*/
private $revokeRefreshTokens = true;

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

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

$this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType;
$this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL;
Expand Down Expand Up @@ -249,4 +255,14 @@ public function revokeRefreshTokens(bool $revokeRefreshTokens): void
{
$this->revokeRefreshTokens = $revokeRefreshTokens;
}

/**
* Sets whether to revoke access tokens after they were refreshed or not (for all grant types).
*
* @param bool $revokeRefreshedAccessTokens
*/
public function revokeRefreshedAccessTokens(bool $revokeRefreshedAccessTokens): void
{
$this->revokeRefreshedAccessTokens = $revokeRefreshedAccessTokens;
}
}
13 changes: 13 additions & 0 deletions src/Grant/AbstractGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
protected $revokeRefreshTokens;

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

/**
* @param ClientRepositoryInterface $clientRepository
*/
Expand Down Expand Up @@ -180,6 +185,14 @@ public function revokeRefreshTokens(bool $revokeRefreshTokens)
$this->revokeRefreshTokens = $revokeRefreshTokens;
}

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

/**
* Validate the client.
*
Expand Down
4 changes: 3 additions & 1 deletion src/Grant/RefreshTokenGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ public function respondToAccessTokenRequest(
}

// Expire old tokens
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
if ($this->revokeRefreshedAccessTokens) {
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
}
if ($this->revokeRefreshTokens) {
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
}
Expand Down
116 changes: 116 additions & 0 deletions tests/Grant/RefreshTokenGrantTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -584,4 +584,120 @@ public function testUnrevokedRefreshToken()

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

public function testRevokedAccessToken()
{
$accessTokenId = 'abcdef';

$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();
$accessTokenRepositoryMock->method('isAccessTokenRevoked')->willReturn(true);
$accessTokenRepositoryMock->expects($this->once())->method('revokeAccessToken')->with($this->equalTo($accessTokenId));

$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();


$oldRefreshToken = $this->cryptStub->doEncrypt(
\json_encode(
[
'client_id' => 'foo',
'refresh_token_id' => 'foo',
'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->revokeRefreshedAccessTokens(true);
$grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M'));

Assert::assertTrue($accessTokenRepositoryMock->isAccessTokenRevoked($accessTokenId));
}

public function testUnrevokedAccessToken()
{
$accessTokenId = 'abcdef';

$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();
$accessTokenRepositoryMock->method('isAccessTokenRevoked')->willReturn(false);
$accessTokenRepositoryMock->expects($this->never())->method('revokeAccessToken');

$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();


$oldRefreshToken = $this->cryptStub->doEncrypt(
\json_encode(
[
'client_id' => 'foo',
'refresh_token_id' => 'foo',
'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($accessTokenRepositoryMock->isAccessTokenRevoked($accessTokenId));
}

}

0 comments on commit 0faa481

Please sign in to comment.