Skip to content

Commit 8c33b52

Browse files
authored
Merge pull request #1189 from dnhb/master
Add switch to prevent revoking of refresh tokens.
2 parents 9bfb699 + d0cf492 commit 8c33b52

File tree

4 files changed

+157
-7
lines changed

4 files changed

+157
-7
lines changed

src/AuthorizationServer.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ class AuthorizationServer implements EmitterAwareInterface
7979
*/
8080
private $defaultScope = '';
8181

82+
/**
83+
* @var bool
84+
*/
85+
private $revokeRefreshTokens = true;
86+
8287
/**
8388
* New server instance.
8489
*
@@ -136,6 +141,7 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc
136141
$grantType->setPrivateKey($this->privateKey);
137142
$grantType->setEmitter($this->getEmitter());
138143
$grantType->setEncryptionKey($this->encryptionKey);
144+
$grantType->setRevokeRefreshTokens($this->revokeRefreshTokens);
139145

140146
$this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType;
141147
$this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL;
@@ -233,4 +239,14 @@ public function setDefaultScope($defaultScope)
233239
{
234240
$this->defaultScope = $defaultScope;
235241
}
242+
243+
/**
244+
* Sets wether to revoke refresh tokens or not (for all grant types).
245+
*
246+
* @param bool $revokeRefreshTokens
247+
*/
248+
public function setRevokeRefreshTokens(bool $revokeRefreshTokens): void
249+
{
250+
$this->revokeRefreshTokens = $revokeRefreshTokens;
251+
}
236252
}

src/Grant/AbstractGrant.php

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ abstract class AbstractGrant implements GrantTypeInterface
9393
*/
9494
protected $defaultScope;
9595

96+
/**
97+
* @var bool
98+
*/
99+
protected $revokeRefreshTokens;
100+
96101
/**
97102
* @param ClientRepositoryInterface $clientRepository
98103
*/
@@ -167,6 +172,14 @@ public function setDefaultScope($scope)
167172
$this->defaultScope = $scope;
168173
}
169174

