Skip to content

Commit

Permalink
Merge branch 'master' into psr-event-dispatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
eugene-borovov authored Jun 4, 2021
2 parents 56b2deb + 4ea27e8 commit 9122091
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 9 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

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

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

- First major release

[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...HEAD
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...HEAD
[8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0
[8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4
[8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3
[8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2
Expand Down
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 EventDispatcherAwareInterface
*/
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->useEventDispatcher($this->eventDispatcher);
$grantType->setEncryptionKey($this->encryptionKey);
$grantType->revokeRefreshTokens($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 revokeRefreshTokens(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 revokeRefreshTokens(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->dispatchEvent(new ClientAuthenticationFailed($clientId, $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
15 changes: 9 additions & 6 deletions src/Grant/RefreshTokenGrant.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,22 @@ 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->dispatchEvent(new AccessTokenIssued($accessToken, $request));
$responseType->setAccessToken($accessToken);

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

if ($refreshToken !== null) {
$this->dispatchEvent(new RefreshTokenIssued($refreshToken, $request));
$responseType->setRefreshToken($refreshToken);
if ($this->revokeRefreshTokens) {
$refreshToken = $this->issueRefreshToken($accessToken);
if ($refreshToken !== null) {
$this->dispatchEvent(new RefreshTokenIssued($refreshToken, $request));
$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->revokeRefreshTokens(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->revokeRefreshTokens(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->revokeRefreshTokens(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 9122091

Please sign in to comment.