diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ea711b0..b70608580 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) - An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) +- Ability to set custom claims on a JWT (PR #1122) ### Fixed - If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 6089adbf0..3ce2369fd 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -16,6 +16,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; @@ -69,6 +70,11 @@ class AuthorizationServer implements EmitterAwareInterface */ private $scopeRepository; + /** + * @var null|ClaimRepositoryInterface + */ + private $claimRepository; + /** * @var string|Key */ @@ -93,6 +99,7 @@ class AuthorizationServer implements EmitterAwareInterface * @param CryptKeyInterface|string $privateKey * @param string|Key $encryptionKey * @param null|ResponseTypeInterface $responseType + * @param null|ClaimRepositoryInterface $claimRepository */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -100,11 +107,13 @@ public function __construct( ScopeRepositoryInterface $scopeRepository, $privateKey, $encryptionKey, - ResponseTypeInterface $responseType = null + ResponseTypeInterface $responseType = null, + ClaimRepositoryInterface $claimRepository = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; $this->scopeRepository = $scopeRepository; + $this->claimRepository = $claimRepository; if ($privateKey instanceof CryptKeyInterface === false) { $privateKey = new CryptKey($privateKey); @@ -137,6 +146,7 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc $grantType->setAccessTokenRepository($this->accessTokenRepository); $grantType->setClientRepository($this->clientRepository); $grantType->setScopeRepository($this->scopeRepository); + $grantType->setClaimRepository($this->claimRepository); $grantType->setDefaultScope($this->defaultScope); $grantType->setPrivateKey($this->privateKey); $grantType->setEmitter($this->getEmitter()); diff --git a/src/Entities/ClaimEntityInterface.php b/src/Entities/ClaimEntityInterface.php new file mode 100644 index 000000000..2c45ed5ee --- /dev/null +++ b/src/Entities/ClaimEntityInterface.php @@ -0,0 +1,27 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Entities; + +interface ClaimEntityInterface +{ + /** + * Get the claim's name. + * + * @return string + */ + public function getName(); + + /** + * Get the claim's value + * + * @return mixed + */ + public function getValue(); +} diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 8b074460e..ca97e3c5f 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -15,6 +15,7 @@ use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token; use League\OAuth2\Server\CryptKeyInterface; +use League\OAuth2\Server\Entities\ClaimEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -59,14 +60,20 @@ private function convertToJWT() { $this->initJwtConfiguration(); - return $this->jwtConfiguration->builder() - ->permittedFor($this->getClient()->getIdentifier()) + $builder = $this->jwtConfiguration->builder(); + + $builder->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy($this->getIdentifier()) ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($this->getExpiryDateTime()) - ->relatedTo((string) $this->getUserIdentifier()) - ->withClaim('scopes', $this->getScopes()) + ->relatedTo((string) $this->getUserIdentifier()); + + foreach ($this->getClaims() as $claim) { + $builder->withClaim($claim->getName(), $claim->getValue()); + } + + return $builder->withClaim('scopes', $this->getScopes()) ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); } @@ -98,6 +105,11 @@ abstract public function getUserIdentifier(); */ abstract public function getScopes(); + /** + * @return ClaimEntityInterface[] + */ + abstract public function getClaims(); + /** * @return string */ diff --git a/src/Entities/Traits/ClaimEntityTrait.php b/src/Entities/Traits/ClaimEntityTrait.php new file mode 100644 index 000000000..2aeb71a90 --- /dev/null +++ b/src/Entities/Traits/ClaimEntityTrait.php @@ -0,0 +1,43 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Entities\Traits; + +trait ClaimEntityTrait +{ + /** + * @var string + */ + protected $name; + + /** + * @var mixed + */ + protected $value; + + /** + * Returns the name of the claim + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Returns the claims value + * + * @return mixed + */ + public function getValue() + { + return $this->value; + } +} diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 83b172322..70aa178e4 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -10,6 +10,7 @@ namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; +use League\OAuth2\Server\Entities\ClaimEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -20,6 +21,11 @@ trait TokenEntityTrait */ protected $scopes = []; + /** + * @var ClaimEntityInterface[] + */ + protected $claims = []; + /** * @var DateTimeImmutable */ @@ -55,6 +61,26 @@ public function getScopes() return \array_values($this->scopes); } + /** + * Associate a claim with the token. + * + * @param ClaimEntityInterface $claim + */ + public function addClaim(ClaimEntityInterface $claim) + { + $this->claims[] = $claim; + } + + /** + * Return an array of claims associated with the token. + * + * @return ClaimEntityInterface[] + */ + public function getClaims() + { + return $this->claims; + } + /** * Get the token's expiry date time. * diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index a03df5090..530100938 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -19,6 +19,7 @@ use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; +use League\OAuth2\Server\Entities\ClaimEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -27,6 +28,7 @@ use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -63,6 +65,12 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $scopeRepository; + /** + * @var null|ClaimRepositoryInterface + */ + protected $claimRepository; + + /** * @var AuthCodeRepositoryInterface */ @@ -122,6 +130,14 @@ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository) $this->scopeRepository = $scopeRepository; } + /** + * @param ClaimRepositoryInterface $claimRepository + */ + public function setClaimRepository(?ClaimRepositoryInterface $claimRepository) + { + $this->claimRepository = $claimRepository; + } + /** * @param RefreshTokenRepositoryInterface $refreshTokenRepository */ @@ -440,6 +456,7 @@ protected function getServerParameter($parameter, ServerRequestInterface $reques * @param ClientEntityInterface $client * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes + * @param ClaimEntityInterface[] $claims * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException @@ -450,7 +467,8 @@ protected function issueAccessToken( DateInterval $accessTokenTTL, ClientEntityInterface $client, $userIdentifier, - array $scopes = [] + array $scopes = [], + array $claims = [] ) { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; @@ -458,6 +476,12 @@ protected function issueAccessToken( $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); $accessToken->setPrivateKey($this->privateKey); + if (\method_exists($accessToken, 'addClaim')) { + foreach ($claims as $claim) { + $accessToken->addClaim($claim); + } + } + while ($maxGenerationAttempts-- > 0) { $accessToken->setIdentifier($this->generateUniqueIdentifier()); try { diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index d08dffc86..2d9f9d738 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -164,8 +164,18 @@ public function respondToAccessTokenRequest( } } + $privateClaims = []; + + if ($this->claimRepository !== null) { + $privateClaims = $this->claimRepository->getClaims( + $this->getIdentifier(), + $client, + $authCodePayload->user_id + ); + } + // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes, $privateClaims); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index d342b269f..fbc130bcd 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -49,8 +49,14 @@ public function respondToAccessTokenRequest( // Finalize the requested scopes $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client); + $privateClaims = []; + + if ($this->claimRepository !== null) { + $privateClaims = $this->claimRepository->getClaims($this->getIdentifier(), $client); + } + // Issue and persist access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes, $privateClaims); // Send event to emitter $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index bf1dd792f..1f90a2f42 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -16,6 +16,7 @@ use League\Event\EmitterAwareInterface; use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; @@ -121,6 +122,13 @@ public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessT */ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository); + /** + * Set the claim repository. + * + * @param ClaimRepositoryInterface $claimRepository + */ + public function setClaimRepository(?ClaimRepositoryInterface $claimRepository); + /** * Set the default scope. * diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index f23cc4e1d..ffee46566 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -194,11 +194,22 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth $authorizationRequest->getUser()->getIdentifier() ); + $privateClaims = []; + + if ($this->claimRepository !== null) { + $privateClaims = $this->claimRepository->getClaims( + $this->getIdentifier(), + $authorizationRequest->getClient(), + $authorizationRequest->getUser()->getIdentifier() + ); + } + $accessToken = $this->issueAccessToken( $this->accessTokenTTL, $authorizationRequest->getClient(), $authorizationRequest->getUser()->getIdentifier(), - $finalizedScopes + $finalizedScopes, + $privateClaims ); $response = new RedirectResponse(); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 80f442616..6bbff639e 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -59,10 +59,23 @@ public function respondToAccessTokenRequest( $scopes, $this->getIdentifier(), $client, - $user->getIdentifier()); + $user->getIdentifier() + ); + + $privateClaims = []; + + if ($this->claimRepository !== null) { + $privateClaims = $this->claimRepository->getClaims($this->getIdentifier(), $client, $user->getIdentifier()); + } // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); + $accessToken = $this->issueAccessToken( + $accessTokenTTL, + $client, + $user->getIdentifier(), + $finalizedScopes, + $privateClaims + ); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index e8248b242..6340d7e5c 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -71,8 +71,24 @@ public function respondToAccessTokenRequest( $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); } + $privateClaims = []; + + if ($this->claimRepository !== null) { + $privateClaims = $this->claimRepository->getClaims( + $this->getIdentifier(), + $client, + $oldRefreshToken['user_id'] + ); + } + // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes); + $accessToken = $this->issueAccessToken( + $accessTokenTTL, + $client, + $oldRefreshToken['user_id'], + $scopes, + $privateClaims + ); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); diff --git a/src/Repositories/ClaimRepositoryInterface.php b/src/Repositories/ClaimRepositoryInterface.php new file mode 100644 index 000000000..5d716fd9a --- /dev/null +++ b/src/Repositories/ClaimRepositoryInterface.php @@ -0,0 +1,34 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Repositories; + +use League\OAuth2\Server\Entities\ClaimEntityInterface; +use League\OAuth2\Server\Entities\ClientEntityInterface; + +/** + * Claim repository interface. + */ +interface ClaimRepositoryInterface extends RepositoryInterface +{ + /** + * Returns claims + * + * @param string $grantType + * @param ClientEntityInterface $clientEntity + * @param string|null $userIdentifier + * + * @return ClaimEntityInterface[] + */ + public function getClaims( + string $grantType, + ClientEntityInterface $clientEntity, + $userIdentifier = null + ); +} diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 618545efe..fbb87ae00 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -420,7 +420,8 @@ public function testIssueAccessToken() new DateInterval('PT1H'), new ClientEntity(), 123, - [new ScopeEntity()] + [new ScopeEntity()], + [] ); $this->assertInstanceOf(AccessTokenEntityInterface::class, $accessToken); } diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index 13ea78bae..0ed74a78e 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -8,6 +8,7 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Grant\ClientCredentialsGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use LeagueTests\Stubs\AccessTokenEntity; @@ -44,10 +45,14 @@ public function testRespondToRequest() $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $claimRepositoryMock = $this->getMockBuilder(ClaimRepositoryInterface::class)->getMock(); + $claimRepositoryMock->method('getClaims')->willReturn([]); + $grant = new ClientCredentialsGrant(); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $grant->setClaimRepository($claimRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 546450384..d5bb0792b 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -9,6 +9,7 @@ use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Grant\ImplicitGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -291,10 +292,14 @@ public function testAccessTokenRepositoryUniqueConstraintCheck() $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $claimRepositoryMock = $this->getMockBuilder(ClaimRepositoryInterface::class)->getMock(); + $claimRepositoryMock->method('getClaims')->willReturn([]); + $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $grant->setClaimRepository($claimRepositoryMock); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index b53ab2357..f5703035f 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -9,6 +9,7 @@ use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Grant\PasswordGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -59,10 +60,14 @@ public function testRespondToRequest() $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $claimRepositoryMock = $this->getMockBuilder(ClaimRepositoryInterface::class)->getMock(); + $claimRepositoryMock->method('getClaims')->willReturn([]); + $grant = new PasswordGrant($userRepositoryMock, $refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $grant->setClaimRepository($claimRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index ef14cabe7..75b96415e 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -9,6 +9,7 @@ use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Grant\RefreshTokenGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\ClaimRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -56,6 +57,9 @@ public function testRespondToRequest() $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); + $claimRepositoryMock = $this->getMockBuilder(ClaimRepositoryInterface::class)->getMock(); + $claimRepositoryMock->method('getClaims')->willReturn([]); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); @@ -67,6 +71,7 @@ public function testRespondToRequest() $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $grant->setClaimRepository($claimRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index da7242651..45e95f372 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -12,6 +12,7 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use LeagueTests\Stubs\AccessTokenEntity; +use LeagueTests\Stubs\ClaimEntity; use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; @@ -32,11 +33,14 @@ public function testGenerateHttpResponse() $scope = new ScopeEntity(); $scope->setIdentifier('basic'); + $claim = new ClaimEntity('_private', [42]); + $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->addScope($scope); + $accessToken->addClaim($claim); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); @@ -61,6 +65,14 @@ public function testGenerateHttpResponse() $this->assertObjectHasAttribute('expires_in', $json); $this->assertObjectHasAttribute('access_token', $json); $this->assertObjectHasAttribute('refresh_token', $json); + // Extract payload from access token + $payloadString = \base64_decode(\explode('.', $json->access_token)[1]); + $this->assertTrue(\is_string($payloadString)); + $payload = \json_decode($payloadString); + $this->assertObjectHasAttribute('_private', $payload); + $this->assertIsArray($payload->_private); + $this->assertCount(1, $payload->_private); + $this->assertEquals(42, $payload->_private[0]); } public function testGenerateHttpResponseWithExtraParams() diff --git a/tests/Stubs/ClaimEntity.php b/tests/Stubs/ClaimEntity.php new file mode 100644 index 000000000..00281595d --- /dev/null +++ b/tests/Stubs/ClaimEntity.php @@ -0,0 +1,18 @@ +name = $name; + $this->value = $value; + } +}