175+
/**
176+
* @param bool $revokeRefreshTokens
177+
*/
178+
public function setRevokeRefreshTokens(bool $revokeRefreshTokens)
179+
{
180+
$this->revokeRefreshTokens = $revokeRefreshTokens;
181+
}
182+
170183
/**
171184
* Validate the client.
172185
*
@@ -178,7 +191,7 @@ public function setDefaultScope($scope)
178191
*/
179192
protected function validateClient(ServerRequestInterface $request)
180193
{
181-
list($clientId, $clientSecret) = $this->getClientCredentials($request);
194+
[$clientId, $clientSecret] = $this->getClientCredentials($request);
182195

183196
if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
184197
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
@@ -239,7 +252,7 @@ protected function getClientEntityOrFail($clientId, ServerRequestInterface $requ
239252
*/
240253
protected function getClientCredentials(ServerRequestInterface $request)
241254
{
242-
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
255+
[$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request);
243256

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

src/Grant/RefreshTokenGrant.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,23 @@ public function respondToAccessTokenRequest(
6565

6666
// Expire old tokens
6767
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
68-
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
68+
if ($this->revokeRefreshTokens) {
69+
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
70+
}
6971

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

7577
// Issue and persist new refresh token if given
76-
$refreshToken = $this->issueRefreshToken($accessToken);
78+
if ($this->revokeRefreshTokens) {
79+
$refreshToken = $this->issueRefreshToken($accessToken);
7780

78-
if ($refreshToken !== null) {
79-
$this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));
80-
$responseType->setRefreshToken($refreshToken);
81+
if ($refreshToken !== null) {
82+
$this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));
83+
$responseType->setRefreshToken($refreshToken);
84+
}
8185
}
8286

8387
return $responseType;

tests/Grant/RefreshTokenGrantTest.php

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use LeagueTests\Stubs\RefreshTokenEntity;
1919
use LeagueTests\Stubs\ScopeEntity;
2020
use LeagueTests\Stubs\StubResponseType;
21+
use PHPUnit\Framework\Assert;
2122
use PHPUnit\Framework\TestCase;
2223

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

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

185188
$oldRefreshToken = $this->cryptStub->doEncrypt(
186189
\json_encode(
@@ -467,4 +470,118 @@ public function testRespondToRequestRevokedToken()
467470

468471
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
469472
}
473+
474+
public function testRevokedRefreshToken()
475+
{
476+
$refreshTokenId = 'foo';
477+
478+
$client = new ClientEntity();
479+
$client->setIdentifier('foo');
480+
$client->setRedirectUri('http://foo/bar');
481+
482+
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
483+
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
484+
485+
$scopeEntity = new ScopeEntity();
486+
$scopeEntity->setIdentifier('foo');
487+
488+
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
489+
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
490+
491+
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
492+
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
493+
$accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf();
494+
495+
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
496+
$refreshTokenRepositoryMock->method('isRefreshTokenRevoked')
497+
->will($this->onConsecutiveCalls(false, true));
498+
$refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken')->with($this->equalTo($refreshTokenId));
499+
500+
$oldRefreshToken = $this->cryptStub->doEncrypt(
501+
\json_encode(
502+
[
503+
'client_id' => 'foo',
504+
'refresh_token_id' => $refreshTokenId,
505+
'access_token_id' => 'abcdef',
506+
'scopes' => ['foo'],
507+
'user_id' => 123,
508+
'expire_time' => \time() + 3600,
509+
]
510+
)
511+
);
512+
513+
$serverRequest = (new ServerRequest())->withParsedBody([
514+
'client_id' => 'foo',
515+
'client_secret' => 'bar',
516+
'refresh_token' => $oldRefreshToken,
517+
'scope' => ['foo'],
518+
]);
519+
520+
$grant = new RefreshTokenGrant($refreshTokenRepositoryMock);
521+
$grant->setClientRepository($clientRepositoryMock);
522+
$grant->setScopeRepository($scopeRepositoryMock);
523+
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
524+
$grant->setEncryptionKey($this->cryptStub->getKey());
525+
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
526+
$grant->setRevokeRefreshTokens(true);
527+
$grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M'));
528+
529+
Assert::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId));
530+
}
531+
532+
public function testUnrevokedRefreshToken()
533+
{
534+
$refreshTokenId = 'foo';
535+
536+
$client = new ClientEntity();
537+
$client->setIdentifier('foo');
538+
$client->setRedirectUri('http://foo/bar');
539+
540+
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
541+
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
542+
543+
$scopeEntity = new ScopeEntity();
544+
$scopeEntity->setIdentifier('foo');
545+
546+
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
547+
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
548+
549+
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
550+
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
551+
$accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf();
552+
553+
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
554+
$refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(false);
555+
$refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken');
556+
557+
$oldRefreshToken = $this->cryptStub->doEncrypt(
558+
\json_encode(
559+
[
560+
'client_id' => 'foo',
561+
'refresh_token_id' => $refreshTokenId,
562+
'access_token_id' => 'abcdef',
563+
'scopes' => ['foo'],
564+
'user_id' => 123,
565+
'expire_time' => \time() + 3600,
566+
]
567+
)
568+
);
569+
570+
$serverRequest = (new ServerRequest())->withParsedBody([
571+
'client_id' => 'foo',
572+
'client_secret' => 'bar',
573+
'refresh_token' => $oldRefreshToken,
574+
'scope' => ['foo'],
575+
]);
576+
577+
$grant = new RefreshTokenGrant($refreshTokenRepositoryMock);
578+
$grant->setClientRepository($clientRepositoryMock);
579+
$grant->setScopeRepository($scopeRepositoryMock);
580+
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
581+
$grant->setEncryptionKey($this->cryptStub->getKey());
582+
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
583+
$grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M'));
584+
585+
Assert::assertFalse($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId));
586+
}
470587
}

0 commit comments

Comments
 (0)