diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 860ffd2d6..d0cb3f2e6 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,4 +1,7 @@ build: + environment: + php: + version: 7.4 nodes: analysis: tests: diff --git a/CHANGELOG.md b/CHANGELOG.md index 19ad33854..7967f6cf3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. method being defined (PR #1051) - An exception is now thrown if a refresh token is accidentally sent in place of an authorization code when using the Auth Code Grant (PR #1057) +- Can now send access token request without being forced to specify a redirect URI (PR #1096) ## [8.0.0] - released 2019-07-13 diff --git a/examples/src/Repositories/AccessTokenRepository.php b/examples/src/Repositories/AccessTokenRepository.php index d7736c763..6cca7976f 100644 --- a/examples/src/Repositories/AccessTokenRepository.php +++ b/examples/src/Repositories/AccessTokenRepository.php @@ -43,7 +43,7 @@ public function isAccessTokenRevoked($tokenId) /** * {@inheritdoc} */ - public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) + public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null, $authorizedAuthCodeIdentifier = null, $authorizedAccessTokenIdentifier = null) { $accessToken = new AccessTokenEntity(); $accessToken->setClient($clientEntity); diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index df92da16e..7ef6894f7 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -418,6 +418,8 @@ protected function getServerParameter($parameter, ServerRequestInterface $reques * @param ClientEntityInterface $client * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes + * @param string|null $authorizedAuthCodeIdentifier + * @param string|null $authorizedAccessTokenIdentifier * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException @@ -428,11 +430,13 @@ protected function issueAccessToken( DateInterval $accessTokenTTL, ClientEntityInterface $client, $userIdentifier, - array $scopes = [] + array $scopes = [], + $authorizedAuthCodeIdentifier = null, + $authorizedAccessTokenIdentifier = null ) { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; - $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); + $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier, $authorizedAuthCodeIdentifier, $authorizedAccessTokenIdentifier); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); $accessToken->setPrivateKey($this->privateKey); diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 33711ef8f..d88f2cd53 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -161,7 +161,7 @@ public function respondToAccessTokenRequest( } // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes, $authCodePayload->auth_code_id); $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $responseType->setAccessToken($accessToken); @@ -265,15 +265,15 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); - } else { - $redirectUri = \is_array($client->getRedirectUri()) - ? $client->getRedirectUri()[0] - : $client->getRedirectUri(); } + $defaultClientRedirectUri = \is_array($client->getRedirectUri()) + ? $client->getRedirectUri()[0] + : $client->getRedirectUri(); + $scopes = $this->validateScopes( $this->getQueryStringParameter('scope', $request, $this->defaultScope), - $redirectUri + $defaultClientRedirectUri ); $stateParameter = $this->getQueryStringParameter('state', $request); diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index a1985bc0c..478e427f4 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -66,7 +66,14 @@ public function respondToAccessTokenRequest( $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_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, + null, + $oldRefreshToken['access_token_id'] + ); $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $responseType->setAccessToken($accessToken); diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 72ddf1f4c..e64bac695 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -24,11 +24,13 @@ interface AccessTokenRepositoryInterface extends RepositoryInterface * * @param ClientEntityInterface $clientEntity * @param ScopeEntityInterface[] $scopes - * @param mixed $userIdentifier + * @param string|null $userIdentifier + * @param string|null $authorizedAuthCodeIdentifier + * @param string|null $authorizedAccessTokenIdentifier * * @return AccessTokenEntityInterface */ - public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null); + public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null, $authorizedAuthCodeIdentifier = null, $authorizedAccessTokenIdentifier = null); /** * Persists a new access token to permanent storage. diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 051c4f267..e8dbdc1b6 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -160,6 +160,48 @@ public function testValidateAuthorizationRequestRedirectUriArray() $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } + public function testValidateAuthorizationRequestWithoutRedirectUri() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + + $request = new ServerRequest( + [], + [], + null, + null, + 'php://input', + [], + [], + [ + 'response_type' => 'code', + 'client_id' => 'foo', + ] + ); + + $authorizationRequest = $grant->validateAuthorizationRequest($request); + $this->assertInstanceOf(AuthorizationRequest::class, $authorizationRequest); + + $this->assertEmpty($authorizationRequest->getRedirectUri()); + } + public function testValidateAuthorizationRequestCodeChallenge() { $client = new ClientEntity();