diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 4d6862157..6102e1e62 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -84,6 +84,11 @@ class AuthorizationServer implements EmitterAwareInterface */ private $revokeRefreshTokens = true; + /** + * @var bool + */ + private $revokeRefreshedAccessTokens = true; + /** * New server instance. * @@ -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; @@ -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; + } } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c8eb6b813..fa4f739e7 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -98,6 +98,11 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $revokeRefreshTokens; + /** + * @var bool + */ + protected $revokeRefreshedAccessTokens; + /** * @param ClientRepositoryInterface $clientRepository */ @@ -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. * diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 2dedf15c3..0fced8689 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -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']); } diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 8f56fac4c..774155a76 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -584,4 +584,119 @@ 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)); + } }