Skip to content

Commit 9122091

Browse files
Merge branch 'master' into psr-event-dispatcher
2 parents 56b2deb + 4ea27e8 commit 9122091

File tree

5 files changed

+162
-9
lines changed

5 files changed

+162
-9
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
9+
## [8.3.0] - released 2021-06-03
810
### Added
911
- The server will now validate redirect uris according to rfc8252 (PR #1203)
1012
- Events emitted now include the refresh token and access token payloads (PR #1211)
13+
- Use the `revokeRefreshTokens()` function to decide whether refresh tokens are revoked or not upon use (PR #1189)
1114

1215
### Changed
1316
- Keys are now validated using `openssl_pkey_get_private()` and openssl_pkey_get_public()` instead of regex matching (PR #1215)
@@ -538,7 +541,8 @@ Version 5 is a complete code rewrite.
538541

539542
- First major release
540543

541-
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...HEAD
544+
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...HEAD
545+
[8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0
542546
[8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4
543547
[8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3
544548
[8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2

src/AuthorizationServer.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ class AuthorizationServer implements EventDispatcherAwareInterface
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->useEventDispatcher($this->eventDispatcher);
138143
$grantType->setEncryptionKey($this->encryptionKey);
144+
$grantType->revokeRefreshTokens($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 revokeRefreshTokens(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 revokeRefreshTokens(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->dispatchEvent(new ClientAuthenticationFailed($clientId, $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 & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,19 +65,22 @@ 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->dispatchEvent(new AccessTokenIssued($accessToken, $request));
7375
$responseType->setAccessToken($accessToken);
7476

7577
// Issue and persist new refresh token if given
76-
$refreshToken = $this->issueRefreshToken($accessToken);
77-
78-
if ($refreshToken !== null) {
79-
$this->dispatchEvent(new RefreshTokenIssued($refreshToken, $request));
80-
$responseType->setRefreshToken($refreshToken);
78+
if ($this->revokeRefreshTokens) {
79+
$refreshToken = $this->issueRefreshToken($accessToken);
80+
if ($refreshToken !== null) {
81+
$this->dispatchEvent(new RefreshTokenIssued($refreshToken, $request));
82+
$responseType->setRefreshToken($refreshToken);
83+
}
8184
}
8285

8386
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->revokeRefreshTokens(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->revokeRefreshTokens(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->revokeRefreshTokens(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)