From 77fd3e6f4d949f9ca9c5a64b6e3f03c05792eecb Mon Sep 17 00:00:00 2001 From: Craig Phillips Date: Sun, 19 Jan 2025 23:52:12 +0000 Subject: [PATCH 01/36] Remove the encrypted payload from AuthCode and RefreshToken The Auth Code and Refresh Token information can be stored in the repository and doesn't have to go in the payload. This will reduce auth code and refresh token size, but will require more calls to the repository --- src/AuthorizationServer.php | 4 +- src/CryptTrait.php | 9 + src/Entities/AuthCodeEntityInterface.php | 12 +- src/Entities/RefreshTokenEntityInterface.php | 28 +-- src/Entities/TokenInterface.php | 10 +- src/Entities/Traits/AuthCodeTrait.php | 34 +++- src/Entities/Traits/RefreshTokenTrait.php | 18 -- src/Entities/Traits/TokenEntityTrait.php | 13 +- src/Grant/AbstractGrant.php | 10 +- src/Grant/AuthCodeGrant.php | 185 ++++++++++++------ src/Grant/RefreshTokenGrant.php | 90 +++++++-- .../AccessTokenRepositoryInterface.php | 2 + .../AuthCodeRepositoryInterface.php | 6 + .../RefreshTokenRepositoryInterface.php | 6 + src/ResponseTypes/BearerTokenResponse.php | 31 +-- 15 files changed, 311 insertions(+), 147 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index c894bbd6b..1ed643ba1 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -48,7 +48,7 @@ class AuthorizationServer implements EmitterAwareInterface protected ResponseTypeInterface $responseType; - private string|Key $encryptionKey; + private Key|string|null $encryptionKey; private string $defaultScope = ''; @@ -62,7 +62,7 @@ public function __construct( private AccessTokenRepositoryInterface $accessTokenRepository, private ScopeRepositoryInterface $scopeRepository, CryptKeyInterface|string $privateKey, - Key|string $encryptionKey, + Key|string|null $encryptionKey, ResponseTypeInterface|null $responseType = null ) { if ($privateKey instanceof CryptKeyInterface === false) { diff --git a/src/CryptTrait.php b/src/CryptTrait.php index ee481b55c..1514e0f67 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -87,4 +87,13 @@ public function setEncryptionKey(Key|string|null $key = null): void { $this->encryptionKey = $key; } + + /** + * Has Encryption key been set + * @return bool True if encryption key has been set + */ + public function canUseCrypt() : bool + { + return !empty($this->encryptionKey); + } } diff --git a/src/Entities/AuthCodeEntityInterface.php b/src/Entities/AuthCodeEntityInterface.php index 68c6a2f5b..d1d88e154 100644 --- a/src/Entities/AuthCodeEntityInterface.php +++ b/src/Entities/AuthCodeEntityInterface.php @@ -14,7 +14,15 @@ interface AuthCodeEntityInterface extends TokenInterface { - public function getRedirectUri(): string|null; + public function getRedirectUri(): ?string; - public function setRedirectUri(string $uri): void; + public function setRedirectUri(?string $uri): void; + + public function setCodeChallenge(?string $codeChallenge): void; + + public function getCodeChallenge(): ?string; + + public function setCodeChallengeMethod(?string $codeChallengeMethod): void; + + public function getCodeChallengeMethod(): ?string; } diff --git a/src/Entities/RefreshTokenEntityInterface.php b/src/Entities/RefreshTokenEntityInterface.php index 79a99f24c..e558c810a 100644 --- a/src/Entities/RefreshTokenEntityInterface.php +++ b/src/Entities/RefreshTokenEntityInterface.php @@ -12,34 +12,8 @@ namespace League\OAuth2\Server\Entities; -use DateTimeImmutable; - -interface RefreshTokenEntityInterface +interface RefreshTokenEntityInterface extends TokenInterface { - /** - * Get the token's identifier. - * - * @return non-empty-string - */ - public function getIdentifier(): string; - - /** - * Set the token's identifier. - * - * @param non-empty-string $identifier - */ - public function setIdentifier(string $identifier): void; - - /** - * Get the token's expiry date time. - */ - public function getExpiryDateTime(): DateTimeImmutable; - - /** - * Set the date time when the token expires. - */ - public function setExpiryDateTime(DateTimeImmutable $dateTime): void; - /** * Set the access token that the refresh token was associated with. */ diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index b9d5c270f..9f4a50c33 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -45,14 +45,14 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void; * * @param non-empty-string $identifier */ - public function setUserIdentifier(string $identifier): void; + public function setUserIdentifier(?string $identifier): void; /** * Get the token user's identifier. * * @return non-empty-string|null */ - public function getUserIdentifier(): string|null; + public function getUserIdentifier(): ?string; /** * Get the client that the token was issued to. @@ -75,4 +75,10 @@ public function addScope(ScopeEntityInterface $scope): void; * @return ScopeEntityInterface[] */ public function getScopes(): array; + + /** + * Set the scopes array (doesn't check for duplicates) + * @param array scopes + */ + public function setScopes(array $scopes): void; } diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index 403500b62..54135f039 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -15,14 +15,44 @@ trait AuthCodeTrait { protected ?string $redirectUri = null; + + /** + * The code challenge (if provided) + */ + protected ?string $codeChallenge; - public function getRedirectUri(): string|null + /** + * The code challenge method (if provided) + */ + protected ?string $codeChallengeMethod; + + public function getRedirectUri(): ?string { return $this->redirectUri; } - public function setRedirectUri(string $uri): void + public function setRedirectUri(?string $uri): void { $this->redirectUri = $uri; } + + public function getCodeChallenge(): ?string + { + return $this->codeChallenge ?? null; + } + + public function setCodeChallenge(?string $codeChallenge): void + { + $this->codeChallenge = $codeChallenge; + } + + public function getCodeChallengeMethod(): ?string + { + return $this->codeChallengeMethod ?? null; + } + + public function setCodeChallengeMethod(?string $codeChallengeMethod): void + { + $this->codeChallengeMethod = $codeChallengeMethod; + } } diff --git a/src/Entities/Traits/RefreshTokenTrait.php b/src/Entities/Traits/RefreshTokenTrait.php index a0d4c8885..44060da32 100644 --- a/src/Entities/Traits/RefreshTokenTrait.php +++ b/src/Entities/Traits/RefreshTokenTrait.php @@ -19,8 +19,6 @@ trait RefreshTokenTrait { protected AccessTokenEntityInterface $accessToken; - protected DateTimeImmutable $expiryDateTime; - /** * {@inheritdoc} */ @@ -36,20 +34,4 @@ public function getAccessToken(): AccessTokenEntityInterface { return $this->accessToken; } - - /** - * Get the token's expiry date time. - */ - public function getExpiryDateTime(): DateTimeImmutable - { - return $this->expiryDateTime; - } - - /** - * Set the date time when the token expires. - */ - public function setExpiryDateTime(DateTimeImmutable $dateTime): void - { - $this->expiryDateTime = $dateTime; - } } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index ad472acd9..a793c2a07 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -52,6 +52,15 @@ public function getScopes(): array return array_values($this->scopes); } + /** + * Set the scopes array (doesn't check for duplicates) + * @param array scopes + */ + public function setScopes(array $scopes): void + { + $this->scopes = $scopes; + } + /** * Get the token's expiry date time. */ @@ -73,7 +82,7 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void * * @param non-empty-string $identifier The identifier of the user */ - public function setUserIdentifier(string $identifier): void + public function setUserIdentifier(?string $identifier): void { $this->userIdentifier = $identifier; } @@ -83,7 +92,7 @@ public function setUserIdentifier(string $identifier): void * * @return non-empty-string|null */ - public function getUserIdentifier(): string|null + public function getUserIdentifier(): ?string { return $this->userIdentifier; } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 5ab81ff77..abcfaf783 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -260,10 +260,10 @@ public function validateScopes(string|array|null $scopes, ?string $redirectUri = throw OAuthServerException::invalidScope($scopeItem, $redirectUri); } - $validScopes[] = $scope; + $validScopes[$scopeItem] = $scope; } - return $validScopes; + return array_values($validScopes); } /** @@ -444,7 +444,9 @@ protected function issueAuthCode( ClientEntityInterface $client, string $userIdentifier, ?string $redirectUri, - array $scopes = [] + array $scopes = [], + ?string $codeChallenge = null, + ?string $codeChallengeMethod = null ): AuthCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; @@ -452,6 +454,8 @@ protected function issueAuthCode( $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL)); $authCode->setClient($client); $authCode->setUserIdentifier($userIdentifier); + $authCode->setCodeChallenge($codeChallenge); + $authCode->setCodeChallengeMethod($codeChallengeMethod); if ($redirectUri !== null) { $authCode->setRedirectUri($redirectUri); diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 9fb5271c3..8a2777265 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -19,6 +19,7 @@ use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface; use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier; use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier; +use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -106,46 +107,97 @@ public function respondToAccessTokenRequest( $this->validateClient($request); } - $encryptedAuthCode = $this->getRequestParameter('code', $request); + $code = $this->getRequestParameter('code', $request); - if ($encryptedAuthCode === null) { + if ($code === null) { throw OAuthServerException::invalidRequest('code'); } - try { - $authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); + if ($this->canUseCrypt()) { + try { + $authCodePayload = json_decode($this->decrypt($code)); - $this->validateAuthorizationCode($authCodePayload, $client, $request); + $ace = $this->authCodeRepository->getNewAuthCode(); - $scopes = $this->scopeRepository->finalizeScopes( - $this->validateScopes($authCodePayload->scopes), - $this->getIdentifier(), - $client, - $authCodePayload->user_id, - $authCodePayload->auth_code_id - ); - } catch (InvalidArgumentException $e) { - throw OAuthServerException::invalidGrant('Cannot validate the provided authorization code'); - } catch (LogicException $e) { - throw OAuthServerException::invalidRequest('code', 'Issue decrypting the authorization code', $e); + if ($ace == null) { + // Probably should throw an exception here instead + return $responseType; + } + + if (isset($authCodePayload->auth_code_id)) + $ace->setIdentifier($authCodePayload->auth_code_id); + + if (isset($authCodePayload->client_id)) + $ace->setClient($this->getClientEntityOrFail($authCodePayload->client_id, $request)); + + if (isset($authCodePayload->user_id)) + $ace->setUserIdentifier((string)$authCodePayload->user_id); + + if (isset($authCodePayload->code_challenge)) + $ace->setCodeChallenge($authCodePayload->code_challenge); + + if (isset($authCodePayload->code_challenge_method)) + $ace->setCodeChallengeMethod($authCodePayload->code_challenge_method); + + if (isset($authCodePayload->redirect_uri)) + $ace->setRedirectUri($authCodePayload->redirect_uri); + + if (isset($authCodePayload->expire_time)) { + $expire = new DateTimeImmutable(); + $expire = $expire->setTimestamp($authCodePayload->expire_time); + + $ace->setExpiryDateTime($expire); + } + + if (isset($authCodePayload->scopes)) { + $scopes = $this->validateScopes($authCodePayload->scopes); + + $ace->setScopes($scopes); + } + + + + } catch (InvalidArgumentException $e) { + throw OAuthServerException::invalidGrant('Cannot validate the provided authorization code'); + } catch (LogicException $e) { + throw OAuthServerException::invalidRequest('code', 'Issue decrypting the authorization code', $e); + } } + else { + // Get the Auth Code Payload from Repository + $ace = $this->authCodeRepository->getAuthCodeEntity($code); + + if (empty($ace)) { + throw OAuthServerException::invalidRequest('code', 'Cannot find authorization code'); + } + } + + $this->validateAuthorizationCode($ace, $client, $request); + + $scopes = $this->scopeRepository->finalizeScopes( + $ace->getScopes(), + $this->getIdentifier(), + $client, + $ace->getUserIdentifier(), + $ace->getIdentifier() + ); $codeVerifier = $this->getRequestParameter('code_verifier', $request); // If a code challenge isn't present but a code verifier is, reject the request to block PKCE downgrade attack - if (!isset($authCodePayload->code_challenge) && $codeVerifier !== null) { + if ($ace->getCodeChallenge() === null && $codeVerifier !== null) { throw OAuthServerException::invalidRequest( 'code_challenge', 'code_verifier received when no code_challenge is present' ); } - if (isset($authCodePayload->code_challenge)) { - $this->validateCodeChallenge($authCodePayload, $codeVerifier); + if ($ace->getCodeChallenge() !== null) { + $this->validateCodeChallenge($ace, $codeVerifier); } // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $ace->getUserIdentifier(), $scopes); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); @@ -158,12 +210,12 @@ public function respondToAccessTokenRequest( } // Revoke used auth code - $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); + $this->authCodeRepository->revokeAuthCode($ace->getIdentifier()); return $responseType; } - private function validateCodeChallenge(object $authCodePayload, ?string $codeVerifier): void + private function validateCodeChallenge(AuthCodeEntityInterface $authCodeEntity, ?string $codeVerifier): void { if ($codeVerifier === null) { throw OAuthServerException::invalidRequest('code_verifier'); @@ -178,21 +230,20 @@ private function validateCodeChallenge(object $authCodePayload, ?string $codeVer ); } - if (property_exists($authCodePayload, 'code_challenge_method')) { - if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) { - $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method]; - if (!isset($authCodePayload->code_challenge) || $codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) { - throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); - } - } else { - throw OAuthServerException::serverError( - sprintf( - 'Unsupported code challenge method `%s`', - $authCodePayload->code_challenge_method - ) - ); + if (isset($this->codeChallengeVerifiers[$authCodeEntity->getCodeChallengeMethod()])) { + $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodeEntity->getCodeChallengeMethod()]; + + if ($authCodeEntity->getCodeChallenge() === null || $codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodeEntity->getCodeChallenge()) === false) { + throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); } + } else { + throw OAuthServerException::serverError( + sprintf( + 'Unsupported code challenge method `%s`', + $authCodeEntity->getCodeChallengeMethod() + ) + ); } } @@ -200,35 +251,42 @@ private function validateCodeChallenge(object $authCodePayload, ?string $codeVer * Validate the authorization code. */ private function validateAuthorizationCode( - stdClass $authCodePayload, + AuthCodeEntityInterface $authCodeEntity, ClientEntityInterface $client, ServerRequestInterface $request ): void { - if (!property_exists($authCodePayload, 'auth_code_id')) { + + try { + if (empty($authCodeEntity->getIdentifier())) { + // Make sure its not empty + throw OAuthServerException::invalidRequest('code', 'Authorization code malformed'); + } + } catch (\Throwable $th) { + // $identifier must not be accessed before initialization throw OAuthServerException::invalidRequest('code', 'Authorization code malformed'); } - if (time() > $authCodePayload->expire_time) { + if (time() > $authCodeEntity->getExpiryDateTime()->getTimestamp()) { throw OAuthServerException::invalidGrant('Authorization code has expired'); } - if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) { + if ($this->authCodeRepository->isAuthCodeRevoked($authCodeEntity->getIdentifier()) === true) { throw OAuthServerException::invalidGrant('Authorization code has been revoked'); } - if ($authCodePayload->client_id !== $client->getIdentifier()) { + if ($authCodeEntity->getClient()->getIdentifier() !== $client->getIdentifier()) { throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client'); } // The redirect URI is required in this request if it was specified // in the authorization request $redirectUri = $this->getRequestParameter('redirect_uri', $request); - if ($authCodePayload->redirect_uri !== null && $redirectUri === null) { + if ($authCodeEntity->getRedirectUri() !== null && $redirectUri === null) { throw OAuthServerException::invalidRequest('redirect_uri'); } // If a redirect URI has been provided ensure it matches the stored redirect URI - if ($redirectUri !== null && $authCodePayload->redirect_uri !== $redirectUri) { + if ($redirectUri !== null && $authCodeEntity->getRedirectUri() !== $redirectUri) { throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI'); } } @@ -365,32 +423,41 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth $authorizationRequest->getClient(), $authorizationRequest->getUser()->getIdentifier(), $authorizationRequest->getRedirectUri(), - $authorizationRequest->getScopes() + $authorizationRequest->getScopes(), + $authorizationRequest->getCodeChallenge(), + $authorizationRequest->getCodeChallengeMethod() ); - $payload = [ - 'client_id' => $authCode->getClient()->getIdentifier(), - 'redirect_uri' => $authCode->getRedirectUri(), - 'auth_code_id' => $authCode->getIdentifier(), - 'scopes' => $authCode->getScopes(), - 'user_id' => $authCode->getUserIdentifier(), - 'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(), - 'code_challenge' => $authorizationRequest->getCodeChallenge(), - 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), - ]; - - $jsonPayload = json_encode($payload); - - if ($jsonPayload === false) { - throw new LogicException('An error was encountered when JSON encoding the authorization request response'); + $code = $authCode->getIdentifier(); + + if ($this->canUseCrypt()) { + $payload = [ + 'client_id' => $authCode->getClient()->getIdentifier(), + 'redirect_uri' => $authCode->getRedirectUri(), + 'auth_code_id' => $authCode->getIdentifier(), + 'scopes' => $authCode->getScopes(), + 'user_id' => $authCode->getUserIdentifier(), + 'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(), + 'code_challenge' => $authorizationRequest->getCodeChallenge(), + 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), + ]; + + $jsonPayload = json_encode($payload); + + if ($jsonPayload === false) { + throw new LogicException('An error was encountered when JSON encoding the authorization request response'); + } + + $code = $this->encrypt($jsonPayload); } + $response = new RedirectResponse(); $response->setRedirectUri( $this->makeRedirectUri( $finalRedirectUri, [ - 'code' => $this->encrypt($jsonPayload), + 'code' => $code, 'state' => $authorizationRequest->getState(), ] ) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 34e3f20b4..b1d5866e0 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -15,7 +15,9 @@ namespace League\OAuth2\Server\Grant; use DateInterval; +use DateTimeImmutable; use Exception; +use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestAccessTokenEvent; @@ -53,18 +55,31 @@ public function respondToAccessTokenRequest( $client = $this->validateClient($request); $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier()); + if ($oldRefreshToken == null) + { + // Probably should throw an exception here instead + return $responseType; + } + + $originalScopes = $oldRefreshToken->getScopes(); + $originalScopeArray = []; + foreach ($originalScopes as $scopeEntity) { + $originalScopeArray[$scopeEntity->getIdentifier()] = $scopeEntity->getIdentifier(); + } + $originalScopeArray = array_values($originalScopeArray); + $scopes = $this->validateScopes( $this->getRequestParameter( 'scope', $request, - implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken['scopes']) + implode(self::SCOPE_DELIMITER_STRING, $originalScopeArray) ) ); // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure // the request doesn't include any new scopes foreach ($scopes as $scope) { - if (in_array($scope->getIdentifier(), $oldRefreshToken['scopes'], true) === false) { + if (in_array($scope->getIdentifier(), $originalScopeArray, true) === false) { throw OAuthServerException::invalidScope($scope->getIdentifier()); } } @@ -72,13 +87,13 @@ public function respondToAccessTokenRequest( $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client); // Expire old tokens - $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); + $this->accessTokenRepository->revokeAccessToken($oldRefreshToken->getAccessToken()->getIdentifier()); if ($this->revokeRefreshTokens) { - $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); + $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken->getIdentifier()); } // Issue and persist new access token - $userId = $oldRefreshToken['user_id']; + $userId = $oldRefreshToken->getUserIdentifier(); if (is_int($userId)) { $userId = (string) $userId; } @@ -100,35 +115,76 @@ public function respondToAccessTokenRequest( /** * @throws OAuthServerException * - * @return array + * @return RefreshTokenEntityInterface */ - protected function validateOldRefreshToken(ServerRequestInterface $request, string $clientId): array + protected function validateOldRefreshToken(ServerRequestInterface $request, string $clientId): ?RefreshTokenEntityInterface { - $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request) + $refreshTokenParam = $this->getRequestParameter('refresh_token', $request) ?? throw OAuthServerException::invalidRequest('refresh_token'); // Validate refresh token - try { - $refreshToken = $this->decrypt($encryptedRefreshToken); - } catch (Exception $e) { - throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); + if ($this->canUseCrypt()) { + try { + $refreshTokenJson = $this->decrypt($refreshTokenParam); + $refreshTokenData = json_decode($refreshTokenJson, true); + + $refreshTokenEntity = $this->refreshTokenRepository->getNewRefreshToken(); + + if ($refreshTokenEntity == null) + { + return null; + } + + if (isset($refreshTokenData['refresh_token_id'])) + $refreshTokenEntity->setIdentifier($refreshTokenData['refresh_token_id']); + + if (isset($refreshTokenData['expire_time'])) { + $expire = new DateTimeImmutable(); + $expire = $expire->setTimestamp($refreshTokenData['expire_time']); + $refreshTokenEntity->setExpiryDateTime($expire); + } + + if (isset($refreshTokenData['client_id'])) { + $client = $this->getClientEntityOrFail($refreshTokenData['client_id'], $request); + $refreshTokenEntity->setClient($client); + } + + if (isset($refreshTokenData['scopes'])) { + $scopes = $this->validateScopes($refreshTokenData['scopes']); + + $refreshTokenEntity->setScopes($scopes); + } + + if (isset($refreshTokenData['user_id'])) + $refreshTokenEntity->setUserIdentifier((string)$refreshTokenData['user_id']); + + if (isset($refreshTokenData['access_token_id'])) { + $accessToken = $this->accessTokenRepository->getAccessTokenEntity($refreshTokenData['access_token_id']); + $refreshTokenEntity->setAccessToken($accessToken); + } + + } catch (Exception $e) { + throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); + } + } + else { + $refreshTokenEntity = $this->refreshTokenRepository->getRefreshTokenEntity($refreshTokenParam); } - $refreshTokenData = json_decode($refreshToken, true); - if ($refreshTokenData['client_id'] !== $clientId) { + if ($refreshTokenEntity->getClient()->getIdentifier() !== $clientId) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request)); throw OAuthServerException::invalidRefreshToken('Token is not linked to client'); } - if ($refreshTokenData['expire_time'] < time()) { + if ($refreshTokenEntity->getExpiryDateTime()->getTimestamp() < time()) { throw OAuthServerException::invalidRefreshToken('Token has expired'); } - if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenData['refresh_token_id']) === true) { + if ($this->refreshTokenRepository->isRefreshTokenRevoked($refreshTokenEntity->getIdentifier()) === true) { throw OAuthServerException::invalidRefreshToken('Token has been revoked'); } - return $refreshTokenData; + return $refreshTokenEntity; } /** diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 8bac8be64..92a3bba3e 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -41,4 +41,6 @@ public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEnt public function revokeAccessToken(string $tokenId): void; public function isAccessTokenRevoked(string $tokenId): bool; + + public function getAccessTokenEntity(string $tokenId): ?AccessTokenEntityInterface; } diff --git a/src/Repositories/AuthCodeRepositoryInterface.php b/src/Repositories/AuthCodeRepositoryInterface.php index 89ff86b87..31204cd14 100644 --- a/src/Repositories/AuthCodeRepositoryInterface.php +++ b/src/Repositories/AuthCodeRepositoryInterface.php @@ -30,4 +30,10 @@ public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): voi public function revokeAuthCode(string $codeId): void; public function isAuthCodeRevoked(string $codeId): bool; + + /** + * Get Auth code entity from repository + * @return ?AuthCodeEntityInterface + */ + public function getAuthCodeEntity(string $codeId): ?AuthCodeEntityInterface; } diff --git a/src/Repositories/RefreshTokenRepositoryInterface.php b/src/Repositories/RefreshTokenRepositoryInterface.php index a25e50133..d7bf14260 100644 --- a/src/Repositories/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/RefreshTokenRepositoryInterface.php @@ -30,4 +30,10 @@ public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshToken public function revokeRefreshToken(string $tokenId): void; public function isRefreshTokenRevoked(string $tokenId): bool; + + /** + * Get Refresh Token entity from repository + * @return ?RefreshTokenRepositoryInterface + */ + public function getRefreshTokenEntity(string $tokenId): ?RefreshTokenRepositoryInterface; } diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index dd49b99ba..3ee5372b8 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -35,20 +35,25 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter ]; if (isset($this->refreshToken)) { - $refreshTokenPayload = json_encode([ - 'client_id' => $this->accessToken->getClient()->getIdentifier(), - 'refresh_token_id' => $this->refreshToken->getIdentifier(), - 'access_token_id' => $this->accessToken->getIdentifier(), - 'scopes' => $this->accessToken->getScopes(), - 'user_id' => $this->accessToken->getUserIdentifier(), - 'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(), - ]); - - if ($refreshTokenPayload === false) { - throw new LogicException('Error encountered JSON encoding the refresh token payload'); + if ($this->canUseCrypt()) { + $refreshTokenPayload = json_encode([ + 'client_id' => $this->accessToken->getClient()->getIdentifier(), + 'refresh_token_id' => $this->refreshToken->getIdentifier(), + 'access_token_id' => $this->accessToken->getIdentifier(), + 'scopes' => $this->accessToken->getScopes(), + 'user_id' => $this->accessToken->getUserIdentifier(), + 'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(), + ]); + + if ($refreshTokenPayload === false) { + throw new LogicException('Error encountered JSON encoding the refresh token payload'); + } + + $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); + } + else { + $responseParams['refresh_token'] = $this->refreshToken->getIdentifier(); } - - $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); } $responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams)); From 0667f32c8297944a255848a1e9680d5700453f3b Mon Sep 17 00:00:00 2001 From: Craig Phillips Date: Sun, 19 Jan 2025 23:52:47 +0000 Subject: [PATCH 02/36] Satisfy tests for Auth Code And Refresh Token --- tests/Grant/AuthCodeGrantTest.php | 151 +++++++++++++++++++++----- tests/Grant/RefreshTokenGrantTest.php | 87 +++++++++++++-- tests/Stubs/RefreshTokenEntity.php | 2 + 3 files changed, 205 insertions(+), 35 deletions(-) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 390001721..ee03fd548 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -623,8 +623,11 @@ public function testRespondToAccessTokenRequest(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -693,8 +696,11 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -754,8 +760,11 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $authCodeGrant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -820,8 +829,11 @@ public function testRespondToAccessTokenRequestForPublicClient(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -885,8 +897,11 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -955,9 +970,12 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + $authCodeRepository, + $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -1029,8 +1047,11 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1100,8 +1121,11 @@ public function testPKCEDowngradeBlocked(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1163,8 +1187,11 @@ public function testRespondToAccessTokenRequestMissingRedirectUri(): void $clientRepositoryMock->method('getClientEntity')->willReturn($client); $clientRepositoryMock->method('validateClient')->willReturn(true); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1213,8 +1240,11 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void $clientRepositoryMock->method('getClientEntity')->willReturn($client); $clientRepositoryMock->method('validateClient')->willReturn(true); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1264,8 +1294,11 @@ public function testRejectAccessTokenRequestIfRedirectUriSpecifiedButNotInOrigin $clientRepositoryMock->method('getClientEntity')->willReturn($client); $clientRepositoryMock->method('validateClient')->willReturn(true); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1359,13 +1392,20 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( @@ -1448,13 +1488,20 @@ public function testRespondToAccessTokenRequestExpiredCode(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( @@ -1512,7 +1559,11 @@ public function testRespondToAccessTokenRequestRevokedCode(): void $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('isAuthCodeRevoked')->willReturn(true); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1521,6 +1572,7 @@ public function testRespondToAccessTokenRequestRevokedCode(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( @@ -1566,9 +1618,21 @@ public function testRespondToAccessTokenRequestClientMismatch(): void $client->setRedirectUri(self::REDIRECT_URI); $client->setConfidential(); + $client2 = new ClientEntity(); + + $client2->setIdentifier('foo'); + $client2->setRedirectUri(self::REDIRECT_URI); + $client2->setConfidential(); + + $client3 = new ClientEntity(); + + $client3->setIdentifier('bar'); + $client3->setRedirectUri(self::REDIRECT_URI); + $client3->setConfidential(); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('getClientEntity')->willReturn($client, $client2, $client3); $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -1577,14 +1641,21 @@ public function testRespondToAccessTokenRequestClientMismatch(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( @@ -1695,8 +1766,11 @@ public function testRespondToAccessTokenRequestNoEncryptionKey(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1727,7 +1801,7 @@ public function testRespondToAccessTokenRequestNoEncryptionKey(): void $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { self::assertEquals($e->getErrorType(), 'invalid_request'); - self::assertEquals($e->getHint(), 'Issue decrypting the authorization code'); + self::assertEquals($e->getHint(), 'Cannot find authorization code'); } } @@ -1757,8 +1831,11 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1832,8 +1909,11 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1907,8 +1987,11 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -1982,8 +2065,11 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -2057,8 +2143,11 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -2158,6 +2247,7 @@ public function testAuthCodeRepositoryFailToPersist(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); $authCodeRepository->method('persistNewAuthCode')->willThrowException(OAuthServerException::serverError('something bad happened')); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); $grant = new AuthCodeGrant( $authCodeRepository, @@ -2220,6 +2310,9 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $refreshTokenRepositoryMock ->expects(self::exactly(2)) ->method('persistNewRefreshToken') @@ -2232,7 +2325,7 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void }); $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -2296,8 +2389,11 @@ public function testRefreshTokenRepositoryFailToPersist(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willThrowException(OAuthServerException::serverError('something bad happened')); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -2364,8 +2460,11 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); + $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index b2dbbadd2..09a0453c9 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -62,6 +62,9 @@ public function testRespondToRequest(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -127,7 +130,7 @@ public function testRespondToRequestNullRefreshToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); - $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); + $accessTokenRepositoryMock->expects(self::never())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); @@ -185,6 +188,9 @@ public function testRespondToReducedScopes(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); @@ -248,14 +254,25 @@ public function testRespondToUnexpectedScope(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $scope1 = new ScopeEntity(); + $scope1->setIdentifier('foo'); + + $scope2 = new ScopeEntity(); + $scope2->setIdentifier('bar'); + + $scope3 = new ScopeEntity(); + $scope3->setIdentifier('foobar'); - $scope = new ScopeEntity(); - $scope->setIdentifier('foobar'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1, $scope2, $scope3); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); @@ -371,19 +388,34 @@ public function testRespondToRequestClientMismatch(): void $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client2 = new ClientEntity(); + $client2->setIdentifier('bar'); + $client2->setRedirectUri('http://foo/bar'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('getClientEntity')->willReturn($client, $client2); $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $scope1 = new ScopeEntity(); + $scope1->setIdentifier('foo'); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -427,15 +459,27 @@ public function testRespondToRequestExpiredToken(): void $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('getClientEntity')->willReturn($client, $client); $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $scope1 = new ScopeEntity(); + $scope1->setIdentifier('foo'); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -479,16 +523,28 @@ public function testRespondToRequestRevokedToken(): void $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('getClientEntity')->willReturn($client, $client); $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(true); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $scope1 = new ScopeEntity(); + $scope1->setIdentifier('foo'); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -543,10 +599,13 @@ public function testRespondToRequestFinalizeScopes(): void $barScopeEntity->setIdentifier('bar'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($fooScopeEntity, $barScopeEntity); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($fooScopeEntity, $barScopeEntity, $fooScopeEntity, $barScopeEntity); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); @@ -621,17 +680,21 @@ public function testRevokedRefreshToken(): void $scopeEntity->setIdentifier('foo'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity, $scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('isRefreshTokenRevoked') ->will(self::onConsecutiveCalls(false, true)); $refreshTokenRepositoryMock->expects(self::once())->method('revokeRefreshToken')->with(self::equalTo($refreshTokenId)); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $oldRefreshToken = json_encode( [ @@ -696,6 +759,9 @@ public function testUnrevokedRefreshToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessTokenEntity); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); @@ -774,6 +840,9 @@ public function testRespondToRequestWithIntUserId(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); + $ace = new AccessTokenEntity(); + $ace->setIdentifier("abcdef"); + $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); diff --git a/tests/Stubs/RefreshTokenEntity.php b/tests/Stubs/RefreshTokenEntity.php index 9d6d79f27..2c378f353 100644 --- a/tests/Stubs/RefreshTokenEntity.php +++ b/tests/Stubs/RefreshTokenEntity.php @@ -7,9 +7,11 @@ use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait; +use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; class RefreshTokenEntity implements RefreshTokenEntityInterface { use RefreshTokenTrait; use EntityTrait; + use TokenEntityTrait; } From c7c6434c0ce257153c528e3930daa9d3d63d0a04 Mon Sep 17 00:00:00 2001 From: Craig Phillips Date: Mon, 20 Jan 2025 21:13:05 +0000 Subject: [PATCH 03/36] Syntax fixes --- src/CryptTrait.php | 3 +- src/Entities/TokenInterface.php | 1 + src/Entities/Traits/AuthCodeTrait.php | 3 +- src/Entities/Traits/RefreshTokenTrait.php | 1 - src/Entities/Traits/TokenEntityTrait.php | 1 + src/Grant/AuthCodeGrant.php | 31 +++++++++---------- src/Grant/RefreshTokenGrant.php | 16 +++++----- .../AuthCodeRepositoryInterface.php | 1 + .../RefreshTokenRepositoryInterface.php | 1 + src/ResponseTypes/BearerTokenResponse.php | 3 +- tests/Grant/AuthCodeGrantTest.php | 2 +- tests/Grant/RefreshTokenGrantTest.php | 20 ++++++------ 12 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 1514e0f67..0b530c880 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -90,9 +90,10 @@ public function setEncryptionKey(Key|string|null $key = null): void /** * Has Encryption key been set + * * @return bool True if encryption key has been set */ - public function canUseCrypt() : bool + public function canUseCrypt(): bool { return !empty($this->encryptionKey); } diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 9f4a50c33..c12f7585b 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -78,6 +78,7 @@ public function getScopes(): array; /** * Set the scopes array (doesn't check for duplicates) + * * @param array scopes */ public function setScopes(array $scopes): void; diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index 54135f039..8dcacfa82 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -15,7 +15,8 @@ trait AuthCodeTrait { protected ?string $redirectUri = null; - + + /** * The code challenge (if provided) */ diff --git a/src/Entities/Traits/RefreshTokenTrait.php b/src/Entities/Traits/RefreshTokenTrait.php index 44060da32..7c7235c0e 100644 --- a/src/Entities/Traits/RefreshTokenTrait.php +++ b/src/Entities/Traits/RefreshTokenTrait.php @@ -12,7 +12,6 @@ namespace League\OAuth2\Server\Entities\Traits; -use DateTimeImmutable; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; trait RefreshTokenTrait diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index a793c2a07..a7f85bbd8 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -54,6 +54,7 @@ public function getScopes(): array /** * Set the scopes array (doesn't check for duplicates) + * * @param array scopes */ public function setScopes(array $scopes): void diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 8a2777265..aebfe3507 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -33,7 +33,6 @@ use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; -use stdClass; use function array_key_exists; use function array_keys; @@ -46,7 +45,6 @@ use function json_decode; use function json_encode; use function preg_match; -use function property_exists; use function sprintf; use function time; @@ -124,23 +122,29 @@ public function respondToAccessTokenRequest( return $responseType; } - if (isset($authCodePayload->auth_code_id)) + if (isset($authCodePayload->auth_code_id)) { $ace->setIdentifier($authCodePayload->auth_code_id); + } - if (isset($authCodePayload->client_id)) + if (isset($authCodePayload->client_id)) { $ace->setClient($this->getClientEntityOrFail($authCodePayload->client_id, $request)); + } - if (isset($authCodePayload->user_id)) + if (isset($authCodePayload->user_id)) { $ace->setUserIdentifier((string)$authCodePayload->user_id); + } - if (isset($authCodePayload->code_challenge)) + if (isset($authCodePayload->code_challenge)) { $ace->setCodeChallenge($authCodePayload->code_challenge); + } - if (isset($authCodePayload->code_challenge_method)) + if (isset($authCodePayload->code_challenge_method)) { $ace->setCodeChallengeMethod($authCodePayload->code_challenge_method); + } - if (isset($authCodePayload->redirect_uri)) + if (isset($authCodePayload->redirect_uri)) { $ace->setRedirectUri($authCodePayload->redirect_uri); + } if (isset($authCodePayload->expire_time)) { $expire = new DateTimeImmutable(); @@ -155,15 +159,12 @@ public function respondToAccessTokenRequest( $ace->setScopes($scopes); } - - } catch (InvalidArgumentException $e) { throw OAuthServerException::invalidGrant('Cannot validate the provided authorization code'); } catch (LogicException $e) { throw OAuthServerException::invalidRequest('code', 'Issue decrypting the authorization code', $e); } - } - else { + } else { // Get the Auth Code Payload from Repository $ace = $this->authCodeRepository->getAuthCodeEntity($code); @@ -255,7 +256,6 @@ private function validateAuthorizationCode( ClientEntityInterface $client, ServerRequestInterface $request ): void { - try { if (empty($authCodeEntity->getIdentifier())) { // Make sure its not empty @@ -441,16 +441,15 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth 'code_challenge' => $authorizationRequest->getCodeChallenge(), 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), ]; - + $jsonPayload = json_encode($payload); - + if ($jsonPayload === false) { throw new LogicException('An error was encountered when JSON encoding the authorization request response'); } $code = $this->encrypt($jsonPayload); } - $response = new RedirectResponse(); $response->setRedirectUri( diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index b1d5866e0..4a47a72e6 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -55,8 +55,7 @@ public function respondToAccessTokenRequest( $client = $this->validateClient($request); $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier()); - if ($oldRefreshToken == null) - { + if ($oldRefreshToken == null) { // Probably should throw an exception here instead return $responseType; } @@ -130,13 +129,13 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, stri $refreshTokenEntity = $this->refreshTokenRepository->getNewRefreshToken(); - if ($refreshTokenEntity == null) - { + if ($refreshTokenEntity == null) { return null; } - if (isset($refreshTokenData['refresh_token_id'])) + if (isset($refreshTokenData['refresh_token_id'])) { $refreshTokenEntity->setIdentifier($refreshTokenData['refresh_token_id']); + } if (isset($refreshTokenData['expire_time'])) { $expire = new DateTimeImmutable(); @@ -155,19 +154,18 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, stri $refreshTokenEntity->setScopes($scopes); } - if (isset($refreshTokenData['user_id'])) + if (isset($refreshTokenData['user_id'])) { $refreshTokenEntity->setUserIdentifier((string)$refreshTokenData['user_id']); + } if (isset($refreshTokenData['access_token_id'])) { $accessToken = $this->accessTokenRepository->getAccessTokenEntity($refreshTokenData['access_token_id']); $refreshTokenEntity->setAccessToken($accessToken); } - } catch (Exception $e) { throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); } - } - else { + } else { $refreshTokenEntity = $this->refreshTokenRepository->getRefreshTokenEntity($refreshTokenParam); } diff --git a/src/Repositories/AuthCodeRepositoryInterface.php b/src/Repositories/AuthCodeRepositoryInterface.php index 31204cd14..fdc335e0f 100644 --- a/src/Repositories/AuthCodeRepositoryInterface.php +++ b/src/Repositories/AuthCodeRepositoryInterface.php @@ -33,6 +33,7 @@ public function isAuthCodeRevoked(string $codeId): bool; /** * Get Auth code entity from repository + * * @return ?AuthCodeEntityInterface */ public function getAuthCodeEntity(string $codeId): ?AuthCodeEntityInterface; diff --git a/src/Repositories/RefreshTokenRepositoryInterface.php b/src/Repositories/RefreshTokenRepositoryInterface.php index d7bf14260..e5db847f5 100644 --- a/src/Repositories/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/RefreshTokenRepositoryInterface.php @@ -33,6 +33,7 @@ public function isRefreshTokenRevoked(string $tokenId): bool; /** * Get Refresh Token entity from repository + * * @return ?RefreshTokenRepositoryInterface */ public function getRefreshTokenEntity(string $tokenId): ?RefreshTokenRepositoryInterface; diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 3ee5372b8..4e98c483a 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -50,8 +50,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter } $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); - } - else { + } else { $responseParams['refresh_token'] = $this->refreshToken->getIdentifier(); } } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index ee03fd548..968e9cba6 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -2247,7 +2247,7 @@ public function testAuthCodeRepositoryFailToPersist(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); $authCodeRepository->method('persistNewAuthCode')->willThrowException(OAuthServerException::serverError('something bad happened')); - $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); $grant = new AuthCodeGrant( $authCodeRepository, diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 09a0453c9..9dcea8114 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -63,7 +63,7 @@ public function testRespondToRequest(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); @@ -189,7 +189,7 @@ public function testRespondToReducedScopes(): void $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -255,7 +255,7 @@ public function testRespondToUnexpectedScope(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -399,7 +399,7 @@ public function testRespondToRequestClientMismatch(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -464,7 +464,7 @@ public function testRespondToRequestExpiredToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -528,7 +528,7 @@ public function testRespondToRequestRevokedToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -604,7 +604,7 @@ public function testRespondToRequestFinalizeScopes(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -687,7 +687,7 @@ public function testRevokedRefreshToken(): void $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -760,7 +760,7 @@ public function testUnrevokedRefreshToken(): void $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessTokenEntity); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -841,7 +841,7 @@ public function testRespondToRequestWithIntUserId(): void $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); - $ace->setIdentifier("abcdef"); + $ace->setIdentifier('abcdef'); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); From 592b964d20e8871fd3f86edbd717b534dc8c9397 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sat, 1 Feb 2025 15:11:33 +0000 Subject: [PATCH 04/36] getRefreshTokenEntity should return RefreshTokenEntityInterface --- src/Repositories/RefreshTokenRepositoryInterface.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Repositories/RefreshTokenRepositoryInterface.php b/src/Repositories/RefreshTokenRepositoryInterface.php index e5db847f5..9c484a1bf 100644 --- a/src/Repositories/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/RefreshTokenRepositoryInterface.php @@ -34,7 +34,7 @@ public function isRefreshTokenRevoked(string $tokenId): bool; /** * Get Refresh Token entity from repository * - * @return ?RefreshTokenRepositoryInterface + * @return ?RefreshTokenEntityInterface */ - public function getRefreshTokenEntity(string $tokenId): ?RefreshTokenRepositoryInterface; + public function getRefreshTokenEntity(string $tokenId): ?RefreshTokenEntityInterface; } From 411e795c93b972a8b895023e35fe88862f57b9fc Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sat, 1 Feb 2025 15:13:07 +0000 Subject: [PATCH 05/36] Exception should be thrown if RefreshTokenEntity is null --- src/Grant/RefreshTokenGrant.php | 4 ++++ tests/Grant/RefreshTokenGrantTest.php | 34 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 4a47a72e6..2e6d141dd 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -169,6 +169,10 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, stri $refreshTokenEntity = $this->refreshTokenRepository->getRefreshTokenEntity($refreshTokenParam); } + if ($refreshTokenEntity === null) { + throw OAuthServerException::invalidRefreshToken('Cannot find refresh token'); + } + if ($refreshTokenEntity->getClient()->getIdentifier() !== $clientId) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_CLIENT_FAILED, $request)); throw OAuthServerException::invalidRefreshToken('Token is not linked to client'); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 9dcea8114..1713fbe9b 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -382,6 +382,40 @@ public function testRespondToRequestInvalidOldToken(): void $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } + public function testRespondToRequestRefreshTokenNotSet(): void + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $client->setRedirectUri('http://foo/bar'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + + $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); + $grant->setClientRepository($clientRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $oldRefreshToken = 'foobar'; + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + ]); + + $responseType = new StubResponseType(); + + $this->expectException(OAuthServerException::class); + $this->expectExceptionCode(8); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } + public function testRespondToRequestClientMismatch(): void { $client = new ClientEntity(); From 3612121d2da2431b4680db27ce519ed35b065b73 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 01:32:45 +0000 Subject: [PATCH 06/36] Removing Encryption from RefreshToken and AuthCode userIdentifier -> UserEntity --- .../Repositories/AccessTokenRepository.php | 8 +- src/AuthorizationServer.php | 12 +- .../BearerTokenValidator.php | 2 - src/CryptTrait.php | 100 --- src/Entities/TokenInterface.php | 12 +- src/Entities/Traits/AccessTokenTrait.php | 9 +- src/Entities/Traits/TokenEntityTrait.php | 21 +- src/Grant/AbstractGrant.php | 14 +- src/Grant/AuthCodeGrant.php | 90 +-- src/Grant/DeviceCodeGrant.php | 13 +- src/Grant/GrantTypeInterface.php | 5 +- src/Grant/ImplicitGrant.php | 4 +- src/Grant/PasswordGrant.php | 4 +- src/Grant/RefreshTokenGrant.php | 64 +- .../AccessTokenRepositoryInterface.php | 3 +- src/Repositories/ScopeRepositoryInterface.php | 3 +- src/ResponseTypes/AbstractResponseType.php | 3 - src/ResponseTypes/BearerTokenResponse.php | 19 +- src/ResponseTypes/ResponseTypeInterface.php | 2 - tests/AuthorizationServerTest.php | 31 +- tests/Grant/AbstractGrantTest.php | 11 +- tests/Grant/AuthCodeGrantTest.php | 691 ++++++++---------- tests/Grant/DeviceCodeGrantTest.php | 24 +- tests/Grant/ImplicitGrantTest.php | 13 +- tests/Grant/RefreshTokenGrantTest.php | 364 ++++----- .../AuthorizationServerMiddlewareTest.php | 2 - .../ResourceServerMiddlewareTest.php | 9 +- .../ResponseTypes/BearerResponseTypeTest.php | 28 +- .../DeviceCodeResponseTypeTest.php | 1 - tests/Stubs/CryptTraitStub.php | 36 - tests/Stubs/GrantType.php | 3 +- tests/Utils/CryptTraitTest.php | 46 -- 32 files changed, 558 insertions(+), 1089 deletions(-) delete mode 100644 src/CryptTrait.php delete mode 100644 tests/Stubs/CryptTraitStub.php delete mode 100644 tests/Utils/CryptTraitTest.php diff --git a/examples/src/Repositories/AccessTokenRepository.php b/examples/src/Repositories/AccessTokenRepository.php index 1eb3e5bdd..e04b3e527 100644 --- a/examples/src/Repositories/AccessTokenRepository.php +++ b/examples/src/Repositories/AccessTokenRepository.php @@ -14,6 +14,7 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use OAuth2ServerExamples\Entities\AccessTokenEntity; @@ -46,20 +47,17 @@ public function isAccessTokenRevoked($tokenId): bool /** * {@inheritdoc} */ - public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null): AccessTokenEntityInterface + public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, ?UserEntityInterface $user = null): AccessTokenEntityInterface { $accessToken = new AccessTokenEntity(); $accessToken->setClient($clientEntity); + $accessToken->setUser($user); foreach ($scopes as $scope) { $accessToken->addScope($scope); } - if ($userIdentifier !== null) { - $accessToken->setUserIdentifier((string) $userIdentifier); - } - return $accessToken; } } diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 1ed643ba1..1de7ad42f 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -14,6 +14,7 @@ use DateInterval; use Defuse\Crypto\Key; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\EventEmitting\EmitterAwareInterface; use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill; use League\OAuth2\Server\Exception\OAuthServerException; @@ -48,8 +49,6 @@ class AuthorizationServer implements EmitterAwareInterface protected ResponseTypeInterface $responseType; - private Key|string|null $encryptionKey; - private string $defaultScope = ''; private bool $revokeRefreshTokens = true; @@ -62,7 +61,6 @@ public function __construct( private AccessTokenRepositoryInterface $accessTokenRepository, private ScopeRepositoryInterface $scopeRepository, CryptKeyInterface|string $privateKey, - Key|string|null $encryptionKey, ResponseTypeInterface|null $responseType = null ) { if ($privateKey instanceof CryptKeyInterface === false) { @@ -70,7 +68,6 @@ public function __construct( } $this->privateKey = $privateKey; - $this->encryptionKey = $encryptionKey; if ($responseType === null) { $responseType = new BearerTokenResponse(); @@ -96,7 +93,6 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval|null $grantType->setDefaultScope($this->defaultScope); $grantType->setPrivateKey($this->privateKey); $grantType->setEmitter($this->getEmitter()); - $grantType->setEncryptionKey($this->encryptionKey); $grantType->revokeRefreshTokens($this->revokeRefreshTokens); $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType; @@ -152,10 +148,10 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * Complete a device authorization request */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, UserEntityInterface $user, bool $userApproved): void { $this->enabledGrantTypes['urn:ietf:params:oauth:grant-type:device_code'] - ->completeDeviceAuthorizationRequest($deviceCode, $userId, $userApproved); + ->completeDeviceAuthorizationRequest($deviceCode, $user, $userApproved); } /** @@ -193,8 +189,6 @@ protected function getResponseType(): ResponseTypeInterface $responseType->setPrivateKey($this->privateKey); } - $responseType->setEncryptionKey($this->encryptionKey); - return $responseType; } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 0442dd48e..5080d0475 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -36,8 +36,6 @@ class BearerTokenValidator implements AuthorizationValidatorInterface { - use CryptTrait; - protected CryptKeyInterface $publicKey; private Configuration $jwtConfiguration; diff --git a/src/CryptTrait.php b/src/CryptTrait.php deleted file mode 100644 index 0b530c880..000000000 --- a/src/CryptTrait.php +++ /dev/null @@ -1,100 +0,0 @@ - - * @copyright Copyright (c) Alex Bilbie - * @license http://mit-license.org/ - * - * @link https://github.com/thephpleague/oauth2-server - */ - -declare(strict_types=1); - -namespace League\OAuth2\Server; - -use Defuse\Crypto\Crypto; -use Defuse\Crypto\Exception\EnvironmentIsBrokenException; -use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException; -use Defuse\Crypto\Key; -use Exception; -use InvalidArgumentException; -use LogicException; - -use function is_string; - -trait CryptTrait -{ - protected string|Key|null $encryptionKey = null; - - /** - * Encrypt data with encryptionKey. - * - * @throws LogicException - */ - protected function encrypt(string $unencryptedData): string - { - try { - if ($this->encryptionKey instanceof Key) { - return Crypto::encrypt($unencryptedData, $this->encryptionKey); - } - - if (is_string($this->encryptionKey)) { - return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); - } - - throw new LogicException('Encryption key not set when attempting to encrypt'); - } catch (Exception $e) { - throw new LogicException($e->getMessage(), 0, $e); - } - } - - /** - * Decrypt data with encryptionKey. - * - * @throws LogicException - */ - protected function decrypt(string $encryptedData): string - { - try { - if ($this->encryptionKey instanceof Key) { - return Crypto::decrypt($encryptedData, $this->encryptionKey); - } - - if (is_string($this->encryptionKey)) { - return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); - } - - throw new LogicException('Encryption key not set when attempting to decrypt'); - } catch (WrongKeyOrModifiedCiphertextException $e) { - $exceptionMessage = 'The authcode or decryption key/password used ' - . 'is not correct'; - - throw new InvalidArgumentException($exceptionMessage, 0, $e); - } catch (EnvironmentIsBrokenException $e) { - $exceptionMessage = 'Auth code decryption failed. This is likely ' - . 'due to an environment issue or runtime bug in the ' - . 'decryption library'; - - throw new LogicException($exceptionMessage, 0, $e); - } catch (Exception $e) { - throw new LogicException($e->getMessage(), 0, $e); - } - } - - public function setEncryptionKey(Key|string|null $key = null): void - { - $this->encryptionKey = $key; - } - - /** - * Has Encryption key been set - * - * @return bool True if encryption key has been set - */ - public function canUseCrypt(): bool - { - return !empty($this->encryptionKey); - } -} diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index c12f7585b..3a0be9e65 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -41,18 +41,18 @@ public function getExpiryDateTime(): DateTimeImmutable; public function setExpiryDateTime(DateTimeImmutable $dateTime): void; /** - * Set the identifier of the user associated with the token. + * Set the user associated with the token. * - * @param non-empty-string $identifier + * @param ?UserEntityInterface $identifier The identifier of the user */ - public function setUserIdentifier(?string $identifier): void; + public function setUser(?UserEntityInterface $user): void; /** - * Get the token user's identifier. + * Get the token user. * - * @return non-empty-string|null + * @return ?UserEntityInterface */ - public function getUserIdentifier(): ?string; + public function getUser(): ?UserEntityInterface; /** * Get the client that the token was issued to. diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 6b1387b5f..1699d1e8e 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -20,6 +20,7 @@ use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use RuntimeException; trait AccessTokenTrait @@ -85,9 +86,11 @@ abstract public function getClient(): ClientEntityInterface; abstract public function getExpiryDateTime(): DateTimeImmutable; /** - * @return non-empty-string|null + * Get the token user. + * + * @return ?UserEntityInterface */ - abstract public function getUserIdentifier(): string|null; + abstract public function getUser(): ?UserEntityInterface; /** * @return ScopeEntityInterface[] @@ -104,6 +107,6 @@ abstract public function getIdentifier(): string; */ private function getSubjectIdentifier(): string { - return $this->getUserIdentifier() ?? $this->getClient()->getIdentifier(); + return $this->getUser()?->getIdentifier() ?? $this->getClient()->getIdentifier(); } } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index a7f85bbd8..f1c8b61ef 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -15,6 +15,7 @@ use DateTimeImmutable; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use function array_values; @@ -28,9 +29,9 @@ trait TokenEntityTrait protected DateTimeImmutable $expiryDateTime; /** - * @var non-empty-string|null + * @var ?UserEntityInterface */ - protected string|null $userIdentifier = null; + protected ?UserEntityInterface $user = null; protected ClientEntityInterface $client; @@ -79,23 +80,23 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void } /** - * Set the identifier of the user associated with the token. + * Set the user associated with the token. * - * @param non-empty-string $identifier The identifier of the user + * @param ?UserEntityInterface $identifier The identifier of the user */ - public function setUserIdentifier(?string $identifier): void + public function setUser(?UserEntityInterface $user): void { - $this->userIdentifier = $identifier; + $this->user = $user; } /** - * Get the token user's identifier. + * Get the token user. * - * @return non-empty-string|null + * @return ?UserEntityInterface */ - public function getUserIdentifier(): ?string + public function getUser(): ?UserEntityInterface { - return $this->userIdentifier; + return $this->user; } /** diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index abcfaf783..b7f600134 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -26,6 +26,7 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; @@ -60,7 +61,6 @@ abstract class AbstractGrant implements GrantTypeInterface { use EmitterAwarePolyfill; - use CryptTrait; protected const SCOPE_DELIMITER_STRING = ' '; @@ -404,12 +404,12 @@ protected function getServerParameter(string $parameter, ServerRequestInterface protected function issueAccessToken( DateInterval $accessTokenTTL, ClientEntityInterface $client, - string|null $userIdentifier, + ?UserEntityInterface $user, array $scopes = [] ): AccessTokenEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; - $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); + $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); $accessToken->setPrivateKey($this->privateKey); @@ -433,7 +433,7 @@ protected function issueAccessToken( /** * Issue an auth code. * - * @param non-empty-string $userIdentifier + * @param ?UserEntityInterface $user * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException @@ -442,7 +442,7 @@ protected function issueAccessToken( protected function issueAuthCode( DateInterval $authCodeTTL, ClientEntityInterface $client, - string $userIdentifier, + ?UserEntityInterface $user, ?string $redirectUri, array $scopes = [], ?string $codeChallenge = null, @@ -453,7 +453,7 @@ protected function issueAuthCode( $authCode = $this->authCodeRepository->getNewAuthCode(); $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL)); $authCode->setClient($client); - $authCode->setUserIdentifier($userIdentifier); + $authCode->setUser($user); $authCode->setCodeChallenge($codeChallenge); $authCode->setCodeChallengeMethod($codeChallengeMethod); @@ -597,7 +597,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, UserEntityInterface $user, bool $userApproved): void { throw new LogicException('This grant cannot complete a device authorization request'); } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index aebfe3507..893c99853 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -111,66 +111,11 @@ public function respondToAccessTokenRequest( throw OAuthServerException::invalidRequest('code'); } - if ($this->canUseCrypt()) { - try { - $authCodePayload = json_decode($this->decrypt($code)); + // Get the Auth Code Payload from Repository + $ace = $this->authCodeRepository->getAuthCodeEntity($code); - $ace = $this->authCodeRepository->getNewAuthCode(); - - if ($ace == null) { - // Probably should throw an exception here instead - return $responseType; - } - - if (isset($authCodePayload->auth_code_id)) { - $ace->setIdentifier($authCodePayload->auth_code_id); - } - - if (isset($authCodePayload->client_id)) { - $ace->setClient($this->getClientEntityOrFail($authCodePayload->client_id, $request)); - } - - if (isset($authCodePayload->user_id)) { - $ace->setUserIdentifier((string)$authCodePayload->user_id); - } - - if (isset($authCodePayload->code_challenge)) { - $ace->setCodeChallenge($authCodePayload->code_challenge); - } - - if (isset($authCodePayload->code_challenge_method)) { - $ace->setCodeChallengeMethod($authCodePayload->code_challenge_method); - } - - if (isset($authCodePayload->redirect_uri)) { - $ace->setRedirectUri($authCodePayload->redirect_uri); - } - - if (isset($authCodePayload->expire_time)) { - $expire = new DateTimeImmutable(); - $expire = $expire->setTimestamp($authCodePayload->expire_time); - - $ace->setExpiryDateTime($expire); - } - - if (isset($authCodePayload->scopes)) { - $scopes = $this->validateScopes($authCodePayload->scopes); - - $ace->setScopes($scopes); - } - - } catch (InvalidArgumentException $e) { - throw OAuthServerException::invalidGrant('Cannot validate the provided authorization code'); - } catch (LogicException $e) { - throw OAuthServerException::invalidRequest('code', 'Issue decrypting the authorization code', $e); - } - } else { - // Get the Auth Code Payload from Repository - $ace = $this->authCodeRepository->getAuthCodeEntity($code); - - if (empty($ace)) { - throw OAuthServerException::invalidRequest('code', 'Cannot find authorization code'); - } + if (empty($ace)) { + throw OAuthServerException::invalidRequest('code', 'Cannot validate the provided authorization code'); } $this->validateAuthorizationCode($ace, $client, $request); @@ -179,7 +124,7 @@ public function respondToAccessTokenRequest( $ace->getScopes(), $this->getIdentifier(), $client, - $ace->getUserIdentifier(), + $ace->getUser(), $ace->getIdentifier() ); @@ -198,7 +143,7 @@ public function respondToAccessTokenRequest( } // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $ace->getUserIdentifier(), $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $ace->getUser(), $scopes); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); @@ -421,7 +366,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth $authCode = $this->issueAuthCode( $this->authCodeTTL, $authorizationRequest->getClient(), - $authorizationRequest->getUser()->getIdentifier(), + $authorizationRequest->getUser(), $authorizationRequest->getRedirectUri(), $authorizationRequest->getScopes(), $authorizationRequest->getCodeChallenge(), @@ -430,27 +375,6 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth $code = $authCode->getIdentifier(); - if ($this->canUseCrypt()) { - $payload = [ - 'client_id' => $authCode->getClient()->getIdentifier(), - 'redirect_uri' => $authCode->getRedirectUri(), - 'auth_code_id' => $authCode->getIdentifier(), - 'scopes' => $authCode->getScopes(), - 'user_id' => $authCode->getUserIdentifier(), - 'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(), - 'code_challenge' => $authorizationRequest->getCodeChallenge(), - 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), - ]; - - $jsonPayload = json_encode($payload); - - if ($jsonPayload === false) { - throw new LogicException('An error was encountered when JSON encoding the authorization request response'); - } - - $code = $this->encrypt($jsonPayload); - } - $response = new RedirectResponse(); $response->setRedirectUri( $this->makeRedirectUri( diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index aa8cba0f5..e9eaf78b2 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -21,6 +21,7 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; @@ -113,7 +114,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, UserEntityInterface $user, bool $userApproved): void { $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode($deviceCode); @@ -121,11 +122,11 @@ public function completeDeviceAuthorizationRequest(string $deviceCode, string $u throw OAuthServerException::invalidRequest('device_code', 'Device code does not exist'); } - if ($userId === '') { + if ($user === null) { throw OAuthServerException::invalidRequest('user_id', 'User ID is required'); } - $deviceCode->setUserIdentifier($userId); + $deviceCode->setUser($user); $deviceCode->setUserApproved($userApproved); $this->deviceCodeRepository->persistDeviceCode($deviceCode); @@ -144,7 +145,7 @@ public function respondToAccessTokenRequest( $deviceCodeEntity = $this->validateDeviceCode($request, $client); // If device code has no user associated, respond with pending or slow down - if (is_null($deviceCodeEntity->getUserIdentifier())) { + if (is_null($deviceCodeEntity->getUser())) { $shouldSlowDown = $this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt()); $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable()); @@ -162,10 +163,10 @@ public function respondToAccessTokenRequest( } // Finalize the requested scopes - $finalizedScopes = $this->scopeRepository->finalizeScopes($deviceCodeEntity->getScopes(), $this->getIdentifier(), $client, $deviceCodeEntity->getUserIdentifier()); + $finalizedScopes = $this->scopeRepository->finalizeScopes($deviceCodeEntity->getScopes(), $this->getIdentifier(), $client, $deviceCodeEntity->getUser()); // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCodeEntity->getUserIdentifier(), $finalizedScopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCodeEntity->getUser(), $finalizedScopes); $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $responseType->setAccessToken($accessToken); diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 4e7dcf0f0..ee39fd097 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -17,6 +17,7 @@ use DateInterval; use Defuse\Crypto\Key; use League\OAuth2\Server\CryptKeyInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\EventEmitting\EmitterAwareInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; @@ -98,7 +99,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ * * If the validation is successful a DeviceCode object is persisted. */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void; + public function completeDeviceAuthorizationRequest(string $deviceCode, UserEntityInterface $user, bool $userApproved): void; /** * Set the client repository. @@ -125,8 +126,6 @@ public function setDefaultScope(string $scope): void; */ public function setPrivateKey(CryptKeyInterface $privateKey): void; - public function setEncryptionKey(Key|string|null $key = null): void; - /** * Enable or prevent the revocation of refresh tokens upon usage. */ diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 81252dea1..c49ab2fb6 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -162,13 +162,13 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth $authorizationRequest->getScopes(), $this->getIdentifier(), $authorizationRequest->getClient(), - $authorizationRequest->getUser()->getIdentifier() + $authorizationRequest->getUser() ); $accessToken = $this->issueAccessToken( $this->accessTokenTTL, $authorizationRequest->getClient(), - $authorizationRequest->getUser()->getIdentifier(), + $authorizationRequest->getUser(), $finalizedScopes ); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index f5d8da322..18b51a44e 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -58,11 +58,11 @@ public function respondToAccessTokenRequest( $scopes, $this->getIdentifier(), $client, - $user->getIdentifier() + $user ); // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user, $finalizedScopes); $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 2e6d141dd..ae56be674 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -60,25 +60,18 @@ public function respondToAccessTokenRequest( return $responseType; } - $originalScopes = $oldRefreshToken->getScopes(); - $originalScopeArray = []; - foreach ($originalScopes as $scopeEntity) { - $originalScopeArray[$scopeEntity->getIdentifier()] = $scopeEntity->getIdentifier(); - } - $originalScopeArray = array_values($originalScopeArray); - $scopes = $this->validateScopes( $this->getRequestParameter( 'scope', $request, - implode(self::SCOPE_DELIMITER_STRING, $originalScopeArray) + implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken->getScopes()) ) ); // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure // the request doesn't include any new scopes foreach ($scopes as $scope) { - if (in_array($scope->getIdentifier(), $originalScopeArray, true) === false) { + if (in_array($scope->getIdentifier(), $oldRefreshToken->getScopes(), true) === false) { throw OAuthServerException::invalidScope($scope->getIdentifier()); } } @@ -92,11 +85,7 @@ public function respondToAccessTokenRequest( } // Issue and persist new access token - $userId = $oldRefreshToken->getUserIdentifier(); - if (is_int($userId)) { - $userId = (string) $userId; - } - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $userId, $scopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken->getUser(), $scopes); $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken)); $responseType->setAccessToken($accessToken); @@ -122,52 +111,7 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, stri ?? throw OAuthServerException::invalidRequest('refresh_token'); // Validate refresh token - if ($this->canUseCrypt()) { - try { - $refreshTokenJson = $this->decrypt($refreshTokenParam); - $refreshTokenData = json_decode($refreshTokenJson, true); - - $refreshTokenEntity = $this->refreshTokenRepository->getNewRefreshToken(); - - if ($refreshTokenEntity == null) { - return null; - } - - if (isset($refreshTokenData['refresh_token_id'])) { - $refreshTokenEntity->setIdentifier($refreshTokenData['refresh_token_id']); - } - - if (isset($refreshTokenData['expire_time'])) { - $expire = new DateTimeImmutable(); - $expire = $expire->setTimestamp($refreshTokenData['expire_time']); - $refreshTokenEntity->setExpiryDateTime($expire); - } - - if (isset($refreshTokenData['client_id'])) { - $client = $this->getClientEntityOrFail($refreshTokenData['client_id'], $request); - $refreshTokenEntity->setClient($client); - } - - if (isset($refreshTokenData['scopes'])) { - $scopes = $this->validateScopes($refreshTokenData['scopes']); - - $refreshTokenEntity->setScopes($scopes); - } - - if (isset($refreshTokenData['user_id'])) { - $refreshTokenEntity->setUserIdentifier((string)$refreshTokenData['user_id']); - } - - if (isset($refreshTokenData['access_token_id'])) { - $accessToken = $this->accessTokenRepository->getAccessTokenEntity($refreshTokenData['access_token_id']); - $refreshTokenEntity->setAccessToken($accessToken); - } - } catch (Exception $e) { - throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); - } - } else { - $refreshTokenEntity = $this->refreshTokenRepository->getRefreshTokenEntity($refreshTokenParam); - } + $refreshTokenEntity = $this->refreshTokenRepository->getRefreshTokenEntity($refreshTokenParam); if ($refreshTokenEntity === null) { throw OAuthServerException::invalidRefreshToken('Cannot find refresh token'); diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 92a3bba3e..81febb09d 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -15,6 +15,7 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; /** @@ -30,7 +31,7 @@ interface AccessTokenRepositoryInterface extends RepositoryInterface public function getNewToken( ClientEntityInterface $clientEntity, array $scopes, - string|null $userIdentifier = null + ?UserEntityInterface $user = null ): AccessTokenEntityInterface; /** diff --git a/src/Repositories/ScopeRepositoryInterface.php b/src/Repositories/ScopeRepositoryInterface.php index e5ae7c716..2242074c2 100644 --- a/src/Repositories/ScopeRepositoryInterface.php +++ b/src/Repositories/ScopeRepositoryInterface.php @@ -14,6 +14,7 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; /** * Scope interface. @@ -39,7 +40,7 @@ public function finalizeScopes( array $scopes, string $grantType, ClientEntityInterface $clientEntity, - string|null $userIdentifier = null, + ?UserEntityInterface $user = null, ?string $authCodeId = null ): array; } diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 2af00e224..137f9e93e 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -15,14 +15,11 @@ namespace League\OAuth2\Server\ResponseTypes; use League\OAuth2\Server\CryptKeyInterface; -use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; abstract class AbstractResponseType implements ResponseTypeInterface { - use CryptTrait; - protected AccessTokenEntityInterface $accessToken; protected RefreshTokenEntityInterface $refreshToken; diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 4e98c483a..004e0322b 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -35,24 +35,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter ]; if (isset($this->refreshToken)) { - if ($this->canUseCrypt()) { - $refreshTokenPayload = json_encode([ - 'client_id' => $this->accessToken->getClient()->getIdentifier(), - 'refresh_token_id' => $this->refreshToken->getIdentifier(), - 'access_token_id' => $this->accessToken->getIdentifier(), - 'scopes' => $this->accessToken->getScopes(), - 'user_id' => $this->accessToken->getUserIdentifier(), - 'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(), - ]); - - if ($refreshTokenPayload === false) { - throw new LogicException('Error encountered JSON encoding the refresh token payload'); - } - - $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); - } else { - $responseParams['refresh_token'] = $this->refreshToken->getIdentifier(); - } + $responseParams['refresh_token'] = $this->refreshToken->getIdentifier(); } $responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams)); diff --git a/src/ResponseTypes/ResponseTypeInterface.php b/src/ResponseTypes/ResponseTypeInterface.php index 8e70ae9f8..66c9d355c 100644 --- a/src/ResponseTypes/ResponseTypeInterface.php +++ b/src/ResponseTypes/ResponseTypeInterface.php @@ -26,6 +26,4 @@ public function setAccessToken(AccessTokenEntityInterface $accessToken): void; public function setRefreshToken(RefreshTokenEntityInterface $refreshToken): void; public function generateHttpResponse(ResponseInterface $response): ResponseInterface; - - public function setEncryptionKey(Key|string|null $key = null): void; } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 72add207b..354ee6525 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -57,7 +57,6 @@ public function testGrantTypeGetsEnabled(): void $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), 'file://' . __DIR__ . '/Stubs/private.key', - base64_encode(random_bytes(36)), new StubResponseType() ); @@ -74,7 +73,6 @@ public function testRespondToRequestInvalidGrantType(): void $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), 'file://' . __DIR__ . '/Stubs/private.key', - base64_encode(random_bytes(36)), new StubResponseType() ); @@ -112,7 +110,6 @@ public function testRespondToRequest(): void $accessTokenRepositoryMock, $scopeRepositoryMock, 'file://' . __DIR__ . '/Stubs/private.key', - base64_encode(random_bytes(36)), new StubResponseType() ); @@ -134,8 +131,7 @@ public function testGetResponseType(): void $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', - 'file://' . __DIR__ . '/Stubs/public.key' + 'file://' . __DIR__ . '/Stubs/private.key' ); $abstractGrantReflection = new ReflectionClass($server); @@ -155,8 +151,7 @@ public function testGetResponseTypeExtended(): void $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', - 'file://' . __DIR__ . '/Stubs/public.key' + 'file://' . __DIR__ . '/Stubs/private.key' ); $abstractGrantReflection = new ReflectionClass($server); @@ -170,32 +165,21 @@ public function testGetResponseTypeExtended(): void $privateKeyProperty = $responseTypeReflection->getProperty('privateKey'); $privateKeyProperty->setAccessible(true); - $encryptionKeyProperty = $responseTypeReflection->getProperty('encryptionKey'); - $encryptionKeyProperty->setAccessible(true); - // generated instances should have keys setup self::assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath()); - self::assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType)); } public function testMultipleRequestsGetDifferentResponseTypeInstances(): void { $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; - $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; $responseTypePrototype = new class() extends BearerTokenResponse { protected CryptKeyInterface $privateKey; - protected Key|string|null $encryptionKey = null; public function getPrivateKey(): CryptKeyInterface { return $this->privateKey; } - - public function getEncryptionKey(): Key|string|null - { - return $this->encryptionKey; - } }; $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -205,7 +189,6 @@ public function getEncryptionKey(): Key|string|null $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), $privateKey, - $encryptionKey, $responseTypePrototype ); @@ -218,7 +201,6 @@ public function getEncryptionKey(): Key|string|null // generated instances should have keys setup self::assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath()); - self::assertSame($encryptionKey, $responseTypeA->getEncryptionKey()); // all instances should be different but based on the same prototype self::assertSame(get_class($responseTypePrototype), get_class($responseTypeA)); @@ -236,8 +218,7 @@ public function testCompleteAuthorizationRequest(): void $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', - 'file://' . __DIR__ . '/Stubs/public.key' + 'file://' . __DIR__ . '/Stubs/private.key' ); $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); @@ -293,8 +274,7 @@ public function testValidateAuthorizationRequest(): void $clientRepositoryMock, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $scopeRepositoryMock, - 'file://' . __DIR__ . '/Stubs/private.key', - 'file://' . __DIR__ . '/Stubs/public.key' + 'file://' . __DIR__ . '/Stubs/private.key' ); $server->setDefaultScope(self::DEFAULT_SCOPE); @@ -323,8 +303,7 @@ public function testValidateAuthorizationRequestUnregistered(): void $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', - 'file://' . __DIR__ . '/Stubs/public.key' + 'file://' . __DIR__ . '/Stubs/private.key' ); $request = (new ServerRequest())->withQueryParams([ diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index adfb880be..99e3f59e8 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -23,6 +23,7 @@ use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; +use LeagueTests\Stubs\UserEntity; use LogicException; use PHPUnit\Framework\TestCase; use ReflectionClass; @@ -440,12 +441,15 @@ public function testIssueAccessToken(): void $issueAccessTokenMethod = $abstractGrantReflection->getMethod('issueAccessToken'); $issueAccessTokenMethod->setAccessible(true); + $user = new UserEntity(); + $user->setIdentifier('123'); + /** @var AccessTokenEntityInterface $accessToken */ $accessToken = $issueAccessTokenMethod->invoke( $grantMock, new DateInterval('PT1H'), new ClientEntity(), - 123, + $user, [new ScopeEntity()] ); @@ -468,13 +472,16 @@ public function testIssueAuthCode(): void $scope = new ScopeEntity(); $scope->setIdentifier('scopeId'); + $user = new UserEntity(); + $user->setIdentifier('123'); + self::assertInstanceOf( AuthCodeEntityInterface::class, $issueAuthCodeMethod->invoke( $grantMock, new DateInterval('PT1H'), new ClientEntity(), - 123, + $user, 'http://foo/bar', [$scope] ) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 968e9cba6..5949344a1 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -4,6 +4,7 @@ namespace LeagueTests\Grant; +use DateTimeImmutable; use DateInterval; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; @@ -22,7 +23,6 @@ use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; -use LeagueTests\Stubs\CryptTraitStub; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; @@ -40,15 +40,13 @@ class AuthCodeGrantTest extends TestCase private const DEFAULT_SCOPE = 'basic'; private const REDIRECT_URI = 'https://foo/bar'; - protected CryptTraitStub $cryptStub; - private const CODE_VERIFIER = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'; private const CODE_CHALLENGE = 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM'; public function setUp(): void { - $this->cryptStub = new CryptTraitStub(); + } public function testGetIdentifier(): void @@ -532,7 +530,6 @@ public function testCompleteAuthorizationRequest(): void $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); - $grant->setEncryptionKey($this->cryptStub->getKey()); self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } @@ -557,7 +554,6 @@ public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); - $grant->setEncryptionKey($this->cryptStub->getKey()); self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } @@ -582,7 +578,6 @@ public function testCompleteAuthorizationRequestDenied(): void $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); - $grant->setEncryptionKey($this->cryptStub->getKey()); try { $grant->completeAuthorizationRequest($authRequest); @@ -626,6 +621,18 @@ public function testRespondToAccessTokenRequest(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -635,7 +642,6 @@ public function testRespondToAccessTokenRequest(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -651,16 +657,7 @@ public function testRespondToAccessTokenRequest(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -699,6 +696,18 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(null); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -708,7 +717,6 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -723,16 +731,7 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => null, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -763,6 +762,18 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $authCodeGrant = new AuthCodeGrant( $authCodeRepository, $refreshTokenRepositoryMock, @@ -772,7 +783,6 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $authCodeGrant->setClientRepository($clientRepositoryMock); $authCodeGrant->setScopeRepository($scopeRepositoryMock); $authCodeGrant->setAccessTokenRepository($accessTokenRepositoryMock); - $authCodeGrant->setEncryptionKey($this->cryptStub->getKey()); $authCodeGrant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -789,16 +799,7 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void [ 'grant_type' => 'authorization_code', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'client_id' => 'foo', - 'expire_time' => time() + 3600, - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -832,6 +833,18 @@ public function testRespondToAccessTokenRequestForPublicClient(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -841,7 +854,6 @@ public function testRespondToAccessTokenRequestForPublicClient(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -857,16 +869,7 @@ public function testRespondToAccessTokenRequestForPublicClient(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -900,6 +903,18 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $refreshTokenRepositoryMock, @@ -909,7 +924,6 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -925,16 +939,7 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -973,6 +978,20 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge(self::CODE_VERIFIER); + $ace->setCodeChallengeMethod('plain'); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $refreshTokenRepositoryMock, @@ -983,7 +1002,6 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -1000,18 +1018,7 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => self::CODE_VERIFIER, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => self::CODE_VERIFIER, - 'code_challenge_method' => 'plain', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1050,6 +1057,20 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge(self::CODE_CHALLENGE); + $ace->setCodeChallengeMethod('S256'); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1060,7 +1081,6 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -1077,18 +1097,7 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => self::CODE_VERIFIER, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => self::CODE_CHALLENGE, - 'code_challenge_method' => 'S256', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1124,6 +1133,18 @@ public function testPKCEDowngradeBlocked(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(null); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1134,7 +1155,6 @@ public function testPKCEDowngradeBlocked(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -1151,19 +1171,7 @@ public function testPKCEDowngradeBlocked(): void 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => self::CODE_VERIFIER, - 'code' => $this->cryptStub->doEncrypt( - json_encode( - [ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], - JSON_THROW_ON_ERROR - ) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1190,13 +1198,24 @@ public function testRespondToAccessTokenRequestMissingRedirectUri(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri('http://foo/bar'); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1210,14 +1229,7 @@ public function testRespondToAccessTokenRequestMissingRedirectUri(): void [ 'client_id' => 'foo', 'grant_type' => 'authorization_code', - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1243,13 +1255,23 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri('http://foo/bar'); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1264,14 +1286,7 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void 'client_id' => 'foo', 'grant_type' => 'authorization_code', 'redirect_uri' => 'http://bar/foo', - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1297,13 +1312,24 @@ public function testRejectAccessTokenRequestIfRedirectUriSpecifiedButNotInOrigin $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(null); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1318,14 +1344,7 @@ public function testRejectAccessTokenRequestIfRedirectUriSpecifiedButNotInOrigin 'client_id' => 'foo', 'grant_type' => 'authorization_code', 'redirect_uri' => 'http://bar/foo', - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'redirect_uri' => null, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1358,7 +1377,6 @@ public function testRespondToAccessTokenRequestMissingCode(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1384,64 +1402,6 @@ public function testRespondToAccessTokenRequestMissingCode(): void $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode(): void - { - $client = new ClientEntity(); - $client->setRedirectUri(self::REDIRECT_URI); - - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); - - $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); - $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); - - $grant = new AuthCodeGrant( - $authCodeRepository, - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') - ); - - $grant->setClientRepository($clientRepositoryMock); - $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); - - $request = new ServerRequest( - [], - [], - null, - 'POST', - 'php://input', - [], - [], - [], - [ - 'grant_type' => 'authorization_code', - 'client_id' => 'foo', - 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => time() + 3600, - ], JSON_THROW_ON_ERROR) - ), - ] - ); - - try { - /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - } catch (OAuthServerException $e) { - self::assertEquals('Authorization code malformed', $e->getHint()); - } - } - public function testRespondToAccessTokenRequestWithAuthCodeNotAString(): void { $client = new ClientEntity(); @@ -1457,7 +1417,6 @@ public function testRespondToAccessTokenRequestWithAuthCodeNotAString(): void ); $grant->setClientRepository($clientRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1494,6 +1453,18 @@ public function testRespondToAccessTokenRequestExpiredCode(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1502,7 +1473,6 @@ public function testRespondToAccessTokenRequestExpiredCode(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1517,16 +1487,7 @@ public function testRespondToAccessTokenRequestExpiredCode(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() - 3600, - 'client_id' => 'foo', - 'user_id' => 123, - 'scopes' => ['foo'], - 'redirect_uri' => 'http://foo/bar', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1563,6 +1524,18 @@ public function testRespondToAccessTokenRequestRevokedCode(): void $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); $grant = new AuthCodeGrant( $authCodeRepositoryMock, @@ -1573,7 +1546,6 @@ public function testRespondToAccessTokenRequestRevokedCode(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1588,16 +1560,7 @@ public function testRespondToAccessTokenRequestRevokedCode(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => 123, - 'scopes' => ['foo'], - 'redirect_uri' => 'http://foo/bar', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1647,6 +1610,18 @@ public function testRespondToAccessTokenRequestClientMismatch(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client3); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1656,7 +1631,6 @@ public function testRespondToAccessTokenRequestClientMismatch(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1671,16 +1645,7 @@ public function testRespondToAccessTokenRequestClientMismatch(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'bar', - 'user_id' => 123, - 'scopes' => ['foo'], - 'redirect_uri' => 'http://foo/bar', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1711,15 +1676,18 @@ public function testRespondToAccessTokenRequestBadCode(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getAuthCodeEntity')->willReturn(null); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1738,70 +1706,12 @@ public function testRespondToAccessTokenRequestBadCode(): void ] ); - try { - /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - } catch (OAuthServerException $e) { - self::assertEquals($e->getErrorType(), 'invalid_grant'); - self::assertEquals($e->getHint(), 'Cannot validate the provided authorization code'); - } - } - - public function testRespondToAccessTokenRequestNoEncryptionKey(): void - { - $client = new ClientEntity(); - - $client->setIdentifier('foo'); - $client->setRedirectUri(self::REDIRECT_URI); - $client->setConfidential(); - - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - $clientRepositoryMock->method('validateClient')->willReturn(true); - - $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); - - $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); - $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); - - $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); - $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); - - $grant = new AuthCodeGrant( - $authCodeRepositoryMock, - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') - ); - $grant->setClientRepository($clientRepositoryMock); - $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - // We deliberately don't set an encryption key here - - $request = new ServerRequest( - [], - [], - null, - 'POST', - 'php://input', - [], - [], - [], - [ - 'grant_type' => 'authorization_code', - 'client_id' => 'foo', - 'redirect_uri' => self::REDIRECT_URI, - 'code' => 'badCode', - ] - ); - try { /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { self::assertEquals($e->getErrorType(), 'invalid_request'); - self::assertEquals($e->getHint(), 'Cannot find authorization code'); + self::assertEquals($e->getHint(), 'Cannot validate the provided authorization code'); } } @@ -1834,6 +1744,20 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge('foobar'); + $ace->setCodeChallengeMethod('plain'); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1844,7 +1768,6 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1860,18 +1783,7 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => self::CODE_VERIFIER, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'plain', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1912,6 +1824,20 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge('foobar'); + $ace->setCodeChallengeMethod('S256'); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -1922,7 +1848,6 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -1938,18 +1863,7 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => 'nope', - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'S256', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -1990,6 +1904,20 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge(self::CODE_CHALLENGE); + $ace->setCodeChallengeMethod('S256'); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -2000,7 +1928,6 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -2016,18 +1943,7 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => 'dqX7C-RbqjHYtytmhGTigKdZCXfxq-+xbsk9_GxUcaE', // Malformed code. Contains `+`. - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => self::CODE_CHALLENGE, - 'code_challenge_method' => 'S256', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -2068,6 +1984,20 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge(self::CODE_CHALLENGE); + $ace->setCodeChallengeMethod('S256'); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -2078,7 +2008,6 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -2094,18 +2023,7 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => 'dqX7C-RbqjHY', // Malformed code. Invalid length. - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => 'R7T1y1HPNFvs1WDCrx4lfoBS6KD2c71pr8OHvULjvv8', - 'code_challenge_method' => 'S256', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -2146,6 +2064,20 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + $ace->setCodeChallenge('foobar'); + $ace->setCodeChallengeMethod('plain'); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -2156,7 +2088,6 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( [], @@ -2171,18 +2102,7 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'plain', - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -2226,7 +2146,7 @@ public function testAuthCodeRepositoryUniqueConstraintCheck(): void $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); - $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); @@ -2254,7 +2174,6 @@ public function testAuthCodeRepositoryFailToPersist(): void $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); - $grant->setEncryptionKey($this->cryptStub->getKey()); $this->expectException(OAuthServerException::class); $this->expectExceptionCode(7); @@ -2313,6 +2232,18 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepository->method('getAuthCodeEntity')->willReturn($ace); + $refreshTokenRepositoryMock ->expects(self::exactly(2)) ->method('persistNewRefreshToken') @@ -2333,7 +2264,6 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -2349,16 +2279,7 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -2392,6 +2313,18 @@ public function testRefreshTokenRepositoryFailToPersist(): void $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -2401,7 +2334,6 @@ public function testRefreshTokenRepositoryFailToPersist(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -2417,16 +2349,7 @@ public function testRefreshTokenRepositoryFailToPersist(): void 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); @@ -2463,6 +2386,18 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v $authCodeRepositoryMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepositoryMock->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $ace = new AuthCodeEntity(); + $ace->setIdentifier(uniqid()); + $ace->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $ace->setClient($client); + $ace->setRedirectUri(self::REDIRECT_URI); + $user = new UserEntity(); + $user->setIdentifier('123'); + $ace->setUser($user); + $ace->setScopes(['foo']); + + $authCodeRepositoryMock->method('getAuthCodeEntity')->willReturn($ace); + $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), @@ -2472,7 +2407,6 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( @@ -2488,16 +2422,7 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v 'grant_type' => 'authorization_code', 'client_id' => 'foo', 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'auth_code_id' => uniqid(), - 'expire_time' => time() + 3600, - 'client_id' => 'foo', - 'user_id' => '123', - 'scopes' => ['foo'], - 'redirect_uri' => self::REDIRECT_URI, - ], JSON_THROW_ON_ERROR) - ), + 'code' => $ace->getIdentifier() ] ); diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 42157a494..80e819a44 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -24,6 +24,7 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; +use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use function base64_encode; @@ -262,9 +263,12 @@ public function testCompleteDeviceAuthorizationRequest(): void 'http://foo/bar', ); - $grant->completeDeviceAuthorizationRequest($deviceCode->getIdentifier(), 'userId', true); + $user = new UserEntity(); + $user->setIdentifier('userId'); - $this::assertEquals('userId', $deviceCode->getUserIdentifier()); + $grant->completeDeviceAuthorizationRequest($deviceCode->getIdentifier(), $user, true); + + $this::assertEquals('userId', $deviceCode->getUser()->getIdentifier()); } public function testDeviceAuthorizationResponse(): void @@ -294,7 +298,6 @@ public function testDeviceAuthorizationResponse(): void $accessRepositoryMock, $scopeRepositoryMock, 'file://' . __DIR__ . '/../Stubs/private.key', - base64_encode(random_bytes(36)), new StubResponseType() ); @@ -337,7 +340,10 @@ public function testRespondToAccessTokenRequest(): void $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); $deviceCodeEntity = new DeviceCodeEntity(); - $deviceCodeEntity->setUserIdentifier('baz'); + $user = new UserEntity(); + $user->setIdentifier('baz'); + + $deviceCodeEntity->setUser($user); $deviceCodeEntity->setIdentifier('deviceCodeEntityIdentifier'); $deviceCodeEntity->setUserCode('123456'); $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('+1 hour')); @@ -353,7 +359,7 @@ public function testRespondToAccessTokenRequest(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken') - ->with($client, $deviceCodeEntity->getScopes(), $deviceCodeEntity->getUserIdentifier()) + ->with($client, $deviceCodeEntity->getScopes(), $deviceCodeEntity->getUser()) ->willReturn($accessTokenEntity); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); @@ -378,7 +384,7 @@ public function testRespondToAccessTokenRequest(): void $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCodeEntity->getIdentifier(), 'baz', true); + $grant->completeDeviceAuthorizationRequest($deviceCodeEntity->getIdentifier(), $user, true); $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', @@ -436,8 +442,10 @@ public function testRespondToRequestMissingDeviceCode(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $user = new UserEntity(); + $user->setIdentifier('baz'); $deviceCodeEntity = new DeviceCodeEntity(); - $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeEntity->setUser($user); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -739,7 +747,7 @@ public function testIssueAccessDeniedError(): void $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCode->getIdentifier(), '1', false); + $grant->completeDeviceAuthorizationRequest($deviceCode->getIdentifier(), new UserEntity(), false); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 617aaa842..f756bb4ed 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -18,7 +18,6 @@ use League\OAuth2\Server\ResponseTypes\RedirectResponse; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\ClientEntity; -use LeagueTests\Stubs\CryptTraitStub; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; @@ -31,11 +30,9 @@ class ImplicitGrantTest extends TestCase private const DEFAULT_SCOPE = 'basic'; private const REDIRECT_URI = 'https://foo/bar'; - protected CryptTraitStub $cryptStub; - public function setUp(): void { - $this->cryptStub = new CryptTraitStub(); + } public function testGetIdentifier(): void @@ -258,7 +255,9 @@ public function testCompleteAuthorizationRequest(): void $accessToken = new AccessTokenEntity(); $accessToken->setClient($client); - $accessToken->setUserIdentifier('userId'); + $user = new UserEntity(); + $user->setIdentifier('userId'); + $accessToken->setUser($user); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); @@ -327,7 +326,9 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void $accessToken = new AccessTokenEntity(); $accessToken->setClient($client); - $accessToken->setUserIdentifier('userId'); + $user = new UserEntity(); + $user->setIdentifier('userId'); + $accessToken->setUser($user); /** @var AccessTokenRepositoryInterface|MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 1713fbe9b..760a0fa08 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -5,6 +5,7 @@ namespace LeagueTests\Grant; use DateInterval; +use DateTimeImmutable; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\CryptKey; @@ -18,10 +19,10 @@ use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\ClientEntity; -use LeagueTests\Stubs\CryptTraitStub; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; +use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use function json_encode; @@ -29,13 +30,6 @@ class RefreshTokenGrantTest extends TestCase { - protected CryptTraitStub $cryptStub; - - public function setUp(): void - { - $this->cryptStub = new CryptTraitStub(); - } - public function testGetIdentifier(): void { $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -68,40 +62,33 @@ public function testRespondToRequest(): void $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); $refreshTokenRepositoryMock->expects(self::once())->method('persistNewRefreshToken')->willReturnSelf(); $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); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => '123', - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', 'scopes' => ['foo'], ]); @@ -133,6 +120,19 @@ public function testRespondToRequestNullRefreshToken(): void $accessTokenRepositoryMock->expects(self::never())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); $refreshTokenRepositoryMock->expects(self::never())->method('persistNewRefreshToken'); @@ -140,32 +140,12 @@ public function testRespondToRequestNullRefreshToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => '123', - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', 'scopes' => ['foo'], ]); @@ -196,6 +176,20 @@ public function testRespondToReducedScopes(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); + $scope = new ScopeEntity(); $scope->setIdentifier('foo'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -206,33 +200,13 @@ public function testRespondToReducedScopes(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo', 'bar'], - 'user_id' => '123', - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', 'scope' => 'foo', ]); @@ -262,6 +236,21 @@ public function testRespondToUnexpectedScope(): void $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo', 'bar']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); + + $scope1 = new ScopeEntity(); $scope1->setIdentifier('foo'); @@ -278,32 +267,12 @@ public function testRespondToUnexpectedScope(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo', 'bar'], - 'user_id' => 123, - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', 'scope' => 'foobar', ]); @@ -331,7 +300,6 @@ public function testRespondToRequestMissingOldToken(): void $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ @@ -363,7 +331,6 @@ public function testRespondToRequestInvalidOldToken(): void $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $oldRefreshToken = 'foobar'; @@ -450,32 +417,12 @@ public function testRespondToRequestClientMismatch(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'bar', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', ]); $responseType = new StubResponseType(); @@ -514,32 +461,12 @@ public function testRespondToRequestExpiredToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => time() - 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', ]); $responseType = new StubResponseType(); @@ -579,32 +506,12 @@ public function testRespondToRequestRevokedToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', ]); $responseType = new StubResponseType(); @@ -645,11 +552,24 @@ public function testRespondToRequestFinalizeScopes(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo', 'bar']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); + $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')); @@ -667,29 +587,10 @@ public function testRespondToRequestFinalizeScopes(): void ->with($client, $finalizedScopes) ->willReturn(new AccessTokenEntity()); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo', 'bar'], - 'user_id' => '123', - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', 'scope' => 'foo bar', ]); @@ -730,29 +631,24 @@ public function testRevokedRefreshToken(): void $refreshTokenRepositoryMock->expects(self::once())->method('revokeRefreshToken')->with(self::equalTo($refreshTokenId)); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => $refreshTokenId, - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => '123', - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => $refreshTokenId, 'scope' => 'foo', ]); @@ -760,7 +656,6 @@ public function testRevokedRefreshToken(): void $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')); @@ -802,29 +697,24 @@ public function testUnrevokedRefreshToken(): void $refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(false); $refreshTokenRepositoryMock->expects(self::never())->method('revokeRefreshToken'); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => $refreshTokenId, - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => '123', - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => $refreshTokenId, 'scope' => 'foo', ]); @@ -834,13 +724,11 @@ public function testUnrevokedRefreshToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey($privateKey); $grant->revokeRefreshTokens(false); $responseType = new BearerTokenResponse(); $responseType->setPrivateKey($privateKey); - $responseType->setEncryptionKey($this->cryptStub->getKey()); $response = $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')) ->generateHttpResponse(new Response()); @@ -852,7 +740,7 @@ public function testUnrevokedRefreshToken(): void self::assertObjectHasProperty('expires_in', $json); self::assertObjectHasProperty('access_token', $json); self::assertObjectHasProperty('refresh_token', $json); - self::assertNotSame($json->refresh_token, $encryptedOldRefreshToken); + self::assertNotSame($json->refresh_token, $refreshTokenId); } public function testRespondToRequestWithIntUserId(): void @@ -882,37 +770,31 @@ public function testRespondToRequestWithIntUserId(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $refreshTokenRepositoryMock->expects(self::once())->method('persistNewRefreshToken')->willReturnSelf(); + $rte = new RefreshTokenEntity(); + $rte->setClient($client); + $rte->setIdentifier('zyxwvu'); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef'); + $rte->setAccessToken($ace); + $rte->setScopes(['foo']); + $user = new UserEntity(); + $user->setIdentifier('123'); + $rte->setUser($user); + $rte->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + + $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); + $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); - $oldRefreshToken = json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => time() + 3600, - ] - ); - - if ($oldRefreshToken === false) { - self::fail('json_encode failed'); - } - - $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken - ); - $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $encryptedOldRefreshToken, + 'refresh_token' => 'zyxwvu', 'scopes' => ['foo'], ]); diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index 814e96a6c..b027d9450 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -51,7 +51,6 @@ public function testValidResponse(): void $accessRepositoryMock, $scopeRepositoryMock, 'file://' . __DIR__ . '/../Stubs/private.key', - base64_encode(random_bytes(36)), new StubResponseType() ); @@ -85,7 +84,6 @@ public function testOAuthErrorResponse(): void $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), 'file://' . __DIR__ . '/../Stubs/private.key', - base64_encode(random_bytes(36)), new StubResponseType() ); diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 4a6d3b79e..38e7b556f 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -14,6 +14,7 @@ use League\OAuth2\Server\ResourceServer; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\ClientEntity; +use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use function func_get_args; @@ -33,7 +34,9 @@ public function testValidResponse(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('test'); - $accessToken->setUserIdentifier('123'); + $user = new UserEntity(); + $user->setIdentifier('123'); + $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -68,7 +71,9 @@ public function testValidResponseExpiredToken(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('test'); - $accessToken->setUserIdentifier('123'); + $user = new UserEntity(); + $user->setIdentifier('123'); + $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 386fb628b..cf91fd7af 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -17,6 +17,7 @@ use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; +use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use function base64_encode; @@ -30,7 +31,6 @@ public function testGenerateHttpResponse(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -44,7 +44,9 @@ public function testGenerateHttpResponse(): void $accessToken->setClient($client); $accessToken->addScope($scope); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $accessToken->setUserIdentifier('userId'); + $user = new UserEntity(); + $user->setIdentifier('userId'); + $accessToken->setUser($user); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -73,7 +75,6 @@ public function testGenerateHttpResponseWithExtraParams(): void { $responseType = new BearerTokenResponseWithParams(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -87,7 +88,9 @@ public function testGenerateHttpResponseWithExtraParams(): void $accessToken->setClient($client); $accessToken->addScope($scope); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $accessToken->setUserIdentifier('userId'); + $user = new UserEntity(); + $user->setIdentifier('userId'); + $accessToken->setUser($user); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -119,14 +122,15 @@ public function testDetermineAccessTokenInHeaderValidToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setUserIdentifier('123'); + $user = new UserEntity(); + $user->setIdentifier('123'); + $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -164,14 +168,15 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setUserIdentifier('123'); + $user = new UserEntity(); + $user->setIdentifier('123'); + $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -206,14 +211,15 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setUserIdentifier('123'); + $user = new UserEntity(); + $user->setIdentifier('userId'); + $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -251,7 +257,6 @@ public function testDetermineAccessTokenInHeaderInvalidToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -274,7 +279,6 @@ public function testDetermineMissingBearerInHeader(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index 93bd9d6b3..d8c0d9224 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -24,7 +24,6 @@ public function testGenerateHttpResponse(): void { $responseType = new DeviceCodeResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); diff --git a/tests/Stubs/CryptTraitStub.php b/tests/Stubs/CryptTraitStub.php deleted file mode 100644 index 19de09008..000000000 --- a/tests/Stubs/CryptTraitStub.php +++ /dev/null @@ -1,36 +0,0 @@ -setEncryptionKey(base64_encode(random_bytes(36))); - } - - public function getKey(): string|Key|null - { - return $this->encryptionKey; - } - - public function doEncrypt(string $unencryptedData): string - { - return $this->encrypt($unencryptedData); - } - - public function doDecrypt(string $encryptedData): string - { - return $this->decrypt($encryptedData); - } -} diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index 16eab4795..b0eeb2676 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -7,6 +7,7 @@ use DateInterval; use Defuse\Crypto\Key; use League\OAuth2\Server\CryptKeyInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\EventEmitting\EventEmitter; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -108,7 +109,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r return true; } - public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, UserEntityInterface $user, bool $userApproved): void { } diff --git a/tests/Utils/CryptTraitTest.php b/tests/Utils/CryptTraitTest.php deleted file mode 100644 index b49b0e9e2..000000000 --- a/tests/Utils/CryptTraitTest.php +++ /dev/null @@ -1,46 +0,0 @@ -cryptStub = new CryptTraitStub(); - } - - public function testEncryptDecryptWithPassword(): void - { - $this->cryptStub->setEncryptionKey(base64_encode(random_bytes(36))); - - $this->encryptDecrypt(); - } - - public function testEncryptDecryptWithKey(): void - { - $this->cryptStub->setEncryptionKey(Key::createNewRandomKey()); - - $this->encryptDecrypt(); - } - - private function encryptDecrypt(): void - { - $payload = 'alex loves whisky'; - $encrypted = $this->cryptStub->doEncrypt($payload); - $plainText = $this->cryptStub->doDecrypt($encrypted); - - self::assertNotEquals($payload, $encrypted); - self::assertEquals($payload, $plainText); - } -} From 8efcf6dbc9f1851e881515e6684a739352674ce0 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 11:57:51 +0000 Subject: [PATCH 07/36] Satisify some tests --- tests/Grant/RefreshTokenGrantTest.php | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 760a0fa08..13ac10dc6 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -117,7 +117,7 @@ public function testRespondToRequestNullRefreshToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); - $accessTokenRepositoryMock->expects(self::never())->method('persistNewAccessToken')->willReturnSelf(); + $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $rte = new RefreshTokenEntity(); @@ -251,17 +251,10 @@ public function testRespondToUnexpectedScope(): void $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); - $scope1 = new ScopeEntity(); - $scope1->setIdentifier('foo'); - - $scope2 = new ScopeEntity(); - $scope2->setIdentifier('bar'); - - $scope3 = new ScopeEntity(); - $scope3->setIdentifier('foobar'); - + $scope = new ScopeEntity(); + $scope->setIdentifier('foobar'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1, $scope2, $scope3); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); @@ -633,7 +626,7 @@ public function testRevokedRefreshToken(): void $rte = new RefreshTokenEntity(); $rte->setClient($client); - $rte->setIdentifier('zyxwvu'); + $rte->setIdentifier($refreshTokenId); $ace = new AccessTokenEntity(); $ace->setIdentifier('abcdef'); $rte->setAccessToken($ace); From a0d572f9b606e26c6e0e40f26a1006e7b958d5f5 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 12:59:15 +0000 Subject: [PATCH 08/36] Remove Private Key from Auth Server, only needed in Access Token --- src/AuthorizationServer.php | 20 +------ src/Entities/Traits/AccessTokenTrait.php | 6 ++- src/Grant/AbstractGrant.php | 11 ---- src/Grant/GrantTypeInterface.php | 5 -- src/ResponseTypes/AbstractResponseType.php | 7 --- tests/AuthorizationServerTest.php | 52 ++----------------- tests/Grant/AbstractGrantTest.php | 1 - tests/Grant/AuthCodeGrantTest.php | 13 ----- tests/Grant/ClientCredentialsGrantTest.php | 1 - tests/Grant/DeviceCodeGrantTest.php | 7 --- tests/Grant/ImplicitGrantTest.php | 5 -- tests/Grant/PasswordGrantTest.php | 2 - tests/Grant/RefreshTokenGrantTest.php | 17 ------ .../AuthorizationServerMiddlewareTest.php | 2 - .../ResponseTypes/BearerResponseTypeTest.php | 7 --- .../DeviceCodeResponseTypeTest.php | 1 - tests/Stubs/GrantType.php | 4 -- 17 files changed, 9 insertions(+), 152 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 1de7ad42f..9a7e7e011 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -43,10 +43,6 @@ class AuthorizationServer implements EmitterAwareInterface */ protected array $grantTypeAccessTokenTTL = []; - protected CryptKeyInterface $privateKey; - - protected CryptKeyInterface $publicKey; - protected ResponseTypeInterface $responseType; private string $defaultScope = ''; @@ -60,15 +56,8 @@ public function __construct( private ClientRepositoryInterface $clientRepository, private AccessTokenRepositoryInterface $accessTokenRepository, private ScopeRepositoryInterface $scopeRepository, - CryptKeyInterface|string $privateKey, ResponseTypeInterface|null $responseType = null ) { - if ($privateKey instanceof CryptKeyInterface === false) { - $privateKey = new CryptKey($privateKey); - } - - $this->privateKey = $privateKey; - if ($responseType === null) { $responseType = new BearerTokenResponse(); } else { @@ -91,7 +80,6 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval|null $grantType->setClientRepository($this->clientRepository); $grantType->setScopeRepository($this->scopeRepository); $grantType->setDefaultScope($this->defaultScope); - $grantType->setPrivateKey($this->privateKey); $grantType->setEmitter($this->getEmitter()); $grantType->revokeRefreshTokens($this->revokeRefreshTokens); @@ -183,13 +171,7 @@ public function respondToAccessTokenRequest(ServerRequestInterface $request, Res */ protected function getResponseType(): ResponseTypeInterface { - $responseType = clone $this->responseType; - - if ($responseType instanceof AbstractResponseType) { - $responseType->setPrivateKey($this->privateKey); - } - - return $responseType; + return clone $this->responseType; } /** diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 1699d1e8e..54fb08de3 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -25,7 +25,7 @@ trait AccessTokenTrait { - private CryptKeyInterface $privateKey; + private ?CryptKeyInterface $privateKey = null; private Configuration $jwtConfiguration; @@ -78,6 +78,10 @@ private function convertToJWT(): Token */ public function toString(): string { + if ($this->privateKey === null) { + return $this->getIdentifier(); + } + return $this->convertToJWT()->toString(); } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index b7f600134..300fc1410 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -80,8 +80,6 @@ abstract class AbstractGrant implements GrantTypeInterface protected DateInterval $refreshTokenTTL; - protected CryptKeyInterface $privateKey; - protected string $defaultScope; protected bool $revokeRefreshTokens = true; @@ -124,14 +122,6 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void $this->refreshTokenTTL = $refreshTokenTTL; } - /** - * Set the private key - */ - public function setPrivateKey(CryptKeyInterface $privateKey): void - { - $this->privateKey = $privateKey; - } - public function setDefaultScope(string $scope): void { $this->defaultScope = $scope; @@ -411,7 +401,6 @@ protected function issueAccessToken( $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); - $accessToken->setPrivateKey($this->privateKey); while ($maxGenerationAttempts-- > 0) { $accessToken->setIdentifier($this->generateUniqueIdentifier()); diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index ee39fd097..0d613b406 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -121,11 +121,6 @@ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): v */ public function setDefaultScope(string $scope): void; - /** - * Set the path to the private key. - */ - public function setPrivateKey(CryptKeyInterface $privateKey): void; - /** * Enable or prevent the revocation of refresh tokens upon usage. */ diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 137f9e93e..b3ce204d4 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -24,8 +24,6 @@ abstract class AbstractResponseType implements ResponseTypeInterface protected RefreshTokenEntityInterface $refreshToken; - protected CryptKeyInterface $privateKey; - public function setAccessToken(AccessTokenEntityInterface $accessToken): void { $this->accessToken = $accessToken; @@ -35,9 +33,4 @@ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken): void { $this->refreshToken = $refreshToken; } - - public function setPrivateKey(CryptKeyInterface $key): void - { - $this->privateKey = $key; - } } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 354ee6525..3015a5845 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -56,7 +56,6 @@ public function testGrantTypeGetsEnabled(): void $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', new StubResponseType() ); @@ -72,7 +71,6 @@ public function testRespondToRequestInvalidGrantType(): void $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', new StubResponseType() ); @@ -109,7 +107,6 @@ public function testRespondToRequest(): void $clientRepository, $accessTokenRepositoryMock, $scopeRepositoryMock, - 'file://' . __DIR__ . '/Stubs/private.key', new StubResponseType() ); @@ -131,7 +128,6 @@ public function testGetResponseType(): void $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key' ); $abstractGrantReflection = new ReflectionClass($server); @@ -141,45 +137,10 @@ public function testGetResponseType(): void self::assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); } - public function testGetResponseTypeExtended(): void - { - $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; - $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; - - $server = new AuthorizationServer( - $clientRepository, - $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), - $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key' - ); - - $abstractGrantReflection = new ReflectionClass($server); - $method = $abstractGrantReflection->getMethod('getResponseType'); - $method->setAccessible(true); - - $responseType = $method->invoke($server); - - $responseTypeReflection = new ReflectionClass($responseType); - - $privateKeyProperty = $responseTypeReflection->getProperty('privateKey'); - $privateKeyProperty->setAccessible(true); - - // generated instances should have keys setup - self::assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath()); - } - public function testMultipleRequestsGetDifferentResponseTypeInstances(): void { - $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; - $responseTypePrototype = new class() extends BearerTokenResponse { - protected CryptKeyInterface $privateKey; - public function getPrivateKey(): CryptKeyInterface - { - return $this->privateKey; - } }; $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -188,7 +149,6 @@ public function getPrivateKey(): CryptKeyInterface $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - $privateKey, $responseTypePrototype ); @@ -199,9 +159,6 @@ public function getPrivateKey(): CryptKeyInterface $responseTypeA = $method->invoke($server); $responseTypeB = $method->invoke($server); - // generated instances should have keys setup - self::assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath()); - // all instances should be different but based on the same prototype self::assertSame(get_class($responseTypePrototype), get_class($responseTypeA)); self::assertSame(get_class($responseTypePrototype), get_class($responseTypeB)); @@ -217,8 +174,7 @@ public function testCompleteAuthorizationRequest(): void $server = new AuthorizationServer( $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), - $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key' + $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock() ); $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); @@ -273,8 +229,7 @@ public function testValidateAuthorizationRequest(): void $server = new AuthorizationServer( $clientRepositoryMock, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), - $scopeRepositoryMock, - 'file://' . __DIR__ . '/Stubs/private.key' + $scopeRepositoryMock ); $server->setDefaultScope(self::DEFAULT_SCOPE); @@ -302,8 +257,7 @@ public function testValidateAuthorizationRequestUnregistered(): void $server = new AuthorizationServer( $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), - $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key' + $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock() ); $request = (new ServerRequest())->withQueryParams([ diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 99e3f59e8..0728528ae 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -434,7 +434,6 @@ public function testIssueAccessToken(): void /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grantMock->setAccessTokenRepository($accessTokenRepoMock); $abstractGrantReflection = new ReflectionClass($grantMock); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 5949344a1..741d23561 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -642,7 +642,6 @@ public function testRespondToAccessTokenRequest(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -717,7 +716,6 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -783,7 +781,6 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $authCodeGrant->setClientRepository($clientRepositoryMock); $authCodeGrant->setScopeRepository($scopeRepositoryMock); $authCodeGrant->setAccessTokenRepository($accessTokenRepositoryMock); - $authCodeGrant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -854,7 +851,6 @@ public function testRespondToAccessTokenRequestForPublicClient(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -924,7 +920,6 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -1002,7 +997,6 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -1081,7 +1075,6 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -1155,7 +1148,6 @@ public function testPKCEDowngradeBlocked(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -2147,8 +2139,6 @@ public function testAuthCodeRepositoryUniqueConstraintCheck(): void new DateInterval('PT10M') ); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } @@ -2264,7 +2254,6 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -2334,7 +2323,6 @@ public function testRefreshTokenRepositoryFailToPersist(): void $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -2407,7 +2395,6 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index 69f756c37..0c14f23d3 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -51,7 +51,6 @@ public function testRespondToRequest(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 80e819a44..746c8a126 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -297,7 +297,6 @@ public function testDeviceAuthorizationResponse(): void $clientRepository, $accessRepositoryMock, $scopeRepositoryMock, - 'file://' . __DIR__ . '/../Stubs/private.key', new StubResponseType() ); @@ -382,7 +381,6 @@ public function testRespondToAccessTokenRequest(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->completeDeviceAuthorizationRequest($deviceCodeEntity->getIdentifier(), $user, true); @@ -462,7 +460,6 @@ public function testRespondToRequestMissingDeviceCode(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -509,7 +506,6 @@ public function testIssueSlowDownError(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -556,7 +552,6 @@ public function testIssueAuthorizationPendingError(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -603,7 +598,6 @@ public function testIssueExpiredTokenError(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -745,7 +739,6 @@ public function testIssueAccessDeniedError(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->completeDeviceAuthorizationRequest($deviceCode->getIdentifier(), new UserEntity(), false); diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index f756bb4ed..a44f1b484 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -267,7 +267,6 @@ public function testCompleteAuthorizationRequest(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new DateInterval('PT10M')); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -295,7 +294,6 @@ public function testCompleteAuthorizationRequestDenied(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new DateInterval('PT10M')); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -349,7 +347,6 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new DateInterval('PT10M')); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -378,7 +375,6 @@ public function testAccessTokenRepositoryFailToPersist(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new DateInterval('PT10M')); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -410,7 +406,6 @@ public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): vo $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new DateInterval('PT10M')); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 8c60a8c78..69156c658 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -67,7 +67,6 @@ public function testRespondToRequest(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -112,7 +111,6 @@ public function testRespondToRequestNullRefreshToken(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 13ac10dc6..16557bfb8 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -82,7 +82,6 @@ public function testRespondToRequest(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); $serverRequest = (new ServerRequest())->withParsedBody([ @@ -140,7 +139,6 @@ public function testRespondToRequestNullRefreshToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -200,7 +198,6 @@ public function testRespondToReducedScopes(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); $serverRequest = (new ServerRequest())->withParsedBody([ @@ -260,7 +257,6 @@ public function testRespondToUnexpectedScope(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -293,7 +289,6 @@ public function testRespondToRequestMissingOldToken(): void $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -324,7 +319,6 @@ public function testRespondToRequestInvalidOldToken(): void $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $oldRefreshToken = 'foobar'; @@ -358,7 +352,6 @@ public function testRespondToRequestRefreshTokenNotSet(): void $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $oldRefreshToken = 'foobar'; @@ -410,7 +403,6 @@ public function testRespondToRequestClientMismatch(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -454,7 +446,6 @@ public function testRespondToRequestExpiredToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -499,7 +490,6 @@ public function testRespondToRequestRevokedToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -563,7 +553,6 @@ public function testRespondToRequestFinalizeScopes(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $scopes = [$fooScopeEntity, $barScopeEntity]; @@ -649,7 +638,6 @@ public function testRevokedRefreshToken(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); @@ -711,17 +699,13 @@ public function testUnrevokedRefreshToken(): void 'scope' => 'foo', ]); - $privateKey = new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'); - $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey($privateKey); $grant->revokeRefreshTokens(false); $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey($privateKey); $response = $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')) ->generateHttpResponse(new Response()); @@ -781,7 +765,6 @@ public function testRespondToRequestWithIntUserId(): void $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); $serverRequest = (new ServerRequest())->withParsedBody([ diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index b027d9450..1e6383570 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -50,7 +50,6 @@ public function testValidResponse(): void $clientRepository, $accessRepositoryMock, $scopeRepositoryMock, - 'file://' . __DIR__ . '/../Stubs/private.key', new StubResponseType() ); @@ -83,7 +82,6 @@ public function testOAuthErrorResponse(): void $clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/../Stubs/private.key', new StubResponseType() ); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index cf91fd7af..043ae0ca5 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -30,7 +30,6 @@ class BearerResponseTypeTest extends TestCase public function testGenerateHttpResponse(): void { $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -74,7 +73,6 @@ public function testGenerateHttpResponse(): void public function testGenerateHttpResponseWithExtraParams(): void { $responseType = new BearerTokenResponseWithParams(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -121,7 +119,6 @@ public function testGenerateHttpResponseWithExtraParams(): void public function testDetermineAccessTokenInHeaderValidToken(): void { $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -167,7 +164,6 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -210,7 +206,6 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void public function testDetermineAccessTokenInHeaderRevokedToken(): void { $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -256,7 +251,6 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void public function testDetermineAccessTokenInHeaderInvalidToken(): void { $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -278,7 +272,6 @@ public function testDetermineAccessTokenInHeaderInvalidToken(): void public function testDetermineMissingBearerInHeader(): void { $responseType = new BearerTokenResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index d8c0d9224..05574f5b8 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -23,7 +23,6 @@ class DeviceCodeResponseTypeTest extends TestCase public function testGenerateHttpResponse(): void { $responseType = new DeviceCodeResponse(); - $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $client = new ClientEntity(); $client->setIdentifier('clientName'); diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index b0eeb2676..dcfa48d90 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -92,10 +92,6 @@ public function setDefaultScope(string $scope): void { } - public function setPrivateKey(CryptKeyInterface $privateKey): void - { - } - public function setEncryptionKey(Key|string|null $key = null): void { } From c858c403c671c140f6da4e02ee39f01a37508b19 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 15:45:15 +0000 Subject: [PATCH 09/36] Remove null --- src/Repositories/AccessTokenRepositoryInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 81febb09d..86a26902f 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -31,7 +31,7 @@ interface AccessTokenRepositoryInterface extends RepositoryInterface public function getNewToken( ClientEntityInterface $clientEntity, array $scopes, - ?UserEntityInterface $user = null + ?UserEntityInterface $user ): AccessTokenEntityInterface; /** From 5e0f156ac697882961a9f0678b57b13166fc495e Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 16:06:03 +0000 Subject: [PATCH 10/36] getDeviceCodeEntityByUserCode --- src/Repositories/DeviceCodeRepositoryInterface.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 09575ab18..59983f14e 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -36,6 +36,13 @@ public function getDeviceCodeEntityByDeviceCode( string $deviceCodeEntity ): ?DeviceCodeEntityInterface; + /** + * Get a device code entity. + */ + public function getDeviceCodeEntityByUserCode( + string $userCode + ): ?DeviceCodeEntityInterface; + /** * Revoke a device code. */ From 5ed7d0a8c3a9be7546d45df6ceedd0a586b6889e Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 16:13:25 +0000 Subject: [PATCH 11/36] set private key on Access Token Repo --- src/Repositories/AccessTokenRepositoryInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 86a26902f..96fef0393 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -12,6 +12,7 @@ namespace League\OAuth2\Server\Repositories; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -44,4 +45,6 @@ public function revokeAccessToken(string $tokenId): void; public function isAccessTokenRevoked(string $tokenId): bool; public function getAccessTokenEntity(string $tokenId): ?AccessTokenEntityInterface; + + public function setPrivateKey(CryptKeyInterface $privateKey): void; } From fd16e690cbacbf34f9b5e010927255f57017115a Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 23:32:23 +0000 Subject: [PATCH 12/36] Throw exceptions if device code has expired or revoked --- src/Grant/DeviceCodeGrant.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index e9eaf78b2..b86fc8d4e 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -126,6 +126,14 @@ public function completeDeviceAuthorizationRequest(string $deviceCode, UserEntit throw OAuthServerException::invalidRequest('user_id', 'User ID is required'); } + if (time() > $deviceCode->getExpiryDateTime()->getTimestamp()) { + throw OAuthServerException::expiredToken('device_code'); + } + + if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCode->getIdentifier()) === true) { + throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked'); + } + $deviceCode->setUser($user); $deviceCode->setUserApproved($userApproved); From 6c3d1a0c2bfc5cdab055c40c5a610e48678b5045 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Mon, 3 Feb 2025 22:25:34 +0000 Subject: [PATCH 13/36] Add different signer ability --- src/Entities/AccessTokenEntityInterface.php | 13 ++- src/Entities/Traits/AccessTokenTrait.php | 86 +++++++++++++------ .../AccessTokenRepositoryInterface.php | 2 - .../ResourceServerMiddlewareTest.php | 4 +- .../ResponseTypes/BearerResponseTypeTest.php | 10 +-- 5 files changed, 77 insertions(+), 38 deletions(-) diff --git a/src/Entities/AccessTokenEntityInterface.php b/src/Entities/AccessTokenEntityInterface.php index 3c998b4d2..301409ece 100644 --- a/src/Entities/AccessTokenEntityInterface.php +++ b/src/Entities/AccessTokenEntityInterface.php @@ -17,12 +17,17 @@ interface AccessTokenEntityInterface extends TokenInterface { /** - * Set a private key used to encrypt the access token. + * Generate a string representation of the access token. */ - public function setPrivateKey(CryptKeyInterface $privateKey): void; + public function toString(): string; /** - * Generate a string representation of the access token. + * Set the algorithm for signing the access token with the given private key + * + * @see https://lcobucci-jwt.readthedocs.io/en/latest/supported-algorithms/ + * + * Symmetric - HS256, HS384, HS512, BLAKE2B + * Asymmetric - ES256, ES384, ES512, RS256, RS384, RS512, EdDSA */ - public function toString(): string; + public function setSigner(string $signerAlgorithm, CryptKeyInterface $privateKey): void; } diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 54fb08de3..af8f89782 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -15,44 +15,80 @@ use DateTimeImmutable; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; -use Lcobucci\JWT\Signer\Rsa\Sha256; +use Lcobucci\JWT\Signer\Hmac\Sha256 as HS256; +use Lcobucci\JWT\Signer\Hmac\Sha384 as HS384; +use Lcobucci\JWT\Signer\Hmac\Sha512 as HS512; +use Lcobucci\JWT\Signer\Blake2b as BLAKE2B; +use Lcobucci\JWT\Signer\Rsa\Sha256 as RS256; +use Lcobucci\JWT\Signer\Rsa\Sha384 as RS384; +use Lcobucci\JWT\Signer\Rsa\Sha512 as RS512; +use Lcobucci\JWT\Signer\Ecdsa\Sha256 as ES256; +use Lcobucci\JWT\Signer\Ecdsa\Sha384 as ES384; +use Lcobucci\JWT\Signer\Ecdsa\Sha512 as ES512; +use Lcobucci\JWT\Signer\Eddsa as EDDSA; +use Lcobucci\JWT\Encoding\ChainedFormatter; +use Lcobucci\JWT\Encoding\JoseEncoder; use Lcobucci\JWT\Token; +use Lcobucci\JWT\Token\Builder; +use Lcobucci\JWT\Signer; +use Lcobucci\JWT\Signer\Key; use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; -use RuntimeException; trait AccessTokenTrait { - private ?CryptKeyInterface $privateKey = null; + private ?Key $privateKey = null; + + private Signer $signer; private Configuration $jwtConfiguration; - /** - * Set the private key used to encrypt this access token. - */ - public function setPrivateKey(CryptKeyInterface $privateKey): void + public function __construct() { - $this->privateKey = $privateKey; + $this->signer = new RS256(); } - /** - * Initialise the JWT Configuration. - */ - public function initJwtConfiguration(): void + public function setSigner(string $signerAlgorithm, CryptKeyInterface $privateKey): void { - $privateKeyContents = $this->privateKey->getKeyContents(); - - if ($privateKeyContents === '') { - throw new RuntimeException('Private key is empty'); + $this->privateKey = InMemory::plainText($privateKey->getKeyContents(), $privateKey->getPassPhrase() ?? ''); + + switch (strtoupper($signerAlgorithm)) { + case 'HS256': + $this->signer = new HS256(); + break; + case 'HS384': + $this->signer = new HS384(); + break; + case 'HS512': + $this->signer = new HS512(); + break; + case 'BLAKE2B': + $this->signer = new BLAKE2B(); + break; + case 'ES256': + $this->signer = new ES256(); + break; + case 'ES384': + $this->signer = new ES384(); + break; + case 'ES512': + $this->signer = new ES512(); + break; + case 'RS256': + $this->signer = new RS256(); + break; + case 'RS384': + $this->signer = new RS384(); + break; + case 'RS512': + $this->signer = new RS512(); + break; + case 'EDDSA': + $this->signer = new EDDSA(); + break; } - - $this->jwtConfiguration = Configuration::forAsymmetricSigner( - new Sha256(), - InMemory::plainText($privateKeyContents, $this->privateKey->getPassPhrase() ?? ''), - InMemory::plainText('empty', 'empty') - ); } /** @@ -60,9 +96,9 @@ public function initJwtConfiguration(): void */ private function convertToJWT(): Token { - $this->initJwtConfiguration(); + $tokenBuilder = (new Builder(new JoseEncoder(), ChainedFormatter::default())); - return $this->jwtConfiguration->builder() + return $tokenBuilder ->permittedFor($this->getClient()->getIdentifier()) ->identifiedBy($this->getIdentifier()) ->issuedAt(new DateTimeImmutable()) @@ -70,7 +106,7 @@ private function convertToJWT(): Token ->expiresAt($this->getExpiryDateTime()) ->relatedTo($this->getSubjectIdentifier()) ->withClaim('scopes', $this->getScopes()) - ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); + ->getToken($this->signer, $this->privateKey); } /** diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 96fef0393..30e60d3b1 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -45,6 +45,4 @@ public function revokeAccessToken(string $tokenId): void; public function isAccessTokenRevoked(string $tokenId): bool; public function getAccessTokenEntity(string $tokenId): ?AccessTokenEntityInterface; - - public function setPrivateKey(CryptKeyInterface $privateKey): void; } diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 38e7b556f..95492592f 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -39,7 +39,7 @@ public function testValidResponse(): void $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $token = $accessToken->toString(); @@ -76,7 +76,7 @@ public function testValidResponseExpiredToken(): void $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $token = $accessToken->toString(); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 043ae0ca5..c10cc6993 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -42,7 +42,7 @@ public function testGenerateHttpResponse(): void $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->addScope($scope); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $user = new UserEntity(); $user->setIdentifier('userId'); $accessToken->setUser($user); @@ -85,7 +85,7 @@ public function testGenerateHttpResponseWithExtraParams(): void $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->addScope($scope); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $user = new UserEntity(); $user->setIdentifier('userId'); $accessToken->setUser($user); @@ -130,7 +130,7 @@ public function testDetermineAccessTokenInHeaderValidToken(): void $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -175,7 +175,7 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -217,7 +217,7 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void $accessToken->setUser($user); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); - $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setSigner('RS256', new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); From 236654e41d87e1459de5423827c693f1ecdd8201 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Mon, 3 Feb 2025 22:25:55 +0000 Subject: [PATCH 14/36] Fixing Device Code test --- tests/Grant/DeviceCodeGrantTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 746c8a126..a5467824b 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -250,6 +250,7 @@ public function testValidateDeviceAuthorizationRequestClientMismatch(): void public function testCompleteDeviceAuthorizationRequest(): void { $deviceCode = new DeviceCodeEntity(); + $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $deviceCode->setIdentifier('deviceCodeEntityIdentifier'); $deviceCode->setUserCode('foo'); From dd3f88073290ba9fb8a9c840b7cafc6c0b50f4d2 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Mon, 3 Feb 2025 22:50:08 +0000 Subject: [PATCH 15/36] Fix scope entity problem --- src/Grant/RefreshTokenGrant.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index ae56be674..2a6bb497a 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -60,11 +60,18 @@ public function respondToAccessTokenRequest( return $responseType; } + $originalScopes = $oldRefreshToken->getScopes(); + $originalScopeArray = []; + foreach ($originalScopes as $scopeEntity) { + $originalScopeArray[$scopeEntity->getIdentifier()] = $scopeEntity->getIdentifier(); + } + $originalScopeArray = array_values($originalScopeArray); + $scopes = $this->validateScopes( $this->getRequestParameter( 'scope', $request, - implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken->getScopes()) + implode(self::SCOPE_DELIMITER_STRING, $originalScopeArray) ) ); From d0e20e3c57f17b1cb722a9068e4a72590b9d592b Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Mon, 3 Feb 2025 22:59:19 +0000 Subject: [PATCH 16/36] Whoops --- src/Grant/RefreshTokenGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 2a6bb497a..099a4d6dd 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -78,7 +78,7 @@ public function respondToAccessTokenRequest( // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure // the request doesn't include any new scopes foreach ($scopes as $scope) { - if (in_array($scope->getIdentifier(), $oldRefreshToken->getScopes(), true) === false) { + if (in_array($scope->getIdentifier(), $originalScopeArray, true) === false) { throw OAuthServerException::invalidScope($scope->getIdentifier()); } } From 82f114f95d49a09df31f91818454676770138ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20F=C3=B6rster?= Date: Sat, 25 Jan 2025 15:01:15 +0100 Subject: [PATCH 17/36] Fix return declarations @ example --- examples/README.md | 7 +++---- examples/src/Repositories/AuthCodeRepository.php | 8 ++++---- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/README.md b/examples/README.md index 69df9cef6..48b6fb8c4 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,12 +1,11 @@ -# Example implementations +# Example implementations (via [`Slim 3`](https://github.com/slimphp/Slim/tree/3.x)) ## Installation 0. Run `composer install` in this directory to install dependencies 0. Create a private key `openssl genrsa -out private.key 2048` -0. Create a public key `openssl rsa -in private.key -pubout > public.key` -0. `cd` into the public directory -0. Start a PHP server `php -S localhost:4444` +0. Export the public key `openssl rsa -in private.key -pubout > public.key` +0. Start local PHP server `php -S 127.0.0.1:4444 -t public/` ## Testing the client credentials grant example diff --git a/examples/src/Repositories/AuthCodeRepository.php b/examples/src/Repositories/AuthCodeRepository.php index 962ed8da9..4d21a30e7 100644 --- a/examples/src/Repositories/AuthCodeRepository.php +++ b/examples/src/Repositories/AuthCodeRepository.php @@ -21,7 +21,7 @@ class AuthCodeRepository implements AuthCodeRepositoryInterface /** * {@inheritdoc} */ - public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity) + public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void { // Some logic to persist the auth code to a database } @@ -29,7 +29,7 @@ public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity) /** * {@inheritdoc} */ - public function revokeAuthCode($codeId) + public function revokeAuthCode($codeId): void { // Some logic to revoke the auth code in a database } @@ -37,7 +37,7 @@ public function revokeAuthCode($codeId) /** * {@inheritdoc} */ - public function isAuthCodeRevoked($codeId) + public function isAuthCodeRevoked($codeId): bool { return false; // The auth code has not been revoked } @@ -45,7 +45,7 @@ public function isAuthCodeRevoked($codeId) /** * {@inheritdoc} */ - public function getNewAuthCode() + public function getNewAuthCode(): AuthCodeEntityInterface { return new AuthCodeEntity(); } From 73d9cf4e4524905fc5cd04318e15b2e38c032000 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Sun, 30 Jun 2024 04:27:45 +0330 Subject: [PATCH 18/36] always validate the client --- examples/src/Repositories/ClientRepository.php | 5 ++++- src/Grant/AuthCodeGrant.php | 9 +-------- src/Grant/ClientCredentialsGrant.php | 7 +------ 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 0b19d57d7..047fe77f7 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -59,7 +59,10 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo return false; } - if (password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false) { + if ( + $clients[$clientIdentifier]['is_confidential'] === true + && password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false + ) { return false; } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 893c99853..48a396990 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -96,14 +96,7 @@ public function respondToAccessTokenRequest( ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ): ResponseTypeInterface { - list($clientId) = $this->getClientCredentials($request); - - $client = $this->getClientEntityOrFail($clientId, $request); - - // Only validate the client if it is confidential - if ($client->isConfidential()) { - $this->validateClient($request); - } + $client = $this->validateClient($request); $code = $this->getRequestParameter('code', $request); diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index bee6abaa1..a24266c4c 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -34,9 +34,7 @@ public function respondToAccessTokenRequest( ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ): ResponseTypeInterface { - list($clientId) = $this->getClientCredentials($request); - - $client = $this->getClientEntityOrFail($clientId, $request); + $client = $this->validateClient($request); if (!$client->isConfidential()) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); @@ -44,9 +42,6 @@ public function respondToAccessTokenRequest( throw OAuthServerException::invalidClient($request); } - // Validate request - $this->validateClient($request); - $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); // Finalize the requested scopes From da0214d4d788b5ef40252dc40b6763e48cdcd2c9 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Sun, 30 Jun 2024 04:28:08 +0330 Subject: [PATCH 19/36] pass grant type to getClientEntity --- examples/src/Repositories/ClientRepository.php | 2 +- src/Grant/AbstractGrant.php | 2 +- src/Repositories/ClientRepositoryInterface.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 047fe77f7..8dcb0af7b 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -28,7 +28,7 @@ class ClientRepository implements ClientRepositoryInterface /** * {@inheritdoc} */ - public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface + public function getClientEntity(string $clientIdentifier, ?string $grantType): ?ClientEntityInterface { $client = new ClientEntity(); diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 300fc1410..c81298faa 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -172,7 +172,7 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity */ protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface { - $client = $this->clientRepository->getClientEntity($clientId); + $client = $this->clientRepository->getClientEntity($clientId, $this->getIdentifier()); if ($client instanceof ClientEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); diff --git a/src/Repositories/ClientRepositoryInterface.php b/src/Repositories/ClientRepositoryInterface.php index 63134ca9d..2d8e27336 100644 --- a/src/Repositories/ClientRepositoryInterface.php +++ b/src/Repositories/ClientRepositoryInterface.php @@ -22,7 +22,7 @@ interface ClientRepositoryInterface extends RepositoryInterface /** * Get a client. */ - public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface; + public function getClientEntity(string $clientIdentifier, ?string $grantType): ?ClientEntityInterface; /** * Validate a client's secret. From 102b87118da611824588d83fe4b1e8f48ecdcb74 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Thu, 29 Aug 2024 17:21:31 +0330 Subject: [PATCH 20/36] fix tests --- tests/Grant/AuthCodeGrantTest.php | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 741d23561..05b35b270 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -746,6 +746,7 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); @@ -813,6 +814,7 @@ public function testRespondToAccessTokenRequestForPublicClient(): void $client->setRedirectUri(self::REDIRECT_URI); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); @@ -882,6 +884,7 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $client->setRedirectUri(self::REDIRECT_URI); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); @@ -1394,6 +1397,58 @@ public function testRespondToAccessTokenRequestMissingCode(): void $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } + public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode(): void + { + $client = new ClientEntity(); + $client->setRedirectUri(self::REDIRECT_URI); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); + + $grant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + + $request = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [], + [], + [], + [ + 'grant_type' => 'authorization_code', + 'client_id' => 'foo', + 'redirect_uri' => self::REDIRECT_URI, + 'code' => $this->cryptStub->doEncrypt( + json_encode([ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ], JSON_THROW_ON_ERROR) + ), + ] + ); + + try { + /* @var StubResponseType $response */ + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); + } catch (OAuthServerException $e) { + self::assertEquals('Authorization code malformed', $e->getHint()); + } + } + public function testRespondToAccessTokenRequestWithAuthCodeNotAString(): void { $client = new ClientEntity(); @@ -1438,6 +1493,7 @@ public function testRespondToAccessTokenRequestExpiredCode(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); @@ -2206,6 +2262,7 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $client->setRedirectUri(self::REDIRECT_URI); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); @@ -2285,6 +2342,7 @@ public function testRefreshTokenRepositoryFailToPersist(): void $client->setRedirectUri(self::REDIRECT_URI); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); @@ -2357,6 +2415,7 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v $client->setRedirectUri(self::REDIRECT_URI); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); From bd8799413dd08d0f1af8d98e04cf875642f4910a Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 1 Oct 2024 17:08:55 +0330 Subject: [PATCH 21/36] add ClientEntityInterface::hasGrantType() --- .../src/Repositories/ClientRepository.php | 2 +- src/Entities/ClientEntityInterface.php | 5 +++ src/Entities/Traits/ClientTrait.php | 8 +++++ src/Grant/AbstractGrant.php | 8 +++-- .../ClientRepositoryInterface.php | 2 +- tests/Grant/AbstractGrantTest.php | 1 + tests/Grant/AuthCodeGrantTest.php | 32 ++++++++++++++----- tests/Grant/PasswordGrantTest.php | 4 ++- tests/Grant/RefreshTokenGrantTest.php | 8 +++-- 9 files changed, 55 insertions(+), 15 deletions(-) diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 8dcb0af7b..047fe77f7 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -28,7 +28,7 @@ class ClientRepository implements ClientRepositoryInterface /** * {@inheritdoc} */ - public function getClientEntity(string $clientIdentifier, ?string $grantType): ?ClientEntityInterface + public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface { $client = new ClientEntity(); diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index f3838b11c..138e831f7 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -38,4 +38,9 @@ public function getRedirectUri(): string|array; * Returns true if the client is confidential. */ public function isConfidential(): bool; + + /** + * Returns true if the client handles the given grant type. + */ + public function hasGrantType(string $grantType): bool; } diff --git a/src/Entities/Traits/ClientTrait.php b/src/Entities/Traits/ClientTrait.php index b179cfac4..0b4327c35 100644 --- a/src/Entities/Traits/ClientTrait.php +++ b/src/Entities/Traits/ClientTrait.php @@ -52,4 +52,12 @@ public function isConfidential(): bool { return $this->isConfidential; } + + /** + * Returns true if the client handles the given grant type. + */ + public function hasGrantType(string $grantType): bool + { + return true; + } } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c81298faa..bca981ca1 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -172,13 +172,17 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity */ protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface { - $client = $this->clientRepository->getClientEntity($clientId, $this->getIdentifier()); + $client = $this->clientRepository->getClientEntity($clientId); if ($client instanceof ClientEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } + if (!$client->hasGrantType($this->getIdentifier())) { + throw OAuthServerException::invalidGrant(); + } + return $client; } @@ -479,7 +483,7 @@ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ? { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); - if ($refreshToken === null) { + if ($refreshToken === null || !$accessToken->getClient()->hasGrantType('refresh_token')) { return null; } diff --git a/src/Repositories/ClientRepositoryInterface.php b/src/Repositories/ClientRepositoryInterface.php index 2d8e27336..63134ca9d 100644 --- a/src/Repositories/ClientRepositoryInterface.php +++ b/src/Repositories/ClientRepositoryInterface.php @@ -22,7 +22,7 @@ interface ClientRepositoryInterface extends RepositoryInterface /** * Get a client. */ - public function getClientEntity(string $clientIdentifier, ?string $grantType): ?ClientEntityInterface; + public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface; /** * Validate a client's secret. diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 0728528ae..f051c3bec 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -399,6 +399,7 @@ public function testIssueRefreshToken(): void $issueRefreshTokenMethod->setAccessible(true); $accessToken = new AccessTokenEntity(); + $accessToken->setClient(new ClientEntity()); /** @var RefreshTokenEntityInterface $refreshToken */ $refreshToken = $issueRefreshTokenMethod->invoke($grantMock, $accessToken); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 05b35b270..93eec9d33 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -611,7 +611,9 @@ public function testRespondToAccessTokenRequest(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -753,7 +755,9 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); @@ -822,7 +826,9 @@ public function testRespondToAccessTokenRequestForPublicClient(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -966,7 +972,9 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -1044,7 +1052,9 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -2270,7 +2280,9 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -2350,7 +2362,9 @@ public function testRefreshTokenRepositoryFailToPersist(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -2423,7 +2437,9 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 69156c658..c8d8baa80 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -46,7 +46,9 @@ public function testRespondToRequest(): void $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 16557bfb8..1c9bf1021 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -164,7 +164,9 @@ public function testRespondToReducedScopes(): void $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); $ace->setIdentifier('abcdef'); @@ -564,10 +566,12 @@ public function testRespondToRequestFinalizeScopes(): void ->with($scopes, $grant->getIdentifier(), $client) ->willReturn($finalizedScopes); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); $accessTokenRepositoryMock ->method('getNewToken') ->with($client, $finalizedScopes) - ->willReturn(new AccessTokenEntity()); + ->willReturn($accessToken); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', From 3c8f14e68081c89e5a2d13eef59a594fee5a9893 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Sat, 5 Oct 2024 00:39:00 +0330 Subject: [PATCH 22/36] use unauthorized_client error --- src/Exception/OAuthServerException.php | 11 ++++++++++- src/Grant/AbstractGrant.php | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 24a38d3fe..df8b58a3d 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -252,8 +252,17 @@ public static function slowDown(string $hint = '', ?Throwable $previous = null): } /** + * Unauthorized client error. + */ + public static function unauthorizedClient(?string $hint = null): static { - return $this->errorType; + return new static( + 'The authenticated client is not authorized to use this authorization grant type.', + 14, + 'unauthorized_client', + 400, + $hint + ); } /** diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index bca981ca1..21b4ceb4f 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -180,7 +180,7 @@ protected function getClientEntityOrFail(string $clientId, ServerRequestInterfac } if (!$client->hasGrantType($this->getIdentifier())) { - throw OAuthServerException::invalidGrant(); + throw OAuthServerException::unauthorizedClient(); } return $client; From 90dd8dbfb029ef45800be9a7817394920e3ad5b2 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Fri, 18 Oct 2024 13:50:30 +0330 Subject: [PATCH 23/36] validate confidential clients --- examples/src/Repositories/ClientRepository.php | 5 +---- src/Grant/AbstractGrant.php | 11 ++++++++--- tests/Grant/AbstractGrantTest.php | 1 + tests/Grant/AuthCodeGrantTest.php | 10 ++++++++-- tests/Grant/PasswordGrantTest.php | 10 +++++++++- tests/Grant/RefreshTokenGrantTest.php | 10 ++++++++-- 6 files changed, 35 insertions(+), 12 deletions(-) diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 047fe77f7..0b19d57d7 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -59,10 +59,7 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo return false; } - if ( - $clients[$clientIdentifier]['is_confidential'] === true - && password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false - ) { + if (password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false) { return false; } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 21b4ceb4f..3dbb97ae6 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -141,12 +141,13 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity { [$clientId, $clientSecret] = $this->getClientCredentials($request); - if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { + $client = $this->getClientEntityOrFail($clientId, $request); + + if ($client->isConfidential() && $this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } - $client = $this->getClientEntityOrFail($clientId, $request); // If a redirect URI is provided ensure it matches what is pre-registered $redirectUri = $this->getRequestParameter('redirect_uri', $request); @@ -481,9 +482,13 @@ protected function issueAuthCode( */ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface { + if (!$accessToken->getClient()->hasGrantType('refresh_token')) { + return null; + } + $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); - if ($refreshToken === null || !$accessToken->getClient()->hasGrantType('refresh_token')) { + if ($refreshToken === null) { return null; } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index f051c3bec..67b50ffc8 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -425,6 +425,7 @@ public function testIssueNullRefreshToken(): void $issueRefreshTokenMethod->setAccessible(true); $accessToken = new AccessTokenEntity(); + $accessToken->setClient(new ClientEntity()); self::assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 93eec9d33..f9ff571dc 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -686,8 +686,11 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -897,8 +900,11 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index c8d8baa80..057250d11 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -92,8 +92,11 @@ public function testRespondToRequestNullRefreshToken(): void $clientRepositoryMock->method('getClientEntity')->willReturn($client); $clientRepositoryMock->method('validateClient')->willReturn(true); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock(); @@ -167,9 +170,14 @@ public function testRespondToRequestMissingPassword(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + $grant = new PasswordGrant($userRepositoryMock, $refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setScopeRepository($scopeRepositoryMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 1c9bf1021..9ba0e9a01 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -114,8 +114,11 @@ public function testRespondToRequestNullRefreshToken(): void $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -604,8 +607,11 @@ public function testRevokedRefreshToken(): void $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity, $scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); $ace->setIdentifier('abcdef'); From b2114f4aacd4f4a54a46b19f6669e840b82a0ca3 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 22 Oct 2024 20:00:33 +0330 Subject: [PATCH 24/36] require client_secret for confidential clients --- src/Grant/AbstractGrant.php | 12 +++++++++--- tests/Grant/AuthCodeGrantTest.php | 11 ++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 3dbb97ae6..b638abbf2 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -143,10 +143,16 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity $client = $this->getClientEntityOrFail($clientId, $request); - if ($client->isConfidential() && $this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + if ($client->isConfidential()) { + if ($clientSecret === '') { + throw OAuthServerException::invalidRequest('client_secret'); + } - throw OAuthServerException::invalidClient($request); + if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + + throw OAuthServerException::invalidClient($request); + } } // If a redirect URI is provided ensure it matches what is pre-registered diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index f9ff571dc..6d0273ba6 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -657,6 +657,7 @@ public function testRespondToAccessTokenRequest(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'redirect_uri' => self::REDIRECT_URI, 'code' => $ace->getIdentifier() ] @@ -1027,6 +1028,7 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => self::CODE_VERIFIER, 'code' => $ace->getIdentifier() @@ -1107,6 +1109,7 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'redirect_uri' => self::REDIRECT_URI, 'code_verifier' => self::CODE_VERIFIER, 'code' => $ace->getIdentifier() @@ -1623,6 +1626,7 @@ public function testRespondToAccessTokenRequestRevokedCode(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'redirect_uri' => self::REDIRECT_URI, 'code' => $ace->getIdentifier() ] @@ -1708,6 +1712,7 @@ public function testRespondToAccessTokenRequestClientMismatch(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'redirect_uri' => self::REDIRECT_URI, 'code' => $ace->getIdentifier() ] @@ -1765,6 +1770,7 @@ public function testRespondToAccessTokenRequestBadCode(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'redirect_uri' => self::REDIRECT_URI, 'code' => 'badCode', ] @@ -1785,7 +1791,6 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void $client->setIdentifier('foo'); $client->setRedirectUri(self::REDIRECT_URI); - $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -1865,7 +1870,6 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void $client->setIdentifier('foo'); $client->setRedirectUri(self::REDIRECT_URI); - $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -1945,7 +1949,6 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $client->setIdentifier('foo'); $client->setRedirectUri(self::REDIRECT_URI); - $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -2025,7 +2028,6 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva $client->setIdentifier('foo'); $client->setRedirectUri(self::REDIRECT_URI); - $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -2105,7 +2107,6 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void $client->setIdentifier('foo'); $client->setRedirectUri(self::REDIRECT_URI); - $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); From dfef82fdd2f95c62846ff1f73e317f986351f0f1 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Sat, 9 Nov 2024 23:34:40 +0330 Subject: [PATCH 25/36] redirect uri is required on auth code --- src/Grant/AbstractGrant.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index b638abbf2..6678b406e 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -155,13 +155,6 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity } } - // If a redirect URI is provided ensure it matches what is pre-registered - $redirectUri = $this->getRequestParameter('redirect_uri', $request); - - if ($redirectUri !== null) { - $this->validateRedirectUri($redirectUri, $client, $request); - } - return $client; } @@ -223,13 +216,13 @@ protected function getClientCredentials(ServerRequestInterface $request): array * @throws OAuthServerException */ protected function validateRedirectUri( - string $redirectUri, + ?string $redirectUri, ClientEntityInterface $client, ServerRequestInterface $request ): void { $validator = new RedirectUriValidator($client->getRedirectUri()); - if (!$validator->validateRedirectUri($redirectUri)) { + if (is_null($redirectUri) || !$validator->validateRedirectUri($redirectUri)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } From 04ecd814143bfd6b4b2a372f004f715549bc8474 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Sat, 9 Nov 2024 23:47:16 +0330 Subject: [PATCH 26/36] fix tests --- src/Grant/AbstractGrant.php | 4 +- tests/Grant/AbstractGrantTest.php | 78 ------------------------------- 2 files changed, 2 insertions(+), 80 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 6678b406e..83e247416 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -216,13 +216,13 @@ protected function getClientCredentials(ServerRequestInterface $request): array * @throws OAuthServerException */ protected function validateRedirectUri( - ?string $redirectUri, + string $redirectUri, ClientEntityInterface $client, ServerRequestInterface $request ): void { $validator = new RedirectUriValidator($client->getRedirectUri()); - if (is_null($redirectUri) || !$validator->validateRedirectUri($redirectUri)) { + if (!$validator->validateRedirectUri($redirectUri)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 67b50ffc8..26039cc96 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -266,84 +266,6 @@ public function testValidateClientInvalidClientSecret(): void $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientInvalidRedirectUri(): void - { - $client = new ClientEntity(); - $client->setRedirectUri('http://foo/bar'); - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - /** @var AbstractGrant $grantMock */ - $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setClientRepository($clientRepositoryMock); - - $abstractGrantReflection = new ReflectionClass($grantMock); - - $serverRequest = (new ServerRequest())->withParsedBody([ - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar/foo', - ]); - - $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); - $validateClientMethod->setAccessible(true); - - $this->expectException(OAuthServerException::class); - - $validateClientMethod->invoke($grantMock, $serverRequest, true, true); - } - - public function testValidateClientInvalidRedirectUriArray(): void - { - $client = new ClientEntity(); - $client->setRedirectUri(['http://foo/bar']); - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - /** @var AbstractGrant $grantMock */ - $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setClientRepository($clientRepositoryMock); - - $abstractGrantReflection = new ReflectionClass($grantMock); - - $serverRequest = (new ServerRequest())->withParsedBody([ - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar/foo', - ]); - - $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); - $validateClientMethod->setAccessible(true); - - $this->expectException(OAuthServerException::class); - - $validateClientMethod->invoke($grantMock, $serverRequest, true, true); - } - - public function testValidateClientMalformedRedirectUri(): void - { - $client = new ClientEntity(); - $client->setRedirectUri('http://foo/bar'); - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - /** @var AbstractGrant $grantMock */ - $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setClientRepository($clientRepositoryMock); - - $abstractGrantReflection = new ReflectionClass($grantMock); - - $serverRequest = (new ServerRequest())->withParsedBody([ - 'client_id' => 'foo', - 'redirect_uri' => ['not', 'a', 'string'], - ]); - - $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); - $validateClientMethod->setAccessible(true); - - $this->expectException(OAuthServerException::class); - - $validateClientMethod->invoke($grantMock, $serverRequest, true, true); - } - public function testValidateClientBadClient(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); From 839aa7db55a649d4c36d282307a48b77864f64af Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 19 Nov 2024 12:51:46 +0330 Subject: [PATCH 27/36] fix tests --- tests/Grant/RefreshTokenGrantTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 9ba0e9a01..3700a8470 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -747,7 +747,9 @@ public function testRespondToRequestWithIntUserId(): void $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenEntity = new AccessTokenEntity(); + $accessTokenEntity->setClient($client); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessTokenEntity); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $ace = new AccessTokenEntity(); $ace->setIdentifier('abcdef'); From 0d6239c44fa2af714a10da6bf61012c7e93597bf Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 4 Dec 2024 21:53:52 +0330 Subject: [PATCH 28/36] bypass bc breaking change and add tests --- src/Entities/ClientEntityInterface.php | 6 ++- src/Entities/Traits/ClientTrait.php | 4 +- src/Grant/AbstractGrant.php | 13 ++++++- tests/Grant/AbstractGrantTest.php | 53 ++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index 138e831f7..beba39d6b 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -40,7 +40,9 @@ public function getRedirectUri(): string|array; public function isConfidential(): bool; /** - * Returns true if the client handles the given grant type. + * Returns true if the client supports the given grant type. + * + * To be added in a future major release. */ - public function hasGrantType(string $grantType): bool; + // public function supportsGrantType(string $grantType): bool; } diff --git a/src/Entities/Traits/ClientTrait.php b/src/Entities/Traits/ClientTrait.php index 0b4327c35..ada53fa5a 100644 --- a/src/Entities/Traits/ClientTrait.php +++ b/src/Entities/Traits/ClientTrait.php @@ -54,9 +54,9 @@ public function isConfidential(): bool } /** - * Returns true if the client handles the given grant type. + * Returns true if the client supports the given grant type. */ - public function hasGrantType(string $grantType): bool + public function supportsGrantType(string $grantType): bool { return true; } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 83e247416..005206f97 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -179,13 +179,22 @@ protected function getClientEntityOrFail(string $clientId, ServerRequestInterfac throw OAuthServerException::invalidClient($request); } - if (!$client->hasGrantType($this->getIdentifier())) { + if ($this->supportsGrantType($client, $this->getIdentifier()) === false) { throw OAuthServerException::unauthorizedClient(); } return $client; } + /** + * Returns true if the given client is authorized to use the given grant type. + */ + protected function supportsGrantType(ClientEntityInterface $client, string $grantType): bool + { + return method_exists($client, 'supportsGrantType') === false + || $client->supportsGrantType($grantType) === true; + } + /** * Gets the client credentials from the request from the request body or * the Http Basic Authorization header @@ -481,7 +490,7 @@ protected function issueAuthCode( */ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface { - if (!$accessToken->getClient()->hasGrantType('refresh_token')) { + if ($this->supportsGrantType($accessToken->getClient(), 'refresh_token') === false) { return null; } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 26039cc96..0e8dd7ba8 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -290,6 +290,32 @@ public function testValidateClientBadClient(): void $validateClientMethod->invoke($grantMock, $serverRequest, true); } + public function testUnauthorizedClient(): void + { + $client = $this->getMockBuilder(ClientEntity::class)->getMock(); + $client->method('supportsGrantType')->willReturn(false); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock + ->expects(self::once()) + ->method('getClientEntity') + ->with('foo') + ->willReturn($client); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setClientRepository($clientRepositoryMock); + + $abstractGrantReflection = new ReflectionClass($grantMock); + + $getClientEntityOrFailMethod = $abstractGrantReflection->getMethod('getClientEntityOrFail'); + $getClientEntityOrFailMethod->setAccessible(true); + + $this->expectException(OAuthServerException::class); + + $getClientEntityOrFailMethod->invoke($grantMock, 'foo', new ServerRequest()); + } + public function testCanRespondToRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -351,6 +377,33 @@ public function testIssueNullRefreshToken(): void self::assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); } + public function testIssueNullRefreshTokenUnauthorizedClient(): void + { + $client = $this->getMockBuilder(ClientEntity::class)->getMock(); + $client + ->expects(self::once()) + ->method('supportsGrantType') + ->with('refresh_token') + ->willReturn(false); + + $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepoMock->expects(self::never())->method('getNewRefreshToken'); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setRefreshTokenTTL(new DateInterval('PT1M')); + $grantMock->setRefreshTokenRepository($refreshTokenRepoMock); + + $abstractGrantReflection = new ReflectionClass($grantMock); + $issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken'); + $issueRefreshTokenMethod->setAccessible(true); + + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + + self::assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); + } + public function testIssueAccessToken(): void { $accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); From 50120e916a1a729dfa72d2f82f8da99980e45987 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 4 Dec 2024 21:56:59 +0330 Subject: [PATCH 29/36] formatting --- src/Entities/ClientEntityInterface.php | 2 +- tests/Grant/AbstractGrantTest.php | 52 +++++++++++++------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index beba39d6b..b0050ecea 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -39,7 +39,7 @@ public function getRedirectUri(): string|array; */ public function isConfidential(): bool; - /** + /* * Returns true if the client supports the given grant type. * * To be added in a future major release. diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 0e8dd7ba8..6cd8d2f15 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -290,32 +290,6 @@ public function testValidateClientBadClient(): void $validateClientMethod->invoke($grantMock, $serverRequest, true); } - public function testUnauthorizedClient(): void - { - $client = $this->getMockBuilder(ClientEntity::class)->getMock(); - $client->method('supportsGrantType')->willReturn(false); - - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock - ->expects(self::once()) - ->method('getClientEntity') - ->with('foo') - ->willReturn($client); - - /** @var AbstractGrant $grantMock */ - $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setClientRepository($clientRepositoryMock); - - $abstractGrantReflection = new ReflectionClass($grantMock); - - $getClientEntityOrFailMethod = $abstractGrantReflection->getMethod('getClientEntityOrFail'); - $getClientEntityOrFailMethod->setAccessible(true); - - $this->expectException(OAuthServerException::class); - - $getClientEntityOrFailMethod->invoke($grantMock, 'foo', new ServerRequest()); - } - public function testCanRespondToRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -559,4 +533,30 @@ public function testCompleteAuthorizationRequest(): void $grantMock->completeAuthorizationRequest(new AuthorizationRequest()); } + + public function testUnauthorizedClient(): void + { + $client = $this->getMockBuilder(ClientEntity::class)->getMock(); + $client->method('supportsGrantType')->willReturn(false); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock + ->expects(self::once()) + ->method('getClientEntity') + ->with('foo') + ->willReturn($client); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setClientRepository($clientRepositoryMock); + + $abstractGrantReflection = new ReflectionClass($grantMock); + + $getClientEntityOrFailMethod = $abstractGrantReflection->getMethod('getClientEntityOrFail'); + $getClientEntityOrFailMethod->setAccessible(true); + + $this->expectException(OAuthServerException::class); + + $getClientEntityOrFailMethod->invoke($grantMock, 'foo', new ServerRequest()); + } } From 7a6ea34e7534dc5045aafacba8705c4bcda8c92e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 6 Feb 2025 22:24:09 +0000 Subject: [PATCH 30/36] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4899ef506..0133b0725 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +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] +### Added +- Added a new function to the provided ClientTrait, `supportsGrantType` to allow the auth server to issue the response `unauthorized_client` when applicable (PR #1420) + +### Fixed +- Clients only validated for Refresh, Device Code, and Password grants if the client is confidential (PR #1420) + ### Changed - Key permission checks ignored on Windows regardless of userland choice as cannot be run successfully on this OS (PR #1447) From c64e2fe2bf16584286a8aa57edfaad800e7049cf Mon Sep 17 00:00:00 2001 From: Craig Phillips Date: Sun, 19 Jan 2025 23:52:12 +0000 Subject: [PATCH 31/36] Remove the encrypted payload from AuthCode and RefreshToken The Auth Code and Refresh Token information can be stored in the repository and doesn't have to go in the payload. This will reduce auth code and refresh token size, but will require more calls to the repository --- src/Entities/Traits/AuthCodeTrait.php | 3 +-- src/Entities/Traits/TokenEntityTrait.php | 1 - src/Grant/AuthCodeGrant.php | 8 ++++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index 8dcacfa82..54135f039 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -15,8 +15,7 @@ trait AuthCodeTrait { protected ?string $redirectUri = null; - - + /** * The code challenge (if provided) */ diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index f1c8b61ef..82ef405e9 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -55,7 +55,6 @@ public function getScopes(): array /** * Set the scopes array (doesn't check for duplicates) - * * @param array scopes */ public function setScopes(array $scopes): void diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 48a396990..e1c3f552b 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -110,6 +110,14 @@ public function respondToAccessTokenRequest( if (empty($ace)) { throw OAuthServerException::invalidRequest('code', 'Cannot validate the provided authorization code'); } + else { + // Get the Auth Code Payload from Repository + $ace = $this->authCodeRepository->getAuthCodeEntity($code); + + if (empty($ace)) { + throw OAuthServerException::invalidRequest('code', 'Cannot find authorization code'); + } + } $this->validateAuthorizationCode($ace, $client, $request); From efa3a616af5ab77c0954217c1f5663c2a6ac75f4 Mon Sep 17 00:00:00 2001 From: Craig Phillips Date: Sun, 19 Jan 2025 23:52:47 +0000 Subject: [PATCH 32/36] Satisfy tests for Auth Code And Refresh Token --- tests/Grant/AuthCodeGrantTest.php | 9 ++++++++- tests/Grant/RefreshTokenGrantTest.php | 4 +--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 6d0273ba6..a22950f05 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1425,13 +1425,20 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode $clientRepositoryMock->method('getClientEntity')->willReturn($client); $clientRepositoryMock->method('validateClient')->willReturn(true); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + + $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); + $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 3700a8470..8fdd96cc1 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -253,10 +253,8 @@ public function testRespondToUnexpectedScope(): void $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); - $scope = new ScopeEntity(); - $scope->setIdentifier('foobar'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1, $scope2, $scope3); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); From 7c7a047d4adf6a7df11d81fe249947eb41c7df14 Mon Sep 17 00:00:00 2001 From: Craig Phillips Date: Mon, 20 Jan 2025 21:13:05 +0000 Subject: [PATCH 33/36] Syntax fixes --- src/Entities/Traits/AuthCodeTrait.php | 3 ++- src/Entities/Traits/TokenEntityTrait.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index 54135f039..8dcacfa82 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -15,7 +15,8 @@ trait AuthCodeTrait { protected ?string $redirectUri = null; - + + /** * The code challenge (if provided) */ diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 82ef405e9..f1c8b61ef 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -55,6 +55,7 @@ public function getScopes(): array /** * Set the scopes array (doesn't check for duplicates) + * * @param array scopes */ public function setScopes(array $scopes): void From 53078b28799f6fb97d976f179c4c779235ab6093 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 01:32:45 +0000 Subject: [PATCH 34/36] Removing Encryption from RefreshToken and AuthCode userIdentifier -> UserEntity --- src/Grant/RefreshTokenGrant.php | 11 ++---- tests/Grant/AuthCodeGrantTest.php | 59 ------------------------------- 2 files changed, 2 insertions(+), 68 deletions(-) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 099a4d6dd..ae56be674 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -60,25 +60,18 @@ public function respondToAccessTokenRequest( return $responseType; } - $originalScopes = $oldRefreshToken->getScopes(); - $originalScopeArray = []; - foreach ($originalScopes as $scopeEntity) { - $originalScopeArray[$scopeEntity->getIdentifier()] = $scopeEntity->getIdentifier(); - } - $originalScopeArray = array_values($originalScopeArray); - $scopes = $this->validateScopes( $this->getRequestParameter( 'scope', $request, - implode(self::SCOPE_DELIMITER_STRING, $originalScopeArray) + implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken->getScopes()) ) ); // The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure // the request doesn't include any new scopes foreach ($scopes as $scope) { - if (in_array($scope->getIdentifier(), $originalScopeArray, true) === false) { + if (in_array($scope->getIdentifier(), $oldRefreshToken->getScopes(), true) === false) { throw OAuthServerException::invalidScope($scope->getIdentifier()); } } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index a22950f05..245058c55 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1416,65 +1416,6 @@ public function testRespondToAccessTokenRequestMissingCode(): void $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode(): void - { - $client = new ClientEntity(); - $client->setRedirectUri(self::REDIRECT_URI); - - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - $clientRepositoryMock->method('validateClient')->willReturn(true); - - $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); - - $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); - $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); - - $grant = new AuthCodeGrant( - $authCodeRepository, - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') - ); - - $grant->setClientRepository($clientRepositoryMock); - $grant->setScopeRepository($scopeRepositoryMock); - $grant->setEncryptionKey($this->cryptStub->getKey()); - - $request = new ServerRequest( - [], - [], - null, - 'POST', - 'php://input', - [], - [], - [], - [ - 'grant_type' => 'authorization_code', - 'client_id' => 'foo', - 'redirect_uri' => self::REDIRECT_URI, - 'code' => $this->cryptStub->doEncrypt( - json_encode([ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => time() + 3600, - ], JSON_THROW_ON_ERROR) - ), - ] - ); - - try { - /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - } catch (OAuthServerException $e) { - self::assertEquals('Authorization code malformed', $e->getHint()); - } - } - public function testRespondToAccessTokenRequestWithAuthCodeNotAString(): void { $client = new ClientEntity(); From 64101003fcc5dd864ef31628a8471c1a1776f8a2 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 2 Feb 2025 11:57:51 +0000 Subject: [PATCH 35/36] Satisify some tests --- tests/Grant/RefreshTokenGrantTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 8fdd96cc1..3700a8470 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -253,8 +253,10 @@ public function testRespondToUnexpectedScope(): void $refreshTokenRepositoryMock->method('getRefreshTokenEntity')->willReturn($rte); + $scope = new ScopeEntity(); + $scope->setIdentifier('foobar'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope1, $scope2, $scope3); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); From fc1116e612db713ab360ac234ba29f1f8d612496 Mon Sep 17 00:00:00 2001 From: craigp1231 Date: Sun, 9 Feb 2025 22:37:03 +0000 Subject: [PATCH 36/36] Fix tests --- tests/Grant/AuthCodeGrantTest.php | 1 + tests/Grant/DeviceCodeGrantTest.php | 1 + tests/Grant/RefreshTokenGrantTest.php | 12 +++++++++--- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 245058c55..cfae6a239 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -735,6 +735,7 @@ public function testRespondToAccessTokenRequestWithDefaultRedirectUri(): void [ 'grant_type' => 'authorization_code', 'client_id' => 'foo', + 'client_secret' => 'bar', 'code' => $ace->getIdentifier() ] ); diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index a5467824b..158ba913b 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -355,6 +355,7 @@ public function testRespondToAccessTokenRequest(): void ->willReturn($deviceCodeEntity); $accessTokenEntity = new AccessTokenEntity(); + $accessTokenEntity->setClient($client); $accessTokenEntity->addScope($scope); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 3700a8470..54917baf0 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -54,10 +54,15 @@ public function testRespondToRequest(): void $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); + $ace = new AccessTokenEntity(); + $ace->setIdentifier('abcdef1'); + $ace->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($ace); $ace = new AccessTokenEntity(); - $ace->setIdentifier('abcdef'); + $ace->setIdentifier('abcdef1'); + $ace->setClient($client); $accessTokenRepositoryMock->method('getAccessTokenEntity')->willReturn($ace); $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); @@ -66,7 +71,8 @@ public function testRespondToRequest(): void $rte->setClient($client); $rte->setIdentifier('zyxwvu'); $ace = new AccessTokenEntity(); - $ace->setIdentifier('abcdef'); + $ace->setIdentifier('abcdef2'); + $ace->setClient($client); $rte->setAccessToken($ace); $rte->setScopes(['foo']); $user = new UserEntity();