From 3082728141f28f369d0f46aafe451674ff13c279 Mon Sep 17 00:00:00 2001 From: omdmhd Date: Fri, 26 Jul 2019 14:07:56 +0430 Subject: [PATCH 001/212] change errorType to invalid_grant on invalid refresh_token --- src/Exception/OAuthServerException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 8e628baa2..b669c6aba 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -226,7 +226,7 @@ public static function serverError($hint, Throwable $previous = null) */ public static function invalidRefreshToken($hint = null, Throwable $previous = null) { - return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous); + return new static('The refresh token is invalid.', 8, 'invalid_grant', 401, $hint, null, $previous); } /** From 4673e7de89f9a221210b16258b6f73a1bb9017d4 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sat, 17 Aug 2019 23:51:23 +0300 Subject: [PATCH 002/212] Abstract CryptKey public methods to the CryptKeyInterface --- src/AuthorizationServer.php | 16 +++++++------- .../BearerTokenValidator.php | 8 +++---- src/CryptKey.php | 16 +++----------- src/CryptKeyInterface.php | 21 +++++++++++++++++++ src/Entities/AccessTokenEntityInterface.php | 4 ++-- src/Entities/Traits/AccessTokenTrait.php | 10 ++++----- src/Grant/AbstractGrant.php | 8 +++---- src/Grant/GrantTypeInterface.php | 6 +++--- src/ResourceServer.php | 6 +++--- src/ResponseTypes/AbstractResponseType.php | 8 +++---- tests/AuthorizationServerTest.php | 3 +-- 11 files changed, 58 insertions(+), 48 deletions(-) create mode 100644 src/CryptKeyInterface.php diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 8b0b2815e..24a5a2726 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -40,12 +40,12 @@ class AuthorizationServer implements EmitterAwareInterface protected $grantTypeAccessTokenTTL = []; /** - * @var CryptKey + * @var CryptKeyInterface */ protected $privateKey; /** - * @var CryptKey + * @var CryptKeyInterface */ protected $publicKey; @@ -82,12 +82,12 @@ class AuthorizationServer implements EmitterAwareInterface /** * New server instance. * - * @param ClientRepositoryInterface $clientRepository + * @param ClientRepositoryInterface $clientRepository * @param AccessTokenRepositoryInterface $accessTokenRepository - * @param ScopeRepositoryInterface $scopeRepository - * @param CryptKey|string $privateKey - * @param string|Key $encryptionKey - * @param null|ResponseTypeInterface $responseType + * @param ScopeRepositoryInterface $scopeRepository + * @param CryptKeyInterface|string $privateKey + * @param string|Key $encryptionKey + * @param null|ResponseTypeInterface $responseType */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -101,7 +101,7 @@ public function __construct( $this->accessTokenRepository = $accessTokenRepository; $this->scopeRepository = $scopeRepository; - if ($privateKey instanceof CryptKey === false) { + if ($privateKey instanceof CryptKeyInterface === false) { $privateKey = new CryptKey($privateKey); } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 7218f4132..2fd087ac8 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -14,7 +14,7 @@ use Lcobucci\JWT\Parser; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\ValidationData; -use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -31,7 +31,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface private $accessTokenRepository; /** - * @var CryptKey + * @var CryptKeyInterface */ protected $publicKey; @@ -46,9 +46,9 @@ public function __construct(AccessTokenRepositoryInterface $accessTokenRepositor /** * Set the public key * - * @param CryptKey $key + * @param CryptKeyInterface $key */ - public function setPublicKey(CryptKey $key) + public function setPublicKey(CryptKeyInterface $key) { $this->publicKey = $key; } diff --git a/src/CryptKey.php b/src/CryptKey.php index 6fc4dff00..0d7a03552 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -14,7 +14,7 @@ use LogicException; use RuntimeException; -class CryptKey +class CryptKey implements CryptKeyInterface { const RSA_KEY_PATTERN = '/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s'; @@ -101,22 +101,12 @@ private function saveKeyToFile($key) return 'file://' . $keyPath; } - /** - * Retrieve key path. - * - * @return string - */ - public function getKeyPath() + public function getKeyPath(): string { return $this->keyPath; } - /** - * Retrieve key pass phrase. - * - * @return null|string - */ - public function getPassPhrase() + public function getPassPhrase(): ?string { return $this->passPhrase; } diff --git a/src/CryptKeyInterface.php b/src/CryptKeyInterface.php new file mode 100644 index 000000000..115f21b2f --- /dev/null +++ b/src/CryptKeyInterface.php @@ -0,0 +1,21 @@ +privateKey = $privateKey; } @@ -36,11 +36,11 @@ public function setPrivateKey(CryptKey $privateKey) /** * Generate a JWT from the access token * - * @param CryptKey $privateKey + * @param CryptKeyInterface $privateKey * * @return Token */ - private function convertToJWT(CryptKey $privateKey) + private function convertToJWT(CryptKeyInterface $privateKey) { return (new Builder()) ->setAudience($this->getClient()->getIdentifier()) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 0ac9e3954..2c342f634 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -15,7 +15,7 @@ use Error; use Exception; use League\Event\EmitterAwareTrait; -use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; @@ -83,7 +83,7 @@ abstract class AbstractGrant implements GrantTypeInterface protected $refreshTokenTTL; /** - * @var CryptKey + * @var CryptKeyInterface */ protected $privateKey; @@ -151,9 +151,9 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) /** * Set the private key * - * @param CryptKey $key + * @param CryptKeyInterface $key */ - public function setPrivateKey(CryptKey $key) + public function setPrivateKey(CryptKeyInterface $key) { $this->privateKey = $key; } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 41ebeb5ff..6bc5329b0 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -14,7 +14,7 @@ use DateInterval; use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; -use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -131,9 +131,9 @@ public function setDefaultScope($scope); /** * Set the path to the private key. * - * @param CryptKey $privateKey + * @param CryptKeyInterface $privateKey */ - public function setPrivateKey(CryptKey $privateKey); + public function setPrivateKey(CryptKeyInterface $privateKey); /** * Set the encryption key diff --git a/src/ResourceServer.php b/src/ResourceServer.php index e1f98d6d1..92a72763e 100644 --- a/src/ResourceServer.php +++ b/src/ResourceServer.php @@ -23,7 +23,7 @@ class ResourceServer private $accessTokenRepository; /** - * @var CryptKey + * @var CryptKeyInterface */ private $publicKey; @@ -36,7 +36,7 @@ class ResourceServer * New server instance. * * @param AccessTokenRepositoryInterface $accessTokenRepository - * @param CryptKey|string $publicKey + * @param CryptKeyInterface|string $publicKey * @param null|AuthorizationValidatorInterface $authorizationValidator */ public function __construct( @@ -46,7 +46,7 @@ public function __construct( ) { $this->accessTokenRepository = $accessTokenRepository; - if ($publicKey instanceof CryptKey === false) { + if ($publicKey instanceof CryptKeyInterface === false) { $publicKey = new CryptKey($publicKey); } $this->publicKey = $publicKey; diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 192f52aae..f5f201908 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -11,7 +11,7 @@ namespace League\OAuth2\Server\ResponseTypes; -use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; @@ -31,7 +31,7 @@ abstract class AbstractResponseType implements ResponseTypeInterface protected $refreshToken; /** - * @var CryptKey + * @var CryptKeyInterface */ protected $privateKey; @@ -54,9 +54,9 @@ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) /** * Set the private key * - * @param CryptKey $key + * @param CryptKeyInterface $key */ - public function setPrivateKey(CryptKey $key) + public function setPrivateKey(CryptKeyInterface $key) { $this->privateKey = $key; } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 870d546f2..76bcdda82 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -4,7 +4,6 @@ use DateInterval; use League\OAuth2\Server\AuthorizationServer; -use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\AuthCodeGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant; @@ -153,7 +152,7 @@ public function testMultipleRequestsGetDifferentResponseTypeInstances() $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; $responseTypePrototype = new class extends BearerTokenResponse { - /* @return null|CryptKey */ + /* @return null|\League\OAuth2\Server\CryptKeyInterface */ public function getPrivateKey() { return $this->privateKey; From 7db4cdb875f576b4b589a00eb7f8a26c2f178d90 Mon Sep 17 00:00:00 2001 From: ErickSkrauch Date: Sun, 18 Aug 2019 00:04:53 +0300 Subject: [PATCH 003/212] Fix CS --- src/AuthorizationServer.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 24a5a2726..8f37ffc5d 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -82,12 +82,12 @@ class AuthorizationServer implements EmitterAwareInterface /** * New server instance. * - * @param ClientRepositoryInterface $clientRepository + * @param ClientRepositoryInterface $clientRepository * @param AccessTokenRepositoryInterface $accessTokenRepository - * @param ScopeRepositoryInterface $scopeRepository - * @param CryptKeyInterface|string $privateKey - * @param string|Key $encryptionKey - * @param null|ResponseTypeInterface $responseType + * @param ScopeRepositoryInterface $scopeRepository + * @param CryptKeyInterface|string $privateKey + * @param string|Key $encryptionKey + * @param null|ResponseTypeInterface $responseType */ public function __construct( ClientRepositoryInterface $clientRepository, From f604109168d9dcaca6e0ad3f53c49908b9e797db Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 18:59:03 +0100 Subject: [PATCH 004/212] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d92765472..ed0bd7a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added (v9) +- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) + ### Fixed - Clients are now explicitly prevented from using the Client Credentials grant unless they are confidential to conform with the OAuth2 spec (PR #1035) From 01d652ab29ddf4d7366bbf8fe107f72c7c760e2a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 19:02:39 +0100 Subject: [PATCH 005/212] Remove type hinting --- src/CryptKey.php | 4 ++-- src/CryptKeyInterface.php | 5 ++--- tests/AuthorizationServerTest.php | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 0d7a03552..0b6343d90 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -101,12 +101,12 @@ private function saveKeyToFile($key) return 'file://' . $keyPath; } - public function getKeyPath(): string + public function getKeyPath() { return $this->keyPath; } - public function getPassPhrase(): ?string + public function getPassPhrase() { return $this->passPhrase; } diff --git a/src/CryptKeyInterface.php b/src/CryptKeyInterface.php index 115f21b2f..86c7b0446 100644 --- a/src/CryptKeyInterface.php +++ b/src/CryptKeyInterface.php @@ -1,5 +1,4 @@ privateKey; From d40a37570cbc19c149f0f4dcca8a318bb6f23d1b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 19:06:05 +0100 Subject: [PATCH 006/212] Add inherit docblocks to CryptKey --- src/CryptKey.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CryptKey.php b/src/CryptKey.php index 0b6343d90..aaf22292c 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -101,11 +101,17 @@ private function saveKeyToFile($key) return 'file://' . $keyPath; } + /** + * {@inheritDoc} + */ public function getKeyPath() { return $this->keyPath; } + /** + * {@inheritDoc} + */ public function getPassPhrase() { return $this->passPhrase; From 4ab302a969ec1a0ee32e75dc771bd8ef64ad6e33 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 19:10:59 +0100 Subject: [PATCH 007/212] Fix capitalisation of inheritdoc --- src/CryptKey.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index aaf22292c..4268cc8f3 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -102,7 +102,7 @@ private function saveKeyToFile($key) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getKeyPath() { @@ -110,7 +110,7 @@ public function getKeyPath() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getPassPhrase() { From e33e6480d1b58d362f2f73a3847baa14d2ddd50d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 20:16:34 +0100 Subject: [PATCH 008/212] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed0bd7a85..c43401973 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added (v9) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) +### Fixed (v9) +- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #993) + ### Fixed - Clients are now explicitly prevented from using the Client Credentials grant unless they are confidential to conform with the OAuth2 spec (PR #1035) From 0a80c1994ca080c3640540d2529f6552a282a157 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 20:17:29 +0100 Subject: [PATCH 009/212] Change HTTP status code to 400 --- src/Exception/OAuthServerException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index b669c6aba..d0e68a2a3 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -226,7 +226,7 @@ public static function serverError($hint, Throwable $previous = null) */ public static function invalidRefreshToken($hint = null, Throwable $previous = null) { - return new static('The refresh token is invalid.', 8, 'invalid_grant', 401, $hint, null, $previous); + return new static('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous); } /** From 5037907be5435878ee21a4185b9ecc3a4908bd36 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 31 Aug 2019 20:19:45 +0100 Subject: [PATCH 010/212] Update travis to also run on version 9 branch --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 820d27554..f442e7f97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,3 +30,4 @@ after_script: branches: only: - master + - 9.0.0-WIP From 48101dde51431b2c9b8038f7fe4c068bb35bd4ce Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Mon, 25 Nov 2019 13:43:14 +0100 Subject: [PATCH 011/212] Device Code grant --- src/AuthorizationServer.php | 37 +++ src/Entities/DeviceCodeEntityInterface.php | 38 +++ src/Entities/Traits/DeviceCodeTrait.php | 127 ++++++++ src/Exception/OAuthServerException.php | 34 ++ src/Grant/AbstractGrant.php | 114 +++++++ src/Grant/DeviceCodeGrant.php | 218 +++++++++++++ src/Grant/GrantTypeInterface.php | 36 ++ .../DeviceCodeRepositoryInterface.php | 79 +++++ .../DeviceAuthorizationRequest.php | 85 +++++ src/ResponseTypes/AbstractResponseType.php | 14 + src/ResponseTypes/DeviceCodeResponse.php | 64 ++++ src/ResponseTypes/ResponseTypeInterface.php | 6 + tests/Grant/DeviceCodeGrantTest.php | 307 ++++++++++++++++++ .../DeviceCodeResponseTypeTest.php | 56 ++++ tests/Stubs/DeviceCodeEntity.php | 13 + 15 files changed, 1228 insertions(+) create mode 100644 src/Entities/DeviceCodeEntityInterface.php create mode 100644 src/Entities/Traits/DeviceCodeTrait.php create mode 100644 src/Grant/DeviceCodeGrant.php create mode 100644 src/Repositories/DeviceCodeRepositoryInterface.php create mode 100644 src/RequestTypes/DeviceAuthorizationRequest.php create mode 100644 src/ResponseTypes/DeviceCodeResponse.php create mode 100644 tests/Grant/DeviceCodeGrantTest.php create mode 100644 tests/ResponseTypes/DeviceCodeResponseTypeTest.php create mode 100644 tests/Stubs/DeviceCodeEntity.php diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 8b0b2815e..a835186a5 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -19,6 +19,7 @@ use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\AbstractResponseType; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -176,6 +177,42 @@ public function completeAuthorizationRequest(AuthorizationRequest $authRequest, ->generateHttpResponse($response); } + /** + * Validate a device authorization request + * + * @param ServerRequestInterface $request + * + * @return DeviceAuthorizationRequest + * @throws OAuthServerException + * + */ + public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) + { + foreach ($this->enabledGrantTypes as $grantType) { + if ($grantType->canRespondToDeviceAuthorizationRequest($request)) { + return $grantType->validateDeviceAuthorizationRequest($request); + } + } + + throw OAuthServerException::unsupportedGrantType(); + } + + /** + * Complete a device authorization request + * + * @param DeviceAuthorizationRequest $deviceRequest + * @param ResponseInterface $response + * + * @return ResponseInterface + * + */ + public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest, ResponseInterface $response) + { + return $this->enabledGrantTypes[$deviceRequest->getGrantTypeId()] + ->completeDeviceAuthorizationRequest($deviceRequest) + ->generateHttpResponse($response); + } + /** * Return an access token response. * diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php new file mode 100644 index 000000000..8de43c5fc --- /dev/null +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -0,0 +1,38 @@ + + * @copyright Copyright (c) Luca Degasperi + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Entities; + +interface DeviceCodeEntityInterface extends TokenInterface +{ + /** + * @return string + */ + public function getUserCode(); + + /** + * @param string $userCode + */ + public function setUserCode($userCode); + + /** + * @return string + */ + public function getVerificationUri(); + + /** + * @param string $verificationUri + */ + public function setVerificationUri($verificationUri); + + /** + * Generate a string representation of the access token. + */ + public function __toString(); +} diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php new file mode 100644 index 000000000..69033a298 --- /dev/null +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -0,0 +1,127 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Entities\Traits; + +use DateTimeImmutable; +use Lcobucci\JWT\Builder; +use Lcobucci\JWT\Signer\Key; +use Lcobucci\JWT\Signer\Rsa\Sha256; +use Lcobucci\JWT\Token; +use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\ScopeEntityInterface; + +trait DeviceCodeTrait +{ + /** + * @var CryptKey + */ + private $privateKey; + + /** + * @var string + */ + private $userCode; + + /** + * @var string + */ + private $verificationUri; + + /** + * Set the private key used to encrypt this access token. + */ + public function setPrivateKey(CryptKey $privateKey) + { + $this->privateKey = $privateKey; + } + + /** + * Generate a JWT from the access token + * + * @param CryptKey $privateKey + * + * @return Token + */ + private function convertToJWT(CryptKey $privateKey) + { + return (new Builder()) + ->permittedFor($this->getClient()->getIdentifier()) + ->identifiedBy($this->getIdentifier()) + ->issuedAt(\time()) + ->canOnlyBeUsedAfter(\time()) + ->expiresAt($this->getExpiryDateTime()->getTimestamp()) + ->relatedTo($this->getUserCode()) + ->withClaim('scopes', $this->getScopes()) + ->getToken(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase())); + } + + /** + * Generate a string representation from the access token + */ + public function __toString() + { + return (string) $this->convertToJWT($this->privateKey); + } + + /** + * @return string + */ + public function getUserCode() + { + return $this->userCode; + } + + /** + * @param string $userCode + * + * @return string + */ + public function setUserCode($userCode) + { + $this->userCode = $userCode; + } + + /** + * @return ClientEntityInterface + */ + abstract public function getClient(); + + /** + * @return DateTimeImmutable + */ + abstract public function getExpiryDateTime(); + + /** + * @return ScopeEntityInterface[] + */ + abstract public function getScopes(); + + /** + * @return string + */ + abstract public function getIdentifier(); + + /** + * @return string + */ + public function getVerificationUri() + { + return $this->verificationUri; + } + + /** + * @param string $verificationUri + */ + public function setVerificationUri($verificationUri) + { + $this->verificationUri = $verificationUri; + } +} diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index ffd595874..203958505 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -271,6 +271,40 @@ public static function invalidGrant($hint = '') ); } + /** + * Expired token error. + * + * @param null|string $hint + * @param Throwable $previous Previous exception + * + * @return static + */ + public static function expiredToken($hint = null, Throwable $previous = null) + { + $errorMessage = 'The `device_code` has expired and the device authorization session has concluded.'; + + return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous); + } + + /** + * Authorization pending. + * + * @param string $hint + * + * @return static + */ + public static function authorizationPending($hint = '') + { + return new static( + 'The authorization request is still pending as the end user hasn\'t yet completed the user interaction steps. ' + . 'The client SHOULD repeat the Access Token Request to the token endpoint', + 12, + 'authorization_pending', + 400, + $hint + ); + } + /** * @return string */ diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 935572f30..1fe911b78 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -20,6 +20,7 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -27,11 +28,13 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; +use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use LogicException; use Psr\Http\Message\ServerRequestInterface; use TypeError; @@ -67,6 +70,11 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $authCodeRepository; + /** + * @var DeviceCodeRepositoryInterface + */ + protected $deviceCodeRepository; + /** * @var RefreshTokenRepositoryInterface */ @@ -132,6 +140,14 @@ public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepos $this->authCodeRepository = $authCodeRepository; } + /** + * @param DeviceCodeRepositoryInterface $deviceCodeRepository + */ + public function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository) + { + $this->deviceCodeRepository = $deviceCodeRepository; + } + /** * @param UserRepositoryInterface $userRepository */ @@ -500,6 +516,51 @@ protected function issueAuthCode( } } + /** + * Issue a device code. + * + * @param DateInterval $deviceCodeTTL + * @param ClientEntityInterface $client + * @param string $verificationUri + * @param ScopeEntityInterface[] $scopes + * + * @return DeviceCodeEntityInterface + * @throws OAuthServerException + * + * @throws UniqueTokenIdentifierConstraintViolationException + */ + protected function issueDeviceCode( + DateInterval $deviceCodeTTL, + ClientEntityInterface $client, + $verificationUri, + array $scopes = [] + ) { + $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; + + $deviceCode = $this->deviceCodeRepository->getNewDeviceCode(); + $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add($deviceCodeTTL)); + $deviceCode->setClient($client); + $deviceCode->setVerificationUri($verificationUri); + + foreach ($scopes as $scope) { + $deviceCode->addScope($scope); + } + + while ($maxGenerationAttempts-- > 0) { + $deviceCode->setIdentifier($this->generateUniqueIdentifier()); + $deviceCode->setUserCode($this->generateUniqueUserCode()); + try { + $this->deviceCodeRepository->persistNewDeviceCode($deviceCode); + + return $deviceCode; + } catch (UniqueTokenIdentifierConstraintViolationException $e) { + if ($maxGenerationAttempts === 0) { + throw $e; + } + } + } + } + /** * @param AccessTokenEntityInterface $accessToken * @@ -560,6 +621,35 @@ protected function generateUniqueIdentifier($length = 40) // @codeCoverageIgnoreEnd } + /** + * Generate a new unique user code. + * + * @param int $length + * + * @return string + * @throws OAuthServerException + * + */ + protected function generateUniqueUserCode($length = 8) + { + try { + $userCode = ''; + while(strlen($userCode) < $length) { + $userCode .= (string)\random_int(0, 9); + } + return $userCode; + // @codeCoverageIgnoreStart + } catch (TypeError $e) { + throw OAuthServerException::serverError('An unexpected error has occurred', $e); + } catch (Error $e) { + throw OAuthServerException::serverError('An unexpected error has occurred', $e); + } catch (Exception $e) { + // If you get this message, the CSPRNG failed hard. + throw OAuthServerException::serverError('Could not generate a random string', $e); + } + // @codeCoverageIgnoreEnd + } + /** * {@inheritdoc} */ @@ -596,4 +686,28 @@ public function completeAuthorizationRequest(AuthorizationRequest $authorization { throw new LogicException('This grant cannot complete an authorization request'); } + + /** + * {@inheritdoc} + */ + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) + { + throw new LogicException('This grant cannot validate an authorization request'); + } + + /** + * {@inheritdoc} + */ + public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceAuthorizationRequest) + { + throw new LogicException('This grant cannot complete a device authorization request'); + } } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php new file mode 100644 index 000000000..48ae58232 --- /dev/null +++ b/src/Grant/DeviceCodeGrant.php @@ -0,0 +1,218 @@ + + * @copyright Copyright (c) Luca Degasperi + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Grant; + +use DateInterval; +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; +use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; +use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; +use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; +use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; +use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use Psr\Http\Message\ServerRequestInterface; + +/** + * Device Code grant class. + */ +class DeviceCodeGrant extends AbstractGrant +{ + /** + * @var DateInterval + */ + private $deviceCodeTTL; + + /** + * @var int + */ + private $retryInterval; + + /** + * @param DeviceCodeRepositoryInterface $deviceCodeRepository + * @param RefreshTokenRepositoryInterface $refreshTokenRepository + * @param DateInterval $deviceCodeTTL + * @param $retryInterval + */ + public function __construct( + DeviceCodeRepositoryInterface $deviceCodeRepository, + RefreshTokenRepositoryInterface $refreshTokenRepository, + DateInterval $deviceCodeTTL, + $retryInterval = 5 + ) { + $this->setDeviceCodeRepository($deviceCodeRepository); + $this->setRefreshTokenRepository($refreshTokenRepository); + + $this->refreshTokenTTL = new DateInterval('P1M'); + + $this->deviceCodeTTL = $deviceCodeTTL; + $this->retryInterval = $retryInterval; + } + + /** + * {@inheritdoc} + */ + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) + { + $clientId = $this->getRequestParameter( + 'client_id', + $request, + $this->getServerParameter('PHP_AUTH_USER', $request) + ); + + if ($clientId === null) { + throw OAuthServerException::invalidRequest('client_id'); + } + + $client = $this->getClientEntityOrFail($clientId, $request); + + $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); + + $deviceAuthorizationRequest = new DeviceAuthorizationRequest(); + $deviceAuthorizationRequest->setGrantTypeId($this->getIdentifier()); + $deviceAuthorizationRequest->setClient($client); + $deviceAuthorizationRequest->setScopes($scopes); + + return $deviceAuthorizationRequest; + } + + /** + * {@inheritdoc} + */ + public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest) + { + $deviceCode = $this->issueDeviceCode( + $this->deviceCodeTTL, + $deviceRequest->getClient(), + 'verification_uri', + $deviceRequest->getScopes() + ); + + $response = new DeviceCodeResponse(); + $response->setDeviceCode($deviceCode); + + return $response; + } + + /** + * {@inheritdoc} + */ + public function respondToAccessTokenRequest( + ServerRequestInterface $request, + ResponseTypeInterface $responseType, + DateInterval $accessTokenTTL + ) { + // Validate request + $client = $this->validateClient($request); + $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); + $deviceCode = $this->validateDeviceCode($request, $client); + + // TODO: if the request is too fast, respond with slow down + + + // if device code has no user associated, respond with pending + if (\is_null($deviceCode->getUserIdentifier())) { + throw OAuthServerException::authorizationPending(); + } + + // Finalize the requested scopes + $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $deviceCode->getUserIdentifier()); + + // Issue and persist new access token + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCode->getUserIdentifier(), $finalizedScopes); + $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); + $responseType->setAccessToken($accessToken); + + // Issue and persist new refresh token if given + $refreshToken = $this->issueRefreshToken($accessToken); + + if ($refreshToken !== null) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $responseType->setRefreshToken($refreshToken); + } + + $this->deviceCodeRepository->revokeDeviceCode($deviceCode->getIdentifier()); + + return $responseType; + } + + /** + * @param ServerRequestInterface $request + * @param ClientEntityInterface $client + * + * @throws OAuthServerException + * + * @return DeviceCodeEntityInterface + */ + protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client) + { + $encryptedDeviceCode = $this->getRequestParameter('device_code', $request); + + if (\is_null($encryptedDeviceCode)) { + throw OAuthServerException::invalidRequest('device_code'); + } + + try { + $deviceCodePayload = \json_decode($this->decrypt($encryptedDeviceCode)); + + if (!\property_exists($deviceCodePayload, 'device_code_id')) { + throw OAuthServerException::invalidRequest('device_code', 'Device code malformed'); + } + + if (\time() > $deviceCodePayload->expire_time) { + throw OAuthServerException::expiredToken('device_code'); + } + + if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) { + throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked'); + } + + if ($deviceCodePayload->client_id !== $client->getIdentifier()) { + throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client'); + } + + } catch (\LogicException $e) { + throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e); + } + + $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( + $deviceCodePayload->device_code_id, + $this->getIdentifier(), + $client + ); + + if ($deviceCode instanceof DeviceCodeEntityInterface === false) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); + + throw OAuthServerException::invalidGrant(); + } + + return $deviceCode; + } + + /** + * {@inheritdoc} + */ + public function getIdentifier() + { + return 'device_code'; + } +} diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 41ebeb5ff..613df39b9 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -15,10 +15,12 @@ use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -100,6 +102,40 @@ public function completeAuthorizationRequest(AuthorizationRequest $authorization */ public function canRespondToAccessTokenRequest(ServerRequestInterface $request); + + /** + * The grant type should return true if it is able to response to a device authorization request + * + * @param ServerRequestInterface $request + * @return bool + */ + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request); + + /** + * If the grant can respond to a device authorization request this method should be called to validate the parameters of + * the request. + * + * If the validation is successful a DeviceAuthorizationRequest object will be returned. This object can be safely + * serialized in a user's session, and can be used during user authentication and authorization. + * + * @param ServerRequestInterface $request + * + * @return DeviceAuthorizationRequest + */ + public function validateDeviceAuthorizationRequest(ServerRequestInterface $request); + + /** + * If the grant can respond to a device authorization request this method should be called to validate the parameters of + * the request. + * + * If the validation is successful a DeviceCode object will be returned. + * + * @param DeviceAuthorizationRequest $deviceAuthorizationRequest + * + * @return ResponseTypeInterface + */ + public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceAuthorizationRequest); + /** * Set the client repository. * diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php new file mode 100644 index 000000000..b3fe10c69 --- /dev/null +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -0,0 +1,79 @@ + + * @copyright Copyright (c) Luca Degasperi + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Repositories; + +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; +use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; + +interface DeviceCodeRepositoryInterface extends RepositoryInterface +{ + /** + * Creates a new DeviceCode + * + * @return DeviceCodeEntityInterface + */ + public function getNewDeviceCode(); + + /** + * Persists a new auth code to permanent storage. + * + * @param DeviceCodeEntityInterface $deviceCodeEntity + * + * @throws UniqueTokenIdentifierConstraintViolationException + */ + public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity); + + /** + * Get a device code entity. + * + * @param string $userCode + * @param string $grantType + * @param ClientEntityInterface $clientEntity + * + * @return DeviceCodeEntityInterface|null + */ + public function getDeviceCodeEntityByUserCode( + $userCode, + $grantType, + ClientEntityInterface $clientEntity + ); + + /** + * Get a device code entity. + * + * @param string $deviceCode + * @param string $grantType + * @param ClientEntityInterface $clientEntity + * + * @return DeviceCodeEntityInterface|null + */ + public function getDeviceCodeEntityByDeviceCode( + $deviceCode, + $grantType, + ClientEntityInterface $clientEntity + ); + + /** + * Revoke a device code. + * + * @param string $codeId + */ + public function revokeDeviceCode($codeId); + + /** + * Check if the device code has been revoked. + * + * @param string $codeId + * + * @return bool Return true if this code has been revoked + */ + public function isDeviceCodeRevoked($codeId); +} diff --git a/src/RequestTypes/DeviceAuthorizationRequest.php b/src/RequestTypes/DeviceAuthorizationRequest.php new file mode 100644 index 000000000..00b2fb2b2 --- /dev/null +++ b/src/RequestTypes/DeviceAuthorizationRequest.php @@ -0,0 +1,85 @@ + + * @copyright Copyright (c) Luca Degasperi + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\RequestTypes; + +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\ScopeEntityInterface; + +class DeviceAuthorizationRequest +{ + /** + * The grant type identifier + * + * @var string + */ + protected $grantTypeId; + + /** + * The client identifier + * + * @var ClientEntityInterface + */ + protected $client; + + /** + * An array of scope identifiers + * + * @var ScopeEntityInterface[] + */ + protected $scopes = []; + + /** + * @return string + */ + public function getGrantTypeId() + { + return $this->grantTypeId; + } + + /** + * @param string $grantTypeId + */ + public function setGrantTypeId($grantTypeId) + { + $this->grantTypeId = $grantTypeId; + } + + /** + * @return ClientEntityInterface + */ + public function getClient() + { + return $this->client; + } + + /** + * @param ClientEntityInterface $client + */ + public function setClient(ClientEntityInterface $client) + { + $this->client = $client; + } + + /** + * @return ScopeEntityInterface[] + */ + public function getScopes() + { + return $this->scopes; + } + + /** + * @param ScopeEntityInterface[] $scopes + */ + public function setScopes(array $scopes) + { + $this->scopes = $scopes; + } +} diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 192f52aae..8d648a1a8 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -14,6 +14,7 @@ use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; abstract class AbstractResponseType implements ResponseTypeInterface @@ -30,6 +31,11 @@ abstract class AbstractResponseType implements ResponseTypeInterface */ protected $refreshToken; + /** + * @var DeviceCodeEntityInterface + */ + protected $deviceCode; + /** * @var CryptKey */ @@ -51,6 +57,14 @@ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) $this->refreshToken = $refreshToken; } + /** + * {@inheritdoc} + */ + public function setDeviceCode(DeviceCodeEntityInterface $deviceCode) + { + $this->deviceCode = $deviceCode; + } + /** * Set the private key * diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php new file mode 100644 index 000000000..0f847fb88 --- /dev/null +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -0,0 +1,64 @@ + + * @copyright Copyright (c) Luca Degasperi + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\ResponseTypes; + +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; +use LogicException; +use Psr\Http\Message\ResponseInterface; + +class DeviceCodeResponse extends AbstractResponseType +{ + /** + * {@inheritdoc} + */ + public function generateHttpResponse(ResponseInterface $response) + { + $expireDateTime = $this->deviceCode->getExpiryDateTime()->getTimestamp(); + + $responseParams = [ + 'expires_in' => $expireDateTime - \time(), + 'device_code' => (string) $this->deviceCode, + 'user_code' => $this->deviceCode->getUserCode(), + 'verification_uri' => $this->deviceCode->getVerificationUri() + ]; + + $responseParams = \json_encode($responseParams); + + if ($responseParams === false) { + throw new LogicException('Error encountered JSON encoding response parameters'); + } + + $response = $response + ->withStatus(200) + ->withHeader('pragma', 'no-cache') + ->withHeader('cache-control', 'no-store') + ->withHeader('content-type', 'application/json; charset=UTF-8'); + + $response->getBody()->write($responseParams); + + return $response; + } + + /** + * Add custom fields to your Bearer Token response here, then override + * AuthorizationServer::getResponseType() to pull in your version of + * this class rather than the default. + * + * @param DeviceCodeEntityInterface $deviceCode + * + * @return array + */ + protected function getExtraParams(DeviceCodeEntityInterface $deviceCode) + { + return []; + } +} diff --git a/src/ResponseTypes/ResponseTypeInterface.php b/src/ResponseTypes/ResponseTypeInterface.php index 5eddd6079..152282562 100644 --- a/src/ResponseTypes/ResponseTypeInterface.php +++ b/src/ResponseTypes/ResponseTypeInterface.php @@ -13,6 +13,7 @@ use Defuse\Crypto\Key; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use Psr\Http\Message\ResponseInterface; @@ -28,6 +29,11 @@ public function setAccessToken(AccessTokenEntityInterface $accessToken); */ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken); + /** + * @param DeviceCodeEntityInterface $deviceCode + */ + public function setDeviceCode(DeviceCodeEntityInterface $deviceCode); + /** * @param ResponseInterface $response * diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php new file mode 100644 index 000000000..cebbe6bbe --- /dev/null +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -0,0 +1,307 @@ +cryptStub = new CryptTraitStub(); + } + + public function testGetIdentifier() + { + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + + $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M'), 5); + $this->assertEquals('device_code', $grant->getIdentifier()); + } + + public function testCanRespondToDeviceAuthorizationRequest() + { + $grant = new DeviceCodeGrant( + $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + + $request = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'scope' => 'basic' + ]); + + $this->assertTrue($grant->canRespondToDeviceAuthorizationRequest($request)); + } + + public function testValidateDeviceAuthorizationRequest() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new DeviceCodeGrant( + $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + + $request = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'scope' => 'basic' + ]); + + $this->assertInstanceOf(DeviceAuthorizationRequest::class, $grant->validateDeviceAuthorizationRequest($request)); + } + + public function testValidateDeviceAuthorizationRequestMissingClient() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new DeviceCodeGrant( + $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + + $request = (new ServerRequest())->withParsedBody([ + 'scope' => 'basic' + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->validateDeviceAuthorizationRequest($request); + } + + public function testValidateDeviceAuthorizationRequestClientMismatch() + { + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn(null); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new DeviceCodeGrant( + $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + + $request = (new ServerRequest())->withParsedBody([ + 'client_id' => 'bar', + 'scope' => 'basic' + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->validateDeviceAuthorizationRequest($request); + } + + public function testCompleteDeviceAuthorizationRequest() + { + $deviceAuthRequest = new DeviceAuthorizationRequest(); + $deviceAuthRequest->setClient(new ClientEntity()); + $deviceAuthRequest->setGrantTypeId('device_code'); + + $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeRepository->method('getNewDeviceCode')->willReturn(new DeviceCodeEntity()); + + $grant = new DeviceCodeGrant( + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + $grant->setEncryptionKey($this->cryptStub->getKey()); + + $this->assertInstanceOf(DeviceCodeResponse::class, $grant->completeDeviceAuthorizationRequest($deviceAuthRequest)); + } + + public function testRespondToRequest() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeEntity = new DeviceCodeEntity(); + $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M')); + $grant->setClientRepository($clientRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'device_code' => $this->cryptStub->doEncrypt( + \json_encode( + [ + 'device_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_code' => '12345678', + 'scopes' => ['foo'], + 'verification_uri' => 'http://foo/bar', + ] + ) + ) + ]); + + $responseType = new StubResponseType(); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + + $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); + $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); + } + + public function testRespondToRequestMissingClient() + { + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn(null); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + + $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M')); + $grant->setClientRepository($clientRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + + $serverRequest = (new ServerRequest())->withQueryParams([ + 'device_code' => $this->cryptStub->doEncrypt( + \json_encode( + [ + 'device_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_code' => '12345678', + 'scopes' => ['foo'], + 'verification_uri' => 'http://foo/bar', + ] + ) + ), + ]); + + $responseType = new StubResponseType(); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } + + public function testRespondToRequestMissingDeviceCode() + { + + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeEntity = new DeviceCodeEntity(); + $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M')); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo' + ]); + + $responseType = new StubResponseType(); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } +} diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php new file mode 100644 index 000000000..3ed13e607 --- /dev/null +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -0,0 +1,56 @@ +setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + + $client = new ClientEntity(); + $client->setIdentifier('clientName'); + + $scope = new ScopeEntity(); + $scope->setIdentifier('basic'); + + $deviceCode = new DeviceCodeEntity(); + $deviceCode->setIdentifier('abcdef'); + $deviceCode->setUserCode('12345678'); + $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); + $deviceCode->setClient($client); + $deviceCode->addScope($scope); + $deviceCode->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + + $responseType->setDeviceCode($deviceCode); + + $response = $responseType->generateHttpResponse(new Response()); + + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('no-cache', $response->getHeader('pragma')[0]); + $this->assertEquals('no-store', $response->getHeader('cache-control')[0]); + $this->assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); + + $response->getBody()->rewind(); + $json = \json_decode($response->getBody()->getContents()); + $this->assertObjectHasAttribute('expires_in', $json); + $this->assertObjectHasAttribute('device_code', $json); + $this->assertObjectHasAttribute('verification_uri', $json); + $this->assertObjectHasAttribute('user_code', $json); + } +} diff --git a/tests/Stubs/DeviceCodeEntity.php b/tests/Stubs/DeviceCodeEntity.php new file mode 100644 index 000000000..d1d16e775 --- /dev/null +++ b/tests/Stubs/DeviceCodeEntity.php @@ -0,0 +1,13 @@ + Date: Tue, 26 Nov 2019 16:43:44 +0100 Subject: [PATCH 012/212] removed unused method on the interface --- src/Grant/DeviceCodeGrant.php | 2 +- .../DeviceCodeRepositoryInterface.php | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 48ae58232..acfb13342 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -42,7 +42,7 @@ class DeviceCodeGrant extends AbstractGrant * @param DeviceCodeRepositoryInterface $deviceCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository * @param DateInterval $deviceCodeTTL - * @param $retryInterval + * @param int $retryInterval */ public function __construct( DeviceCodeRepositoryInterface $deviceCodeRepository, diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index b3fe10c69..23ef28d89 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -31,21 +31,6 @@ public function getNewDeviceCode(); */ public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity); - /** - * Get a device code entity. - * - * @param string $userCode - * @param string $grantType - * @param ClientEntityInterface $clientEntity - * - * @return DeviceCodeEntityInterface|null - */ - public function getDeviceCodeEntityByUserCode( - $userCode, - $grantType, - ClientEntityInterface $clientEntity - ); - /** * Get a device code entity. * From fb6cfd40d2c7de046377ccc5e3480a5dc72a420c Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Tue, 26 Nov 2019 21:56:47 +0100 Subject: [PATCH 013/212] fixing the response --- src/Entities/DeviceCodeEntityInterface.php | 5 -- src/Entities/Traits/DeviceCodeTrait.php | 73 ++++--------------- src/Grant/DeviceCodeGrant.php | 16 ++++ src/ResponseTypes/AbstractResponseType.php | 14 ---- src/ResponseTypes/DeviceCodeResponse.php | 25 ++++++- src/ResponseTypes/ResponseTypeInterface.php | 6 -- .../DeviceCodeResponseTypeTest.php | 3 +- 7 files changed, 56 insertions(+), 86 deletions(-) diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index 8de43c5fc..b1b4f1301 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -30,9 +30,4 @@ public function getVerificationUri(); * @param string $verificationUri */ public function setVerificationUri($verificationUri); - - /** - * Generate a string representation of the access token. - */ - public function __toString(); } diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index 69033a298..c772988cf 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Alex Bilbie + * @author Luca Degasperi + * @copyright Copyright (c) Luca Degasperi * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server @@ -10,21 +10,11 @@ namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; -use Lcobucci\JWT\Builder; -use Lcobucci\JWT\Signer\Key; -use Lcobucci\JWT\Signer\Rsa\Sha256; -use Lcobucci\JWT\Token; -use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; trait DeviceCodeTrait { - /** - * @var CryptKey - */ - private $privateKey; - /** * @var string */ @@ -36,57 +26,38 @@ trait DeviceCodeTrait private $verificationUri; /** - * Set the private key used to encrypt this access token. + * @return string */ - public function setPrivateKey(CryptKey $privateKey) + public function getUserCode() { - $this->privateKey = $privateKey; + return $this->userCode; } /** - * Generate a JWT from the access token - * - * @param CryptKey $privateKey + * @param string $userCode * - * @return Token + * @return string */ - private function convertToJWT(CryptKey $privateKey) + public function setUserCode($userCode) { - return (new Builder()) - ->permittedFor($this->getClient()->getIdentifier()) - ->identifiedBy($this->getIdentifier()) - ->issuedAt(\time()) - ->canOnlyBeUsedAfter(\time()) - ->expiresAt($this->getExpiryDateTime()->getTimestamp()) - ->relatedTo($this->getUserCode()) - ->withClaim('scopes', $this->getScopes()) - ->getToken(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase())); + $this->userCode = $userCode; } - /** - * Generate a string representation from the access token - */ - public function __toString() - { - return (string) $this->convertToJWT($this->privateKey); - } /** * @return string */ - public function getUserCode() + public function getVerificationUri() { - return $this->userCode; + return $this->verificationUri; } /** - * @param string $userCode - * - * @return string + * @param string $verificationUri */ - public function setUserCode($userCode) + public function setVerificationUri($verificationUri) { - $this->userCode = $userCode; + $this->verificationUri = $verificationUri; } /** @@ -108,20 +79,4 @@ abstract public function getScopes(); * @return string */ abstract public function getIdentifier(); - - /** - * @return string - */ - public function getVerificationUri() - { - return $this->verificationUri; - } - - /** - * @param string $verificationUri - */ - public function setVerificationUri($verificationUri) - { - $this->verificationUri = $verificationUri; - } } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index acfb13342..7e6b7e6ba 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -106,8 +106,24 @@ public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $d $deviceRequest->getScopes() ); + $payload = [ + 'client_id' => $deviceCode->getClient()->getIdentifier(), + 'device_code_id' => $deviceCode->getIdentifier(), + 'scopes' => $deviceCode->getScopes(), + 'user_code' => $deviceCode->getUserCode(), + 'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(), + 'verification_uri' => $deviceCode->getVerificationUri() + ]; + + $jsonPayload = \json_encode($payload); + + if ($jsonPayload === false) { + throw new LogicException('An error was encountered when JSON encoding the authorization request response'); + } + $response = new DeviceCodeResponse(); $response->setDeviceCode($deviceCode); + $response->setPayload($this->encrypt($jsonPayload)); return $response; } diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 8d648a1a8..192f52aae 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -14,7 +14,6 @@ use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; abstract class AbstractResponseType implements ResponseTypeInterface @@ -31,11 +30,6 @@ abstract class AbstractResponseType implements ResponseTypeInterface */ protected $refreshToken; - /** - * @var DeviceCodeEntityInterface - */ - protected $deviceCode; - /** * @var CryptKey */ @@ -57,14 +51,6 @@ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) $this->refreshToken = $refreshToken; } - /** - * {@inheritdoc} - */ - public function setDeviceCode(DeviceCodeEntityInterface $deviceCode) - { - $this->deviceCode = $deviceCode; - } - /** * Set the private key * diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 0f847fb88..95d63a04a 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -17,6 +17,16 @@ class DeviceCodeResponse extends AbstractResponseType { + /** + * @var DeviceCodeEntityInterface + */ + protected $deviceCode; + + /** + * @var string + */ + protected $payload; + /** * {@inheritdoc} */ @@ -26,7 +36,7 @@ public function generateHttpResponse(ResponseInterface $response) $responseParams = [ 'expires_in' => $expireDateTime - \time(), - 'device_code' => (string) $this->deviceCode, + 'device_code' => $this->payload, 'user_code' => $this->deviceCode->getUserCode(), 'verification_uri' => $this->deviceCode->getVerificationUri() ]; @@ -48,6 +58,19 @@ public function generateHttpResponse(ResponseInterface $response) return $response; } + public function setPayload($payload) + { + $this->payload = $payload; + } + + /** + * {@inheritdoc} + */ + public function setDeviceCode(DeviceCodeEntityInterface $deviceCode) + { + $this->deviceCode = $deviceCode; + } + /** * Add custom fields to your Bearer Token response here, then override * AuthorizationServer::getResponseType() to pull in your version of diff --git a/src/ResponseTypes/ResponseTypeInterface.php b/src/ResponseTypes/ResponseTypeInterface.php index 152282562..5eddd6079 100644 --- a/src/ResponseTypes/ResponseTypeInterface.php +++ b/src/ResponseTypes/ResponseTypeInterface.php @@ -13,7 +13,6 @@ use Defuse\Crypto\Key; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use Psr\Http\Message\ResponseInterface; @@ -29,11 +28,6 @@ public function setAccessToken(AccessTokenEntityInterface $accessToken); */ public function setRefreshToken(RefreshTokenEntityInterface $refreshToken); - /** - * @param DeviceCodeEntityInterface $deviceCode - */ - public function setDeviceCode(DeviceCodeEntityInterface $deviceCode); - /** * @param ResponseInterface $response * diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index 3ed13e607..0677a9ee4 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -33,10 +33,10 @@ public function testGenerateHttpResponse() $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $deviceCode->setClient($client); $deviceCode->addScope($scope); - $deviceCode->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $responseType->setDeviceCode($deviceCode); + $responseType->setPayload('test'); $response = $responseType->generateHttpResponse(new Response()); @@ -50,6 +50,7 @@ public function testGenerateHttpResponse() $json = \json_decode($response->getBody()->getContents()); $this->assertObjectHasAttribute('expires_in', $json); $this->assertObjectHasAttribute('device_code', $json); + $this->assertEquals('test', $json->device_code); $this->assertObjectHasAttribute('verification_uri', $json); $this->assertObjectHasAttribute('user_code', $json); } From 080c37d794db1a84129c1ce1f86b22db1672efc4 Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Tue, 26 Nov 2019 22:12:33 +0100 Subject: [PATCH 014/212] added verification uri --- src/Grant/DeviceCodeGrant.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 7e6b7e6ba..df15cc1b4 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -38,6 +38,11 @@ class DeviceCodeGrant extends AbstractGrant */ private $retryInterval; + /** + * @var string + */ + private $verificationUri; + /** * @param DeviceCodeRepositoryInterface $deviceCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository @@ -102,7 +107,7 @@ public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $d $deviceCode = $this->issueDeviceCode( $this->deviceCodeTTL, $deviceRequest->getClient(), - 'verification_uri', + $this->verificationUri, $deviceRequest->getScopes() ); @@ -224,6 +229,16 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt return $deviceCode; } + /** + * Set the verification uri + * + * @param $verificationUri + */ + public function setVerificationUri($verificationUri) + { + $this->verificationUri = $verificationUri; + } + /** * {@inheritdoc} */ From 25454751dd403adc507b3612b0ee38ed69437d0a Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Thu, 28 Nov 2019 10:28:16 +0100 Subject: [PATCH 015/212] Fix Authorship --- src/Entities/DeviceCodeEntityInterface.php | 4 ++-- src/Entities/Traits/DeviceCodeTrait.php | 4 ++-- src/Grant/DeviceCodeGrant.php | 4 ++-- src/Repositories/DeviceCodeRepositoryInterface.php | 4 ++-- src/RequestTypes/DeviceAuthorizationRequest.php | 4 ++-- src/ResponseTypes/DeviceCodeResponse.php | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index b1b4f1301..b0027320d 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Luca Degasperi + * @author Alex Bilbie + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index c772988cf..8958f9f85 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Luca Degasperi + * @author Alex Bilbie + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index df15cc1b4..3bde3e081 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -2,8 +2,8 @@ /** * OAuth 2.0 Device Code grant. * - * @author Luca Degasperi - * @copyright Copyright (c) Luca Degasperi + * @author Alex Bilbie + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 23ef28d89..64055dd7b 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Luca Degasperi + * @author Alex Bilbie + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server diff --git a/src/RequestTypes/DeviceAuthorizationRequest.php b/src/RequestTypes/DeviceAuthorizationRequest.php index 00b2fb2b2..00bf818b4 100644 --- a/src/RequestTypes/DeviceAuthorizationRequest.php +++ b/src/RequestTypes/DeviceAuthorizationRequest.php @@ -1,7 +1,7 @@ - * @copyright Copyright (c) Luca Degasperi + * @author Alex Bilbie + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 95d63a04a..3ead086d8 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -2,8 +2,8 @@ /** * OAuth 2.0 Bearer Token Response. * - * @author Luca Degasperi - * @copyright Copyright (c) Luca Degasperi + * @author Alex Bilbie + * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server From b5da0d28cc929ed55ab0d765fd8cab97412b97b7 Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Thu, 28 Nov 2019 10:50:25 +0100 Subject: [PATCH 016/212] Stycle CI fixes --- src/AuthorizationServer.php | 5 ++--- src/Entities/Traits/DeviceCodeTrait.php | 1 - src/Exception/OAuthServerException.php | 2 +- src/Grant/AbstractGrant.php | 15 ++++++++------- src/Grant/DeviceCodeGrant.php | 11 +++++------ src/Grant/GrantTypeInterface.php | 3 +-- .../DeviceCodeRepositoryInterface.php | 4 ++-- src/ResponseTypes/DeviceCodeResponse.php | 2 +- tests/Grant/DeviceCodeGrantTest.php | 16 ++++++---------- 9 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index a835186a5..ab8e4f018 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -183,8 +183,8 @@ public function completeAuthorizationRequest(AuthorizationRequest $authRequest, * @param ServerRequestInterface $request * * @return DeviceAuthorizationRequest - * @throws OAuthServerException * + * @throws OAuthServerException */ public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) { @@ -201,10 +201,9 @@ public function validateDeviceAuthorizationRequest(ServerRequestInterface $reque * Complete a device authorization request * * @param DeviceAuthorizationRequest $deviceRequest - * @param ResponseInterface $response + * @param ResponseInterface $response * * @return ResponseInterface - * */ public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest, ResponseInterface $response) { diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index 8958f9f85..b7614a3bb 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -43,7 +43,6 @@ public function setUserCode($userCode) $this->userCode = $userCode; } - /** * @return string */ diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 203958505..0b50b74ef 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -275,7 +275,7 @@ public static function invalidGrant($hint = '') * Expired token error. * * @param null|string $hint - * @param Throwable $previous Previous exception + * @param Throwable $previous Previous exception * * @return static */ diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 1fe911b78..ca8dbcd41 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -519,14 +519,14 @@ protected function issueAuthCode( /** * Issue a device code. * - * @param DateInterval $deviceCodeTTL - * @param ClientEntityInterface $client - * @param string $verificationUri + * @param DateInterval $deviceCodeTTL + * @param ClientEntityInterface $client + * @param string $verificationUri * @param ScopeEntityInterface[] $scopes * * @return DeviceCodeEntityInterface - * @throws OAuthServerException * + * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException */ protected function issueDeviceCode( @@ -627,16 +627,17 @@ protected function generateUniqueIdentifier($length = 40) * @param int $length * * @return string - * @throws OAuthServerException * + * @throws OAuthServerException */ protected function generateUniqueUserCode($length = 8) { try { $userCode = ''; - while(strlen($userCode) < $length) { - $userCode .= (string)\random_int(0, 9); + while (\strlen($userCode) < $length) { + $userCode .= (string) \random_int(0, 9); } + return $userCode; // @codeCoverageIgnoreStart } catch (TypeError $e) { diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 3bde3e081..c619def1c 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -15,8 +15,8 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; -use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; +use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; @@ -44,10 +44,10 @@ class DeviceCodeGrant extends AbstractGrant private $verificationUri; /** - * @param DeviceCodeRepositoryInterface $deviceCodeRepository + * @param DeviceCodeRepositoryInterface $deviceCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param DateInterval $deviceCodeTTL - * @param int $retryInterval + * @param DateInterval $deviceCodeTTL + * @param int $retryInterval */ public function __construct( DeviceCodeRepositoryInterface $deviceCodeRepository, @@ -117,7 +117,7 @@ public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $d 'scopes' => $deviceCode->getScopes(), 'user_code' => $deviceCode->getUserCode(), 'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(), - 'verification_uri' => $deviceCode->getVerificationUri() + 'verification_uri' => $deviceCode->getVerificationUri(), ]; $jsonPayload = \json_encode($payload); @@ -209,7 +209,6 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt if ($deviceCodePayload->client_id !== $client->getIdentifier()) { throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client'); } - } catch (\LogicException $e) { throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e); } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 613df39b9..964bfaa8e 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -15,7 +15,6 @@ use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -102,11 +101,11 @@ public function completeAuthorizationRequest(AuthorizationRequest $authorization */ public function canRespondToAccessTokenRequest(ServerRequestInterface $request); - /** * The grant type should return true if it is able to response to a device authorization request * * @param ServerRequestInterface $request + * * @return bool */ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request); diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 64055dd7b..856d3a6d0 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -34,8 +34,8 @@ public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity /** * Get a device code entity. * - * @param string $deviceCode - * @param string $grantType + * @param string $deviceCode + * @param string $grantType * @param ClientEntityInterface $clientEntity * * @return DeviceCodeEntityInterface|null diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 3ead086d8..98192b9e1 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -38,7 +38,7 @@ public function generateHttpResponse(ResponseInterface $response) 'expires_in' => $expireDateTime - \time(), 'device_code' => $this->payload, 'user_code' => $this->deviceCode->getUserCode(), - 'verification_uri' => $this->deviceCode->getVerificationUri() + 'verification_uri' => $this->deviceCode->getVerificationUri(), ]; $responseParams = \json_encode($responseParams); diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index cebbe6bbe..f16798daf 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -7,13 +7,11 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Grant\DeviceCodeGrant; -use League\OAuth2\Server\Grant\PasswordGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use LeagueTests\Stubs\AccessTokenEntity; @@ -23,7 +21,6 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; -use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -60,7 +57,7 @@ public function testCanRespondToDeviceAuthorizationRequest() $request = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', - 'scope' => 'basic' + 'scope' => 'basic', ]); $this->assertTrue($grant->canRespondToDeviceAuthorizationRequest($request)); @@ -89,7 +86,7 @@ public function testValidateDeviceAuthorizationRequest() $request = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', - 'scope' => 'basic' + 'scope' => 'basic', ]); $this->assertInstanceOf(DeviceAuthorizationRequest::class, $grant->validateDeviceAuthorizationRequest($request)); @@ -117,7 +114,7 @@ public function testValidateDeviceAuthorizationRequestMissingClient() $grant->setDefaultScope(self::DEFAULT_SCOPE); $request = (new ServerRequest())->withParsedBody([ - 'scope' => 'basic' + 'scope' => 'basic', ]); $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); @@ -145,7 +142,7 @@ public function testValidateDeviceAuthorizationRequestClientMismatch() $request = (new ServerRequest())->withParsedBody([ 'client_id' => 'bar', - 'scope' => 'basic' + 'scope' => 'basic', ]); $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); @@ -218,7 +215,7 @@ public function testRespondToRequest() 'verification_uri' => 'http://foo/bar', ] ) - ) + ), ]); $responseType = new StubResponseType(); @@ -267,7 +264,6 @@ public function testRespondToRequestMissingClient() public function testRespondToRequestMissingDeviceCode() { - $client = new ClientEntity(); $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -295,7 +291,7 @@ public function testRespondToRequestMissingDeviceCode() $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ - 'client_id' => 'foo' + 'client_id' => 'foo', ]); $responseType = new StubResponseType(); From f4a8e79ed73a5d4c6c13a0741015d0bc1e361143 Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Thu, 28 Nov 2019 11:20:44 +0100 Subject: [PATCH 017/212] phpstan fixes --- src/Grant/DeviceCodeGrant.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index c619def1c..4249cee7f 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -21,6 +21,7 @@ use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use LogicException; use Psr\Http\Message\ServerRequestInterface; /** @@ -231,7 +232,7 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt /** * Set the verification uri * - * @param $verificationUri + * @param string $verificationUri */ public function setVerificationUri($verificationUri) { From a0749a5a823e1e6075167da46688773f2b4d9680 Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Thu, 28 Nov 2019 11:47:47 +0100 Subject: [PATCH 018/212] more phpstan fixes --- src/Grant/DeviceCodeGrant.php | 49 +++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 4249cee7f..c86ced34f 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -156,10 +156,10 @@ public function respondToAccessTokenRequest( } // Finalize the requested scopes - $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $deviceCode->getUserIdentifier()); + $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, (string) $deviceCode->getUserIdentifier()); // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $deviceCode->getUserIdentifier(), $finalizedScopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, (string) $deviceCode->getUserIdentifier(), $finalizedScopes); $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $responseType->setAccessToken($accessToken); @@ -191,27 +191,23 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt if (\is_null($encryptedDeviceCode)) { throw OAuthServerException::invalidRequest('device_code'); } + + $deviceCodePayload = $this->decodeDeviceCode($encryptedDeviceCode); - try { - $deviceCodePayload = \json_decode($this->decrypt($encryptedDeviceCode)); - - if (!\property_exists($deviceCodePayload, 'device_code_id')) { - throw OAuthServerException::invalidRequest('device_code', 'Device code malformed'); - } + if (!\property_exists($deviceCodePayload, 'device_code_id')) { + throw OAuthServerException::invalidRequest('device_code', 'Device code malformed'); + } - if (\time() > $deviceCodePayload->expire_time) { - throw OAuthServerException::expiredToken('device_code'); - } + if (\time() > $deviceCodePayload->expire_time) { + throw OAuthServerException::expiredToken('device_code'); + } - if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) { - throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked'); - } + if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) { + throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked'); + } - if ($deviceCodePayload->client_id !== $client->getIdentifier()) { - throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client'); - } - } catch (\LogicException $e) { - throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e); + if ($deviceCodePayload->client_id !== $client->getIdentifier()) { + throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client'); } $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( @@ -229,6 +225,21 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt return $deviceCode; } + /** + * @param string $encryptedDeviceCode + * + * @throws OAuthServerException + * + * @return \stdClass + */ + protected function decodeDeviceCode($encryptedDeviceCode) { + try { + return \json_decode($this->decrypt($encryptedDeviceCode)); + } catch (LogicException $e) { + throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e); + } + } + /** * Set the verification uri * From 699ddaeb5e5a9b599624da920d043cbb2d9a959f Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Thu, 28 Nov 2019 11:53:17 +0100 Subject: [PATCH 019/212] fix --- src/Grant/DeviceCodeGrant.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index c86ced34f..b49605357 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -232,7 +232,8 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt * * @return \stdClass */ - protected function decodeDeviceCode($encryptedDeviceCode) { + protected function decodeDeviceCode($encryptedDeviceCode) + { try { return \json_decode($this->decrypt($encryptedDeviceCode)); } catch (LogicException $e) { From f44acded04a8014673792bf8ec85e9a2ff308e79 Mon Sep 17 00:00:00 2001 From: Luca Degasperi Date: Thu, 28 Nov 2019 11:54:57 +0100 Subject: [PATCH 020/212] last fix --- src/Grant/DeviceCodeGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index b49605357..0dd6d1623 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -191,7 +191,7 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt if (\is_null($encryptedDeviceCode)) { throw OAuthServerException::invalidRequest('device_code'); } - + $deviceCodePayload = $this->decodeDeviceCode($encryptedDeviceCode); if (!\property_exists($deviceCodePayload, 'device_code_id')) { From 5fee7e447e7b057e0fcbb91ff62281b62c345db8 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 21:10:14 +0000 Subject: [PATCH 021/212] Update examples to include device code grant --- examples/README.md | 29 +++++ examples/public/device_code.php | 104 ++++++++++++++++++ examples/src/Entities/DeviceCodeEntity.php | 20 ++++ .../src/Repositories/DeviceCodeRepository.php | 63 +++++++++++ src/Grant/DeviceCodeGrant.php | 2 +- 5 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 examples/public/device_code.php create mode 100644 examples/src/Entities/DeviceCodeEntity.php create mode 100644 examples/src/Repositories/DeviceCodeRepository.php diff --git a/examples/README.md b/examples/README.md index d8898b46c..8213e61c0 100644 --- a/examples/README.md +++ b/examples/README.md @@ -51,3 +51,32 @@ curl -X "POST" "http://localhost:4444/refresh_token.php/access_token" \ --data-urlencode "client_secret=abc123" \ --data-urlencode "refresh_token={{REFRESH_TOKEN}}" ``` + +## Testing the device authorization grant example + +Send the following cURL request. This will return a device code which can be exchanged for an access token. + +``` +curl -X "POST" "http://localhost:4444/device_code.php/device_authorization" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -H "Accept: 1.0" \ + --data-urlencode "client_id=myawesomeapp" \ + --data-urlencode "client_secret=abc123" \ + --data-urlencode "scope=basic email" +``` + +We have set up the example so that a user ID is already associated with the device code. In a production application you +would implement an authorization view to allow a user to authorize the device. + +Issue the following cURL request to exchange your device code for an access token. Replace `{{DEVICE_CODE}}` with the +device code returned from your first cURL post: + +``` +curl -X "POST" "http://localhost:4444/device_code.php/access_token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -H "Accept: 1.0" \ + --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \ + --data-urlencode "device_code={{DEVICE_CODE}}" \ + --data-urlencode "client_id=myawesomeapp" \ + --data-urlencode "client_secret=abc123" +``` \ No newline at end of file diff --git a/examples/public/device_code.php b/examples/public/device_code.php new file mode 100644 index 000000000..ef45b64cb --- /dev/null +++ b/examples/public/device_code.php @@ -0,0 +1,104 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +include __DIR__ . '/../vendor/autoload.php'; + +use League\OAuth2\Server\AuthorizationServer; +use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Grant\DeviceCodeGrant; +use OAuth2ServerExamples\Repositories\AccessTokenRepository; +use OAuth2ServerExamples\Repositories\ClientRepository; +use OAuth2ServerExamples\Repositories\DeviceCodeRepository; +use OAuth2ServerExamples\Repositories\RefreshTokenRepository; +use OAuth2ServerExamples\Repositories\ScopeRepository; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; +use Slim\App; +use Zend\Diactoros\Stream; + +$app = new App([ + 'settings' => [ + 'displayErrorDetails' => true, + ], + AuthorizationServer::class => function () { + // Init our repositories + $clientRepository = new ClientRepository(); + $scopeRepository = new ScopeRepository(); + $accessTokenRepository = new AccessTokenRepository(); + $refreshTokenRepository = new RefreshTokenRepository(); + $deviceCodeRepository = new DeviceCodeRepository(); + + $privateKeyPath = 'file://' . __DIR__ . '/../private.key'; + + // Set up the authorization server + $server = new AuthorizationServer( + $clientRepository, + $accessTokenRepository, + $scopeRepository, + $privateKeyPath, + 'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen' + ); + + // Enable the device code grant on the server with a token TTL of 1 hour + $server->enableGrantType( + new DeviceCodeGrant( + $deviceCodeRepository, + $refreshTokenRepository, + new \DateInterval('PT10M'), + 5 + ), + new \DateInterval('PT1H') + ); + + return $server; + }, +]); + +$app->post('/device_authorization', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) { + /* @var \League\OAuth2\Server\AuthorizationServer $server */ + $server = $app->getContainer()->get(AuthorizationServer::class); + + try { + $deviceAuthRequest = $server->validateDeviceAuthorizationRequest($request); + + // Once the user has logged in, set the user on the authorization request + //$deviceAuthRequest->setUser(); + + // Once the user has approved or denied the client, update the status + //$deviceAuthRequest->setAuthorizationApproved(true); + + // Return the HTTP redirect response + return $server->completeDeviceAuthorizationRequest($deviceAuthRequest, $response); + } catch (OAuthServerException $exception) { + return $exception->generateHttpResponse($response); + } catch (\Exception $exception) { + $body = new Stream('php://temp', 'r+'); + $body->write($exception->getMessage()); + + return $response->withStatus(500)->withBody($body); + } +}); + +$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) { + /* @var \League\OAuth2\Server\AuthorizationServer $server */ + $server = $app->getContainer()->get(AuthorizationServer::class); + + try { + return $server->respondToAccessTokenRequest($request, $response); + } catch (OAuthServerException $exception) { + return $exception->generateHttpResponse($response); + } catch (\Exception $exception) { + $body = new Stream('php://temp', 'r+'); + $body->write($exception->getMessage()); + + return $response->withStatus(500)->withBody($body); + } +}); + +$app->run(); diff --git a/examples/src/Entities/DeviceCodeEntity.php b/examples/src/Entities/DeviceCodeEntity.php new file mode 100644 index 000000000..92665b421 --- /dev/null +++ b/examples/src/Entities/DeviceCodeEntity.php @@ -0,0 +1,20 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace OAuth2ServerExamples\Entities; + +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; +use League\OAuth2\Server\Entities\Traits\DeviceCodeTrait; +use League\OAuth2\Server\Entities\Traits\EntityTrait; +use League\OAuth2\Server\Entities\Traits\TokenEntityTrait; + +class DeviceCodeEntity implements DeviceCodeEntityInterface +{ + use EntityTrait, DeviceCodeTrait, TokenEntityTrait; +} diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php new file mode 100644 index 000000000..a9b941d10 --- /dev/null +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -0,0 +1,63 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace OAuth2ServerExamples\Repositories; + +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; +use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; +use OAuth2ServerExamples\Entities\DeviceCodeEntity; + +class DeviceCodeRepository implements DeviceCodeRepositoryInterface +{ + /** + * {@inheritDoc} + */ + public function getNewDeviceCode() + { + return new DeviceCodeEntity(); + } + + /** + * {@inheritDoc} + */ + public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) + { + // Some logic to persist a new device code to a database + } + + /** + * {@inheritDoc} + */ + public function getDeviceCodeEntityByDeviceCode($deviceCode, $grantType, ClientEntityInterface $clientEntity) + { + $deviceCode = new DeviceCodeEntity(); + + // The user identifier should be set when the user authenticates on the OAuth server + $deviceCode->setUserIdentifier(1); + + return $deviceCode; + } + + /** + * {@inheritDoc} + */ + public function revokeDeviceCode($codeId) + { + // Some logic to revoke device code + } + + /** + * {@inheritDoc} + */ + public function isDeviceCodeRevoked($codeId) + { + // Some logic to check if a device code has been revoked + } +} diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 0dd6d1623..07313d301 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -256,6 +256,6 @@ public function setVerificationUri($verificationUri) */ public function getIdentifier() { - return 'device_code'; + return 'urn:ietf:params:oauth:grant-type:device_code'; } } From 925944f5112e80222e2da0769168b79a71961f8c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 21:40:22 +0000 Subject: [PATCH 022/212] Fix grant identifier in test --- tests/Grant/DeviceCodeGrantTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index f16798daf..e121ff0cb 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -44,7 +44,7 @@ public function testGetIdentifier() $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M'), 5); - $this->assertEquals('device_code', $grant->getIdentifier()); + $this->assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); } public function testCanRespondToDeviceAuthorizationRequest() From 3d8749672640f9a24e9fa302be78c41566b518a1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 21:43:01 +0000 Subject: [PATCH 023/212] Add device code RFC to readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 92a12f3ef..cc54b3271 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,11 @@ Out of the box it supports the following grants: * Authorization code grant -* Implicit grant * Client credentials grant -* Resource owner password credentials grant +* Device authorization grant +* Implicit grant * Refresh grant +* Resource owner password credentials grant The following RFCs are implemented: @@ -24,6 +25,7 @@ The following RFCs are implemented: * [RFC6750 " The OAuth 2.0 Authorization Framework: Bearer Token Usage"](https://tools.ietf.org/html/rfc6750) * [RFC7519 "JSON Web Token (JWT)"](https://tools.ietf.org/html/rfc7519) * [RFC7636 "Proof Key for Code Exchange by OAuth Public Clients"](https://tools.ietf.org/html/rfc7636) +* [RFC8628 "OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628) This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](https://twitter.com/alexbilbie). From 4270a57aef5dfda8c8f9b1a86aad723d78ca349b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 22:11:43 +0000 Subject: [PATCH 024/212] Move device code specific code to grant --- src/Grant/AbstractGrant.php | 60 ---------------------------------- src/Grant/DeviceCodeGrant.php | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 60 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index ca8dbcd41..000257cee 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -20,7 +20,6 @@ use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; -use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -28,7 +27,6 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; @@ -70,11 +68,6 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected $authCodeRepository; - /** - * @var DeviceCodeRepositoryInterface - */ - protected $deviceCodeRepository; - /** * @var RefreshTokenRepositoryInterface */ @@ -140,14 +133,6 @@ public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepos $this->authCodeRepository = $authCodeRepository; } - /** - * @param DeviceCodeRepositoryInterface $deviceCodeRepository - */ - public function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository) - { - $this->deviceCodeRepository = $deviceCodeRepository; - } - /** * @param UserRepositoryInterface $userRepository */ @@ -516,51 +501,6 @@ protected function issueAuthCode( } } - /** - * Issue a device code. - * - * @param DateInterval $deviceCodeTTL - * @param ClientEntityInterface $client - * @param string $verificationUri - * @param ScopeEntityInterface[] $scopes - * - * @return DeviceCodeEntityInterface - * - * @throws OAuthServerException - * @throws UniqueTokenIdentifierConstraintViolationException - */ - protected function issueDeviceCode( - DateInterval $deviceCodeTTL, - ClientEntityInterface $client, - $verificationUri, - array $scopes = [] - ) { - $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; - - $deviceCode = $this->deviceCodeRepository->getNewDeviceCode(); - $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add($deviceCodeTTL)); - $deviceCode->setClient($client); - $deviceCode->setVerificationUri($verificationUri); - - foreach ($scopes as $scope) { - $deviceCode->addScope($scope); - } - - while ($maxGenerationAttempts-- > 0) { - $deviceCode->setIdentifier($this->generateUniqueIdentifier()); - $deviceCode->setUserCode($this->generateUniqueUserCode()); - try { - $this->deviceCodeRepository->persistNewDeviceCode($deviceCode); - - return $deviceCode; - } catch (UniqueTokenIdentifierConstraintViolationException $e) { - if ($maxGenerationAttempts === 0) { - throw $e; - } - } - } - } - /** * @param AccessTokenEntityInterface $accessToken * diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 07313d301..1b63a7801 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -12,6 +12,7 @@ namespace League\OAuth2\Server\Grant; use DateInterval; +use DateTimeImmutable; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -29,6 +30,11 @@ */ class DeviceCodeGrant extends AbstractGrant { + /** + * @var DeviceCodeRepositoryInterface + */ + protected $deviceCodeRepository; + /** * @var DateInterval */ @@ -258,4 +264,59 @@ public function getIdentifier() { return 'urn:ietf:params:oauth:grant-type:device_code'; } + + /** + * @param DeviceCodeRepositoryInterface $deviceCodeRepository + */ + public function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository) + { + $this->deviceCodeRepository = $deviceCodeRepository; + } + + /** + * Issue a device code. + * + * @param DateInterval $deviceCodeTTL + * @param ClientEntityInterface $client + * @param string $verificationUri + * @param ScopeEntityInterface[] $scopes + * + * @return DeviceCodeEntityInterface + * + * @throws OAuthServerException + * @throws UniqueTokenIdentifierConstraintViolationException + */ + protected function issueDeviceCode( + DateInterval $deviceCodeTTL, + ClientEntityInterface $client, + $verificationUri, + array $scopes = [] + ) { + $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; + + $deviceCode = $this->deviceCodeRepository->getNewDeviceCode(); + $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add($deviceCodeTTL)); + $deviceCode->setClient($client); + $deviceCode->setVerificationUri($verificationUri); + + foreach ($scopes as $scope) { + $deviceCode->addScope($scope); + } + + while ($maxGenerationAttempts-- > 0) { + $deviceCode->setIdentifier($this->generateUniqueIdentifier()); + $deviceCode->setUserCode($this->generateUniqueUserCode()); + try { + $this->deviceCodeRepository->persistNewDeviceCode($deviceCode); + + return $deviceCode; + } catch (UniqueTokenIdentifierConstraintViolationException $e) { + if ($maxGenerationAttempts === 0) { + throw $e; + } + } + } + } + + } From 481994686acae50d13a6796c7b6bb405eac2aaf1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 22:29:54 +0000 Subject: [PATCH 025/212] Move generateUniqueUserCode to Device Code Grant --- src/Grant/AbstractGrant.php | 30 ------------------------------ src/Grant/DeviceCodeGrant.php | 31 +++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 000257cee..dc5460ea3 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -561,36 +561,6 @@ protected function generateUniqueIdentifier($length = 40) // @codeCoverageIgnoreEnd } - /** - * Generate a new unique user code. - * - * @param int $length - * - * @return string - * - * @throws OAuthServerException - */ - protected function generateUniqueUserCode($length = 8) - { - try { - $userCode = ''; - while (\strlen($userCode) < $length) { - $userCode .= (string) \random_int(0, 9); - } - - return $userCode; - // @codeCoverageIgnoreStart - } catch (TypeError $e) { - throw OAuthServerException::serverError('An unexpected error has occurred', $e); - } catch (Error $e) { - throw OAuthServerException::serverError('An unexpected error has occurred', $e); - } catch (Exception $e) { - // If you get this message, the CSPRNG failed hard. - throw OAuthServerException::serverError('Could not generate a random string', $e); - } - // @codeCoverageIgnoreEnd - } - /** * {@inheritdoc} */ diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 1b63a7801..4f43bcb52 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -30,6 +30,8 @@ */ class DeviceCodeGrant extends AbstractGrant { + const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; + /** * @var DeviceCodeRepositoryInterface */ @@ -318,5 +320,34 @@ protected function issueDeviceCode( } } + /** + * Generate a new unique user code. + * + * @param int $length + * + * @return string + * + * @throws OAuthServerException + */ + protected function generateUniqueUserCode($length = 8) + { + try { + $userCode = ''; + + while (\strlen($userCode) < $length) { + $userCode .= (string) \random_int(0, 9); + } + return $userCode; + // @codeCoverageIgnoreStart + } catch (TypeError $e) { + throw OAuthServerException::serverError('An unexpected error has occurred', $e); + } catch (Error $e) { + throw OAuthServerException::serverError('An unexpected error has occurred', $e); + } catch (Exception $e) { + // If you get this message, the CSPRNG failed hard. + throw OAuthServerException::serverError('Could not generate a random string', $e); + } + // @codeCoverageIgnoreEnd + } } From a1246f2bb13aa361fff80ddfbd471b1e0a16a28c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 22:38:58 +0000 Subject: [PATCH 026/212] Change user code to be alpha based --- src/Grant/DeviceCodeGrant.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 4f43bcb52..8e5bca427 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -333,9 +333,10 @@ protected function generateUniqueUserCode($length = 8) { try { $userCode = ''; + $userCodeCharacters = 'BCDFGHJKLMNPQRSTVWXZ'; while (\strlen($userCode) < $length) { - $userCode .= (string) \random_int(0, 9); + $userCode .= $userCodeCharacters[\random_int(0, 19)]; } return $userCode; From 854849c226bc128b20c85bea99e8a227e33bee0b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 23:02:49 +0000 Subject: [PATCH 027/212] Add missing use statements --- src/Grant/DeviceCodeGrant.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 8e5bca427..a3052b2d5 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -13,6 +13,8 @@ use DateInterval; use DateTimeImmutable; +use Error; +use Exception; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -24,14 +26,13 @@ use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; +use TypeError; /** * Device Code grant class. */ class DeviceCodeGrant extends AbstractGrant { - const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; - /** * @var DeviceCodeRepositoryInterface */ From 3ad3c1d1f0f760af54653b2402e815a1b022e92c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 19 Dec 2019 23:04:37 +0000 Subject: [PATCH 028/212] Make error message specific to device grant --- src/Grant/AbstractGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index dc5460ea3..7934ba0b2 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -611,7 +611,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r */ public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) { - throw new LogicException('This grant cannot validate an authorization request'); + throw new LogicException('This grant cannot validate a device authorization request'); } /** From dfde59608685080eb46b698442e9b53b4c8e2cf3 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 21 Dec 2019 16:23:42 +0000 Subject: [PATCH 029/212] Fix phpstan errors --- src/Grant/DeviceCodeGrant.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index a3052b2d5..23ebd9886 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -17,7 +17,9 @@ use Exception; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; +use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; From d3f0a791e4f11b2bbdc94b40d2e47b55d9bd01b8 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 21 Dec 2019 16:26:45 +0000 Subject: [PATCH 030/212] Fix styling issues --- examples/src/Repositories/DeviceCodeRepository.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index a9b941d10..f4df12795 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -17,7 +17,7 @@ class DeviceCodeRepository implements DeviceCodeRepositoryInterface { /** - * {@inheritDoc} + * {@inheritdoc} */ public function getNewDeviceCode() { @@ -25,7 +25,7 @@ public function getNewDeviceCode() } /** - * {@inheritDoc} + * {@inheritdoc} */ public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) { @@ -33,7 +33,7 @@ public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity } /** - * {@inheritDoc} + * {@inheritdoc} */ public function getDeviceCodeEntityByDeviceCode($deviceCode, $grantType, ClientEntityInterface $clientEntity) { @@ -46,7 +46,7 @@ public function getDeviceCodeEntityByDeviceCode($deviceCode, $grantType, ClientE } /** - * {@inheritDoc} + * {@inheritdoc} */ public function revokeDeviceCode($codeId) { @@ -54,7 +54,7 @@ public function revokeDeviceCode($codeId) } /** - * {@inheritDoc} + * {@inheritdoc} */ public function isDeviceCodeRevoked($codeId) { From 71693f3d225af039ce035f251978c8985e7a8231 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 23 Dec 2019 21:34:24 +0000 Subject: [PATCH 031/212] Add slow down response --- src/Exception/OAuthServerException.php | 19 +++++ src/Grant/DeviceCodeGrant.php | 35 +++++++-- tests/Grant/DeviceCodeGrantTest.php | 105 ++++++++++++++++++++++++- 3 files changed, 149 insertions(+), 10 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 0b50b74ef..325c813e5 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -305,6 +305,25 @@ public static function authorizationPending($hint = '') ); } + /** + * Slow down error used with the Device Authorization Grant. + * + * @param string $hint + * @param Throwable $previous Previous exception + * + * @return static + */ + public static function slowDown($hint = '') + { + return new static( + 'example message', + 13, + 'slow_down', + 400, + $hint + ); + } + /** * @return string */ diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 23ebd9886..6aeb0b537 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -21,6 +21,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; +use League\OAuth2\Server\Repositories\DeviceAuthorizationRequestRepository; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; @@ -40,6 +41,11 @@ class DeviceCodeGrant extends AbstractGrant */ protected $deviceCodeRepository; + /** + * @var DeviceAuthorizationRequestRepository + */ + protected $deviceAuthorizationRequestRepository; + /** * @var DateInterval */ @@ -56,18 +62,21 @@ class DeviceCodeGrant extends AbstractGrant private $verificationUri; /** - * @param DeviceCodeRepositoryInterface $deviceCodeRepository - * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param DateInterval $deviceCodeTTL - * @param int $retryInterval + * @param DeviceCodeRepositoryInterface $deviceCodeRepository + * @param DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, + * @param RefreshTokenRepositoryInterface $refreshTokenRepository + * @param DateInterval $deviceCodeTTL + * @param int $retryInterval */ public function __construct( DeviceCodeRepositoryInterface $deviceCodeRepository, + DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, DateInterval $deviceCodeTTL, $retryInterval = 5 ) { $this->setDeviceCodeRepository($deviceCodeRepository); + $this->setDeviceAuthorizationRequestRepository($deviceAuthorizationRequestRepository); $this->setRefreshTokenRepository($refreshTokenRepository); $this->refreshTokenTTL = new DateInterval('P1M'); @@ -158,8 +167,13 @@ public function respondToAccessTokenRequest( $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); $deviceCode = $this->validateDeviceCode($request, $client); - // TODO: if the request is too fast, respond with slow down + $lastRequest = $this->deviceAuthorizationRequestRepository->getLast($client->getIdentifier()); + + if ($lastRequest !== null && $lastRequest->getTimestamp() + $this->retryInterval > \time()) { + throw OAuthServerException::slowDown(); + } + $this->deviceAuthorizationRequestRepository->persist($client->getIdentifier()); // if device code has no user associated, respond with pending if (\is_null($deviceCode->getUserIdentifier())) { @@ -273,11 +287,20 @@ public function getIdentifier() /** * @param DeviceCodeRepositoryInterface $deviceCodeRepository */ - public function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository) + private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository) { $this->deviceCodeRepository = $deviceCodeRepository; } + /** + * @param DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository + */ + private function setDeviceAuthorizationRequestRepository( + DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository + ) { + $this->deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; + } + /** * Issue a device code. * diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index e121ff0cb..8ecc717d1 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -9,6 +9,7 @@ use League\OAuth2\Server\Grant\DeviceCodeGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; +use League\OAuth2\Server\Repositories\DeviceAuthorizationRequestRepository; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -41,9 +42,17 @@ public function setUp(): void public function testGetIdentifier() { $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); - $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M'), 5); + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $requestRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M'), + 5 + ); + $this->assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); } @@ -51,6 +60,7 @@ public function testCanRespondToDeviceAuthorizationRequest() { $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -77,6 +87,7 @@ public function testValidateDeviceAuthorizationRequest() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -106,6 +117,7 @@ public function testValidateDeviceAuthorizationRequestMissingClient() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -133,6 +145,7 @@ public function testValidateDeviceAuthorizationRequestClientMismatch() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -161,6 +174,7 @@ public function testCompleteDeviceAuthorizationRequest() $grant = new DeviceCodeGrant( $deviceCodeRepository, + $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -185,6 +199,8 @@ public function testRespondToRequest() $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); + $requestRepositoryMock->method('getLast')->willReturn(null); $deviceCodeEntity = new DeviceCodeEntity(); $deviceCodeEntity->setUserIdentifier('baz'); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); @@ -194,7 +210,13 @@ public function testRespondToRequest() $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M')); + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $requestRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -233,10 +255,17 @@ public function testRespondToRequestMissingClient() $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); - $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M')); + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $requestRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); @@ -274,6 +303,7 @@ public function testRespondToRequestMissingDeviceCode() $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); $deviceCodeEntity = new DeviceCodeEntity(); $deviceCodeEntity->setUserIdentifier('baz'); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); @@ -283,7 +313,13 @@ public function testRespondToRequestMissingDeviceCode() $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new DeviceCodeGrant($deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M')); + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $requestRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); @@ -296,7 +332,68 @@ public function testRespondToRequestMissingDeviceCode() $responseType = new StubResponseType(); + // TODO: We need to be more specific with this exception + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } + + public function testIssueSlowDownError() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); + $requestRepositoryMock->method('getLast')->willReturn(new \DateTimeImmutable()); + $deviceCodeEntity = new DeviceCodeEntity(); + $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $requestRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'device_code' => $this->cryptStub->doEncrypt( + \json_encode( + [ + 'device_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_code' => '12345678', + 'scopes' => ['foo'], + 'verification_uri' => 'http://foo/bar', + ] + ) + ), + ]); + + $responseType = new StubResponseType(); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(13); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } From a103953636e9e2d5cc070ba1ba9ad42b36ed8bfa Mon Sep 17 00:00:00 2001 From: Arie Timmerman Date: Mon, 27 Jan 2020 19:32:49 +0100 Subject: [PATCH 032/212] Return invalid_grant error when the authorization code is revoked --- src/Grant/AuthCodeGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 0020eb1d9..33711ef8f 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -200,7 +200,7 @@ private function validateAuthorizationCode( } if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) { - throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked'); + throw OAuthServerException::invalidGrant('Authorization code has been revoked'); } if ($authCodePayload->client_id !== $client->getIdentifier()) { From caedd1c9c8b0842c710427250928b30bc4a65455 Mon Sep 17 00:00:00 2001 From: Arie Timmerman Date: Tue, 28 Jan 2020 21:58:19 +0100 Subject: [PATCH 033/212] Added test --- tests/Grant/AuthCodeGrantTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 45c1e4914..051c4f267 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1174,6 +1174,7 @@ public function testRespondToAccessTokenRequestRevokedCode() $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Authorization code has been revoked'); + $this->assertEquals($e->getErrorType(), 'invalid_grant'); } } From bd05611cea41ecda88c22efbc3823a1b35a45a8d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 31 Jan 2020 22:20:52 +0000 Subject: [PATCH 034/212] Updating PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99441ce07..20f91b740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added support for PHP 7.4 (PR #1075) ### Fixed (v9) -- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #993) +- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) ### Changed - If an error is encountered when running `preg_match()` to validate an RSA key, the server will now throw a RuntimeException (PR #1047) From 492508973e9a31748c714bed1609b9ff83283bd4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 31 Jan 2020 22:32:15 +0000 Subject: [PATCH 035/212] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20f91b740..0e006dd4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added support for PHP 7.4 (PR #1075) ### Fixed (v9) -- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) +- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) ### Changed - If an error is encountered when running `preg_match()` to validate an RSA key, the server will now throw a RuntimeException (PR #1047) From 5657640b7440ab2254ff0caf69ddfacb730c3f18 Mon Sep 17 00:00:00 2001 From: Paul Jay Date: Thu, 20 Feb 2020 09:18:17 +0100 Subject: [PATCH 036/212] RefreshTokenGrant calls finalizeScopes method --- src/Grant/RefreshTokenGrant.php | 2 + tests/Grant/RefreshTokenGrantTest.php | 73 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index b6302bca5..a1985bc0c 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -59,6 +59,8 @@ public function respondToAccessTokenRequest( } } + $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client); + // Expire old tokens $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index cd71544f3..47a7fc8a5 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -51,6 +51,7 @@ public function testRespondToRequest() $scopeEntity->setIdentifier('foo'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -107,6 +108,7 @@ public function testRespondToRequestNullRefreshToken() $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -169,6 +171,7 @@ public function testRespondToReducedScopes() $scope->setIdentifier('foo'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scope]); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); @@ -450,4 +453,74 @@ public function testRespondToRequestRevokedToken() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } + + public function testRespondToRequestFinalizeScopes() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $fooScopeEntity = new ScopeEntity(); + $fooScopeEntity->setIdentifier('foo'); + + $barScopeEntity = new ScopeEntity(); + $barScopeEntity->setIdentifier('bar'); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($fooScopeEntity, $barScopeEntity); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + $refreshTokenRepositoryMock->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')); + + + $scopes = [$fooScopeEntity, $barScopeEntity]; + $finalizedScopes = [$fooScopeEntity]; + + $scopeRepositoryMock + ->expects($this->once()) + ->method('finalizeScopes') + ->with($scopes, $grant->getIdentifier(), $client) + ->willReturn($finalizedScopes); + + $accessTokenRepositoryMock + ->method('getNewToken') + ->with($client, $finalizedScopes) + ->willReturn(new AccessTokenEntity()); + + $oldRefreshToken = $this->cryptStub->doEncrypt( + \json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo', 'bar'], + 'user_id' => 123, + 'expire_time' => \time() + 3600, + ] + ) + ); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + 'scope' => ['foo', 'bar'], + ]); + + $responseType = new StubResponseType(); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } } From b879026e921de00e1101ce9dba17a0bbd7d5e2fe Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 25 Feb 2020 22:02:35 +0000 Subject: [PATCH 037/212] Add a hint when the user authentication fails. Fixes issue #1097 --- src/Grant/PasswordGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 7579fd0d2..1fbac44d8 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -104,7 +104,7 @@ protected function validateUser(ServerRequestInterface $request, ClientEntityInt if ($user instanceof UserEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidGrant(); + throw OAuthServerException::invalidGrant('Check the user credentials provided'); } return $user; From be7b5567f56fcba860d5448e992975002146849c Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 25 Feb 2020 22:04:36 +0000 Subject: [PATCH 038/212] Update the changelog with a placeholder --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e006dd4d..3c130a688 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - When storing a key, we no longer touch the file before writing it as this is an unnecessary step (PR #1064) - Prefix native PHP functions in namespaces with backslashes for micro-optimisations (PR #1071) +### Changed (v9) +- Added an exception hint when user credential check fails for the Password Grant (PR #XX) + ### Removed - Support for PHP 7.1 (PR #1075) From d8e5f06a4f657681169297a3df3fe76e6c45cb6a Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 25 Feb 2020 22:09:00 +0000 Subject: [PATCH 039/212] Update pull request number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c130a688..d484ac65a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Prefix native PHP functions in namespaces with backslashes for micro-optimisations (PR #1071) ### Changed (v9) -- Added an exception hint when user credential check fails for the Password Grant (PR #XX) +- Added an exception hint when user credential check fails for the Password Grant (PR #1098) ### Removed - Support for PHP 7.1 (PR #1075) From c7c44c6892fe881633f28d4336f6b4ba42295c6a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 29 Feb 2020 20:16:06 +0000 Subject: [PATCH 040/212] Remove hint if user credentials are incorrect --- CHANGELOG.md | 3 --- src/Grant/PasswordGrant.php | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d484ac65a..0e006dd4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - When storing a key, we no longer touch the file before writing it as this is an unnecessary step (PR #1064) - Prefix native PHP functions in namespaces with backslashes for micro-optimisations (PR #1071) -### Changed (v9) -- Added an exception hint when user credential check fails for the Password Grant (PR #1098) - ### Removed - Support for PHP 7.1 (PR #1075) diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 1fbac44d8..7579fd0d2 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -104,7 +104,7 @@ protected function validateUser(ServerRequestInterface $request, ClientEntityInt if ($user instanceof UserEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidGrant('Check the user credentials provided'); + throw OAuthServerException::invalidGrant(); } return $user; From 0331fbc6b0ac0de3654f4ae4016809b296072c98 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 8 Mar 2020 20:49:15 +0000 Subject: [PATCH 041/212] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e006dd4d..19ad33854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added (v9) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) +- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) ### Added - Added support for PHP 7.4 (PR #1075) From 0a6c4f1a656a33488212c63a021a5eaad1963ed8 Mon Sep 17 00:00:00 2001 From: Patrick Rodacker Date: Thu, 16 Apr 2020 21:09:48 +0200 Subject: [PATCH 042/212] introduce an AuthorizationRequestInterface for the AuthorizationRequest --- src/AuthorizationServer.php | 14 ++- src/Grant/AbstractGrant.php | 4 +- src/Grant/AuthCodeGrant.php | 7 +- src/Grant/GrantTypeInterface.php | 8 +- src/Grant/ImplicitGrant.php | 3 +- src/RequestTypes/AuthorizationRequest.php | 2 +- .../AuthorizationRequestInterface.php | 107 ++++++++++++++++++ 7 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 src/RequestTypes/AuthorizationRequestInterface.php diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 8b0b2815e..23563bebb 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -18,7 +18,7 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\AbstractResponseType; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -148,7 +148,7 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc * * @throws OAuthServerException * - * @return AuthorizationRequest + * @return AuthorizationRequestInterface */ public function validateAuthorizationRequest(ServerRequestInterface $request) { @@ -164,13 +164,15 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) /** * Complete an authorization request * - * @param AuthorizationRequest $authRequest - * @param ResponseInterface $response + * @param AuthorizationRequestInterface $authRequest + * @param ResponseInterface $response * * @return ResponseInterface */ - public function completeAuthorizationRequest(AuthorizationRequest $authRequest, ResponseInterface $response) - { + public function completeAuthorizationRequest( + AuthorizationRequestInterface $authRequest, + ResponseInterface $response + ) { return $this->enabledGrantTypes[$authRequest->getGrantTypeId()] ->completeAuthorizationRequest($authRequest) ->generateHttpResponse($response); diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c4797292a..b3c9d9607 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -31,7 +31,7 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; -use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; use TypeError; @@ -592,7 +592,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) /** * {@inheritdoc} */ - public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) { throw new LogicException('This grant cannot complete an authorization request'); } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 8a30bfc9f..9fb86c119 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -22,6 +22,7 @@ use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -327,7 +328,7 @@ function ($method) { /** * {@inheritdoc} */ - public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); @@ -392,11 +393,11 @@ public function completeAuthorizationRequest(AuthorizationRequest $authorization /** * Get the client redirect URI if not set in the request. * - * @param AuthorizationRequest $authorizationRequest + * @param AuthorizationRequestInterface $authorizationRequest * * @return string */ - private function getClientRedirectUri(AuthorizationRequest $authorizationRequest) + private function getClientRedirectUri(AuthorizationRequestInterface $authorizationRequest) { return \is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 41ebeb5ff..1fe2c615c 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -18,7 +18,7 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -74,7 +74,7 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request * * @param ServerRequestInterface $request * - * @return AuthorizationRequest + * @return AuthorizationRequestInterface */ public function validateAuthorizationRequest(ServerRequestInterface $request); @@ -83,11 +83,11 @@ public function validateAuthorizationRequest(ServerRequestInterface $request); * The AuthorizationRequest object's $userId property must be set to the authenticated user and the * $authorizationApproved property must reflect their desire to authorize or deny the client. * - * @param AuthorizationRequest $authorizationRequest + * @param AuthorizationRequestInterface $authorizationRequest * * @return ResponseTypeInterface */ - public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest); + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest); /** * The grant type should return true if it is able to respond to this request. diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 17f289bfb..41f6dec11 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -15,6 +15,7 @@ use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -164,7 +165,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) /** * {@inheritdoc} */ - public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index 6441e144d..d05e5b394 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -13,7 +13,7 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; -class AuthorizationRequest +class AuthorizationRequest implements AuthorizationRequestInterface { /** * The grant type identifier diff --git a/src/RequestTypes/AuthorizationRequestInterface.php b/src/RequestTypes/AuthorizationRequestInterface.php new file mode 100644 index 000000000..869dc1ea2 --- /dev/null +++ b/src/RequestTypes/AuthorizationRequestInterface.php @@ -0,0 +1,107 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\RequestTypes; + +use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; + +interface AuthorizationRequestInterface +{ + /** + * @return UserEntityInterface|null + */ + public function getUser(); + + /** + * @param string $state + */ + public function setState($state); + + /** + * @return ClientEntityInterface + */ + public function getClient(); + + /** + * @param bool $authorizationApproved + */ + public function setAuthorizationApproved($authorizationApproved); + + /** + * @param ScopeEntityInterface[] $scopes + */ + public function setScopes(array $scopes); + + /** + * @param string|null $redirectUri + */ + public function setRedirectUri($redirectUri); + + /** + * @return string|null + */ + public function getRedirectUri(); + + /** + * @return string + */ + public function getCodeChallengeMethod(); + + /** + * @param string $grantTypeId + */ + public function setGrantTypeId($grantTypeId); + + /** + * @param UserEntityInterface $user + */ + public function setUser(UserEntityInterface $user); + + /** + * @param ClientEntityInterface $client + */ + public function setClient(ClientEntityInterface $client); + + /** + * @param string $codeChallenge + */ + public function setCodeChallenge($codeChallenge); + + /** + * @return bool + */ + public function isAuthorizationApproved(); + + /** + * @return string|null + */ + public function getState(); + + /** + * @return string + */ + public function getCodeChallenge(); + + /** + * @param string $codeChallengeMethod + */ + public function setCodeChallengeMethod($codeChallengeMethod); + + /** + * @return ScopeEntityInterface[] + */ + public function getScopes(); + + /** + * @return string + */ + public function getGrantTypeId(); +} From 666f9974a61ed005338ab56b357e6c3cba58fc6f Mon Sep 17 00:00:00 2001 From: Patrick Rodacker Date: Thu, 16 Apr 2020 21:10:27 +0200 Subject: [PATCH 043/212] make sure we cover the setState method of the interface as well --- tests/Grant/AuthCodeGrantTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 01153cd4f..1164fe8c6 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -2018,6 +2018,7 @@ public function testPublicClientAuthCodeRequestRejectedWhenCodeChallengeRequired 'response_type' => 'code', 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', + 'state' => 'foo', ]); $this->expectException(OAuthServerException::class); From 2561a06b7372c19a947fc65d0468ff06fa10aa91 Mon Sep 17 00:00:00 2001 From: Patrick Rodacker Date: Thu, 16 Apr 2020 21:10:47 +0200 Subject: [PATCH 044/212] test complete authorization request with multiple request uris --- tests/Grant/AuthCodeGrantTest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 1164fe8c6..c3a7fa1ec 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -475,6 +475,29 @@ public function testCompleteAuthorizationRequest() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } + public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient() + { + $client = new ClientEntity(); + $client->setRedirectUri(['uriOne', 'uriTwo']); + $authRequest = new AuthorizationRequest(); + $authRequest->setAuthorizationApproved(true); + $authRequest->setClient($client); + $authRequest->setGrantTypeId('authorization_code'); + $authRequest->setUser(new UserEntity()); + + $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->setEncryptionKey($this->cryptStub->getKey()); + + $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + } + public function testCompleteAuthorizationRequestDenied() { $authRequest = new AuthorizationRequest(); From f1ac1a2baa411578b23bf8108f91230f1acdd65d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 17 Apr 2020 00:59:33 +0100 Subject: [PATCH 045/212] Update change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbef1d19d..08b25a9d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added (v9) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) +- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) ### Added - Added support for PHP 7.4 (PR #1075) From fc03e245fafac821b3a40c309d64f850b077e93e Mon Sep 17 00:00:00 2001 From: Patrick Rodacker Date: Fri, 17 Apr 2020 07:38:22 +0200 Subject: [PATCH 046/212] refactor by extracting the creation of the authorization request to a dedicated function in AbstractAuthorizeGrant --- src/Grant/AbstractAuthorizeGrant.php | 11 +++++++++++ src/Grant/AuthCodeGrant.php | 3 +-- src/Grant/ImplicitGrant.php | 3 +-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index 716c9bbf4..3a8bb4074 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -11,6 +11,9 @@ namespace League\OAuth2\Server\Grant; +use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; + abstract class AbstractAuthorizeGrant extends AbstractGrant { /** @@ -26,4 +29,12 @@ public function makeRedirectUri($uri, $params = [], $queryDelimiter = '?') return $uri . \http_build_query($params); } + + /** + * @return AuthorizationRequestInterface + */ + protected function createAuthorizationRequest() + { + return new AuthorizationRequest(); + } } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index cba499bd0..914a8d62e 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -21,7 +21,6 @@ use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; -use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -279,7 +278,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $stateParameter = $this->getQueryStringParameter('state', $request); - $authorizationRequest = new AuthorizationRequest(); + $authorizationRequest = $this->createAuthorizationRequest(); $authorizationRequest->setGrantTypeId($this->getIdentifier()); $authorizationRequest->setClient($client); $authorizationRequest->setRedirectUri($redirectUri); diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 41f6dec11..f4b15f22f 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -14,7 +14,6 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; -use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; @@ -148,7 +147,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $stateParameter = $this->getQueryStringParameter('state', $request); - $authorizationRequest = new AuthorizationRequest(); + $authorizationRequest = $this->createAuthorizationRequest(); $authorizationRequest->setGrantTypeId($this->getIdentifier()); $authorizationRequest->setClient($client); $authorizationRequest->setRedirectUri($redirectUri); From e55338b54ab410fb67db8c6e8de707470f06df85 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 18 Apr 2020 10:59:05 +0100 Subject: [PATCH 047/212] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08b25a9d9..49ed0cb0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed (v9) - If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) +### Changed (v9) +- Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) + ### Changed - If an error is encountered when running `preg_match()` to validate an RSA key, the server will now throw a RuntimeException (PR #1047) - Replaced deprecated methods with recommended ones when using `Lcobucci\JWT\Builder` to build a JWT token. (PR #1060) From ebeb0f42ee57318e3f19065beb2161fac27bd3fc Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 18 Apr 2020 12:03:04 +0100 Subject: [PATCH 048/212] Allow auth code ID to be passed to finalizeScopes. Fixes #672 --- CHANGELOG.md | 1 + src/Grant/AuthCodeGrant.php | 3 ++- src/Grant/PasswordGrant.php | 7 +++++-- src/Repositories/ScopeRepositoryInterface.php | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ed0cb0b..3610ba7df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed (v9) - Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) +- Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112) ### Changed - If an error is encountered when running `preg_match()` to validate an RSA key, the server will now throw a RuntimeException (PR #1047) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 914a8d62e..b6fe8824e 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -119,7 +119,8 @@ public function respondToAccessTokenRequest( $this->validateScopes($authCodePayload->scopes), $this->getIdentifier(), $client, - $authCodePayload->user_id + $authCodePayload->user_id, + $authCodePayload->auth_code_id ); } catch (LogicException $e) { throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 7579fd0d2..cff5d40c3 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -53,8 +53,11 @@ public function respondToAccessTokenRequest( $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); $user = $this->validateUser($request, $client); - // Finalize the requested scopes - $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier()); + $finalizedScopes = $this->scopeRepository->finalizeScopes( + $scopes, + $this->getIdentifier(), + $client, + $user->getIdentifier()); // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); diff --git a/src/Repositories/ScopeRepositoryInterface.php b/src/Repositories/ScopeRepositoryInterface.php index 997aac2c8..9dbc0a896 100644 --- a/src/Repositories/ScopeRepositoryInterface.php +++ b/src/Repositories/ScopeRepositoryInterface.php @@ -34,6 +34,7 @@ public function getScopeEntityByIdentifier($identifier); * @param string $grantType * @param ClientEntityInterface $clientEntity * @param null|string $userIdentifier + * @param null|string $authCodeId * * @return ScopeEntityInterface[] */ @@ -41,6 +42,7 @@ public function finalizeScopes( array $scopes, $grantType, ClientEntityInterface $clientEntity, - $userIdentifier = null + $userIdentifier = null, + $authCodeId = null ); } From a2ddfdc8d87640216c613b8a6712378ac75989d5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:10:31 +0100 Subject: [PATCH 049/212] Add device grant middleware to slow requests --- src/Grant/DeviceCodeGrant.php | 2 +- src/Middleware/DeviceGrantMiddleware.php | 42 +++++++++++++++++++ .../DeviceAuthorizationRequestRepository.php | 30 +++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/Middleware/DeviceGrantMiddleware.php create mode 100644 src/Repositories/DeviceAuthorizationRequestRepository.php diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 6aeb0b537..709100b6c 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -173,7 +173,7 @@ public function respondToAccessTokenRequest( throw OAuthServerException::slowDown(); } - $this->deviceAuthorizationRequestRepository->persist($client->getIdentifier()); + $this->deviceAuthorizationRequestRepository->persist($deviceCode); // if device code has no user associated, respond with pending if (\is_null($deviceCode->getUserIdentifier())) { diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php new file mode 100644 index 000000000..bbf41710f --- /dev/null +++ b/src/Middleware/DeviceGrantMiddleware.php @@ -0,0 +1,42 @@ +deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; + $this->responseFactory = $responseFactory; + } + + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface + { + $queryParameters = $request->getQueryParams(); + $deviceCode = $queryParameters['device_code']; + + // Get the last timestamp this client requested an access code + $lastRequestTimeStamp = $this->deviceAuthorizationRequestRepository->getLast($deviceCode); + + // If the request is within the last 5 seconds, issue a slowdown notification + if ($lastRequestTimeStamp + 5 > time()) { + return OAuthServerException::slowDown()->generateHttpResponse($this->responseFactory->createResponse()); + } + + return $handler->handle($request); + } +} \ No newline at end of file diff --git a/src/Repositories/DeviceAuthorizationRequestRepository.php b/src/Repositories/DeviceAuthorizationRequestRepository.php new file mode 100644 index 000000000..8b313eb0a --- /dev/null +++ b/src/Repositories/DeviceAuthorizationRequestRepository.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (c) Alex Bilbie + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Repositories; + +use DateTimeImmutable; + +/** + * Device authorization request storage interface. + */ +interface DeviceAuthorizationRequestRepository extends RepositoryInterface +{ + /** + * @param string $deviceCode + * + * @return DateTimeImmutable; + */ + public function getLast($deviceCode); + + /** + * @param string $deviceCode + */ + public function persist(); +} From eba69bba551c5943e0d8731e8e2f5ab0a358a468 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:14:23 +0100 Subject: [PATCH 050/212] StyleCI fixes --- src/Grant/DeviceCodeGrant.php | 2 +- src/Middleware/DeviceGrantMiddleware.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 709100b6c..17e715a13 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -20,8 +20,8 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; -use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\DeviceAuthorizationRequestRepository; +use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php index bbf41710f..f3d93c9ea 100644 --- a/src/Middleware/DeviceGrantMiddleware.php +++ b/src/Middleware/DeviceGrantMiddleware.php @@ -33,7 +33,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $lastRequestTimeStamp = $this->deviceAuthorizationRequestRepository->getLast($deviceCode); // If the request is within the last 5 seconds, issue a slowdown notification - if ($lastRequestTimeStamp + 5 > time()) { + if ($lastRequestTimeStamp + 5 > \time()) { return OAuthServerException::slowDown()->generateHttpResponse($this->responseFactory->createResponse()); } From 7621652ae3c8f5e17de446be3dff8cb5916e7389 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:15:53 +0100 Subject: [PATCH 051/212] StyleCI fixes --- src/Middleware/DeviceGrantMiddleware.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php index f3d93c9ea..847f6bc97 100644 --- a/src/Middleware/DeviceGrantMiddleware.php +++ b/src/Middleware/DeviceGrantMiddleware.php @@ -18,8 +18,7 @@ class DeviceGrantMiddleware implements MiddlewareInterface public function __construct( DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, ResponseFactoryInterface $responseFactory - ) - { + ) { $this->deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; $this->responseFactory = $responseFactory; } @@ -32,7 +31,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface // Get the last timestamp this client requested an access code $lastRequestTimeStamp = $this->deviceAuthorizationRequestRepository->getLast($deviceCode); - // If the request is within the last 5 seconds, issue a slowdown notification + // If the request is within the last 5 seconds, issue a slowdown notification if ($lastRequestTimeStamp + 5 > \time()) { return OAuthServerException::slowDown()->generateHttpResponse($this->responseFactory->createResponse()); } From 1c1e2cf19e76e4d6f634b76f65e5421dd1a91f07 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:18:26 +0100 Subject: [PATCH 052/212] Update composer dependencies --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 7991a0cc8..50b523a4f 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "lcobucci/jwt": "^3.3.1", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", - "ext-json": "*" + "ext-json": "*", + "psr/http-server-middleware": "^1.0" }, "require-dev": { "phpunit/phpunit": "^7.5.13 || ^8.2.3", From 9cb2dfefd2b16fb01971384a55b314389aeb0c2e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:22:13 +0100 Subject: [PATCH 053/212] trying to fix styleCI issue --- src/Middleware/DeviceGrantMiddleware.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php index 847f6bc97..6d614eb49 100644 --- a/src/Middleware/DeviceGrantMiddleware.php +++ b/src/Middleware/DeviceGrantMiddleware.php @@ -18,7 +18,8 @@ class DeviceGrantMiddleware implements MiddlewareInterface public function __construct( DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, ResponseFactoryInterface $responseFactory - ) { + ) + { $this->deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; $this->responseFactory = $responseFactory; } @@ -38,4 +39,4 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $handler->handle($request); } -} \ No newline at end of file +} From df1ed20f5e9222ccbb8989c24eab4016fe22ef33 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:23:29 +0100 Subject: [PATCH 054/212] Fix brackets formatting --- src/Middleware/DeviceGrantMiddleware.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php index 6d614eb49..6d1d2a3a4 100644 --- a/src/Middleware/DeviceGrantMiddleware.php +++ b/src/Middleware/DeviceGrantMiddleware.php @@ -18,8 +18,7 @@ class DeviceGrantMiddleware implements MiddlewareInterface public function __construct( DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, ResponseFactoryInterface $responseFactory - ) - { + ) { $this->deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; $this->responseFactory = $responseFactory; } From b10cec43a7534ad854ea316144cd54e3f1ea4dfa Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:45:00 +0100 Subject: [PATCH 055/212] Fix bug --- src/Repositories/DeviceAuthorizationRequestRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repositories/DeviceAuthorizationRequestRepository.php b/src/Repositories/DeviceAuthorizationRequestRepository.php index 8b313eb0a..9da6b82df 100644 --- a/src/Repositories/DeviceAuthorizationRequestRepository.php +++ b/src/Repositories/DeviceAuthorizationRequestRepository.php @@ -26,5 +26,5 @@ public function getLast($deviceCode); /** * @param string $deviceCode */ - public function persist(); + public function persist($deviceCode); } From 8ac43b6a72cacd0dc0dff315e51b31c0acd2224d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 19 Apr 2020 12:56:58 +0100 Subject: [PATCH 056/212] Fix bugs --- src/Exception/OAuthServerException.php | 8 +++++--- src/Grant/DeviceCodeGrant.php | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 325c813e5..8a89512ef 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -309,18 +309,20 @@ public static function authorizationPending($hint = '') * Slow down error used with the Device Authorization Grant. * * @param string $hint - * @param Throwable $previous Previous exception + * @param Throwable $previous * * @return static */ - public static function slowDown($hint = '') + public static function slowDown($hint = '', Throwable $previous = null) { return new static( 'example message', 13, 'slow_down', 400, - $hint + $hint, + null, + $previous ); } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 17e715a13..ec15a600b 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -173,7 +173,7 @@ public function respondToAccessTokenRequest( throw OAuthServerException::slowDown(); } - $this->deviceAuthorizationRequestRepository->persist($deviceCode); + $this->deviceAuthorizationRequestRepository->persist($deviceCode->getUserCode()); // if device code has no user associated, respond with pending if (\is_null($deviceCode->getUserIdentifier())) { From 2382db824ed163a72255fce7b6e1a1992ba9fd23 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 30 Sep 2020 12:38:45 +0100 Subject: [PATCH 057/212] Update changelog --- CHANGELOG.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09c68db66..69fe55c1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,37 +5,37 @@ 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 (v9) +- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) +- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) +- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) + ### Added - Add a `getRedirectUri` function to the `OAuthServerException` class (PR #1123) +### Fixed (v9) +- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) + ### Fixed - Fix typo in parameter hint. `code_challenged` changed to `code_challenge`. Thrown by Auth Code Grant when the code challenge does not match the regex. (PR #1130) - Undefined offset was returned when no client redirect URI was set. Now throw an invalidClient exception if no redirect URI is set against a client (PR #1140) +### Changed (v9) +- Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) +- Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112) + ## [8.1.1] - released 2020-07-01 ### Fixed - If you provide a valid redirect_uri with the auth code grant and an invalid scope, the server will use the given redirect_uri instead of the default client redirect uri (PR #1126) - ## [8.1.0] - released 2020-04-29 -### Added (v9) -- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) -- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) -- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) - ### Added - Added support for PHP 7.4 (PR #1075) -### Fixed (v9) -- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) - -### Changed (v9) -- Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) -- Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112) - ### Changed - If an error is encountered when running `preg_match()` to validate an RSA key, the server will now throw a RuntimeException (PR #1047) - Replaced deprecated methods with recommended ones when using `Lcobucci\JWT\Builder` to build a JWT token. (PR #1060) From e80ac28d59398d9c5b0fe222972725e2016daa2e Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 11:39:48 +0000 Subject: [PATCH 058/212] Readded changes for v9 to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 771661a6b..a68f7147f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ 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 (v9) +- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) +- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) +- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) + ### Fixed (v9) - If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) From 0ba3f42e82ce8fc2040d556c55709f068f08b736 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 11:48:55 +0000 Subject: [PATCH 059/212] Add redirect URI to client for RefreshToken test --- tests/Grant/RefreshTokenGrantTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index a2c6ed82f..d6a0d42e2 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -474,7 +474,10 @@ public function testRespondToRequestRevokedToken() public function testRespondToRequestFinalizeScopes() { $client = new ClientEntity(); + $client->setIdentifier('foo'); + $client->setRedirectUri('http://foo/bar'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); From a1fd8f0cc9991039aa66904b04cc1d97c3ae7a09 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 9 Dec 2020 11:51:27 +0000 Subject: [PATCH 060/212] Apply StyleCI fixes --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 2f86c2169..b062c4461 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -17,11 +17,11 @@ use Lcobucci\JWT\Signer\Key\LocalFileReference; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Token\InvalidTokenStructure; -use League\OAuth2\Server\CryptKeyInterface; use Lcobucci\JWT\Token\UnsupportedHeaderFound; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\ValidAt; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; From 946aad10bce27dda6a77077c16fb3b8dd4b91974 Mon Sep 17 00:00:00 2001 From: rhertogh Date: Sun, 15 Aug 2021 12:00:59 +0200 Subject: [PATCH 061/212] Added exception chaining in BearerTokenValidator Added exception chaining for RequiredConstraintsViolated in `\League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator::validateAuthorization()` similar to line 103. --- src/AuthorizationValidators/BearerTokenValidator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index e41e1a2b7..6f767ceef 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -108,7 +108,7 @@ public function validateAuthorization(ServerRequestInterface $request) $constraints = $this->jwtConfiguration->validationConstraints(); $this->jwtConfiguration->validator()->assert($token, ...$constraints); } catch (RequiredConstraintsViolated $exception) { - throw OAuthServerException::accessDenied('Access token could not be verified'); + throw OAuthServerException::accessDenied('Access token could not be verified', null, $exception); } $claims = $token->claims(); From 86186321d5157f5e6902fa3ead96addb1cd922ff Mon Sep 17 00:00:00 2001 From: sephster Date: Mon, 17 Jan 2022 20:50:32 +0000 Subject: [PATCH 062/212] Update tests to only run on PHP 7.4, 8.0, and 8.1 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f1db9e1b..092a9312d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php: [7.2, 7.3, 7.4, 8.0] + php: [7.4, 8.0, 8.1] stability: [prefer-lowest, prefer-stable] name: PHP ${{ matrix.php }} - ${{ matrix.stability }} From 9f39bf9ee7f55f928ef2922afe19204d4fcb9829 Mon Sep 17 00:00:00 2001 From: sephster Date: Mon, 17 Jan 2022 21:02:14 +0000 Subject: [PATCH 063/212] Update dependencies for PHP 7.4+ --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 7cfbc0cc4..57e646555 100644 --- a/composer.json +++ b/composer.json @@ -4,19 +4,19 @@ "homepage": "https://oauth2.thephpleague.com/", "license": "MIT", "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "ext-openssl": "*", "league/event": "^2.2", - "lcobucci/jwt": "^3.4.6 || ^4.0.4", + "lcobucci/jwt": "^4.1.5", "psr/http-message": "^1.0.1", - "defuse/php-encryption": "^2.2.1", + "defuse/php-encryption": "^2.3.1", "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^8.5.13", - "laminas/laminas-diactoros": "^2.4.1", - "phpstan/phpstan": "^0.12.57", - "phpstan/phpstan-phpunit": "^0.12.16", + "phpunit/phpunit": "^9.5.11", + "laminas/laminas-diactoros": "^2.8.0", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1.0.0", "roave/security-advisories": "dev-master" }, "repositories": [ From ec05e01c7433fbb8c6d83067e0ababfa27cad50b Mon Sep 17 00:00:00 2001 From: sephster Date: Mon, 17 Jan 2022 22:49:57 +0000 Subject: [PATCH 064/212] Add mock returns for revoke refresh token tests --- tests/Grant/RefreshTokenGrantTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index ef14cabe7..5b5491bbc 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -563,6 +563,7 @@ public function testRevokedRefreshToken() $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -621,6 +622,7 @@ public function testUnrevokedRefreshToken() $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('finalizeScopes')->willReturn([$scopeEntity]); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); From 19dc018d31da58893b1c31cdb07c2e61a9c9a2bb Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 24 Jan 2022 23:11:48 +0000 Subject: [PATCH 065/212] re-introduce phpstan --- composer.json | 7 ++++++- phpstan.neon => phpstan.neon.dist.tmp | 0 src/Exception/OAuthServerException.php | 2 +- tests/Utils/CryptKeyTest.php | 8 -------- 4 files changed, 7 insertions(+), 10 deletions(-) rename phpstan.neon => phpstan.neon.dist.tmp (100%) diff --git a/composer.json b/composer.json index 57e646555..52b6df7ac 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "require-dev": { "phpunit/phpunit": "^9.5.11", "laminas/laminas-diactoros": "^2.8.0", - "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1.0.0", "roave/security-advisories": "dev-master" }, @@ -68,5 +68,10 @@ "psr-4": { "LeagueTests\\": "tests/" } + }, + "config": { + "allow-plugins": { + "ocramius/package-versions": true + } } } diff --git a/phpstan.neon b/phpstan.neon.dist.tmp similarity index 100% rename from phpstan.neon rename to phpstan.neon.dist.tmp diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index fc1b9f022..f57c68951 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -57,7 +57,7 @@ class OAuthServerException extends Exception * @param null|string $redirectUri A HTTP URI to redirect the user back to * @param Throwable $previous Previous exception */ - public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) + final public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index b9c53b660..55808fc17 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -94,10 +94,6 @@ public function testECKeyType() $this->assertEquals('mystrongpassword', $key->getPassPhrase()); } catch (\Throwable $e) { $this->fail('The EC key was not created'); - } finally { - if (isset($path)) { - @\unlink($path); - } } } @@ -119,10 +115,6 @@ public function testRSAKeyType() $this->assertEquals('mystrongpassword', $key->getPassPhrase()); } catch (\Throwable $e) { $this->fail('The RSA key was not created'); - } finally { - if (isset($path)) { - @\unlink($path); - } } } From 6844f4ea7444125bf854ace4b39ebf9d45aee150 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 29 Jan 2022 14:08:59 +0000 Subject: [PATCH 066/212] Add revokeRefreshTokens to GrantTypeInterface --- CHANGELOG.md | 1 + src/Grant/GrantTypeInterface.php | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 49ea711b0..7c1307ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added +- GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #XXXX) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) - An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index bf1dd792f..9f66639c0 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -141,4 +141,13 @@ public function setPrivateKey(CryptKeyInterface $privateKey); * @param string|Key|null $key */ public function setEncryptionKey($key = null); + + /** + * Enable or prevent the revocation of refresh tokens upon usage. + * + * @param bool $willRevoke + * + * @return void + */ + public function revokeRefreshTokens(bool $willRevoke); } From 39cca0b460f2dd2999023e3638a392f87ac9716e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Feb 2022 10:17:20 +0000 Subject: [PATCH 067/212] change phpstan.neon.dist name --- phpstan.neon.dist.tmp => phpstan.neon.dist | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename phpstan.neon.dist.tmp => phpstan.neon.dist (100%) diff --git a/phpstan.neon.dist.tmp b/phpstan.neon.dist similarity index 100% rename from phpstan.neon.dist.tmp rename to phpstan.neon.dist From c15f5d9d02f677d3fd8b400c7650f34ae79ebaab Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 2 Feb 2022 10:18:00 +0000 Subject: [PATCH 068/212] Update phpstan default config --- phpstan.neon.dist | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ba1fb4915..b815a6d4d 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,8 +1,5 @@ -includes: - - vendor/phpstan/phpstan-phpunit/extension.neon - - vendor/phpstan/phpstan-phpunit/rules.neon -services: - - - class: LeagueTests\PHPStan\AbstractGrantExtension - tags: - - phpstan.broker.dynamicMethodReturnTypeExtension +parameters: + level: 1 + paths: + - src + - tests \ No newline at end of file From e7b4c4dac984b1a92ccbe3571b329b5056ad25ba Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 15:42:50 +0000 Subject: [PATCH 069/212] Ignore unresolvable phpstan error --- phpstan.neon.dist | 5 ++++- src/Grant/AbstractGrant.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b815a6d4d..aaa03768a 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,4 +2,7 @@ parameters: level: 1 paths: - src - - tests \ No newline at end of file + - tests + ignoreErrors: + - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return string but return statement is missing\.#' + - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' \ No newline at end of file diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index a03df5090..957d83c32 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -444,7 +444,7 @@ protected function getServerParameter($parameter, ServerRequestInterface $reques * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * - * @return AccessTokenEntityInterface + * @return string */ protected function issueAccessToken( DateInterval $accessTokenTTL, From 2e9582d2de070701eeac424234bd805dad50638c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 16:09:26 +0000 Subject: [PATCH 070/212] Add getKeyContents() to CryptKeyInterface --- CHANGELOG.md | 1 + phpstan.neon.dist | 2 +- src/CryptKey.php | 4 +--- src/CryptKeyInterface.php | 7 +++++++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1307ebd..c4ffbe134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) - An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) +- Added function `getKeyContents()` to the `CryptKeyInterface` (PR #XXXX) ### Fixed - If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index aaa03768a..b9f3634b9 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 1 + level: 2 paths: - src - tests diff --git a/src/CryptKey.php b/src/CryptKey.php index 45604f16d..b9f8ef314 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -84,9 +84,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = } /** - * Get key contents - * - * @return string Key contents + * {@inheritdoc} */ public function getKeyContents(): string { diff --git a/src/CryptKeyInterface.php b/src/CryptKeyInterface.php index 86c7b0446..15c34b8d7 100644 --- a/src/CryptKeyInterface.php +++ b/src/CryptKeyInterface.php @@ -17,4 +17,11 @@ public function getKeyPath(); * @return null|string */ public function getPassPhrase(); + + /** + * Get key contents + * + * @return string Key contents + */ + public function getKeyContents(): string; } From e29a40cd3d210ae1a5a3ab1303abe675efef0ba0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 16:23:34 +0000 Subject: [PATCH 071/212] Ensure UnencryptedToken is returned when parsing token --- src/AuthorizationValidators/BearerTokenValidator.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 7687843c5..6ee52f85e 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -14,6 +14,7 @@ use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; +use Lcobucci\JWT\UnencryptedToken; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\StrictValidAt; use Lcobucci\JWT\Validation\Constraint\ValidAt; @@ -111,6 +112,10 @@ public function validateAuthorization(ServerRequestInterface $request) throw OAuthServerException::accessDenied('Access token could not be verified'); } + if (! $token instanceof UnencryptedToken) { + throw OAuthServerException::accessDenied('Access token is not an instance of UnencryptedToken'); + } + $claims = $token->claims(); // Check if token has been revoked From d32367beffb06e4162a82288391c9347780577e9 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 16:25:50 +0000 Subject: [PATCH 072/212] Ignore unresolvable phpstan error --- phpstan.neon.dist | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index b9f3634b9..48fe80973 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -5,4 +5,5 @@ parameters: - tests ignoreErrors: - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return string but return statement is missing\.#' - - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' \ No newline at end of file + - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' + - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueRefreshToken\(\) should return League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInterface\|null but return statement is missing\.#' \ No newline at end of file From ece2df18c3819be25bd1bbf19580309735055310 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 16:32:12 +0000 Subject: [PATCH 073/212] Revert accidental commit --- src/Grant/AbstractGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 957d83c32..a03df5090 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -444,7 +444,7 @@ protected function getServerParameter($parameter, ServerRequestInterface $reques * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * - * @return string + * @return AccessTokenEntityInterface */ protected function issueAccessToken( DateInterval $accessTokenTTL, From 9275867522b622aa8e643fad848b52cd259fb9bf Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 16:34:07 +0000 Subject: [PATCH 074/212] Update phpstan ignore parameters --- phpstan.neon.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 48fe80973..fd9811988 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,6 @@ parameters: - src - tests ignoreErrors: - - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return string but return statement is missing\.#' + - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return League\\OAuth2\\Server\\Entities\\AccessTokenEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueRefreshToken\(\) should return League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInterface\|null but return statement is missing\.#' \ No newline at end of file From 285d90c00d8d47e5f67485b0b4fa062ddfce4a3e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 22 Feb 2022 16:40:04 +0000 Subject: [PATCH 075/212] Fix name of variable from allowedRedirectUri to allowedRedirectUris --- src/RedirectUriValidators/RedirectUriValidator.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 2cb020801..4cd82b4a9 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -21,12 +21,12 @@ class RedirectUriValidator implements RedirectUriValidatorInterface * * @param string|array $allowedRedirectUris */ - public function __construct($allowedRedirectUri) + public function __construct($allowedRedirectUris) { - if (\is_string($allowedRedirectUri)) { - $this->allowedRedirectUris = [$allowedRedirectUri]; - } elseif (\is_array($allowedRedirectUri)) { - $this->allowedRedirectUris = $allowedRedirectUri; + if (\is_string($allowedRedirectUris)) { + $this->allowedRedirectUris = [$allowedRedirectUris]; + } elseif (\is_array($allowedRedirectUris)) { + $this->allowedRedirectUris = $allowedRedirectUris; } else { $this->allowedRedirectUris = []; } From 89c89a905dd727119f48ba207e8814790c5a1aec Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 4 May 2022 21:48:21 +0100 Subject: [PATCH 076/212] update PHPStan level --- phpstan.neon.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index fd9811988..32d859033 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 2 + level: 4 paths: - src - tests From 14f5133908459090b99279c1769757c6fc97aba4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 4 May 2022 21:48:51 +0100 Subject: [PATCH 077/212] Fix condition that always evaluates to false --- src/Grant/AbstractGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index a03df5090..7745ab504 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -262,7 +262,7 @@ protected function getClientCredentials(ServerRequestInterface $request) $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); - if ($clientSecret !== null && !\is_string($clientSecret)) { + if ($clientSecret === null) { throw OAuthServerException::invalidRequest('client_secret'); } From 492b531044418ffefdeba02cdac1be7cc26d564b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 4 May 2022 21:54:59 +0100 Subject: [PATCH 078/212] Revert type check on scopes as needlessly complicated --- src/Grant/AbstractGrant.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 7745ab504..0d5972d29 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -303,14 +303,8 @@ protected function validateRedirectUri( */ public function validateScopes($scopes, $redirectUri = null) { - if ($scopes === null) { - $scopes = []; - } elseif (\is_string($scopes)) { - $scopes = $this->convertScopesQueryStringToArray($scopes); - } - if (!\is_array($scopes)) { - throw OAuthServerException::invalidRequest('scope'); + $scopes = $this->convertScopesQueryStringToArray($scopes); } $validScopes = []; From b5c67af3705f2baf73e5e578b2ab3a11dcdd26b0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 4 May 2022 21:57:00 +0100 Subject: [PATCH 079/212] Remove always false check on state parameter --- src/Grant/ImplicitGrant.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index f23cc4e1d..a39bf0733 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -151,10 +151,6 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $stateParameter = $this->getQueryStringParameter('state', $request); - if ($stateParameter !== null && !\is_string($stateParameter)) { - throw OAuthServerException::invalidRequest('state'); - } - $authorizationRequest = $this->createAuthorizationRequest(); $authorizationRequest->setGrantTypeId($this->getIdentifier()); $authorizationRequest->setClient($client); From 157c48a52183b1dc692dd17c70413cc8de3aad0b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 4 May 2022 22:09:43 +0100 Subject: [PATCH 080/212] Remove exception catches that aren't thrown --- src/Middleware/AuthorizationServerMiddleware.php | 5 ----- src/Middleware/ResourceServerMiddleware.php | 5 ----- 2 files changed, 10 deletions(-) diff --git a/src/Middleware/AuthorizationServerMiddleware.php b/src/Middleware/AuthorizationServerMiddleware.php index 9b78b4585..f1a743af5 100644 --- a/src/Middleware/AuthorizationServerMiddleware.php +++ b/src/Middleware/AuthorizationServerMiddleware.php @@ -43,11 +43,6 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res $response = $this->server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - // @codeCoverageIgnoreStart - } catch (Exception $exception) { - return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) - ->generateHttpResponse($response); - // @codeCoverageIgnoreEnd } // Pass the request and response on to the next responder in the chain diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index e152a9999..8a5103f3e 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -43,11 +43,6 @@ public function __invoke(ServerRequestInterface $request, ResponseInterface $res $request = $this->server->validateAuthenticatedRequest($request); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - // @codeCoverageIgnoreStart - } catch (Exception $exception) { - return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) - ->generateHttpResponse($response); - // @codeCoverageIgnoreEnd } // Pass the request and response on to the next responder in the chain From 745717557ab64db7a3822ac48be4dbeaf2924b06 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 28 Oct 2022 20:49:16 +0200 Subject: [PATCH 081/212] Upgrade league/event to version 3. --- composer.json | 2 +- src/AuthorizationServer.php | 6 +-- src/EventEmitting/AbstractEvent.php | 49 +++++++++++++++++++++ src/EventEmitting/EmitterAwareInterface.php | 14 ++++++ src/EventEmitting/EmitterAwarePolyfill.php | 44 ++++++++++++++++++ src/EventEmitting/EventEmitter.php | 22 +++++++++ src/Grant/AbstractGrant.php | 4 +- src/Grant/GrantTypeInterface.php | 2 +- src/RequestEvent.php | 4 +- 9 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 src/EventEmitting/AbstractEvent.php create mode 100644 src/EventEmitting/EmitterAwareInterface.php create mode 100644 src/EventEmitting/EmitterAwarePolyfill.php create mode 100644 src/EventEmitting/EventEmitter.php diff --git a/composer.json b/composer.json index 593f75a75..201986987 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,7 @@ "require": { "php": "^7.2 || ^8.0", "ext-openssl": "*", - "league/event": "^2.2", + "league/event": "^3.0", "league/uri": "^6.4", "lcobucci/jwt": "^3.4.6 || ^4.0.4", "psr/http-message": "^1.0.1", diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 4d6862157..d95cd01cb 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -11,8 +11,8 @@ use DateInterval; use Defuse\Crypto\Key; -use League\Event\EmitterAwareInterface; -use League\Event\EmitterAwareTrait; +use League\OAuth2\Server\EventEmitting\EmitterAwareInterface; +use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -27,7 +27,7 @@ class AuthorizationServer implements EmitterAwareInterface { - use EmitterAwareTrait; + use EmitterAwarePolyfill; /** * @var GrantTypeInterface[] diff --git a/src/EventEmitting/AbstractEvent.php b/src/EventEmitting/AbstractEvent.php new file mode 100644 index 000000000..1ef378111 --- /dev/null +++ b/src/EventEmitting/AbstractEvent.php @@ -0,0 +1,49 @@ +name = $name; + } + + public function eventName(): string + { + return $this->name; + } + + /** + * Backwards compatibility method + * + * @deprecated use eventName instead + */ + public function getName(): string + { + return $this->name; + } + + public function isPropagationStopped(): bool + { + return $this->propagationStopped; + } + + public function stopPropagation(): self + { + $this->propagationStopped = true; + + return $this; + } +} \ No newline at end of file diff --git a/src/EventEmitting/EmitterAwareInterface.php b/src/EventEmitting/EmitterAwareInterface.php new file mode 100644 index 000000000..de63dcc67 --- /dev/null +++ b/src/EventEmitting/EmitterAwareInterface.php @@ -0,0 +1,14 @@ +emitter) { + $this->emitter = new EventEmitter(); + } + + return $this->emitter; + } + + /** + * @return $this + */ + public function setEmitter(EventEmitter $emitter) + { + $this->emitter = $emitter; + + return $this; + } + + public function getEventDispatcher(): EventDispatcherInterface + { + return $this->getEmitter(); + } + + public function getListenerRegistry(): ListenerRegistry + { + return $this->getEmitter(); + } +} \ No newline at end of file diff --git a/src/EventEmitting/EventEmitter.php b/src/EventEmitting/EventEmitter.php new file mode 100644 index 000000000..4305d9ded --- /dev/null +++ b/src/EventEmitting/EventEmitter.php @@ -0,0 +1,22 @@ +subscribeTo($event, $listener, $priority); + + return $this; + } + + public function emit(object $event): object + { + return $this->dispatch($event); + } +} \ No newline at end of file diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 0a5b514fc..74325f3eb 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -14,7 +14,6 @@ use DateTimeImmutable; use Error; use Exception; -use League\Event\EmitterAwareTrait; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; @@ -22,6 +21,7 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator; @@ -42,7 +42,7 @@ */ abstract class AbstractGrant implements GrantTypeInterface { - use EmitterAwareTrait, CryptTrait; + use EmitterAwarePolyfill, CryptTrait; const SCOPE_DELIMITER_STRING = ' '; diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 41ebeb5ff..bab929b07 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -13,8 +13,8 @@ use DateInterval; use Defuse\Crypto\Key; -use League\Event\EmitterAwareInterface; use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\EventEmitting\EmitterAwareInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; diff --git a/src/RequestEvent.php b/src/RequestEvent.php index b1ca3f6b8..f9dd2a237 100644 --- a/src/RequestEvent.php +++ b/src/RequestEvent.php @@ -9,10 +9,10 @@ namespace League\OAuth2\Server; -use League\Event\Event; +use League\OAuth2\Server\EventEmitting\AbstractEvent; use Psr\Http\Message\ServerRequestInterface; -class RequestEvent extends Event +class RequestEvent extends AbstractEvent { const CLIENT_AUTHENTICATION_FAILED = 'client.authentication.failed'; const USER_AUTHENTICATION_FAILED = 'user.authentication.failed'; From 8fdd7af6711ff11e16bf2f876b8eccfe339aee17 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 28 Oct 2022 20:52:50 +0200 Subject: [PATCH 082/212] newlines at the end of files --- src/EventEmitting/AbstractEvent.php | 2 +- src/EventEmitting/EmitterAwareInterface.php | 2 +- src/EventEmitting/EmitterAwarePolyfill.php | 2 +- src/EventEmitting/EventEmitter.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EventEmitting/AbstractEvent.php b/src/EventEmitting/AbstractEvent.php index 1ef378111..6b2735c55 100644 --- a/src/EventEmitting/AbstractEvent.php +++ b/src/EventEmitting/AbstractEvent.php @@ -46,4 +46,4 @@ public function stopPropagation(): self return $this; } -} \ No newline at end of file +} diff --git a/src/EventEmitting/EmitterAwareInterface.php b/src/EventEmitting/EmitterAwareInterface.php index de63dcc67..19269ab68 100644 --- a/src/EventEmitting/EmitterAwareInterface.php +++ b/src/EventEmitting/EmitterAwareInterface.php @@ -11,4 +11,4 @@ public function getEmitter(): EventEmitter; * @return $this */ public function setEmitter(EventEmitter $emitter); -} \ No newline at end of file +} diff --git a/src/EventEmitting/EmitterAwarePolyfill.php b/src/EventEmitting/EmitterAwarePolyfill.php index 10493541f..80c139260 100644 --- a/src/EventEmitting/EmitterAwarePolyfill.php +++ b/src/EventEmitting/EmitterAwarePolyfill.php @@ -41,4 +41,4 @@ public function getListenerRegistry(): ListenerRegistry { return $this->getEmitter(); } -} \ No newline at end of file +} diff --git a/src/EventEmitting/EventEmitter.php b/src/EventEmitting/EventEmitter.php index 4305d9ded..07f396c4d 100644 --- a/src/EventEmitting/EventEmitter.php +++ b/src/EventEmitting/EventEmitter.php @@ -19,4 +19,4 @@ public function emit(object $event): object { return $this->dispatch($event); } -} \ No newline at end of file +} From edd76edc5816ee21f76dfcf7e5411fe0f2ae49c4 Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 28 Oct 2022 20:56:44 +0200 Subject: [PATCH 083/212] CS --- src/EventEmitting/AbstractEvent.php | 2 +- src/EventEmitting/EmitterAwareInterface.php | 2 +- src/EventEmitting/EmitterAwarePolyfill.php | 4 ++-- src/EventEmitting/EventEmitter.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EventEmitting/AbstractEvent.php b/src/EventEmitting/AbstractEvent.php index 6b2735c55..6e3101397 100644 --- a/src/EventEmitting/AbstractEvent.php +++ b/src/EventEmitting/AbstractEvent.php @@ -6,7 +6,7 @@ use League\Event\HasEventName; use Psr\EventDispatcher\StoppableEventInterface; -class AbstractEvent implements StoppableEventInterface, HasEventName +class AbstractEvent implements StoppableEventInterface, HasEventName { /** * @var string diff --git a/src/EventEmitting/EmitterAwareInterface.php b/src/EventEmitting/EmitterAwareInterface.php index 19269ab68..de63dcc67 100644 --- a/src/EventEmitting/EmitterAwareInterface.php +++ b/src/EventEmitting/EmitterAwareInterface.php @@ -11,4 +11,4 @@ public function getEmitter(): EventEmitter; * @return $this */ public function setEmitter(EventEmitter $emitter); -} +} \ No newline at end of file diff --git a/src/EventEmitting/EmitterAwarePolyfill.php b/src/EventEmitting/EmitterAwarePolyfill.php index 80c139260..89ce5bdef 100644 --- a/src/EventEmitting/EmitterAwarePolyfill.php +++ b/src/EventEmitting/EmitterAwarePolyfill.php @@ -15,7 +15,7 @@ trait EmitterAwarePolyfill public function getEmitter(): EventEmitter { - if ( ! $this->emitter) { + if (!$this->emitter) { $this->emitter = new EventEmitter(); } @@ -41,4 +41,4 @@ public function getListenerRegistry(): ListenerRegistry { return $this->getEmitter(); } -} +} \ No newline at end of file diff --git a/src/EventEmitting/EventEmitter.php b/src/EventEmitting/EventEmitter.php index 07f396c4d..4305d9ded 100644 --- a/src/EventEmitting/EventEmitter.php +++ b/src/EventEmitting/EventEmitter.php @@ -19,4 +19,4 @@ public function emit(object $event): object { return $this->dispatch($event); } -} +} \ No newline at end of file From 269c0bbd74fd3592524cd48bb6ed5f718bd5c5cc Mon Sep 17 00:00:00 2001 From: Frank de Jonge Date: Fri, 28 Oct 2022 21:30:25 +0200 Subject: [PATCH 084/212] CS --- src/EventEmitting/EmitterAwareInterface.php | 2 +- src/EventEmitting/EmitterAwarePolyfill.php | 2 +- src/EventEmitting/EventEmitter.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/EventEmitting/EmitterAwareInterface.php b/src/EventEmitting/EmitterAwareInterface.php index de63dcc67..19269ab68 100644 --- a/src/EventEmitting/EmitterAwareInterface.php +++ b/src/EventEmitting/EmitterAwareInterface.php @@ -11,4 +11,4 @@ public function getEmitter(): EventEmitter; * @return $this */ public function setEmitter(EventEmitter $emitter); -} \ No newline at end of file +} diff --git a/src/EventEmitting/EmitterAwarePolyfill.php b/src/EventEmitting/EmitterAwarePolyfill.php index 89ce5bdef..a44b065f7 100644 --- a/src/EventEmitting/EmitterAwarePolyfill.php +++ b/src/EventEmitting/EmitterAwarePolyfill.php @@ -41,4 +41,4 @@ public function getListenerRegistry(): ListenerRegistry { return $this->getEmitter(); } -} \ No newline at end of file +} diff --git a/src/EventEmitting/EventEmitter.php b/src/EventEmitting/EventEmitter.php index 4305d9ded..07f396c4d 100644 --- a/src/EventEmitting/EventEmitter.php +++ b/src/EventEmitting/EventEmitter.php @@ -19,4 +19,4 @@ public function emit(object $event): object { return $this->dispatch($event); } -} \ No newline at end of file +} From 2f214a4deb1160cc54c08d1960db8068e51eaed1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 14 Nov 2022 19:24:03 +0000 Subject: [PATCH 085/212] Add ReturnTypeWillChange to jsonSerialize --- tests/Stubs/ScopeEntity.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Stubs/ScopeEntity.php b/tests/Stubs/ScopeEntity.php index 4e4a6bec5..4c93d91dc 100644 --- a/tests/Stubs/ScopeEntity.php +++ b/tests/Stubs/ScopeEntity.php @@ -9,6 +9,7 @@ class ScopeEntity implements ScopeEntityInterface { use EntityTrait; + #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->getIdentifier(); From b3d8a74105af010ef35dbb7e3c7a2fbbb631f480 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 12:11:37 +0000 Subject: [PATCH 086/212] Fix tests by reverting phpstan changes --- src/Grant/AbstractGrant.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 0d5972d29..a03df5090 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -262,7 +262,7 @@ protected function getClientCredentials(ServerRequestInterface $request) $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); - if ($clientSecret === null) { + if ($clientSecret !== null && !\is_string($clientSecret)) { throw OAuthServerException::invalidRequest('client_secret'); } @@ -303,10 +303,16 @@ protected function validateRedirectUri( */ public function validateScopes($scopes, $redirectUri = null) { - if (!\is_array($scopes)) { + if ($scopes === null) { + $scopes = []; + } elseif (\is_string($scopes)) { $scopes = $this->convertScopesQueryStringToArray($scopes); } + if (!\is_array($scopes)) { + throw OAuthServerException::invalidRequest('scope'); + } + $validScopes = []; foreach ($scopes as $scopeItem) { From f04eef8eb7bc05e0f25c7eae8d232dd8dba6f652 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 12:16:02 +0000 Subject: [PATCH 087/212] Fix types --- src/Grant/AbstractGrant.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index a03df5090..d13188c2a 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -294,8 +294,8 @@ protected function validateRedirectUri( /** * Validate scopes in the request. * - * @param string|array $scopes - * @param string $redirectUri + * @param null|string|array $scopes + * @param string $redirectUri * * @throws OAuthServerException * @@ -349,7 +349,7 @@ private function convertScopesQueryStringToArray(string $scopes) * @param ServerRequestInterface $request * @param mixed $default * - * @return null|string + * @return mixed */ protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null) { From 0e5d3dda5b679ea0a8fb44519965d753622295f5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 14:15:39 +0000 Subject: [PATCH 088/212] Fix PHPStan errors --- src/RedirectUriValidators/RedirectUriValidator.php | 2 +- tests/Stubs/GrantType.php | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 29f763544..8723917f3 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -109,7 +109,7 @@ private function matchUriExcludingPort($redirectUri) * * @param string $url * - * @return array + * @return string */ private function parseUrlAndRemovePort($url) { diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index 5270a47a0..f419d9360 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -6,12 +6,14 @@ use DateInterval; use League\Event\EmitterInterface; -use League\OAuth2\Server\CryptKey; +use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; +use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -61,8 +63,9 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) return $authRequest; } - public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) { + return new BearerTokenResponse(); } public function canRespondToAccessTokenRequest(ServerRequestInterface $request) @@ -86,11 +89,15 @@ public function setDefaultScope($scope) { } - public function setPrivateKey(CryptKey $privateKey) + public function setPrivateKey(CryptKeyInterface $privateKey) { } public function setEncryptionKey($key = null) { } + + public function revokeRefreshTokens(bool $willRevoke) + { + } } From c4f493f6d8bec422ea252b0df4ca925bad9cbf90 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 15:20:28 +0000 Subject: [PATCH 089/212] Support PHPStan level 5 --- phpstan.neon.dist | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 32d859033..c54e4aa14 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,9 +1,10 @@ parameters: - level: 4 + level: 5 paths: - src - tests ignoreErrors: - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return League\\OAuth2\\Server\\Entities\\AccessTokenEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' - - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueRefreshToken\(\) should return League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInterface\|null but return statement is missing\.#' \ No newline at end of file + - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueRefreshToken\(\) should return League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInterface\|null but return statement is missing\.#' + - '#Parameter \#1 \$contents of static method Lcobucci\\JWT\\Signer\\Key\\InMemory::plainText\(\) expects non-empty-string, string given\.#' From f67cfb63217338d87ef33bcd18f871b72620f1cb Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 15:57:31 +0000 Subject: [PATCH 090/212] Fix return types for CryptKeyTest and CryptTraitTest --- phpstan.neon.dist | 2 +- tests/Utils/CryptKeyTest.php | 12 ++++++------ tests/Utils/CryptTraitTest.php | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index c54e4aa14..3b729de80 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 5 + level: 6 paths: - src - tests diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 55808fc17..453d30dfa 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -7,14 +7,14 @@ class CryptKeyTest extends TestCase { - public function testNoFile() + public function testNoFile(): void { $this->expectException(\LogicException::class); new CryptKey('undefined file'); } - public function testKeyCreation() + public function testKeyCreation(): void { $keyFile = __DIR__ . '/../Stubs/public.key'; $key = new CryptKey($keyFile, 'secret'); @@ -23,7 +23,7 @@ public function testKeyCreation() $this->assertEquals('secret', $key->getPassPhrase()); } - public function testKeyString() + public function testKeyString(): void { $keyContent = \file_get_contents(__DIR__ . '/../Stubs/public.key'); @@ -52,7 +52,7 @@ public function testKeyString() ); } - public function testUnsupportedKeyType() + public function testUnsupportedKeyType(): void { $this->expectException(\LogicException::class); $this->expectExceptionMessage('Unable to read key'); @@ -76,7 +76,7 @@ public function testUnsupportedKeyType() } } - public function testECKeyType() + public function testECKeyType(): void { try { // Create the keypair @@ -97,7 +97,7 @@ public function testECKeyType() } } - public function testRSAKeyType() + public function testRSAKeyType(): void { try { // Create the keypair diff --git a/tests/Utils/CryptTraitTest.php b/tests/Utils/CryptTraitTest.php index a07d6d50c..912c4e568 100644 --- a/tests/Utils/CryptTraitTest.php +++ b/tests/Utils/CryptTraitTest.php @@ -8,28 +8,28 @@ class CryptTraitTest extends TestCase { - protected $cryptStub; + protected CryptTraitStub $cryptStub; protected function setUp(): void { $this->cryptStub = new CryptTraitStub(); } - public function testEncryptDecryptWithPassword() + public function testEncryptDecryptWithPassword(): void { $this->cryptStub->setEncryptionKey(\base64_encode(\random_bytes(36))); $this->encryptDecrypt(); } - public function testEncryptDecryptWithKey() + public function testEncryptDecryptWithKey(): void { $this->cryptStub->setEncryptionKey(Key::createNewRandomKey()); $this->encryptDecrypt(); } - private function encryptDecrypt() + private function encryptDecrypt(): void { $payload = 'alex loves whisky'; $encrypted = $this->cryptStub->doEncrypt($payload); From b2e680a4504788e7a7cd63ea1a356cdc46e1b3aa Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 18:32:09 +0000 Subject: [PATCH 091/212] Fix return types for StubResponseType --- tests/Stubs/StubResponseType.php | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/tests/Stubs/StubResponseType.php b/tests/Stubs/StubResponseType.php index 7aa71f67e..f04d44395 100644 --- a/tests/Stubs/StubResponseType.php +++ b/tests/Stubs/StubResponseType.php @@ -16,40 +16,30 @@ public function __construct() { } - public function getAccessToken() + public function getAccessToken(): AccessTokenEntityInterface { return $this->accessToken; } - public function getRefreshToken() + public function getRefreshToken(): RefreshTokenEntityInterface|null { return $this->refreshToken; } - /** - * @param \League\OAuth2\Server\Entities\AccessTokenEntityInterface $accessToken - */ - public function setAccessToken(AccessTokenEntityInterface $accessToken) + public function setAccessToken(AccessTokenEntityInterface $accessToken): void { $this->accessToken = $accessToken; } - /** - * @param \League\OAuth2\Server\Entities\RefreshTokenEntityInterface $refreshToken - */ - public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) + public function setRefreshToken(RefreshTokenEntityInterface $refreshToken): void { $this->refreshToken = $refreshToken; } /** - * @param ServerRequestInterface $request - * * @throws \League\OAuth2\Server\Exception\OAuthServerException - * - * @return \Psr\Http\Message\ServerRequestInterface */ - public function validateAccessToken(ServerRequestInterface $request) + public function validateAccessToken(ServerRequestInterface $request): ServerRequestInterface { if ($request->getHeader('authorization')[0] === 'Basic test') { return $request->withAttribute('oauth_access_token_id', 'test'); @@ -58,12 +48,7 @@ public function validateAccessToken(ServerRequestInterface $request) throw OAuthServerException::accessDenied(); } - /** - * @param ResponseInterface $response - * - * @return ResponseInterface - */ - public function generateHttpResponse(ResponseInterface $response) + public function generateHttpResponse(ResponseInterface $response): ResponseInterface { return new Response(); } From 2d116206135d7658a48c17132bf22b777b7521d6 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 18:40:36 +0000 Subject: [PATCH 092/212] Add return types for stubs/GrantType --- tests/Stubs/GrantType.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index f419d9360..be1a5ab67 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -19,25 +19,25 @@ final class GrantType implements GrantTypeInterface { - private $emitter; + private EmitterInterface $emitter; - public function setEmitter(EmitterInterface $emitter = null) + public function setEmitter(EmitterInterface $emitter = null): self { $this->emitter = $emitter; return $this; } - public function getEmitter() + public function getEmitter(): EmitterInterface { return $this->emitter; } - public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void { } - public function getIdentifier() + public function getIdentifier(): string { return 'grant_type_identifier'; } @@ -50,12 +50,12 @@ public function respondToAccessTokenRequest( return $responseType; } - public function canRespondToAuthorizationRequest(ServerRequestInterface $request) + public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool { return true; } - public function validateAuthorizationRequest(ServerRequestInterface $request) + public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequest { $authRequest = new AuthorizationRequest(); $authRequest->setGrantTypeId(self::class); @@ -63,41 +63,41 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) return $authRequest; } - public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): BearerTokenResponse { return new BearerTokenResponse(); } - public function canRespondToAccessTokenRequest(ServerRequestInterface $request) + public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool { return true; } - public function setClientRepository(ClientRepositoryInterface $clientRepository) + public function setClientRepository(ClientRepositoryInterface $clientRepository): void { } - public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository) + public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void { } - public function setScopeRepository(ScopeRepositoryInterface $scopeRepository) + public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void { } - public function setDefaultScope($scope) + public function setDefaultScope($scope): void { } - public function setPrivateKey(CryptKeyInterface $privateKey) + public function setPrivateKey(CryptKeyInterface $privateKey): void { } - public function setEncryptionKey($key = null) + public function setEncryptionKey($key = null): void { } - public function revokeRefreshTokens(bool $willRevoke) + public function revokeRefreshTokens(bool $willRevoke): void { } } From b5ebfd62733741dabbf20389c9538abda72fb1d3 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 19:34:20 +0000 Subject: [PATCH 093/212] Fix types for stubs/CryptTraitStub --- tests/Stubs/CryptTraitStub.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/Stubs/CryptTraitStub.php b/tests/Stubs/CryptTraitStub.php index d4d8f1c68..faaaa27f3 100644 --- a/tests/Stubs/CryptTraitStub.php +++ b/tests/Stubs/CryptTraitStub.php @@ -2,6 +2,7 @@ namespace LeagueTests\Stubs; +use Defuse\Crypto\Key; use League\OAuth2\Server\CryptTrait; class CryptTraitStub @@ -13,17 +14,17 @@ public function __construct() $this->setEncryptionKey(\base64_encode(\random_bytes(36))); } - public function getKey() + public function getKey(): string|Key|null { return $this->encryptionKey; } - public function doEncrypt($unencryptedData) + public function doEncrypt(string $unencryptedData): string { return $this->encrypt($unencryptedData); } - public function doDecrypt($encryptedData) + public function doDecrypt(string $encryptedData): string { return $this->decrypt($encryptedData); } From 840d350ff81062c29282b1146b8334b5332869be Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 20:00:04 +0000 Subject: [PATCH 094/212] Set types in stubs/ClientEntity --- tests/Stubs/ClientEntity.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Stubs/ClientEntity.php b/tests/Stubs/ClientEntity.php index d908248ba..7d8c1a243 100644 --- a/tests/Stubs/ClientEntity.php +++ b/tests/Stubs/ClientEntity.php @@ -10,12 +10,12 @@ class ClientEntity implements ClientEntityInterface { use EntityTrait, ClientTrait; - public function setRedirectUri($uri) + public function setRedirectUri(string|array $uri): void { $this->redirectUri = $uri; } - public function setConfidential() + public function setConfidential(): void { $this->isConfidential = true; } From 4244acc1bccb1edfc445d5ceb5e09405960ba6dc Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 20:07:01 +0000 Subject: [PATCH 095/212] Fix array type for PHPStan --- tests/ResponseTypes/BearerTokenResponseWithParams.php | 6 +++++- tests/Stubs/ClientEntity.php | 3 +++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/ResponseTypes/BearerTokenResponseWithParams.php b/tests/ResponseTypes/BearerTokenResponseWithParams.php index 4ba09f4b9..71c1665b2 100644 --- a/tests/ResponseTypes/BearerTokenResponseWithParams.php +++ b/tests/ResponseTypes/BearerTokenResponseWithParams.php @@ -7,7 +7,11 @@ class BearerTokenResponseWithParams extends BearerTokenResponse { - protected function getExtraParams(AccessTokenEntityInterface $accessToken) + + /** + * @return array + */ + protected function getExtraParams(AccessTokenEntityInterface $accessToken): array { return ['foo' => 'bar', 'token_type' => 'Should not overwrite']; } diff --git a/tests/Stubs/ClientEntity.php b/tests/Stubs/ClientEntity.php index 7d8c1a243..c77ca136b 100644 --- a/tests/Stubs/ClientEntity.php +++ b/tests/Stubs/ClientEntity.php @@ -10,6 +10,9 @@ class ClientEntity implements ClientEntityInterface { use EntityTrait, ClientTrait; + /** + * @param string|string[] $uri + */ public function setRedirectUri(string|array $uri): void { $this->redirectUri = $uri; From ddd6a304d4a43d62260ea25f2587181f75c4b23a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 20:18:59 +0000 Subject: [PATCH 096/212] Add return types for tests --- tests/ResourceServerTest.php | 2 +- tests/ResponseTypes/BearerResponseTypeTest.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/ResourceServerTest.php b/tests/ResourceServerTest.php index 7281070e2..cd622c488 100644 --- a/tests/ResourceServerTest.php +++ b/tests/ResourceServerTest.php @@ -11,7 +11,7 @@ class ResourceServerTest extends TestCase { - public function testValidateAuthenticatedRequest() + public function testValidateAuthenticatedRequest(): void { $server = new ResourceServer( $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index da7242651..7b7c85fa1 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -20,7 +20,7 @@ class BearerResponseTypeTest extends TestCase { - public function testGenerateHttpResponse() + public function testGenerateHttpResponse(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -63,7 +63,7 @@ public function testGenerateHttpResponse() $this->assertObjectHasAttribute('refresh_token', $json); } - public function testGenerateHttpResponseWithExtraParams() + public function testGenerateHttpResponseWithExtraParams(): void { $responseType = new BearerTokenResponseWithParams(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -109,7 +109,7 @@ public function testGenerateHttpResponseWithExtraParams() $this->assertEquals('bar', $json->foo); } - public function testDetermineAccessTokenInHeaderValidToken() + public function testDetermineAccessTokenInHeaderValidToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -152,7 +152,7 @@ public function testDetermineAccessTokenInHeaderValidToken() $this->assertEquals([], $request->getAttribute('oauth_scopes')); } - public function testDetermineAccessTokenInHeaderInvalidJWT() + public function testDetermineAccessTokenInHeaderInvalidJWT(): void { $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -196,7 +196,7 @@ public function testDetermineAccessTokenInHeaderInvalidJWT() } } - public function testDetermineAccessTokenInHeaderRevokedToken() + public function testDetermineAccessTokenInHeaderRevokedToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -241,7 +241,7 @@ public function testDetermineAccessTokenInHeaderRevokedToken() } } - public function testDetermineAccessTokenInHeaderInvalidToken() + public function testDetermineAccessTokenInHeaderInvalidToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -264,7 +264,7 @@ public function testDetermineAccessTokenInHeaderInvalidToken() } } - public function testDetermineMissingBearerInHeader() + public function testDetermineMissingBearerInHeader(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); From bf8b73a464fb3989fa8bd26c26011242c8179b7a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 20:22:00 +0000 Subject: [PATCH 097/212] Add return types for tests --- .../RedirectUriValidatorTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/RedirectUriValidators/RedirectUriValidatorTest.php b/tests/RedirectUriValidators/RedirectUriValidatorTest.php index 2f297c264..11e7a07e4 100644 --- a/tests/RedirectUriValidators/RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/RedirectUriValidatorTest.php @@ -7,7 +7,7 @@ class RedirectUriValidatorTest extends TestCase { - public function testInvalidNonLoopbackUri() + public function testInvalidNonLoopbackUri(): void { $validator = new RedirectUriValidator([ 'https://example.com:8443/endpoint', @@ -22,7 +22,7 @@ public function testInvalidNonLoopbackUri() ); } - public function testValidNonLoopbackUri() + public function testValidNonLoopbackUri(): void { $validator = new RedirectUriValidator([ 'https://example.com:8443/endpoint', @@ -37,7 +37,7 @@ public function testValidNonLoopbackUri() ); } - public function testInvalidLoopbackUri() + public function testInvalidLoopbackUri(): void { $validator = new RedirectUriValidator('http://127.0.0.1:8443/endpoint'); @@ -49,7 +49,7 @@ public function testInvalidLoopbackUri() ); } - public function testValidLoopbackUri() + public function testValidLoopbackUri(): void { $validator = new RedirectUriValidator('http://127.0.0.1:8443/endpoint'); @@ -61,7 +61,7 @@ public function testValidLoopbackUri() ); } - public function testValidIpv6LoopbackUri() + public function testValidIpv6LoopbackUri(): void { $validator = new RedirectUriValidator('http://[::1]:8443/endpoint'); @@ -73,7 +73,7 @@ public function testValidIpv6LoopbackUri() ); } - public function testCanValidateUrn() + public function testCanValidateUrn(): void { $validator = new RedirectUriValidator('urn:ietf:wg:oauth:2.0:oob'); @@ -83,7 +83,7 @@ public function testCanValidateUrn() ); } - public function canValidateCustomSchemeHost() + public function canValidateCustomSchemeHost(): void { $validator = new RedirectUriValidator('msal://redirect'); @@ -93,7 +93,7 @@ public function canValidateCustomSchemeHost() ); } - public function canValidateCustomSchemePath() + public function canValidateCustomSchemePath(): void { $validator = new RedirectUriValidator('com.example.app:/oauth2redirect/example-provider'); From f3007f99f3d095f69b274caed51814a57cfd3649 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 15 Nov 2022 20:23:05 +0000 Subject: [PATCH 098/212] Add return types for tests --- tests/Middleware/ResourceServerMiddlewareTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 106111885..994d5b5d2 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -16,7 +16,7 @@ class ResourceServerMiddlewareTest extends TestCase { - public function testValidResponse() + public function testValidResponse(): void { $server = new ResourceServer( $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), @@ -51,7 +51,7 @@ function () { $this->assertEquals(200, $response->getStatusCode()); } - public function testValidResponseExpiredToken() + public function testValidResponseExpiredToken(): void { $server = new ResourceServer( $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), @@ -86,7 +86,7 @@ function () { $this->assertEquals(401, $response->getStatusCode()); } - public function testErrorResponse() + public function testErrorResponse(): void { $server = new ResourceServer( $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), From bca5d67f6f5e44935dd1f96e507571cf4e6ec60e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 2 Dec 2022 12:00:23 +0000 Subject: [PATCH 099/212] Add types to satisfy phpstan --- src/CryptTrait.php | 7 +- src/Grant/GrantTypeInterface.php | 7 +- .../RedirectUriValidator.php | 8 +- .../AccessTokenRepositoryInterface.php | 28 ++---- .../AuthCodeRepositoryInterface.php | 29 +----- .../RefreshTokenRepositoryInterface.php | 29 +----- src/RequestTypes/AuthorizationRequest.php | 84 ++++------------ .../AuthorizationRequestInterface.php | 84 ++++------------ src/ResponseTypes/AbstractResponseType.php | 17 +--- src/ResponseTypes/BearerTokenResponse.php | 11 +-- src/ResponseTypes/RedirectResponse.php | 17 +--- src/ResponseTypes/ResponseTypeInterface.php | 24 +---- tests/AuthorizationServerTest.php | 23 +++-- .../BearerTokenValidatorTest.php | 4 +- .../PlainVerifierTest.php | 4 +- .../S256VerifierTest.php | 8 +- tests/Exception/OAuthServerExceptionTest.php | 23 ++--- tests/Grant/AbstractGrantTest.php | 56 +++++------ tests/Grant/AuthCodeGrantTest.php | 98 ++++++++++--------- tests/Grant/ClientCredentialsGrantTest.php | 4 +- tests/Grant/ImplicitGrantTest.php | 41 ++++---- tests/Grant/PasswordGrantTest.php | 12 +-- tests/Grant/RefreshTokenGrantTest.php | 26 ++--- .../AuthorizationServerMiddlewareTest.php | 8 +- tests/Stubs/GrantType.php | 3 +- 25 files changed, 225 insertions(+), 430 deletions(-) diff --git a/src/CryptTrait.php b/src/CryptTrait.php index a17e69709..9b746dc9f 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -75,12 +75,7 @@ protected function decrypt($encryptedData) } } - /** - * Set the encryption key - * - * @param string|Key $key - */ - public function setEncryptionKey($key = null) + public function setEncryptionKey(Key|string|null $key = null): void { $this->encryptionKey = $key; } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 9f66639c0..d7e003835 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -135,12 +135,7 @@ public function setDefaultScope($scope); */ public function setPrivateKey(CryptKeyInterface $privateKey); - /** - * Set the encryption key - * - * @param string|Key|null $key - */ - public function setEncryptionKey($key = null); + public function setEncryptionKey(Key|string|null $key = null): void; /** * Enable or prevent the revocation of refresh tokens upon usage. diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 8723917f3..3efa356d3 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -15,16 +15,16 @@ class RedirectUriValidator implements RedirectUriValidatorInterface { /** - * @var array + * @var string[] */ - private $allowedRedirectUris; + private array $allowedRedirectUris; /** * New validator instance for the given uri * - * @param string|array $allowedRedirectUris + * @param string[]|string $allowedRedirectUris */ - public function __construct($allowedRedirectUris) + public function __construct(array|string $allowedRedirectUris) { if (\is_string($allowedRedirectUris)) { $this->allowedRedirectUris = [$allowedRedirectUris]; diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index 72ddf1f4c..fb1c967ce 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -22,36 +22,22 @@ interface AccessTokenRepositoryInterface extends RepositoryInterface /** * Create a new access token * - * @param ClientEntityInterface $clientEntity * @param ScopeEntityInterface[] $scopes * @param mixed $userIdentifier * * @return AccessTokenEntityInterface */ - public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null); + public function getNewToken( + ClientEntityInterface $clientEntity, + array $scopes, $userIdentifier = null + ): AccessTokenEntityInterface; /** - * Persists a new access token to permanent storage. - * - * @param AccessTokenEntityInterface $accessTokenEntity - * * @throws UniqueTokenIdentifierConstraintViolationException */ - public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity); + public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void; - /** - * Revoke an access token. - * - * @param string $tokenId - */ - public function revokeAccessToken($tokenId); + public function revokeAccessToken(string $tokenId): void; - /** - * Check if the access token has been revoked. - * - * @param string $tokenId - * - * @return bool Return true if this token has been revoked - */ - public function isAccessTokenRevoked($tokenId); + public function isAccessTokenRevoked(string $tokenId): bool; } diff --git a/src/Repositories/AuthCodeRepositoryInterface.php b/src/Repositories/AuthCodeRepositoryInterface.php index 2dc285b83..64954aaf5 100644 --- a/src/Repositories/AuthCodeRepositoryInterface.php +++ b/src/Repositories/AuthCodeRepositoryInterface.php @@ -17,35 +17,14 @@ */ interface AuthCodeRepositoryInterface extends RepositoryInterface { - /** - * Creates a new AuthCode - * - * @return AuthCodeEntityInterface - */ - public function getNewAuthCode(); + public function getNewAuthCode(): AuthCodeEntityInterface; /** - * Persists a new auth code to permanent storage. - * - * @param AuthCodeEntityInterface $authCodeEntity - * * @throws UniqueTokenIdentifierConstraintViolationException */ - public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity); + public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void; - /** - * Revoke an auth code. - * - * @param string $codeId - */ - public function revokeAuthCode($codeId); + public function revokeAuthCode(string $codeId): void; - /** - * Check if the auth code has been revoked. - * - * @param string $codeId - * - * @return bool Return true if this code has been revoked - */ - public function isAuthCodeRevoked($codeId); + public function isAuthCodeRevoked(string $codeId): bool; } diff --git a/src/Repositories/RefreshTokenRepositoryInterface.php b/src/Repositories/RefreshTokenRepositoryInterface.php index a769cf6d3..106a2ef7d 100644 --- a/src/Repositories/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/RefreshTokenRepositoryInterface.php @@ -17,35 +17,14 @@ */ interface RefreshTokenRepositoryInterface extends RepositoryInterface { - /** - * Creates a new refresh token - * - * @return RefreshTokenEntityInterface|null - */ - public function getNewRefreshToken(); + public function getNewRefreshToken(): ?RefreshTokenEntityInterface; /** - * Create a new refresh token_name. - * - * @param RefreshTokenEntityInterface $refreshTokenEntity - * * @throws UniqueTokenIdentifierConstraintViolationException */ - public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity); + public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void; - /** - * Revoke the refresh token. - * - * @param string $tokenId - */ - public function revokeRefreshToken($tokenId); + public function revokeRefreshToken(string $tokenId): void; - /** - * Check if the refresh token has been revoked. - * - * @param string $tokenId - * - * @return bool Return true if this token has been revoked - */ - public function isRefreshTokenRevoked($tokenId); + public function isRefreshTokenRevoked(string $tokenId): bool; } diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index d05e5b394..f9460d0f5 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -78,50 +78,32 @@ class AuthorizationRequest implements AuthorizationRequestInterface */ protected $codeChallengeMethod; - /** - * @return string - */ - public function getGrantTypeId() + public function getGrantTypeId(): string { return $this->grantTypeId; } - /** - * @param string $grantTypeId - */ - public function setGrantTypeId($grantTypeId) + public function setGrantTypeId(string $grantTypeId): void { $this->grantTypeId = $grantTypeId; } - /** - * @return ClientEntityInterface - */ - public function getClient() + public function getClient(): ClientEntityInterface { return $this->client; } - /** - * @param ClientEntityInterface $client - */ - public function setClient(ClientEntityInterface $client) + public function setClient(ClientEntityInterface $client): void { $this->client = $client; } - /** - * @return UserEntityInterface|null - */ - public function getUser() + public function getUser(): ?UserEntityInterface { return $this->user; } - /** - * @param UserEntityInterface $user - */ - public function setUser(UserEntityInterface $user) + public function setUser(UserEntityInterface $user): void { $this->user = $user; } @@ -129,7 +111,7 @@ public function setUser(UserEntityInterface $user) /** * @return ScopeEntityInterface[] */ - public function getScopes() + public function getScopes(): array { return $this->scopes; } @@ -137,87 +119,57 @@ public function getScopes() /** * @param ScopeEntityInterface[] $scopes */ - public function setScopes(array $scopes) + public function setScopes(array $scopes): void { $this->scopes = $scopes; } - /** - * @return bool - */ - public function isAuthorizationApproved() + public function isAuthorizationApproved(): bool { return $this->authorizationApproved; } - /** - * @param bool $authorizationApproved - */ - public function setAuthorizationApproved($authorizationApproved) + public function setAuthorizationApproved(bool $authorizationApproved): void { $this->authorizationApproved = $authorizationApproved; } - /** - * @return string|null - */ - public function getRedirectUri() + public function getRedirectUri(): ?string { return $this->redirectUri; } - /** - * @param string|null $redirectUri - */ - public function setRedirectUri($redirectUri) + public function setRedirectUri(?string $redirectUri): void { $this->redirectUri = $redirectUri; } - /** - * @return string|null - */ - public function getState() + public function getState(): ?string { return $this->state; } - /** - * @param string $state - */ - public function setState($state) + public function setState(string $state): void { $this->state = $state; } - /** - * @return string - */ - public function getCodeChallenge() + public function getCodeChallenge(): ?string { return $this->codeChallenge; } - /** - * @param string $codeChallenge - */ - public function setCodeChallenge($codeChallenge) + public function setCodeChallenge(string $codeChallenge): void { $this->codeChallenge = $codeChallenge; } - /** - * @return string - */ - public function getCodeChallengeMethod() + public function getCodeChallengeMethod(): ?string { return $this->codeChallengeMethod; } - /** - * @param string $codeChallengeMethod - */ - public function setCodeChallengeMethod($codeChallengeMethod) + public function setCodeChallengeMethod(string $codeChallengeMethod): void { $this->codeChallengeMethod = $codeChallengeMethod; } diff --git a/src/RequestTypes/AuthorizationRequestInterface.php b/src/RequestTypes/AuthorizationRequestInterface.php index 869dc1ea2..10ea08ace 100644 --- a/src/RequestTypes/AuthorizationRequestInterface.php +++ b/src/RequestTypes/AuthorizationRequestInterface.php @@ -15,93 +15,45 @@ interface AuthorizationRequestInterface { - /** - * @return UserEntityInterface|null - */ - public function getUser(); + public function getUser(): UserEntityInterface|null; - /** - * @param string $state - */ - public function setState($state); + public function setState(string $state): void; - /** - * @return ClientEntityInterface - */ - public function getClient(); + public function getClient(): ClientEntityInterface; - /** - * @param bool $authorizationApproved - */ - public function setAuthorizationApproved($authorizationApproved); + public function setAuthorizationApproved(bool $authorizationApproved): void; /** * @param ScopeEntityInterface[] $scopes */ - public function setScopes(array $scopes); + public function setScopes(array $scopes): void; - /** - * @param string|null $redirectUri - */ - public function setRedirectUri($redirectUri); + public function setRedirectUri(?string $redirectUri): void; - /** - * @return string|null - */ - public function getRedirectUri(); + public function getRedirectUri(): ?string; - /** - * @return string - */ - public function getCodeChallengeMethod(); + public function getCodeChallengeMethod(): ?string; - /** - * @param string $grantTypeId - */ - public function setGrantTypeId($grantTypeId); + public function setGrantTypeId(string $grantTypeId): void; - /** - * @param UserEntityInterface $user - */ - public function setUser(UserEntityInterface $user); + public function setUser(UserEntityInterface $user): void; - /** - * @param ClientEntityInterface $client - */ - public function setClient(ClientEntityInterface $client); + public function setClient(ClientEntityInterface $client): void; - /** - * @param string $codeChallenge - */ - public function setCodeChallenge($codeChallenge); + public function setCodeChallenge(string $codeChallenge): void; - /** - * @return bool - */ - public function isAuthorizationApproved(); + public function isAuthorizationApproved(): bool; - /** - * @return string|null - */ - public function getState(); + public function getState(): ?string; - /** - * @return string - */ - public function getCodeChallenge(); + public function getCodeChallenge(): ?string; - /** - * @param string $codeChallengeMethod - */ - public function setCodeChallengeMethod($codeChallengeMethod); + public function setCodeChallengeMethod(string $codeChallengeMethod): void; /** * @return ScopeEntityInterface[] */ - public function getScopes(); + public function getScopes(): array; - /** - * @return string - */ - public function getGrantTypeId(); + public function getGrantTypeId(): string; } diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index f5f201908..dd2c15041 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -35,28 +35,17 @@ abstract class AbstractResponseType implements ResponseTypeInterface */ protected $privateKey; - /** - * {@inheritdoc} - */ - public function setAccessToken(AccessTokenEntityInterface $accessToken) + public function setAccessToken(AccessTokenEntityInterface $accessToken): void { $this->accessToken = $accessToken; } - /** - * {@inheritdoc} - */ - public function setRefreshToken(RefreshTokenEntityInterface $refreshToken) + public function setRefreshToken(RefreshTokenEntityInterface $refreshToken): void { $this->refreshToken = $refreshToken; } - /** - * Set the private key - * - * @param CryptKeyInterface $key - */ - public function setPrivateKey(CryptKeyInterface $key) + public function setPrivateKey(CryptKeyInterface $key): void { $this->privateKey = $key; } diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 33c1606e8..c06216346 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -18,10 +18,7 @@ class BearerTokenResponse extends AbstractResponseType { - /** - * {@inheritdoc} - */ - public function generateHttpResponse(ResponseInterface $response) + public function generateHttpResponse(ResponseInterface $response): ResponseInterface { $expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp(); @@ -70,11 +67,9 @@ public function generateHttpResponse(ResponseInterface $response) * AuthorizationServer::getResponseType() to pull in your version of * this class rather than the default. * - * @param AccessTokenEntityInterface $accessToken - * - * @return array + * @return mixed[] */ - protected function getExtraParams(AccessTokenEntityInterface $accessToken) + protected function getExtraParams(AccessTokenEntityInterface $accessToken): array { return []; } diff --git a/src/ResponseTypes/RedirectResponse.php b/src/ResponseTypes/RedirectResponse.php index e4639148d..2006a8f0d 100644 --- a/src/ResponseTypes/RedirectResponse.php +++ b/src/ResponseTypes/RedirectResponse.php @@ -15,25 +15,14 @@ class RedirectResponse extends AbstractResponseType { - /** - * @var string - */ - private $redirectUri; + private string $redirectUri; - /** - * @param string $redirectUri - */ - public function setRedirectUri($redirectUri) + public function setRedirectUri(string $redirectUri): void { $this->redirectUri = $redirectUri; } - /** - * @param ResponseInterface $response - * - * @return ResponseInterface - */ - public function generateHttpResponse(ResponseInterface $response) + public function generateHttpResponse(ResponseInterface $response): ResponseInterface { return $response->withStatus(302)->withHeader('Location', $this->redirectUri); } diff --git a/src/ResponseTypes/ResponseTypeInterface.php b/src/ResponseTypes/ResponseTypeInterface.php index 5eddd6079..71efba5e8 100644 --- a/src/ResponseTypes/ResponseTypeInterface.php +++ b/src/ResponseTypes/ResponseTypeInterface.php @@ -18,27 +18,11 @@ interface ResponseTypeInterface { - /** - * @param AccessTokenEntityInterface $accessToken - */ - public function setAccessToken(AccessTokenEntityInterface $accessToken); + public function setAccessToken(AccessTokenEntityInterface $accessToken): void; - /** - * @param RefreshTokenEntityInterface $refreshToken - */ - public function setRefreshToken(RefreshTokenEntityInterface $refreshToken); + public function setRefreshToken(RefreshTokenEntityInterface $refreshToken): void; - /** - * @param ResponseInterface $response - * - * @return ResponseInterface - */ - public function generateHttpResponse(ResponseInterface $response); + public function generateHttpResponse(ResponseInterface $response): ResponseInterface; - /** - * Set the encryption key - * - * @param string|Key|null $key - */ - public function setEncryptionKey($key = null); + public function setEncryptionKey(Key|string|null $key = null): void; } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index ff395cdbe..e49e8ac1a 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -60,7 +60,7 @@ public function testGrantTypeGetsEnabled() } */ - public function testRespondToRequestInvalidGrantType() + public function testRespondToRequestInvalidGrantType(): void { $server = new AuthorizationServer( $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), @@ -81,7 +81,7 @@ public function testRespondToRequestInvalidGrantType() } } - public function testRespondToRequest() + public function testRespondToRequest(): void { $client = new ClientEntity(); @@ -118,7 +118,7 @@ public function testRespondToRequest() $this->assertEquals(200, $response->getStatusCode()); } - public function testGetResponseType() + public function testGetResponseType(): void { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -137,7 +137,7 @@ public function testGetResponseType() $this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); } - public function testGetResponseTypeExtended() + public function testGetResponseTypeExtended(): void { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; @@ -170,19 +170,18 @@ public function testGetResponseTypeExtended() $this->assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType)); } - public function testMultipleRequestsGetDifferentResponseTypeInstances() + public function testMultipleRequestsGetDifferentResponseTypeInstances(): void { $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; $responseTypePrototype = new class extends BearerTokenResponse { - /* @return null|CryptKeyInterface */ - public function getPrivateKey() + public function getPrivateKey(): CryptKeyInterface|null { return $this->privateKey; } - public function getEncryptionKey() + public function getEncryptionKey(): string|null { return $this->encryptionKey; } @@ -222,7 +221,7 @@ public function getEncryptionKey() $this->assertNotSame($responseTypeA, $responseTypeB); } - public function testCompleteAuthorizationRequest() + public function testCompleteAuthorizationRequest(): void { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -257,7 +256,7 @@ public function testCompleteAuthorizationRequest() ); } - public function testValidateAuthorizationRequest() + public function testValidateAuthorizationRequest(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -304,7 +303,7 @@ public function testValidateAuthorizationRequest() $this->assertInstanceOf(AuthorizationRequest::class, $server->validateAuthorizationRequest($request)); } - public function testValidateAuthorizationRequestWithMissingRedirectUri() + public function testValidateAuthorizationRequestWithMissingRedirectUri(): void { $client = new ClientEntity(); $client->setConfidential(); @@ -350,7 +349,7 @@ public function testValidateAuthorizationRequestWithMissingRedirectUri() } } - public function testValidateAuthorizationRequestUnregistered() + public function testValidateAuthorizationRequestUnregistered(): void { $server = new AuthorizationServer( $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 838d2bbae..51ab8ecba 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -15,7 +15,7 @@ class BearerTokenValidatorTest extends TestCase { - public function testBearerTokenValidatorAcceptsValidToken() + public function testBearerTokenValidatorAcceptsValidToken(): void { $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -43,7 +43,7 @@ public function testBearerTokenValidatorAcceptsValidToken() $this->assertArrayHasKey('authorization', $validRequest->getHeaders()); } - public function testBearerTokenValidatorRejectsExpiredToken() + public function testBearerTokenValidatorRejectsExpiredToken(): void { $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/CodeChallengeVerifiers/PlainVerifierTest.php b/tests/CodeChallengeVerifiers/PlainVerifierTest.php index 437c0c2f6..62d3da2c7 100644 --- a/tests/CodeChallengeVerifiers/PlainVerifierTest.php +++ b/tests/CodeChallengeVerifiers/PlainVerifierTest.php @@ -7,14 +7,14 @@ class PlainVerifierTest extends TestCase { - public function testGetMethod() + public function testGetMethod(): void { $verifier = new PlainVerifier(); $this->assertEquals('plain', $verifier->getMethod()); } - public function testVerifyCodeChallenge() + public function testVerifyCodeChallenge(): void { $verifier = new PlainVerifier(); diff --git a/tests/CodeChallengeVerifiers/S256VerifierTest.php b/tests/CodeChallengeVerifiers/S256VerifierTest.php index 58185a452..14ef1957f 100644 --- a/tests/CodeChallengeVerifiers/S256VerifierTest.php +++ b/tests/CodeChallengeVerifiers/S256VerifierTest.php @@ -7,14 +7,14 @@ class S256VerifierTest extends TestCase { - public function testGetMethod() + public function testGetMethod(): void { $verifier = new S256Verifier(); $this->assertEquals('S256', $verifier->getMethod()); } - public function testVerifyCodeChallengeSucceeds() + public function testVerifyCodeChallengeSucceeds(): void { $codeChallenge = $this->createCodeChallenge('foo'); $verifier = new S256Verifier(); @@ -22,7 +22,7 @@ public function testVerifyCodeChallengeSucceeds() $this->assertTrue($verifier->verifyCodeChallenge('foo', $codeChallenge)); } - public function testVerifyCodeChallengeFails() + public function testVerifyCodeChallengeFails(): void { $codeChallenge = $this->createCodeChallenge('bar'); $verifier = new S256Verifier(); @@ -30,7 +30,7 @@ public function testVerifyCodeChallengeFails() $this->assertFalse($verifier->verifyCodeChallenge('foo', $codeChallenge)); } - private function createCodeChallenge($codeVerifier) + private function createCodeChallenge(string $codeVerifier): string { return \strtr(\rtrim(\base64_encode(\hash('sha256', $codeVerifier, true)), '='), '+/', '-_'); } diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index 38b86d433..d0d6641b6 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -9,10 +9,11 @@ use League\OAuth2\Server\Grant\AbstractGrant; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; class OAuthServerExceptionTest extends TestCase { - public function testInvalidClientExceptionSetsAuthenticateHeader() + public function testInvalidClientExceptionSetsAuthenticateHeader(): void { $serverRequest = (new ServerRequest()) ->withParsedBody([ @@ -29,7 +30,7 @@ public function testInvalidClientExceptionSetsAuthenticateHeader() } } - public function testInvalidClientExceptionSetsBearerAuthenticateHeader() + public function testInvalidClientExceptionSetsBearerAuthenticateHeader(): void { $serverRequest = (new ServerRequest()) ->withParsedBody([ @@ -46,7 +47,7 @@ public function testInvalidClientExceptionSetsBearerAuthenticateHeader() } } - public function testInvalidClientExceptionOmitsAuthenticateHeader() + public function testInvalidClientExceptionOmitsAuthenticateHeader(): void { $serverRequest = (new ServerRequest()) ->withParsedBody([ @@ -62,7 +63,7 @@ public function testInvalidClientExceptionOmitsAuthenticateHeader() } } - public function testInvalidClientExceptionOmitsAuthenticateHeaderGivenEmptyAuthorizationHeader() + public function testInvalidClientExceptionOmitsAuthenticateHeaderGivenEmptyAuthorizationHeader(): void { $serverRequest = (new ServerRequest()) ->withParsedBody([ @@ -84,7 +85,7 @@ public function testInvalidClientExceptionOmitsAuthenticateHeaderGivenEmptyAutho * * @throws OAuthServerException */ - private function issueInvalidClientException($serverRequest) + private function issueInvalidClientException(ServerRequestInterface $serverRequest): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('validateClient')->willReturn(false); @@ -100,21 +101,21 @@ private function issueInvalidClientException($serverRequest) $validateClientMethod->invoke($grantMock, $serverRequest); } - public function testHasRedirect() + public function testHasRedirect(): void { $exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error'); $this->assertTrue($exceptionWithRedirect->hasRedirect()); } - public function testDoesNotHaveRedirect() + public function testDoesNotHaveRedirect(): void { $exceptionWithoutRedirect = OAuthServerException::accessDenied('Some hint'); $this->assertFalse($exceptionWithoutRedirect->hasRedirect()); } - public function testHasPrevious() + public function testHasPrevious(): void { $previous = new Exception('This is the previous'); $exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $previous); @@ -124,21 +125,21 @@ public function testHasPrevious() $this->assertSame('This is the previous', $previousMessage); } - public function testDoesNotHavePrevious() + public function testDoesNotHavePrevious(): void { $exceptionWithoutPrevious = OAuthServerException::accessDenied(); $this->assertNull($exceptionWithoutPrevious->getPrevious()); } - public function testCanGetRedirectionUri() + public function testCanGetRedirectionUri(): void { $exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error'); $this->assertSame('https://example.com/error', $exceptionWithRedirect->getRedirectUri()); } - public function testInvalidCredentialsIsInvalidGrant() + public function testInvalidCredentialsIsInvalidGrant(): void { $exception = OAuthServerException::invalidCredentials(); diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 618545efe..9bf569c66 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -24,7 +24,7 @@ class AbstractGrantTest extends TestCase { - public function testHttpBasicWithPassword() + public function testHttpBasicWithPassword(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -37,7 +37,7 @@ public function testHttpBasicWithPassword() $this->assertSame(['Open', 'Sesame'], $basicAuthMethod->invoke($grantMock, $serverRequest)); } - public function testHttpBasicNoPassword() + public function testHttpBasicNoPassword(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -50,7 +50,7 @@ public function testHttpBasicNoPassword() $this->assertSame(['Open', ''], $basicAuthMethod->invoke($grantMock, $serverRequest)); } - public function testHttpBasicNotBasic() + public function testHttpBasicNotBasic(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -63,7 +63,7 @@ public function testHttpBasicNotBasic() $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } - public function testHttpBasicNotBase64() + public function testHttpBasicNotBase64(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -76,7 +76,7 @@ public function testHttpBasicNotBase64() $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } - public function testHttpBasicNoColon() + public function testHttpBasicNoColon(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -89,7 +89,7 @@ public function testHttpBasicNoColon() $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } - public function testGetClientCredentialsClientSecretNotAString() + public function testGetClientCredentialsClientSecretNotAString(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -121,7 +121,7 @@ public function testGetClientCredentialsClientSecretNotAString() $getClientCredentialsMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientPublic() + public function testValidateClientPublic(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -146,7 +146,7 @@ public function testValidateClientPublic() $this->assertEquals($client, $result); } - public function testValidateClientConfidential() + public function testValidateClientConfidential(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -173,7 +173,7 @@ public function testValidateClientConfidential() $this->assertEquals($client, $result); } - public function testValidateClientMissingClientId() + public function testValidateClientMissingClientId(): void { $client = new ClientEntity(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -194,7 +194,7 @@ public function testValidateClientMissingClientId() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientMissingClientSecret() + public function testValidateClientMissingClientSecret(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('validateClient')->willReturn(false); @@ -217,7 +217,7 @@ public function testValidateClientMissingClientSecret() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientInvalidClientSecret() + public function testValidateClientInvalidClientSecret(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('validateClient')->willReturn(false); @@ -241,7 +241,7 @@ public function testValidateClientInvalidClientSecret() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientInvalidRedirectUri() + public function testValidateClientInvalidRedirectUri(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -267,7 +267,7 @@ public function testValidateClientInvalidRedirectUri() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientInvalidRedirectUriArray() + public function testValidateClientInvalidRedirectUriArray(): void { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar']); @@ -293,7 +293,7 @@ public function testValidateClientInvalidRedirectUriArray() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientMalformedRedirectUri() + public function testValidateClientMalformedRedirectUri(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -319,7 +319,7 @@ public function testValidateClientMalformedRedirectUri() $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - public function testValidateClientBadClient() + public function testValidateClientBadClient(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('validateClient')->willReturn(false); @@ -343,7 +343,7 @@ public function testValidateClientBadClient() $validateClientMethod->invoke($grantMock, $serverRequest, true); } - public function testCanRespondToRequest() + public function testCanRespondToRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); @@ -355,7 +355,7 @@ public function testCanRespondToRequest() $this->assertTrue($grantMock->canRespondToAccessTokenRequest($serverRequest)); } - public function testIssueRefreshToken() + public function testIssueRefreshToken(): void { $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepoMock @@ -379,7 +379,7 @@ public function testIssueRefreshToken() $this->assertEquals($accessToken, $refreshToken->getAccessToken()); } - public function testIssueNullRefreshToken() + public function testIssueNullRefreshToken(): void { $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepoMock @@ -400,7 +400,7 @@ public function testIssueNullRefreshToken() $this->assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); } - public function testIssueAccessToken() + public function testIssueAccessToken(): void { $accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepoMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -425,7 +425,7 @@ public function testIssueAccessToken() $this->assertInstanceOf(AccessTokenEntityInterface::class, $accessToken); } - public function testIssueAuthCode() + public function testIssueAuthCode(): void { $authCodeRepoMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepoMock->expects($this->once())->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); @@ -451,7 +451,7 @@ public function testIssueAuthCode() ); } - public function testGetCookieParameter() + public function testGetCookieParameter(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); @@ -468,7 +468,7 @@ public function testGetCookieParameter() $this->assertEquals('foo', $method->invoke($grantMock, 'bar', $serverRequest, 'foo')); } - public function testGetQueryStringParameter() + public function testGetQueryStringParameter(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); @@ -485,7 +485,7 @@ public function testGetQueryStringParameter() $this->assertEquals('foo', $method->invoke($grantMock, 'bar', $serverRequest, 'foo')); } - public function testValidateScopes() + public function testValidateScopes(): void { $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -498,7 +498,7 @@ public function testValidateScopes() $this->assertEquals([$scope, $scope, $scope], $grantMock->validateScopes('basic test 0 ')); } - public function testValidateScopesBadScope() + public function testValidateScopesBadScope(): void { $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(null); @@ -512,7 +512,7 @@ public function testValidateScopesBadScope() $grantMock->validateScopes('basic '); } - public function testGenerateUniqueIdentifier() + public function testGenerateUniqueIdentifier(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -523,13 +523,13 @@ public function testGenerateUniqueIdentifier() $this->assertIsString($method->invoke($grantMock)); } - public function testCanRespondToAuthorizationRequest() + public function testCanRespondToAuthorizationRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $this->assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest())); } - public function testValidateAuthorizationRequest() + public function testValidateAuthorizationRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -538,7 +538,7 @@ public function testValidateAuthorizationRequest() $grantMock->validateAuthorizationRequest(new ServerRequest()); } - public function testCompleteAuthorizationRequest() + public function testCompleteAuthorizationRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 7e5cb80cd..086f37dde 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -46,7 +46,7 @@ public function setUp(): void $this->cryptStub = new CryptTraitStub(); } - public function testGetIdentifier() + public function testGetIdentifier(): void { $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -57,7 +57,7 @@ public function testGetIdentifier() $this->assertEquals('authorization_code', $grant->getIdentifier()); } - public function testCanRespondToAuthorizationRequest() + public function testCanRespondToAuthorizationRequest(): void { $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -82,7 +82,7 @@ public function testCanRespondToAuthorizationRequest() $this->assertTrue($grant->canRespondToAuthorizationRequest($request)); } - public function testValidateAuthorizationRequest() + public function testValidateAuthorizationRequest(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -122,7 +122,7 @@ public function testValidateAuthorizationRequest() $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - public function testValidateAuthorizationRequestRedirectUriArray() + public function testValidateAuthorizationRequestRedirectUriArray(): void { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar']); @@ -161,7 +161,7 @@ public function testValidateAuthorizationRequestRedirectUriArray() $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - public function testValidateAuthorizationRequestWithoutRedirectUri() + public function testValidateAuthorizationRequestWithoutRedirectUri(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -203,7 +203,7 @@ public function testValidateAuthorizationRequestWithoutRedirectUri() $this->assertEmpty($authorizationRequest->getRedirectUri()); } - public function testValidateAuthorizationRequestCodeChallenge() + public function testValidateAuthorizationRequestCodeChallenge(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -243,7 +243,7 @@ public function testValidateAuthorizationRequestCodeChallenge() $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooShort() + public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooShort(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -270,7 +270,7 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooSho $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLong() + public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLong(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -297,7 +297,7 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLon $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters() + public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -324,7 +324,7 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestMissingClientId() + public function testValidateAuthorizationRequestMissingClientId(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -345,7 +345,7 @@ public function testValidateAuthorizationRequestMissingClientId() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestInvalidClientId() + public function testValidateAuthorizationRequestInvalidClientId(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn(null); @@ -368,7 +368,7 @@ public function testValidateAuthorizationRequestInvalidClientId() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestBadRedirectUriString() + public function testValidateAuthorizationRequestBadRedirectUriString(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -394,7 +394,7 @@ public function testValidateAuthorizationRequestBadRedirectUriString() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestBadRedirectUriArray() + public function testValidateAuthorizationRequestBadRedirectUriArray(): void { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar']); @@ -420,7 +420,7 @@ public function testValidateAuthorizationRequestBadRedirectUriArray() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestInvalidCodeChallengeMethod() + public function testValidateAuthorizationRequestInvalidCodeChallengeMethod(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -455,7 +455,7 @@ public function testValidateAuthorizationRequestInvalidCodeChallengeMethod() $grant->validateAuthorizationRequest($request); } - public function testCompleteAuthorizationRequest() + public function testCompleteAuthorizationRequest(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -476,7 +476,7 @@ public function testCompleteAuthorizationRequest() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient() + public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient(): void { $client = new ClientEntity(); $client->setRedirectUri(['uriOne', 'uriTwo']); @@ -499,7 +499,7 @@ public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testCompleteAuthorizationRequestDenied() + public function testCompleteAuthorizationRequestDenied(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(false); @@ -523,7 +523,7 @@ public function testCompleteAuthorizationRequestDenied() $grant->completeAuthorizationRequest($authRequest); } - public function testRespondToAccessTokenRequest() + public function testRespondToAccessTokenRequest(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -592,7 +592,7 @@ public function testRespondToAccessTokenRequest() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRespondToAccessTokenRequestUsingHttpBasicAuth() + public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -658,7 +658,7 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRespondToAccessTokenRequestForPublicClient() + public function testRespondToAccessTokenRequestForPublicClient(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -726,7 +726,7 @@ public function testRespondToAccessTokenRequestForPublicClient() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRespondToAccessTokenRequestNullRefreshToken() + public function testRespondToAccessTokenRequestNullRefreshToken(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -794,7 +794,7 @@ public function testRespondToAccessTokenRequestNullRefreshToken() $this->assertNull($response->getRefreshToken()); } - public function testRespondToAccessTokenRequestCodeChallengePlain() + public function testRespondToAccessTokenRequestCodeChallengePlain(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -867,7 +867,7 @@ public function testRespondToAccessTokenRequestCodeChallengePlain() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRespondToAccessTokenRequestCodeChallengeS256() + public function testRespondToAccessTokenRequestCodeChallengeS256(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -940,7 +940,7 @@ public function testRespondToAccessTokenRequestCodeChallengeS256() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRespondToAccessTokenRequestMissingRedirectUri() + public function testRespondToAccessTokenRequestMissingRedirectUri(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -988,7 +988,7 @@ public function testRespondToAccessTokenRequestMissingRedirectUri() $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - public function testRespondToAccessTokenRequestRedirectUriMismatch() + public function testRespondToAccessTokenRequestRedirectUriMismatch(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1037,7 +1037,7 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch() $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - public function testRespondToAccessTokenRequestMissingCode() + public function testRespondToAccessTokenRequestMissingCode(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -1082,7 +1082,7 @@ public function testRespondToAccessTokenRequestMissingCode() $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode() + public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -1135,7 +1135,7 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode } } - public function testRespondToAccessTokenRequestWithAuthCodeNotAString() + public function testRespondToAccessTokenRequestWithAuthCodeNotAString(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -1173,7 +1173,7 @@ public function testRespondToAccessTokenRequestWithAuthCodeNotAString() $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - public function testRespondToAccessTokenRequestExpiredCode() + public function testRespondToAccessTokenRequestExpiredCode(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -1226,7 +1226,7 @@ public function testRespondToAccessTokenRequestExpiredCode() } } - public function testRespondToAccessTokenRequestRevokedCode() + public function testRespondToAccessTokenRequestRevokedCode(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1291,7 +1291,7 @@ public function testRespondToAccessTokenRequestRevokedCode() } } - public function testRespondToAccessTokenRequestClientMismatch() + public function testRespondToAccessTokenRequestClientMismatch(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1352,7 +1352,7 @@ public function testRespondToAccessTokenRequestClientMismatch() } } - public function testRespondToAccessTokenRequestBadCodeEncryption() + public function testRespondToAccessTokenRequestBadCodeEncryption(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1402,7 +1402,7 @@ public function testRespondToAccessTokenRequestBadCodeEncryption() } } - public function testRespondToAccessTokenRequestBadCodeVerifierPlain() + public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1475,7 +1475,7 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain() } } - public function testRespondToAccessTokenRequestBadCodeVerifierS256() + public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1548,7 +1548,7 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256() } } - public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidChars() + public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidChars(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1621,7 +1621,7 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva } } - public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidLength() + public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidLength(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1694,7 +1694,7 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva } } - public function testRespondToAccessTokenRequestMissingCodeVerifier() + public function testRespondToAccessTokenRequestMissingCodeVerifier(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1766,7 +1766,7 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier() } } - public function testAuthCodeRepositoryUniqueConstraintCheck() + public function testAuthCodeRepositoryUniqueConstraintCheck(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -1799,7 +1799,7 @@ public function testAuthCodeRepositoryUniqueConstraintCheck() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testAuthCodeRepositoryFailToPersist() + public function testAuthCodeRepositoryFailToPersist(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -1824,7 +1824,7 @@ public function testAuthCodeRepositoryFailToPersist() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop() + public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -1848,7 +1848,7 @@ public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testRefreshTokenRepositoryUniqueConstraintCheck() + public function testRefreshTokenRepositoryUniqueConstraintCheck(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1926,7 +1926,7 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRefreshTokenRepositoryFailToPersist() + public function testRefreshTokenRepositoryFailToPersist(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -1997,7 +1997,7 @@ public function testRefreshTokenRepositoryFailToPersist() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop() + public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -2068,7 +2068,7 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - public function testCompleteAuthorizationRequestNoUser() + public function testCompleteAuthorizationRequestNoUser(): void { $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -2081,7 +2081,7 @@ public function testCompleteAuthorizationRequestNoUser() $grant->completeAuthorizationRequest(new AuthorizationRequest()); } - public function testPublicClientAuthCodeRequestRejectedWhenCodeChallengeRequiredButNotGiven() + public function testPublicClientAuthCodeRequestRejectedWhenCodeChallengeRequiredButNotGiven(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -2116,7 +2116,7 @@ public function testPublicClientAuthCodeRequestRejectedWhenCodeChallengeRequired $grant->validateAuthorizationRequest($request); } - public function testUseValidRedirectUriIfScopeCheckFails() + public function testUseValidRedirectUriIfScopeCheckFails(): void { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar', 'http://bar/foo']); @@ -2162,9 +2162,11 @@ public function testUseValidRedirectUriIfScopeCheckFails() } } - public function testThrowExceptionWhenNoClientRedirectUriRegistered() + public function testThrowExceptionWhenNoClientRedirectUriRegistered(): void { - $client = (new ClientEntity())->setConfidential(); + $client = new ClientEntity(); + + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index 13ea78bae..e3d9b9268 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -20,13 +20,13 @@ class ClientCredentialsGrantTest extends TestCase { const DEFAULT_SCOPE = 'basic'; - public function testGetIdentifier() + public function testGetIdentifier(): void { $grant = new ClientCredentialsGrant(); $this->assertEquals('client_credentials', $grant->getIdentifier()); } - public function testRespondToRequest() + public function testRespondToRequest(): void { $client = new ClientEntity(); $client->setConfidential(); diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 546450384..45777b755 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -26,23 +26,20 @@ class ImplicitGrantTest extends TestCase { const DEFAULT_SCOPE = 'basic'; - /** - * CryptTrait stub - */ - protected $cryptStub; + protected CryptTraitStub $cryptStub; public function setUp(): void { $this->cryptStub = new CryptTraitStub(); } - public function testGetIdentifier() + public function testGetIdentifier(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); $this->assertEquals('implicit', $grant->getIdentifier()); } - public function testCanRespondToAccessTokenRequest() + public function testCanRespondToAccessTokenRequest(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); @@ -51,7 +48,7 @@ public function testCanRespondToAccessTokenRequest() ); } - public function testRespondToAccessTokenRequest() + public function testRespondToAccessTokenRequest(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); @@ -64,7 +61,7 @@ public function testRespondToAccessTokenRequest() ); } - public function testCanRespondToAuthorizationRequest() + public function testCanRespondToAuthorizationRequest(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); @@ -76,7 +73,7 @@ public function testCanRespondToAuthorizationRequest() $this->assertTrue($grant->canRespondToAuthorizationRequest($request)); } - public function testValidateAuthorizationRequest() + public function testValidateAuthorizationRequest(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -101,7 +98,7 @@ public function testValidateAuthorizationRequest() $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - public function testValidateAuthorizationRequestRedirectUriArray() + public function testValidateAuthorizationRequestRedirectUriArray(): void { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar']); @@ -126,7 +123,7 @@ public function testValidateAuthorizationRequestRedirectUriArray() $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - public function testValidateAuthorizationRequestMissingClientId() + public function testValidateAuthorizationRequestMissingClientId(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -141,7 +138,7 @@ public function testValidateAuthorizationRequestMissingClientId() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestInvalidClientId() + public function testValidateAuthorizationRequestInvalidClientId(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn(null); @@ -160,7 +157,7 @@ public function testValidateAuthorizationRequestInvalidClientId() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestBadRedirectUriString() + public function testValidateAuthorizationRequestBadRedirectUriString(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -182,7 +179,7 @@ public function testValidateAuthorizationRequestBadRedirectUriString() $grant->validateAuthorizationRequest($request); } - public function testValidateAuthorizationRequestBadRedirectUriArray() + public function testValidateAuthorizationRequestBadRedirectUriArray(): void { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar']); @@ -204,7 +201,7 @@ public function testValidateAuthorizationRequestBadRedirectUriArray() $grant->validateAuthorizationRequest($request); } - public function testCompleteAuthorizationRequest() + public function testCompleteAuthorizationRequest(): void { $client = new ClientEntity(); $client->setIdentifier('identifier'); @@ -233,7 +230,7 @@ public function testCompleteAuthorizationRequest() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testCompleteAuthorizationRequestDenied() + public function testCompleteAuthorizationRequestDenied(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(false); @@ -259,7 +256,7 @@ public function testCompleteAuthorizationRequestDenied() $grant->completeAuthorizationRequest($authRequest); } - public function testAccessTokenRepositoryUniqueConstraintCheck() + public function testAccessTokenRepositoryUniqueConstraintCheck(): void { $client = new ClientEntity(); $client->setIdentifier('identifier'); @@ -299,7 +296,7 @@ public function testAccessTokenRepositoryUniqueConstraintCheck() $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - public function testAccessTokenRepositoryFailToPersist() + public function testAccessTokenRepositoryFailToPersist(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -326,7 +323,7 @@ public function testAccessTokenRepositoryFailToPersist() $grant->completeAuthorizationRequest($authRequest); } - public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop() + public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): void { $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -353,7 +350,7 @@ public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop() $grant->completeAuthorizationRequest($authRequest); } - public function testSetRefreshTokenTTL() + public function testSetRefreshTokenTTL(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); @@ -362,7 +359,7 @@ public function testSetRefreshTokenTTL() $grant->setRefreshTokenTTL(new DateInterval('PT10M')); } - public function testSetRefreshTokenRepository() + public function testSetRefreshTokenRepository(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); @@ -373,7 +370,7 @@ public function testSetRefreshTokenRepository() $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); } - public function testCompleteAuthorizationRequestNoUser() + public function testCompleteAuthorizationRequestNoUser(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index b53ab2357..1eb2e0895 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -25,7 +25,7 @@ class PasswordGrantTest extends TestCase { const DEFAULT_SCOPE = 'basic'; - public function testGetIdentifier() + public function testGetIdentifier(): void { $userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -34,7 +34,7 @@ public function testGetIdentifier() $this->assertEquals('password', $grant->getIdentifier()); } - public function testRespondToRequest() + public function testRespondToRequest(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -80,7 +80,7 @@ public function testRespondToRequest() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } - public function testRespondToRequestNullRefreshToken() + public function testRespondToRequestNullRefreshToken(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); @@ -125,7 +125,7 @@ public function testRespondToRequestNullRefreshToken() $this->assertNull($responseType->getRefreshToken()); } - public function testRespondToRequestMissingUsername() + public function testRespondToRequestMissingUsername(): void { $client = new ClientEntity(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -153,7 +153,7 @@ public function testRespondToRequestMissingUsername() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestMissingPassword() + public function testRespondToRequestMissingPassword(): void { $client = new ClientEntity(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -182,7 +182,7 @@ public function testRespondToRequestMissingPassword() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestBadCredentials() + public function testRespondToRequestBadCredentials(): void { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 5b5491bbc..f61c4e530 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -33,7 +33,7 @@ public function setUp(): void $this->cryptStub = new CryptTraitStub(); } - public function testGetIdentifier() + public function testGetIdentifier(): void { $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -41,7 +41,7 @@ public function testGetIdentifier() $this->assertEquals('refresh_token', $grant->getIdentifier()); } - public function testRespondToRequest() + public function testRespondToRequest(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -99,7 +99,7 @@ public function testRespondToRequest() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } - public function testRespondToRequestNullRefreshToken() + public function testRespondToRequestNullRefreshToken(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -157,7 +157,7 @@ public function testRespondToRequestNullRefreshToken() $this->assertNull($responseType->getRefreshToken()); } - public function testRespondToReducedScopes() + public function testRespondToReducedScopes(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -215,7 +215,7 @@ public function testRespondToReducedScopes() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } - public function testRespondToUnexpectedScope() + public function testRespondToUnexpectedScope(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -270,7 +270,7 @@ public function testRespondToUnexpectedScope() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestMissingOldToken() + public function testRespondToRequestMissingOldToken(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -301,7 +301,7 @@ public function testRespondToRequestMissingOldToken() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestInvalidOldToken() + public function testRespondToRequestInvalidOldToken(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -335,7 +335,7 @@ public function testRespondToRequestInvalidOldToken() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestClientMismatch() + public function testRespondToRequestClientMismatch(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -383,7 +383,7 @@ public function testRespondToRequestClientMismatch() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestExpiredToken() + public function testRespondToRequestExpiredToken(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -428,7 +428,7 @@ public function testRespondToRequestExpiredToken() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestRevokedToken() + public function testRespondToRequestRevokedToken(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -474,7 +474,7 @@ public function testRespondToRequestRevokedToken() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestFinalizeScopes() + public function testRespondToRequestFinalizeScopes(): void { $client = new ClientEntity(); @@ -547,7 +547,7 @@ public function testRespondToRequestFinalizeScopes() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRevokedRefreshToken() + public function testRevokedRefreshToken(): void { $refreshTokenId = 'foo'; @@ -606,7 +606,7 @@ public function testRevokedRefreshToken() Assert::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); } - public function testUnrevokedRefreshToken() + public function testUnrevokedRefreshToken(): void { $refreshTokenId = 'foo'; diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index 69c8d3791..696b5cfe3 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -22,7 +22,7 @@ class AuthorizationServerMiddlewareTest extends TestCase { const DEFAULT_SCOPE = 'basic'; - public function testValidResponse() + public function testValidResponse(): void { $client = new ClientEntity(); $client->setConfidential(); @@ -68,7 +68,7 @@ function () { $this->assertEquals(200, $response->getStatusCode()); } - public function testOAuthErrorResponse() + public function testOAuthErrorResponse(): void { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepository->method('validateClient')->willReturn(false); @@ -103,7 +103,7 @@ function () { $this->assertEquals(401, $response->getStatusCode()); } - public function testOAuthErrorResponseRedirectUri() + public function testOAuthErrorResponseRedirectUri(): void { $exception = OAuthServerException::invalidScope('test', 'http://foo/bar'); $response = $exception->generateHttpResponse(new Response()); @@ -115,7 +115,7 @@ public function testOAuthErrorResponseRedirectUri() ); } - public function testOAuthErrorResponseRedirectUriFragment() + public function testOAuthErrorResponseRedirectUriFragment(): void { $exception = OAuthServerException::invalidScope('test', 'http://foo/bar'); $response = $exception->generateHttpResponse(new Response(), true); diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index be1a5ab67..d08d99b03 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -5,6 +5,7 @@ namespace LeagueTests\Stubs; use DateInterval; +use Defuse\Crypto\Key; use League\Event\EmitterInterface; use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Grant\GrantTypeInterface; @@ -93,7 +94,7 @@ public function setPrivateKey(CryptKeyInterface $privateKey): void { } - public function setEncryptionKey($key = null): void + public function setEncryptionKey(Key|string|null $key = null): void { } From dc8a8af319c54683eb2f2193747d19c5dc144151 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Anne?= Date: Fri, 21 Apr 2023 11:18:22 +0200 Subject: [PATCH 100/212] Remove requirement of json extension --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 9699116ee..24684a273 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,6 @@ "lcobucci/jwt": "^4.3 || ^5.0", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.3", - "ext-json": "*", "lcobucci/clock": "^2.2 || ^3.0" }, "require-dev": { From 54942a7640ab3a1e216c103b071bf74aa2f6bf1c Mon Sep 17 00:00:00 2001 From: erikn69 Date: Tue, 18 Apr 2023 12:11:18 -0500 Subject: [PATCH 101/212] Bump laminas/laminas-diactoros, psr/http-message versions --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9699116ee..94e89de99 100644 --- a/composer.json +++ b/composer.json @@ -9,14 +9,14 @@ "league/event": "^2.2", "league/uri": "^6.7", "lcobucci/jwt": "^4.3 || ^5.0", - "psr/http-message": "^1.0.1", + "psr/http-message": "^1.0.1 || ^2.0", "defuse/php-encryption": "^2.3", "ext-json": "*", "lcobucci/clock": "^2.2 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^9.6.6", - "laminas/laminas-diactoros": "^2.24.0", + "laminas/laminas-diactoros": "^2.24.0 || ^3.0.0", "phpstan/phpstan": "^0.12.57", "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master" From 8f89bf254d3975a4dc263660dd724dedc21511b5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Jun 2023 21:44:02 +0100 Subject: [PATCH 102/212] Remove skipping of test --- tests/Utils/CryptKeyTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 7190864c6..b9c53b660 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -54,9 +54,6 @@ public function testKeyString() public function testUnsupportedKeyType() { - if (\str_starts_with(\phpversion(), '8.0')) { - $this->markTestSkipped('Cannot generate key on PHP 8.0 runner. Investigating'); - } $this->expectException(\LogicException::class); $this->expectExceptionMessage('Unable to read key'); From 75f210dc7be2d81761a9a13a79b5e9b3c5f37b3c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Jun 2023 21:56:22 +0100 Subject: [PATCH 103/212] Run 8.0 tests on Ubuntu 20.04 --- .github/workflows/tests.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6b82845c8..1d17d65c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,13 +8,17 @@ on: jobs: tests: - runs-on: ubuntu-latest - strategy: fail-fast: false matrix: - php: [8.0, 8.1, 8.2] + php: [8.1, 8.2] + os: [ubuntu-22.04] stability: [prefer-lowest, prefer-stable] + include: + - os: ubuntu-20.04 + php: 8.0 + + runs-on: ${{ matrix.os }} name: PHP ${{ matrix.php }} - ${{ matrix.stability }} From a10cde3daf65c0c19c5c8a05e9b40cd435016489 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Jun 2023 21:58:33 +0100 Subject: [PATCH 104/212] Include stability preferences for composer install --- .github/workflows/tests.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1d17d65c5..ade0c245b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,10 @@ jobs: include: - os: ubuntu-20.04 php: 8.0 + stability: prefer-lowest + - os: ubuntu-20.04 + php: 8.0 + stability: prefer-stable runs-on: ${{ matrix.os }} From 9a97128a1fea3fdfd257d9f5c652f4f83898c92e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Jun 2023 22:26:18 +0100 Subject: [PATCH 105/212] Upgrade laminas/diactoros --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 613784833..43dee449c 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.6.6", - "laminas/laminas-diactoros": "^2.24.0 || ^3.0.0", + "laminas/laminas-diactoros": "^3.0.0", "phpstan/phpstan": "^0.12.57", "phpstan/phpstan-phpunit": "^0.12.16", "roave/security-advisories": "dev-master" From 3b88400b45866fce443e89dc34dc3ea2a6df5f0c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Jun 2023 22:35:38 +0100 Subject: [PATCH 106/212] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d502a650..32aaabedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ 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] +### Changed +- Bumped the versions for laminas/diactoros and psr/http-message to support +PSR-7 v2.0 (PR #1339) ## [8.5.1] - released 2023-04-04 ### Fixed From 8ab731e84eef904b5913ba31b38116acf8ea50b6 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 16 Jun 2023 16:32:47 +0100 Subject: [PATCH 107/212] Update changelog for version 8.5.2 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 32aaabedd..2e57b2118 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [8.5.2] - released 2023-06-16 ### Changed - Bumped the versions for laminas/diactoros and psr/http-message to support PSR-7 v2.0 (PR #1339) @@ -594,7 +596,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.1...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.2...HEAD +[8.5.2]: https://github.com/thephpleague/oauth2-server/compare/8.5.1...8.5.2 [8.5.1]: https://github.com/thephpleague/oauth2-server/compare/8.5.0...8.5.1 [8.5.0]: https://github.com/thephpleague/oauth2-server/compare/8.4.1...8.5.0 [8.4.1]: https://github.com/thephpleague/oauth2-server/compare/8.4.0...8.4.1 From 0143d52cea8c30cc1ccbb3e9ab9ec24566566931 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 5 Jul 2023 23:50:06 +0100 Subject: [PATCH 108/212] Remove potential key from exception message --- src/CryptKey.php | 2 +- tests/Utils/CryptKeyTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index 14cd8e7b4..bef2f3cdb 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -64,7 +64,7 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = throw new LogicException('Unable to read key from file ' . $keyPath); } } else { - throw new LogicException('Unable to read key from file ' . $keyPath); + throw new LogicException('Invalid key supplied'); } if ($keyPermissionsCheck === true) { diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index b9c53b660..8edadb8b0 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -55,7 +55,7 @@ public function testKeyString() public function testUnsupportedKeyType() { $this->expectException(\LogicException::class); - $this->expectExceptionMessage('Unable to read key'); + $this->expectExceptionMessage('Invalid key supplied'); try { // Create the keypair From 20f07b06ac56e7f667d33fe3399cf9918ef119ce Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 5 Jul 2023 23:55:12 +0100 Subject: [PATCH 109/212] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e57b2118..6505c27e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.5.3] - released 2023-07-06 +### Security +- If a key string is provided to the CryptKey constructor with an invalid +passphrase, the LogicException message generated will contain the given key. +The key is no longer leaked via this exception (PR #1353) + ## [8.5.2] - released 2023-06-16 ### Changed - Bumped the versions for laminas/diactoros and psr/http-message to support From 605f6f0b7d134d7c0153f6bb9369d5b8e63d9d0b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 5 Jul 2023 23:56:21 +0100 Subject: [PATCH 110/212] Change wording of changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6505c27e5..4ab849c11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [8.5.3] - released 2023-07-06 ### Security - If a key string is provided to the CryptKey constructor with an invalid -passphrase, the LogicException message generated will contain the given key. +passphrase, the LogicException message generated will expose the given key. The key is no longer leaked via this exception (PR #1353) ## [8.5.2] - released 2023-06-16 From eb91b4190e7f6169053ebf8ffa352d47e756b2ce Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 6 Jul 2023 00:01:32 +0100 Subject: [PATCH 111/212] Update changelog links --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ab849c11..83ea5d328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -602,7 +602,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.2...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.3...HEAD +[8.5.3]: https://github.com/thephpleague/oauth2-server/compare/8.5.2...8.5.3 [8.5.2]: https://github.com/thephpleague/oauth2-server/compare/8.5.1...8.5.2 [8.5.1]: https://github.com/thephpleague/oauth2-server/compare/8.5.0...8.5.1 [8.5.0]: https://github.com/thephpleague/oauth2-server/compare/8.4.1...8.5.0 From cc599afe2f05d94a02918d7c2a3e266c9a4bc844 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 20 Jul 2023 00:06:31 +0100 Subject: [PATCH 112/212] More PHPStan fixes --- phpstan.neon.dist | 1 - src/AuthorizationServer.php | 4 +-- .../BearerTokenValidator.php | 8 ++--- src/Entities/AccessTokenEntityInterface.php | 2 +- src/Entities/AuthCodeEntityInterface.php | 10 ++---- src/Entities/RefreshTokenEntityInterface.php | 20 +++-------- src/Entities/TokenInterface.php | 28 ++++------------ src/Entities/Traits/AccessTokenTrait.php | 4 +-- src/Entities/Traits/AuthCodeTrait.php | 10 ++---- src/Entities/Traits/EntityTrait.php | 2 +- src/Entities/Traits/RefreshTokenTrait.php | 22 ++++--------- src/Entities/Traits/TokenEntityTrait.php | 24 +++++--------- src/Exception/OAuthServerException.php | 18 +++++----- src/Grant/AbstractAuthorizeGrant.php | 2 +- src/Grant/AbstractGrant.php | 33 ++++++++++--------- src/Grant/AuthCodeGrant.php | 5 ++- src/Grant/GrantTypeInterface.php | 12 +++---- src/Grant/ImplicitGrant.php | 4 +-- src/Grant/RefreshTokenGrant.php | 4 +-- 19 files changed, 79 insertions(+), 134 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 3b729de80..ea81a8e66 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,4 +7,3 @@ parameters: - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return League\\OAuth2\\Server\\Entities\\AccessTokenEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueRefreshToken\(\) should return League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInterface\|null but return statement is missing\.#' - - '#Parameter \#1 \$contents of static method Lcobucci\\JWT\\Signer\\Key\\InMemory::plainText\(\) expects non-empty-string, string given\.#' diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 6089adbf0..85369a0bc 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -128,7 +128,7 @@ public function __construct( * @param GrantTypeInterface $grantType * @param null|DateInterval $accessTokenTTL */ - public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null) + public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null): void { if ($accessTokenTTL === null) { $accessTokenTTL = new DateInterval('PT1H'); @@ -237,7 +237,7 @@ protected function getResponseType() * * @param string $defaultScope */ - public function setDefaultScope($defaultScope) + public function setDefaultScope($defaultScope): void { $this->defaultScope = $defaultScope; } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 9f2062719..f9b0ae5db 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -57,7 +57,7 @@ public function __construct(AccessTokenRepositoryInterface $accessTokenRepositor * * @param CryptKeyInterface $key */ - public function setPublicKey(CryptKeyInterface $key) + public function setPublicKey(CryptKeyInterface $key): void { $this->publicKey = $key; @@ -67,7 +67,7 @@ public function setPublicKey(CryptKeyInterface $key) /** * Initialise the JWT configuration. */ - private function initJwtConfiguration() + private function initJwtConfiguration(): void { $this->jwtConfiguration = Configuration::forSymmetricSigner( new Sha256(), @@ -136,9 +136,9 @@ public function validateAuthorization(ServerRequestInterface $request) * * @param mixed $aud * - * @return array|string + * @return array|string */ - private function convertSingleRecordAudToString($aud) + private function convertSingleRecordAudToString($aud): array|string { return \is_array($aud) && \count($aud) === 1 ? $aud[0] : $aud; } diff --git a/src/Entities/AccessTokenEntityInterface.php b/src/Entities/AccessTokenEntityInterface.php index 041d528b3..33306b174 100644 --- a/src/Entities/AccessTokenEntityInterface.php +++ b/src/Entities/AccessTokenEntityInterface.php @@ -16,7 +16,7 @@ interface AccessTokenEntityInterface extends TokenInterface /** * Set a private key used to encrypt the access token. */ - public function setPrivateKey(CryptKeyInterface $privateKey); + public function setPrivateKey(CryptKeyInterface $privateKey): void; /** * Generate a string representation of the access token. diff --git a/src/Entities/AuthCodeEntityInterface.php b/src/Entities/AuthCodeEntityInterface.php index 00e939c2c..40d581245 100644 --- a/src/Entities/AuthCodeEntityInterface.php +++ b/src/Entities/AuthCodeEntityInterface.php @@ -11,13 +11,7 @@ interface AuthCodeEntityInterface extends TokenInterface { - /** - * @return string|null - */ - public function getRedirectUri(); + public function getRedirectUri(): string|null; - /** - * @param string $uri - */ - public function setRedirectUri($uri); + public function setRedirectUri(string $uri): void; } diff --git a/src/Entities/RefreshTokenEntityInterface.php b/src/Entities/RefreshTokenEntityInterface.php index 551ad0527..63f9732c6 100644 --- a/src/Entities/RefreshTokenEntityInterface.php +++ b/src/Entities/RefreshTokenEntityInterface.php @@ -22,36 +22,26 @@ public function getIdentifier(); /** * Set the token's identifier. - * - * @param mixed $identifier */ - public function setIdentifier($identifier); + public function setIdentifier(mixed $identifier): void; /** * Get the token's expiry date time. - * - * @return DateTimeImmutable */ - public function getExpiryDateTime(); + public function getExpiryDateTime(): DateTimeImmutable; /** * Set the date time when the token expires. - * - * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTimeImmutable $dateTime); + public function setExpiryDateTime(DateTimeImmutable $dateTime): void; /** * Set the access token that the refresh token was associated with. - * - * @param AccessTokenEntityInterface $accessToken */ - public function setAccessToken(AccessTokenEntityInterface $accessToken); + public function setAccessToken(AccessTokenEntityInterface $accessToken): void; /** * Get the access token that the refresh token was originally associated with. - * - * @return AccessTokenEntityInterface */ - public function getAccessToken(); + public function getAccessToken(): AccessTokenEntityInterface; } diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 7b063e138..75aa261af 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -22,10 +22,8 @@ public function getIdentifier(); /** * Set the token's identifier. - * - * @param mixed $identifier */ - public function setIdentifier($identifier); + public function setIdentifier(mixed $identifier): void; /** * Get the token's expiry date time. @@ -36,45 +34,33 @@ public function getExpiryDateTime(); /** * Set the date time when the token expires. - * - * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTimeImmutable $dateTime); + public function setExpiryDateTime(DateTimeImmutable $dateTime): void; /** * Set the identifier of the user associated with the token. - * - * @param string|int|null $identifier The identifier of the user */ - public function setUserIdentifier($identifier); + public function setUserIdentifier(string|int|null $identifier): void; /** * Get the token user's identifier. - * - * @return string|int|null */ - public function getUserIdentifier(); + public function getUserIdentifier(): string|int|null; /** * Get the client that the token was issued to. - * - * @return ClientEntityInterface */ - public function getClient(); + public function getClient(): ClientEntityInterface; /** * Set the client that the token was issued to. - * - * @param ClientEntityInterface $client */ - public function setClient(ClientEntityInterface $client); + public function setClient(ClientEntityInterface $client): void; /** * Associate a scope with the token. - * - * @param ScopeEntityInterface $scope */ - public function addScope(ScopeEntityInterface $scope); + public function addScope(ScopeEntityInterface $scope): void; /** * Return an array of scopes associated with the token. diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 65537ac9b..9056db833 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -33,7 +33,7 @@ trait AccessTokenTrait /** * Set the private key used to encrypt this access token. */ - public function setPrivateKey(CryptKeyInterface $privateKey) + public function setPrivateKey(CryptKeyInterface $privateKey): void { $this->privateKey = $privateKey; } @@ -41,7 +41,7 @@ public function setPrivateKey(CryptKeyInterface $privateKey) /** * Initialise the JWT Configuration. */ - public function initJwtConfiguration() + public function initJwtConfiguration(): void { $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index 542d36789..35cbff295 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -16,18 +16,12 @@ trait AuthCodeTrait */ protected $redirectUri; - /** - * @return string|null - */ - public function getRedirectUri() + public function getRedirectUri(): string|null { return $this->redirectUri; } - /** - * @param string $uri - */ - public function setRedirectUri($uri) + public function setRedirectUri(string $uri): void { $this->redirectUri = $uri; } diff --git a/src/Entities/Traits/EntityTrait.php b/src/Entities/Traits/EntityTrait.php index 05452923a..6748aea18 100644 --- a/src/Entities/Traits/EntityTrait.php +++ b/src/Entities/Traits/EntityTrait.php @@ -27,7 +27,7 @@ public function getIdentifier() /** * @param mixed $identifier */ - public function setIdentifier($identifier) + public function setIdentifier($identifier): void { $this->identifier = $identifier; } diff --git a/src/Entities/Traits/RefreshTokenTrait.php b/src/Entities/Traits/RefreshTokenTrait.php index f0f15444c..568b2a733 100644 --- a/src/Entities/Traits/RefreshTokenTrait.php +++ b/src/Entities/Traits/RefreshTokenTrait.php @@ -14,20 +14,14 @@ trait RefreshTokenTrait { - /** - * @var AccessTokenEntityInterface - */ - protected $accessToken; + protected AccessTokenEntityInterface $accessToken; - /** - * @var DateTimeImmutable - */ - protected $expiryDateTime; + protected DateTimeImmutable $expiryDateTime; /** * {@inheritdoc} */ - public function setAccessToken(AccessTokenEntityInterface $accessToken) + public function setAccessToken(AccessTokenEntityInterface $accessToken): void { $this->accessToken = $accessToken; } @@ -35,27 +29,23 @@ public function setAccessToken(AccessTokenEntityInterface $accessToken) /** * {@inheritdoc} */ - public function getAccessToken() + public function getAccessToken(): AccessTokenEntityInterface { return $this->accessToken; } /** * Get the token's expiry date time. - * - * @return DateTimeImmutable */ - public function getExpiryDateTime() + public function getExpiryDateTime(): DateTimeImmutable { return $this->expiryDateTime; } /** * Set the date time when the token expires. - * - * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTimeImmutable $dateTime) + public function setExpiryDateTime(DateTimeImmutable $dateTime): void { $this->expiryDateTime = $dateTime; } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 83b172322..d9cfa6fa2 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -40,7 +40,7 @@ trait TokenEntityTrait * * @param ScopeEntityInterface $scope */ - public function addScope(ScopeEntityInterface $scope) + public function addScope(ScopeEntityInterface $scope): void { $this->scopes[$scope->getIdentifier()] = $scope; } @@ -50,27 +50,23 @@ public function addScope(ScopeEntityInterface $scope) * * @return ScopeEntityInterface[] */ - public function getScopes() + public function getScopes(): array { return \array_values($this->scopes); } /** * Get the token's expiry date time. - * - * @return DateTimeImmutable */ - public function getExpiryDateTime() + public function getExpiryDateTime(): DateTimeImmutable { return $this->expiryDateTime; } /** * Set the date time when the token expires. - * - * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTimeImmutable $dateTime) + public function setExpiryDateTime(DateTimeImmutable $dateTime): void { $this->expiryDateTime = $dateTime; } @@ -80,7 +76,7 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime) * * @param string|int|null $identifier The identifier of the user */ - public function setUserIdentifier($identifier) + public function setUserIdentifier($identifier): void { $this->userIdentifier = $identifier; } @@ -90,27 +86,23 @@ public function setUserIdentifier($identifier) * * @return string|int|null */ - public function getUserIdentifier() + public function getUserIdentifier(): string|int|null { return $this->userIdentifier; } /** * Get the client that the token was issued to. - * - * @return ClientEntityInterface */ - public function getClient() + public function getClient(): ClientEntityInterface { return $this->client; } /** * Set the client that the token was issued to. - * - * @param ClientEntityInterface $client */ - public function setClient(ClientEntityInterface $client) + public function setClient(ClientEntityInterface $client): void { $this->client = $client; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index f57c68951..32104ef0d 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -37,9 +37,9 @@ class OAuthServerException extends Exception private $redirectUri; /** - * @var array + * @var array */ - private $payload; + private array $payload; /** * @var ServerRequestInterface @@ -76,9 +76,9 @@ final public function __construct($message, $code, $errorType, $httpStatusCode = /** * Returns the current payload. * - * @return array + * @return array */ - public function getPayload() + public function getPayload(): array { $payload = $this->payload; @@ -94,9 +94,9 @@ public function getPayload() /** * Updates the current payload. * - * @param array $payload + * @param array $payload */ - public function setPayload(array $payload) + public function setPayload(array $payload): void { $this->payload = $payload; } @@ -106,7 +106,7 @@ public function setPayload(array $payload) * * @param ServerRequestInterface $serverRequest */ - public function setServerRequest(ServerRequestInterface $serverRequest) + public function setServerRequest(ServerRequestInterface $serverRequest): void { $this->serverRequest = $serverRequest; } @@ -318,9 +318,9 @@ public function generateHttpResponse(ResponseInterface $response, $useFragment = /** * Get all headers that have to be send with the error response. * - * @return array Array with header values + * @return array Array with header values */ - public function getHttpHeaders() + public function getHttpHeaders(): array { $headers = [ 'Content-type' => 'application/json', diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index 3a8bb4074..9a78ae562 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -18,7 +18,7 @@ abstract class AbstractAuthorizeGrant extends AbstractGrant { /** * @param string $uri - * @param array $params + * @param mixed[] $params * @param string $queryDelimiter * * @return string diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index d13188c2a..9cdbde944 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -101,7 +101,7 @@ abstract class AbstractGrant implements GrantTypeInterface /** * @param ClientRepositoryInterface $clientRepository */ - public function setClientRepository(ClientRepositoryInterface $clientRepository) + public function setClientRepository(ClientRepositoryInterface $clientRepository): void { $this->clientRepository = $clientRepository; } @@ -109,7 +109,7 @@ public function setClientRepository(ClientRepositoryInterface $clientRepository) /** * @param AccessTokenRepositoryInterface $accessTokenRepository */ - public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository) + public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void { $this->accessTokenRepository = $accessTokenRepository; } @@ -117,7 +117,7 @@ public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessT /** * @param ScopeRepositoryInterface $scopeRepository */ - public function setScopeRepository(ScopeRepositoryInterface $scopeRepository) + public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void { $this->scopeRepository = $scopeRepository; } @@ -125,7 +125,7 @@ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository) /** * @param RefreshTokenRepositoryInterface $refreshTokenRepository */ - public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) + public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void { $this->refreshTokenRepository = $refreshTokenRepository; } @@ -133,7 +133,7 @@ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refre /** * @param AuthCodeRepositoryInterface $authCodeRepository */ - public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository) + public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository): void { $this->authCodeRepository = $authCodeRepository; } @@ -141,7 +141,7 @@ public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepos /** * @param UserRepositoryInterface $userRepository */ - public function setUserRepository(UserRepositoryInterface $userRepository) + public function setUserRepository(UserRepositoryInterface $userRepository): void { $this->userRepository = $userRepository; } @@ -149,7 +149,7 @@ public function setUserRepository(UserRepositoryInterface $userRepository) /** * {@inheritdoc} */ - public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void { $this->refreshTokenTTL = $refreshTokenTTL; } @@ -159,7 +159,7 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) * * @param CryptKeyInterface $key */ - public function setPrivateKey(CryptKeyInterface $key) + public function setPrivateKey(CryptKeyInterface $key): void { $this->privateKey = $key; } @@ -167,7 +167,7 @@ public function setPrivateKey(CryptKeyInterface $key) /** * @param string $scope */ - public function setDefaultScope($scope) + public function setDefaultScope($scope): void { $this->defaultScope = $scope; } @@ -248,9 +248,9 @@ protected function getClientEntityOrFail($clientId, ServerRequestInterface $requ * * @param ServerRequestInterface $request * - * @return array + * @return string[] */ - protected function getClientCredentials(ServerRequestInterface $request) + protected function getClientCredentials(ServerRequestInterface $request): array { [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request); @@ -283,8 +283,9 @@ protected function validateRedirectUri( string $redirectUri, ClientEntityInterface $client, ServerRequestInterface $request - ) { + ): void { $validator = new RedirectUriValidator($client->getRedirectUri()); + if (!$validator->validateRedirectUri($redirectUri)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); @@ -294,14 +295,14 @@ protected function validateRedirectUri( /** * Validate scopes in the request. * - * @param null|string|array $scopes + * @param null|string|string[] $scopes * @param string $redirectUri * * @throws OAuthServerException * * @return ScopeEntityInterface[] */ - public function validateScopes($scopes, $redirectUri = null) + public function validateScopes($scopes, $redirectUri = null): array { if ($scopes === null) { $scopes = []; @@ -333,9 +334,9 @@ public function validateScopes($scopes, $redirectUri = null) * * @param string $scopes * - * @return array + * @return string[] */ - private function convertScopesQueryStringToArray(string $scopes) + private function convertScopesQueryStringToArray(string $scopes): array { return \array_filter(\explode(self::SCOPE_DELIMITER_STRING, \trim($scopes)), function ($scope) { return $scope !== ''; diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index d08dffc86..125562d6f 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -24,7 +24,6 @@ use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestRefreshTokenEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; -use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -77,7 +76,7 @@ public function __construct( /** * Disable the requirement for a code challenge for public clients. */ - public function disableRequireCodeChallengeForPublicClients() + public function disableRequireCodeChallengeForPublicClients(): void { $this->requireCodeChallengeForPublicClients = false; } @@ -194,7 +193,7 @@ private function validateAuthorizationCode( $authCodePayload, ClientEntityInterface $client, ServerRequestInterface $request - ) { + ): void { if (!\property_exists($authCodePayload, 'auth_code_id')) { throw OAuthServerException::invalidRequest('code', 'Authorization code malformed'); } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index d7e003835..f8dffd578 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -32,7 +32,7 @@ interface GrantTypeInterface extends EmitterAwareInterface * * @param DateInterval $refreshTokenTTL */ - public function setRefreshTokenTTL(DateInterval $refreshTokenTTL); + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void; /** * Return the grant identifier that can be used in matching up requests. @@ -105,35 +105,35 @@ public function canRespondToAccessTokenRequest(ServerRequestInterface $request); * * @param ClientRepositoryInterface $clientRepository */ - public function setClientRepository(ClientRepositoryInterface $clientRepository); + public function setClientRepository(ClientRepositoryInterface $clientRepository): void; /** * Set the access token repository. * * @param AccessTokenRepositoryInterface $accessTokenRepository */ - public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository); + public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void; /** * Set the scope repository. * * @param ScopeRepositoryInterface $scopeRepository */ - public function setScopeRepository(ScopeRepositoryInterface $scopeRepository); + public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void; /** * Set the default scope. * * @param string $scope */ - public function setDefaultScope($scope); + public function setDefaultScope($scope): void; /** * Set the path to the private key. * * @param CryptKeyInterface $privateKey */ - public function setPrivateKey(CryptKeyInterface $privateKey); + public function setPrivateKey(CryptKeyInterface $privateKey): void; public function setEncryptionKey(Key|string|null $key = null): void; diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index a39bf0733..e6acede56 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -47,7 +47,7 @@ public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#') * * @throw LogicException */ - public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void { throw new LogicException('The Implicit Grant does not return refresh tokens'); } @@ -57,7 +57,7 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) * * @throw LogicException */ - public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) + public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void { throw new LogicException('The Implicit Grant does not return refresh tokens'); } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index e8248b242..541faa4ab 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -95,9 +95,9 @@ public function respondToAccessTokenRequest( * * @throws OAuthServerException * - * @return array + * @return array */ - protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId) + protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId): array { $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request); if (!\is_string($encryptedRefreshToken)) { From cfcdf2870525a699b4e3aacbfef061334b649fe0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 25 Jul 2023 23:36:26 +0100 Subject: [PATCH 113/212] Fix PHP stan issues to level 8 --- composer.json | 8 +- phpstan.neon.dist | 2 +- .../BearerTokenValidator.php | 9 +- src/CryptKey.php | 12 +- src/Entities/Traits/AccessTokenTrait.php | 9 +- src/Grant/AbstractGrant.php | 15 +- src/Grant/AuthCodeGrant.php | 7 + tests/AuthorizationServerTest.php | 5 +- tests/Grant/AuthCodeGrantTest.php | 425 +++++++----------- tests/Grant/RefreshTokenGrantTest.php | 300 ++++++++----- tests/PHPStan/AbstractGrantExtension.php | 2 +- tests/Stubs/GrantType.php | 4 +- tests/Utils/CryptKeyTest.php | 15 + 13 files changed, 423 insertions(+), 390 deletions(-) diff --git a/composer.json b/composer.json index b4e02c928..5f1ec4721 100644 --- a/composer.json +++ b/composer.json @@ -16,9 +16,10 @@ "require-dev": { "phpunit/phpunit": "^9.5.11", "laminas/laminas-diactoros": "^2.8.0", - "phpstan/phpstan": "^1.4", + "phpstan/phpstan": "^1.10.26", "phpstan/phpstan-phpunit": "^1.0.0", - "roave/security-advisories": "dev-master" + "roave/security-advisories": "dev-master", + "phpstan/extension-installer": "^1.3" }, "repositories": [ { @@ -72,7 +73,8 @@ }, "config": { "allow-plugins": { - "ocramius/package-versions": true + "ocramius/package-versions": true, + "phpstan/extension-installer": true } } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ea81a8e66..44d720f86 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 6 + level: 8 paths: - src - tests diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index f9b0ae5db..d8854efb5 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -24,6 +24,7 @@ use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use Psr\Http\Message\ServerRequestInterface; +use RuntimeException; class BearerTokenValidator implements AuthorizationValidatorInterface { @@ -74,13 +75,19 @@ private function initJwtConfiguration(): void InMemory::plainText('empty', 'empty') ); + $publicKeyContents = $this->publicKey->getKeyContents(); + + if ($publicKeyContents === '') { + throw new RuntimeException('Public key is empty'); + } + $this->jwtConfiguration->setValidationConstraints( \class_exists(LooseValidAt::class) ? new LooseValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))) : new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), new SignedWith( new Sha256(), - InMemory::plainText($this->publicKey->getKeyContents(), $this->publicKey->getPassPhrase() ?? '') + InMemory::plainText($publicKeyContents, $this->publicKey->getPassPhrase() ?? '') ) ); } diff --git a/src/CryptKey.php b/src/CryptKey.php index b9f8ef314..351bf9cf0 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -13,6 +13,8 @@ use LogicException; +use function file_get_contents; + class CryptKey implements CryptKeyInterface { /** @deprecated left for backward compatibility check */ @@ -58,8 +60,16 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = if (!\is_readable($keyPath)) { throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } - $this->keyContents = \file_get_contents($keyPath); + + $keyContents = file_get_contents($keyPath); + + if ($keyContents === false) { + throw new LogicException('Unable to read key from file ' . $keyPath); + } + + $this->keyContents = $keyContents; $this->keyPath = $keyPath; + if (!$this->isValidKey($this->keyContents, $this->passPhrase ?? '')) { throw new LogicException('Unable to read key from file ' . $keyPath); } diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 9056db833..35300fa4c 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -17,6 +17,7 @@ use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use RuntimeException; trait AccessTokenTrait { @@ -43,9 +44,15 @@ public function setPrivateKey(CryptKeyInterface $privateKey): void */ public function initJwtConfiguration(): void { + $privateKeyContents = $this->privateKey->getKeyContents(); + + if ($privateKeyContents === '') { + throw new RuntimeException('Private key is empty'); + } + $this->jwtConfiguration = Configuration::forAsymmetricSigner( new Sha256(), - InMemory::plainText($this->privateKey->getKeyContents(), $this->privateKey->getPassPhrase() ?? ''), + InMemory::plainText($privateKeyContents, $this->privateKey->getPassPhrase() ?? ''), InMemory::plainText('empty', 'empty') ); } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 9cdbde944..ee8cc6ae9 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -1,4 +1,5 @@ getQueryStringParameter('code_challenge_method', $request, 'plain'); + if ($codeChallengeMethod === null) { + throw OAuthServerException::invalidRequest( + 'code_challenge_method', + 'Code challenge method must be provided when `code_challenge` is set.' + ); + } + if (\array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) { throw OAuthServerException::invalidRequest( 'code_challenge_method', diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index e49e8ac1a..3e9805472 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -3,6 +3,7 @@ namespace LeagueTests; use DateInterval; +use Defuse\Crypto\Key; use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequestFactory; @@ -21,13 +22,11 @@ use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; -use LeagueTests\Stubs\GrantType; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; class AuthorizationServerTest extends TestCase { @@ -181,7 +180,7 @@ public function getPrivateKey(): CryptKeyInterface|null return $this->privateKey; } - public function getEncryptionKey(): string|null + public function getEncryptionKey(): Key|string|null { return $this->encryptionKey; } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 086f37dde..b5843c29b 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -28,6 +28,8 @@ use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; +use function json_encode; + class AuthCodeGrantTest extends TestCase { const DEFAULT_SCOPE = 'basic'; @@ -571,16 +573,14 @@ public function testRespondToAccessTokenRequest(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -637,16 +637,14 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void 'grant_type' => 'authorization_code', 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( - \json_encode( - [ - 'auth_code_id' => \uniqid(), - 'client_id' => 'foo', - 'expire_time' => \time() + 3600, - 'user_id' => 123, - 'scopes' => ['foo'], - 'redirect_uri' => 'http://foo/bar', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'client_id' => 'foo', + 'expire_time' => \time() + 3600, + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -705,16 +703,14 @@ public function testRespondToAccessTokenRequestForPublicClient(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -773,16 +769,14 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -844,18 +838,16 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void 'redirect_uri' => 'http://foo/bar', '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' => 'http://foo/bar', - 'code_challenge' => self::CODE_VERIFIER, - 'code_challenge_method' => 'plain', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => self::CODE_VERIFIER, + 'code_challenge_method' => 'plain', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -917,18 +909,16 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void 'redirect_uri' => 'http://foo/bar', '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' => 'http://foo/bar', - 'code_challenge' => self::CODE_CHALLENGE, - 'code_challenge_method' => 'S256', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => self::CODE_CHALLENGE, + 'code_challenge_method' => 'S256', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -970,14 +960,12 @@ 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_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1019,14 +1007,12 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void '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_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1113,16 +1099,14 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -1204,16 +1188,14 @@ public function testRespondToAccessTokenRequestExpiredCode(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -1268,16 +1250,14 @@ public function testRespondToAccessTokenRequestRevokedCode(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -1330,16 +1310,14 @@ public function testRespondToAccessTokenRequestClientMismatch(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -1451,18 +1429,16 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void 'redirect_uri' => 'http://foo/bar', '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' => 'http://foo/bar', - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'plain', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => 'foobar', + 'code_challenge_method' => 'plain', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1524,18 +1500,16 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void 'redirect_uri' => 'http://foo/bar', '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' => 'http://foo/bar', - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'S256', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => 'foobar', + 'code_challenge_method' => 'S256', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1597,18 +1571,16 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva 'redirect_uri' => 'http://foo/bar', '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' => 'http://foo/bar', - 'code_challenge' => self::CODE_CHALLENGE, - 'code_challenge_method' => 'S256', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => self::CODE_CHALLENGE, + 'code_challenge_method' => 'S256', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1670,18 +1642,16 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva 'redirect_uri' => 'http://foo/bar', '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' => 'http://foo/bar', - 'code_challenge' => 'R7T1y1HPNFvs1WDCrx4lfoBS6KD2c71pr8OHvULjvv8', - 'code_challenge_method' => 'S256', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => 'R7T1y1HPNFvs1WDCrx4lfoBS6KD2c71pr8OHvULjvv8', + 'code_challenge_method' => 'S256', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1742,18 +1712,16 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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', - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'plain', - ] - ) + json_encode([ + 'auth_code_id' => \uniqid(), + 'expire_time' => \time() + 3600, + 'client_id' => 'foo', + 'user_id' => 123, + 'scopes' => ['foo'], + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => 'foobar', + 'code_challenge_method' => 'plain', + ], JSON_THROW_ON_ERROR) ), ] ); @@ -1905,16 +1873,14 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -1973,16 +1939,14 @@ public function testRefreshTokenRepositoryFailToPersist(): void 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -2044,16 +2008,14 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', '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_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) ), ] ); @@ -2161,47 +2123,4 @@ public function testUseValidRedirectUriIfScopeCheckFails(): void $this->assertStringStartsWith('http://bar/foo', $response->getHeader('Location')[0]); } } - - public function testThrowExceptionWhenNoClientRedirectUriRegistered(): void - { - $client = new ClientEntity(); - - $client->setConfidential(); - - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(null); - - $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') - ); - - $grant->setClientRepository($clientRepositoryMock); - $grant->setScopeRepository($scopeRepositoryMock); - $grant->setDefaultScope(self::DEFAULT_SCOPE); - - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - [], - [], - [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar/foo', - ] - ); - - $this->expectException(OAuthServerException::class); - $this->expectExceptionCode(4); - - $grant->validateAuthorizationRequest($request); - } } diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index f61c4e530..aa2bb101e 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -72,23 +72,29 @@ public function testRespondToRequest(): void $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); - $oldRefreshToken = $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, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scopes' => ['foo'], ]); @@ -130,23 +136,29 @@ public function testRespondToRequestNullRefreshToken(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = $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, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scopes' => ['foo'], ]); @@ -188,23 +200,29 @@ public function testRespondToReducedScopes(): void $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); - $oldRefreshToken = $this->cryptStub->doEncrypt( - \json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo', 'bar'], - 'user_id' => 123, - 'expire_time' => \time() + 3600, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scope' => 'foo', ]); @@ -242,23 +260,29 @@ public function testRespondToUnexpectedScope(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = $this->cryptStub->doEncrypt( - \json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo', 'bar'], - 'user_id' => 123, - 'expire_time' => \time() + 3600, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scope' => 'foobar', ]); @@ -356,23 +380,29 @@ public function testRespondToRequestClientMismatch(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = $this->cryptStub->doEncrypt( - \json_encode( - [ - 'client_id' => 'bar', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => \time() + 3600, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, ]); $responseType = new StubResponseType(); @@ -401,23 +431,29 @@ public function testRespondToRequestExpiredToken(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = $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, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, ]); $responseType = new StubResponseType(); @@ -447,23 +483,29 @@ public function testRespondToRequestRevokedToken(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = $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, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, ]); $responseType = new StubResponseType(); @@ -522,23 +564,29 @@ public function testRespondToRequestFinalizeScopes(): void ->with($client, $finalizedScopes) ->willReturn(new AccessTokenEntity()); - $oldRefreshToken = $this->cryptStub->doEncrypt( - \json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => 'zyxwvu', - 'access_token_id' => 'abcdef', - 'scopes' => ['foo', 'bar'], - 'user_id' => 123, - 'expire_time' => \time() + 3600, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scope' => ['foo', 'bar'], ]); @@ -574,23 +622,29 @@ public function testRevokedRefreshToken(): void ->will($this->onConsecutiveCalls(false, true)); $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken')->with($this->equalTo($refreshTokenId)); - $oldRefreshToken = $this->cryptStub->doEncrypt( - \json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => $refreshTokenId, - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => \time() + 3600, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scope' => ['foo'], ]); @@ -632,23 +686,29 @@ public function testUnrevokedRefreshToken(): void $refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(false); $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); - $oldRefreshToken = $this->cryptStub->doEncrypt( - \json_encode( - [ - 'client_id' => 'foo', - 'refresh_token_id' => $refreshTokenId, - 'access_token_id' => 'abcdef', - 'scopes' => ['foo'], - 'user_id' => 123, - 'expire_time' => \time() + 3600, - ] - ) + $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) { + $this->fail('json_encode failed'); + } + + $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, + 'refresh_token' => $encryptedOldRefreshToken, 'scope' => ['foo'], ]); diff --git a/tests/PHPStan/AbstractGrantExtension.php b/tests/PHPStan/AbstractGrantExtension.php index da65ab961..121648e13 100644 --- a/tests/PHPStan/AbstractGrantExtension.php +++ b/tests/PHPStan/AbstractGrantExtension.php @@ -33,7 +33,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method { return TypeCombinator::union(...[ new StringType(), - isset($methodCall->args[2]) ? $scope->getType($methodCall->args[2]->value) : new NullType(), + isset($methodCall->getArgs[2]) ? $scope->getType($methodCall->getArgs[2]->value) : new NullType(), ]); } } diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index d08d99b03..37c75fdcd 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -20,7 +20,7 @@ final class GrantType implements GrantTypeInterface { - private EmitterInterface $emitter; + private ?EmitterInterface $emitter; public function setEmitter(EmitterInterface $emitter = null): self { @@ -29,7 +29,7 @@ public function setEmitter(EmitterInterface $emitter = null): self return $this; } - public function getEmitter(): EmitterInterface + public function getEmitter(): ?EmitterInterface { return $this->emitter; } diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 453d30dfa..542fdc2b9 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -64,6 +64,11 @@ public function testUnsupportedKeyType(): void 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_DSA, ]); + + if ($res === false) { + $this->fail('The keypair was not created'); + } + // Get private key \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); $path = self::generateKeyPath($keyContent); @@ -85,6 +90,11 @@ public function testECKeyType(): void 'curve_name' => 'prime256v1', 'private_key_type' => OPENSSL_KEYTYPE_EC, ]); + + if ($res === false) { + $this->fail('The keypair was not created'); + } + // Get private key \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); @@ -106,6 +116,11 @@ public function testRSAKeyType(): void 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); + + if ($res === false) { + $this->fail('The keypair was not created'); + } + // Get private key \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); From e16ae3f6d3134025ff97af4d23c103c34ce0cfa1 Mon Sep 17 00:00:00 2001 From: erikn69 Date: Mon, 14 Aug 2023 08:56:19 -0500 Subject: [PATCH 114/212] Bump league/uri to ^7.0 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 43dee449c..5c9dfb398 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^8.0", "ext-openssl": "*", "league/event": "^2.2", - "league/uri": "^6.7", + "league/uri": "^6.7 || ^7.0", "lcobucci/jwt": "^4.3 || ^5.0", "psr/http-message": "^1.0.1 || ^2.0", "defuse/php-encryption": "^2.3", From 404f00cf5402c76bd724c306f74de42af5d0eb4d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 25 Aug 2023 23:33:15 +0100 Subject: [PATCH 115/212] Update changelog for v8.5.4 --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83ea5d328..cddd18a3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [8.5.4] - released 2023-08-25 +### Added +- Support for league/uri ^7.0 + ## [8.5.3] - released 2023-07-06 ### Security - If a key string is provided to the CryptKey constructor with an invalid @@ -602,7 +606,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.3...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.4...HEAD +[8.5.4]: https://github.com/thephpleague/oauth2-server/compare/8.5.3...8.5.4 [8.5.3]: https://github.com/thephpleague/oauth2-server/compare/8.5.2...8.5.3 [8.5.2]: https://github.com/thephpleague/oauth2-server/compare/8.5.1...8.5.2 [8.5.1]: https://github.com/thephpleague/oauth2-server/compare/8.5.0...8.5.1 From ab7714d073844497fd222d5d0a217629089936bc Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 25 Aug 2023 23:35:12 +0100 Subject: [PATCH 116/212] Add pr number to changelog record --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cddd18a3c..46b8c78e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [8.5.4] - released 2023-08-25 ### Added -- Support for league/uri ^7.0 +- Support for league/uri ^7.0 (PR #1367) ## [8.5.3] - released 2023-07-06 ### Security From 5e9444d929c9d37ed81179e3d3e4614b090d4133 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 28 Aug 2023 22:10:05 +0100 Subject: [PATCH 117/212] Update flow and tests --- examples/public/device_code.php | 3 +- .../src/Repositories/DeviceCodeRepository.php | 2 +- src/AuthorizationServer.php | 9 +- src/Entities/DeviceCodeEntityInterface.php | 32 +- src/Entities/TokenInterface.php | 67 +---- src/Entities/Traits/DeviceCodeTrait.php | 61 ++-- src/Entities/Traits/EntityTrait.php | 15 +- src/Entities/Traits/TokenEntityTrait.php | 53 +--- src/Exception/OAuthServerException.php | 41 ++- src/Grant/AbstractGrant.php | 8 +- src/Grant/DeviceCodeGrant.php | 93 +++--- src/Grant/GrantTypeInterface.php | 4 +- .../DeviceAuthorizationRequestRepository.php | 30 -- .../DeviceCodeRepositoryInterface.php | 10 +- .../DeviceAuthorizationRequest.php | 21 ++ src/ResponseTypes/DeviceCodeResponse.php | 4 +- tests/AuthorizationServerTest.php | 4 +- tests/Grant/AbstractGrantTest.php | 51 ++-- tests/Grant/AuthCodeGrantTest.php | 2 + tests/Grant/DeviceCodeGrantTest.php | 283 +++++++++++++++--- tests/Grant/ImplicitGrantTest.php | 2 + .../ResponseTypes/BearerResponseTypeTest.php | 2 + .../DeviceCodeResponseTypeTest.php | 1 + 23 files changed, 447 insertions(+), 351 deletions(-) delete mode 100644 src/Repositories/DeviceAuthorizationRequestRepository.php diff --git a/examples/public/device_code.php b/examples/public/device_code.php index ef45b64cb..503a554ee 100644 --- a/examples/public/device_code.php +++ b/examples/public/device_code.php @@ -67,8 +67,9 @@ try { $deviceAuthRequest = $server->validateDeviceAuthorizationRequest($request); + // TODO: I don't think this is right as the user can't approve the request via the same client... // Once the user has logged in, set the user on the authorization request - //$deviceAuthRequest->setUser(); + //$deviceAuthRequest->setUserIdentifier(); // Once the user has approved or denied the client, update the status //$deviceAuthRequest->setAuthorizationApproved(true); diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index f4df12795..e4b76d368 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -27,7 +27,7 @@ public function getNewDeviceCode() /** * {@inheritdoc} */ - public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) + public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) { // Some logic to persist a new device code to a database } diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 75d5a67df..eaf4a8fc9 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -184,7 +184,7 @@ public function completeAuthorizationRequest(AuthorizationRequest $authRequest, } /** - * Validate a device authorization request + * Respond to device authorization request * * @param ServerRequestInterface $request * @@ -192,11 +192,13 @@ public function completeAuthorizationRequest(AuthorizationRequest $authRequest, * * @throws OAuthServerException */ - public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request, ResponseInterface $response) { foreach ($this->enabledGrantTypes as $grantType) { if ($grantType->canRespondToDeviceAuthorizationRequest($request)) { - return $grantType->validateDeviceAuthorizationRequest($request); + return $grantType + ->respondToDeviceAuthorizationRequest($request) + ->generateHttpResponse($response); } } @@ -213,6 +215,7 @@ public function validateDeviceAuthorizationRequest(ServerRequestInterface $reque */ public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest, ResponseInterface $response) { + // TODO: Check why we aren't just using completeAuthorizationRequest return $this->enabledGrantTypes[$deviceRequest->getGrantTypeId()] ->completeDeviceAuthorizationRequest($deviceRequest) ->generateHttpResponse($response); diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index b0027320d..571d7ee26 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -9,25 +9,19 @@ namespace League\OAuth2\Server\Entities; +use DateTimeImmutable; + interface DeviceCodeEntityInterface extends TokenInterface { - /** - * @return string - */ - public function getUserCode(); - - /** - * @param string $userCode - */ - public function setUserCode($userCode); - - /** - * @return string - */ - public function getVerificationUri(); - - /** - * @param string $verificationUri - */ - public function setVerificationUri($verificationUri); + public function getUserCode(): string; + + public function setUserCode(string $userCode); + + public function getVerificationUri(): string; + + public function setVerificationUri(string $verificationUri); + + public function getLastPolledAt(): ?DateTimeImmutable; + + public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void; } diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 7b063e138..995e3b2ea 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -13,73 +13,26 @@ interface TokenInterface { - /** - * Get the token's identifier. - * - * @return string - */ - public function getIdentifier(); + public function getIdentifier(): string; - /** - * Set the token's identifier. - * - * @param mixed $identifier - */ - public function setIdentifier($identifier); + public function setIdentifier(mixed $identifier): void; - /** - * Get the token's expiry date time. - * - * @return DateTimeImmutable - */ - public function getExpiryDateTime(); + public function getExpiryDateTime(): DateTimeImmutable; - /** - * Set the date time when the token expires. - * - * @param DateTimeImmutable $dateTime - */ - public function setExpiryDateTime(DateTimeImmutable $dateTime); + public function setExpiryDateTime(DateTimeImmutable $dateTime): void; - /** - * Set the identifier of the user associated with the token. - * - * @param string|int|null $identifier The identifier of the user - */ - public function setUserIdentifier($identifier); + public function setUserIdentifier(string|int|null $identifier): void; - /** - * Get the token user's identifier. - * - * @return string|int|null - */ - public function getUserIdentifier(); + public function getUserIdentifier(): string|int|null; - /** - * Get the client that the token was issued to. - * - * @return ClientEntityInterface - */ - public function getClient(); + public function getClient(): ClientEntityInterface; - /** - * Set the client that the token was issued to. - * - * @param ClientEntityInterface $client - */ - public function setClient(ClientEntityInterface $client); + public function setClient(ClientEntityInterface $client): void; - /** - * Associate a scope with the token. - * - * @param ScopeEntityInterface $scope - */ - public function addScope(ScopeEntityInterface $scope); + public function addScope(ScopeEntityInterface $scope): void; /** - * Return an array of scopes associated with the token. - * * @return ScopeEntityInterface[] */ - public function getScopes(); + public function getScopes(): array; } diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index b7614a3bb..a9c0873de 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -15,67 +15,48 @@ trait DeviceCodeTrait { - /** - * @var string - */ - private $userCode; + private string $userCode; + private string $verificationUri; + private ?DateTimeImmutable $lastPolledAt = null; - /** - * @var string - */ - private $verificationUri; - - /** - * @return string - */ - public function getUserCode() + public function getUserCode(): string { return $this->userCode; } - /** - * @param string $userCode - * - * @return string - */ - public function setUserCode($userCode) + public function setUserCode(string $userCode): void { $this->userCode = $userCode; } - /** - * @return string - */ - public function getVerificationUri() + public function getVerificationUri(): string { return $this->verificationUri; } - /** - * @param string $verificationUri - */ - public function setVerificationUri($verificationUri) + public function setVerificationUri(string $verificationUri) { $this->verificationUri = $verificationUri; } - /** - * @return ClientEntityInterface - */ - abstract public function getClient(); + abstract public function getClient(): ClientEntityInterface; - /** - * @return DateTimeImmutable - */ - abstract public function getExpiryDateTime(); + abstract public function getExpiryDateTime(): DateTimeImmutable; /** * @return ScopeEntityInterface[] */ - abstract public function getScopes(); + abstract public function getScopes(): array; - /** - * @return string - */ - abstract public function getIdentifier(); + abstract public function getIdentifier(): string; + + public function getLastPolledAt(): ?DateTimeImmutable + { + return $this->lastPolledAt; + } + + public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void + { + $this->lastPolledAt = $lastPolledAt; + } } diff --git a/src/Entities/Traits/EntityTrait.php b/src/Entities/Traits/EntityTrait.php index 05452923a..a440731ba 100644 --- a/src/Entities/Traits/EntityTrait.php +++ b/src/Entities/Traits/EntityTrait.php @@ -11,23 +11,14 @@ trait EntityTrait { - /** - * @var string - */ - protected $identifier; + protected string $identifier; - /** - * @return mixed - */ - public function getIdentifier() + public function getIdentifier(): string { return $this->identifier; } - /** - * @param mixed $identifier - */ - public function setIdentifier($identifier) + public function setIdentifier(mixed $identifier): void { $this->identifier = $identifier; } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 83b172322..1748ca1b9 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -13,104 +13,81 @@ use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use function array_values; + trait TokenEntityTrait { /** * @var ScopeEntityInterface[] */ - protected $scopes = []; + protected array $scopes = []; - /** - * @var DateTimeImmutable - */ - protected $expiryDateTime; + protected DateTimeImmutable $expiryDateTime; - /** - * @var string|int|null - */ - protected $userIdentifier; + protected string|int|null $userIdentifier = null; - /** - * @var ClientEntityInterface - */ - protected $client; + protected ClientEntityInterface $client; /** * Associate a scope with the token. - * - * @param ScopeEntityInterface $scope */ - public function addScope(ScopeEntityInterface $scope) + public function addScope(ScopeEntityInterface $scope): void { $this->scopes[$scope->getIdentifier()] = $scope; } /** * Return an array of scopes associated with the token. - * - * @return ScopeEntityInterface[] */ - public function getScopes() + public function getScopes(): array { - return \array_values($this->scopes); + return array_values($this->scopes); } /** * Get the token's expiry date time. - * - * @return DateTimeImmutable */ - public function getExpiryDateTime() + public function getExpiryDateTime(): DateTimeImmutable { return $this->expiryDateTime; } /** * Set the date time when the token expires. - * - * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTimeImmutable $dateTime) + public function setExpiryDateTime(DateTimeImmutable $dateTime): void { $this->expiryDateTime = $dateTime; } /** * Set the identifier of the user associated with the token. - * - * @param string|int|null $identifier The identifier of the user */ - public function setUserIdentifier($identifier) + public function setUserIdentifier(string|int|null $identifier): void { $this->userIdentifier = $identifier; } /** * Get the token user's identifier. - * - * @return string|int|null */ - public function getUserIdentifier() + public function getUserIdentifier(): string|int|null { return $this->userIdentifier; } /** * Get the client that the token was issued to. - * - * @return ClientEntityInterface */ - public function getClient() + public function getClient(): ClientEntityInterface { return $this->client; } /** * Set the client that the token was issued to. - * - * @param ClientEntityInterface $client */ - public function setClient(ClientEntityInterface $client) + public function setClient(ClientEntityInterface $client): void { $this->client = $client; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index fd8ef14b7..f911ac16e 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -281,27 +281,24 @@ public static function invalidGrant($hint = '') */ public static function expiredToken($hint = null, Throwable $previous = null) { - $errorMessage = 'The `device_code` has expired and the device authorization session has concluded.'; + $errorMessage = 'The `device_code` has expired and the device ' . + 'authorization session has concluded.'; return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous); } - /** - * Authorization pending. - * - * @param string $hint - * - * @return static - */ - public static function authorizationPending($hint = '') + public static function authorizationPending($hint = '', Throwable $previous = null) { - return new static( - 'The authorization request is still pending as the end user hasn\'t yet completed the user interaction steps. ' - . 'The client SHOULD repeat the Access Token Request to the token endpoint', + return new static ( + 'The authorization request is still pending as the end user ' . + 'hasn\'t yet completed the user interaction steps. The client ' . + 'SHOULD repeat the Access Token Request to the token endpoint', 12, 'authorization_pending', 400, - $hint + $hint, + null, + $previous ); } @@ -316,14 +313,16 @@ public static function authorizationPending($hint = '') public static function slowDown($hint = '', Throwable $previous = null) { return new static( - 'example message', - 13, - 'slow_down', - 400, - $hint, - null, - $previous - ); + 'The authorization request is still pending and polling should ' . + 'continue, but the interval MUST be increased ' . + 'by 5 seconds for this and all subsequent requests.', + 13, + 'slow_down', + 400, + $hint, + null, + $previous + ); } /** diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index f9d18c3ad..8162a573d 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -38,6 +38,8 @@ use Psr\Http\Message\ServerRequestInterface; use TypeError; +use function array_key_exists; + /** * Abstract grant class. */ @@ -591,7 +593,7 @@ public function canRespondToAccessTokenRequest(ServerRequestInterface $request) $requestParameters = (array) $request->getParsedBody(); return ( - \array_key_exists('grant_type', $requestParameters) + array_key_exists('grant_type', $requestParameters) && $requestParameters['grant_type'] === $this->getIdentifier() ); } @@ -631,7 +633,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r /** * {@inheritdoc} */ - public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request) { throw new LogicException('This grant cannot validate a device authorization request'); } @@ -639,7 +641,7 @@ public function validateDeviceAuthorizationRequest(ServerRequestInterface $reque /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceAuthorizationRequest) + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId) { throw new LogicException('This grant cannot complete a device authorization request'); } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index ec15a600b..13e0ef634 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -20,7 +20,6 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; -use League\OAuth2\Server\Repositories\DeviceAuthorizationRequestRepository; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; @@ -31,6 +30,12 @@ use Psr\Http\Message\ServerRequestInterface; use TypeError; +use function is_null; +use function json_decode; +use function json_encode; +use function property_exists; +use function time; + /** * Device Code grant class. */ @@ -41,11 +46,6 @@ class DeviceCodeGrant extends AbstractGrant */ protected $deviceCodeRepository; - /** - * @var DeviceAuthorizationRequestRepository - */ - protected $deviceAuthorizationRequestRepository; - /** * @var DateInterval */ @@ -63,20 +63,17 @@ class DeviceCodeGrant extends AbstractGrant /** * @param DeviceCodeRepositoryInterface $deviceCodeRepository - * @param DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, * @param RefreshTokenRepositoryInterface $refreshTokenRepository * @param DateInterval $deviceCodeTTL * @param int $retryInterval */ public function __construct( DeviceCodeRepositoryInterface $deviceCodeRepository, - DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, DateInterval $deviceCodeTTL, $retryInterval = 5 ) { $this->setDeviceCodeRepository($deviceCodeRepository); - $this->setDeviceAuthorizationRequestRepository($deviceAuthorizationRequestRepository); $this->setRefreshTokenRepository($refreshTokenRepository); $this->refreshTokenTTL = new DateInterval('P1M'); @@ -96,7 +93,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r /** * {@inheritdoc} */ - public function validateDeviceAuthorizationRequest(ServerRequestInterface $request) + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request) { $clientId = $this->getRequestParameter( 'client_id', @@ -110,42 +107,31 @@ public function validateDeviceAuthorizationRequest(ServerRequestInterface $reque $client = $this->getClientEntityOrFail($clientId, $request); - $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); + // TODO: Make sure the grant type is set... - $deviceAuthorizationRequest = new DeviceAuthorizationRequest(); - $deviceAuthorizationRequest->setGrantTypeId($this->getIdentifier()); - $deviceAuthorizationRequest->setClient($client); - $deviceAuthorizationRequest->setScopes($scopes); + $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); - return $deviceAuthorizationRequest; - } + // TODO: Don't think I need the deviceauthorizationrequest any more. Might repurpose it... + // $deviceAuthorizationRequest = new DeviceAuthorizationRequest(); + // $deviceAuthorizationRequest->setGrantTypeId($this->getIdentifier()); - /** - * {@inheritdoc} - */ - public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest) - { $deviceCode = $this->issueDeviceCode( $this->deviceCodeTTL, - $deviceRequest->getClient(), + $client, $this->verificationUri, - $deviceRequest->getScopes() + $scopes ); $payload = [ - 'client_id' => $deviceCode->getClient()->getIdentifier(), 'device_code_id' => $deviceCode->getIdentifier(), - 'scopes' => $deviceCode->getScopes(), 'user_code' => $deviceCode->getUserCode(), - 'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(), 'verification_uri' => $deviceCode->getVerificationUri(), + 'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(), + 'client_id' => $deviceCode->getClient()->getIdentifier(), + 'scopes' => $deviceCode->getScopes(), ]; - $jsonPayload = \json_encode($payload); - - if ($jsonPayload === false) { - throw new LogicException('An error was encountered when JSON encoding the authorization request response'); - } + $jsonPayload = json_encode($payload, JSON_THROW_ON_ERROR); $response = new DeviceCodeResponse(); $response->setDeviceCode($deviceCode); @@ -154,6 +140,18 @@ public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $d return $response; } + /** + * {@inheritdoc} + */ + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId) + { + $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode($deviceCode); + + $deviceCode->setUserIdentifier($userId); + + $this->deviceCodeRepository->persistDeviceCode($deviceCode); + } + /** * {@inheritdoc} */ @@ -163,20 +161,24 @@ public function respondToAccessTokenRequest( DateInterval $accessTokenTTL ) { // Validate request + // TODO: Check that the correct grant type has been sent $client = $this->validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); $deviceCode = $this->validateDeviceCode($request, $client); - $lastRequest = $this->deviceAuthorizationRequestRepository->getLast($client->getIdentifier()); + // TODO: Should the last poll and user check be done in the validateDeviceCode method? + $lastPoll = $deviceCode->getLastPolledAt(); - if ($lastRequest !== null && $lastRequest->getTimestamp() + $this->retryInterval > \time()) { + if ($lastPoll !== null && $lastPoll->getTimestamp() + $this->retryInterval > time()) { throw OAuthServerException::slowDown(); } - $this->deviceAuthorizationRequestRepository->persist($deviceCode->getUserCode()); + $deviceCode->setLastPolledAt(new DateTimeImmutable()); + + $this->deviceCodeRepository->persistDeviceCode($deviceCode); // if device code has no user associated, respond with pending - if (\is_null($deviceCode->getUserIdentifier())) { + if (is_null($deviceCode->getUserIdentifier())) { throw OAuthServerException::authorizationPending(); } @@ -213,17 +215,17 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt { $encryptedDeviceCode = $this->getRequestParameter('device_code', $request); - if (\is_null($encryptedDeviceCode)) { + if (is_null($encryptedDeviceCode)) { throw OAuthServerException::invalidRequest('device_code'); } $deviceCodePayload = $this->decodeDeviceCode($encryptedDeviceCode); - if (!\property_exists($deviceCodePayload, 'device_code_id')) { + if (!property_exists($deviceCodePayload, 'device_code_id')) { throw OAuthServerException::invalidRequest('device_code', 'Device code malformed'); } - if (\time() > $deviceCodePayload->expire_time) { + if (time() > $deviceCodePayload->expire_time) { throw OAuthServerException::expiredToken('device_code'); } @@ -260,7 +262,7 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt protected function decodeDeviceCode($encryptedDeviceCode) { try { - return \json_decode($this->decrypt($encryptedDeviceCode)); + return json_decode($this->decrypt($encryptedDeviceCode)); } catch (LogicException $e) { throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e); } @@ -292,15 +294,6 @@ private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCo $this->deviceCodeRepository = $deviceCodeRepository; } - /** - * @param DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository - */ - private function setDeviceAuthorizationRequestRepository( - DeviceAuthorizationRequestRepository $deviceAuthorizationRequestRepository - ) { - $this->deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; - } - /** * Issue a device code. * @@ -335,7 +328,7 @@ protected function issueDeviceCode( $deviceCode->setIdentifier($this->generateUniqueIdentifier()); $deviceCode->setUserCode($this->generateUniqueUserCode()); try { - $this->deviceCodeRepository->persistNewDeviceCode($deviceCode); + $this->deviceCodeRepository->persistDeviceCode($deviceCode); return $deviceCode; } catch (UniqueTokenIdentifierConstraintViolationException $e) { diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 964bfaa8e..2ccb0f1a6 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -121,7 +121,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r * * @return DeviceAuthorizationRequest */ - public function validateDeviceAuthorizationRequest(ServerRequestInterface $request); + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request); /** * If the grant can respond to a device authorization request this method should be called to validate the parameters of @@ -133,7 +133,7 @@ public function validateDeviceAuthorizationRequest(ServerRequestInterface $reque * * @return ResponseTypeInterface */ - public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceAuthorizationRequest); + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId); /** * Set the client repository. diff --git a/src/Repositories/DeviceAuthorizationRequestRepository.php b/src/Repositories/DeviceAuthorizationRequestRepository.php deleted file mode 100644 index 9da6b82df..000000000 --- a/src/Repositories/DeviceAuthorizationRequestRepository.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @copyright Copyright (c) Alex Bilbie - * @license http://mit-license.org/ - * - * @link https://github.com/thephpleague/oauth2-server - */ - -namespace League\OAuth2\Server\Repositories; - -use DateTimeImmutable; - -/** - * Device authorization request storage interface. - */ -interface DeviceAuthorizationRequestRepository extends RepositoryInterface -{ - /** - * @param string $deviceCode - * - * @return DateTimeImmutable; - */ - public function getLast($deviceCode); - - /** - * @param string $deviceCode - */ - public function persist($deviceCode); -} diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 856d3a6d0..7d455f2f1 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -23,27 +23,23 @@ interface DeviceCodeRepositoryInterface extends RepositoryInterface public function getNewDeviceCode(); /** - * Persists a new auth code to permanent storage. + * Persists a device code to permanent storage. * * @param DeviceCodeEntityInterface $deviceCodeEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ - public function persistNewDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity); + public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity); /** * Get a device code entity. * * @param string $deviceCode - * @param string $grantType - * @param ClientEntityInterface $clientEntity * * @return DeviceCodeEntityInterface|null */ public function getDeviceCodeEntityByDeviceCode( - $deviceCode, - $grantType, - ClientEntityInterface $clientEntity + $deviceCode ); /** diff --git a/src/RequestTypes/DeviceAuthorizationRequest.php b/src/RequestTypes/DeviceAuthorizationRequest.php index 00bf818b4..9168829c2 100644 --- a/src/RequestTypes/DeviceAuthorizationRequest.php +++ b/src/RequestTypes/DeviceAuthorizationRequest.php @@ -28,6 +28,12 @@ class DeviceAuthorizationRequest */ protected $client; + private bool $authorizationApproved; + + private string $userCode; + + private string|int $userIdentifier; + /** * An array of scope identifiers * @@ -82,4 +88,19 @@ public function setScopes(array $scopes) { $this->scopes = $scopes; } + + public function getUserCode(): string + { + return $this->userCode; + } + + public function getUserIdentifier(): string|int + { + return $this->userIdentifier; + } + + public function authorizationApproved(): bool + { + return $this->authorizationApproved; + } } diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 98192b9e1..bb3193b9a 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -35,10 +35,12 @@ public function generateHttpResponse(ResponseInterface $response) $expireDateTime = $this->deviceCode->getExpiryDateTime()->getTimestamp(); $responseParams = [ - 'expires_in' => $expireDateTime - \time(), 'device_code' => $this->payload, 'user_code' => $this->deviceCode->getUserCode(), 'verification_uri' => $this->deviceCode->getVerificationUri(), + 'expires_in' => $expireDateTime - \time(), + // TODO: Add interval in here + // TODO: Potentially add in verification_uri_complete - it is optional ]; $responseParams = \json_encode($responseParams); diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index af8c89d8a..38349f6a1 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -7,7 +7,6 @@ use Laminas\Diactoros\ServerRequest; use Laminas\Diactoros\ServerRequestFactory; use League\OAuth2\Server\AuthorizationServer; -use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\AuthCodeGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant; @@ -21,13 +20,11 @@ use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; -use LeagueTests\Stubs\GrantType; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; class AuthorizationServerTest extends TestCase { @@ -248,6 +245,7 @@ public function testCompleteAuthorizationRequest() $client = new ClientEntity(); $client->setRedirectUri(self::REDIRECT_URI); + $client->setIdentifier('clientIdentifier'); $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 618545efe..0abfdb926 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -3,6 +3,7 @@ namespace LeagueTests\Grant; use DateInterval; +use ReflectionClass; use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; @@ -28,7 +29,7 @@ public function testHttpBasicWithPassword() { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . \base64_encode('Open:Sesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); @@ -41,7 +42,7 @@ public function testHttpBasicNoPassword() { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . \base64_encode('Open:')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); @@ -54,7 +55,7 @@ public function testHttpBasicNotBasic() { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Foo ' . \base64_encode('Open:Sesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); @@ -67,7 +68,7 @@ public function testHttpBasicNotBase64() { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ||'); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); @@ -80,7 +81,7 @@ public function testHttpBasicNoColon() { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . \base64_encode('OpenSesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); @@ -97,7 +98,7 @@ public function testGetClientCredentialsClientSecretNotAString() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = new ServerRequest( [], @@ -133,7 +134,7 @@ public function testValidateClientPublic() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -158,7 +159,7 @@ public function testValidateClientConfidential() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -183,7 +184,7 @@ public function testValidateClientMissingClientId() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = new ServerRequest(); $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); @@ -203,7 +204,7 @@ public function testValidateClientMissingClientSecret() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -226,7 +227,7 @@ public function testValidateClientInvalidClientSecret() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -252,7 +253,7 @@ public function testValidateClientInvalidRedirectUri() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -278,7 +279,7 @@ public function testValidateClientInvalidRedirectUriArray() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -304,7 +305,7 @@ public function testValidateClientMalformedRedirectUri() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -328,7 +329,7 @@ public function testValidateClientBadClient() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -347,6 +348,7 @@ public function testCanRespondToRequest() { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); + $grantMock->setDefaultScope('defaultScope'); $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'foobar', @@ -368,7 +370,7 @@ public function testIssueRefreshToken() $grantMock->setRefreshTokenTTL(new DateInterval('PT1M')); $grantMock->setRefreshTokenRepository($refreshTokenRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken'); $issueRefreshTokenMethod->setAccessible(true); @@ -392,7 +394,7 @@ public function testIssueNullRefreshToken() $grantMock->setRefreshTokenTTL(new \DateInterval('PT1M')); $grantMock->setRefreshTokenRepository($refreshTokenRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken'); $issueRefreshTokenMethod->setAccessible(true); @@ -410,7 +412,7 @@ public function testIssueAccessToken() $grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grantMock->setAccessTokenRepository($accessTokenRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueAccessTokenMethod = $abstractGrantReflection->getMethod('issueAccessToken'); $issueAccessTokenMethod->setAccessible(true); @@ -434,10 +436,13 @@ public function testIssueAuthCode() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setAuthCodeRepository($authCodeRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueAuthCodeMethod = $abstractGrantReflection->getMethod('issueAuthCode'); $issueAuthCodeMethod->setAccessible(true); + $scope = new ScopeEntity(); + $scope->setIdentifier('scopeIdentifier'); + $this->assertInstanceOf( AuthCodeEntityInterface::class, $issueAuthCodeMethod->invoke( @@ -446,7 +451,7 @@ public function testIssueAuthCode() new ClientEntity(), 123, 'http://foo/bar', - [new ScopeEntity()] + [$scope] ) ); } @@ -456,7 +461,7 @@ public function testGetCookieParameter() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $method = $abstractGrantReflection->getMethod('getCookieParameter'); $method->setAccessible(true); @@ -473,7 +478,7 @@ public function testGetQueryStringParameter() $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $method = $abstractGrantReflection->getMethod('getQueryStringParameter'); $method->setAccessible(true); @@ -516,7 +521,7 @@ public function testGenerateUniqueIdentifier() { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $method = $abstractGrantReflection->getMethod('generateUniqueIdentifier'); $method->setAccessible(true); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 7c2c20d11..c55de7397 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -460,6 +460,7 @@ public function testCompleteAuthorizationRequest() { $client = new ClientEntity(); $client->setRedirectUri(self::REDIRECT_URI); + $client->setIdentifier('clientIdentifier'); $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -1824,6 +1825,7 @@ public function testAuthCodeRepositoryUniqueConstraintCheck() { $client = new ClientEntity(); $client->setRedirectUri(self::REDIRECT_URI); + $client->setIdentifier('clientIdentifier'); $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index e2c075179..cb82d6111 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -3,14 +3,16 @@ namespace LeagueTests\Grant; use DateInterval; +use DateTimeImmutable; +use Laminas\Diactoros\Response; use Laminas\Diactoros\ServerRequest; +use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Grant\DeviceCodeGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; -use League\OAuth2\Server\Repositories\DeviceAuthorizationRequestRepository; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; @@ -25,6 +27,9 @@ use LeagueTests\Stubs\StubResponseType; use PHPUnit\Framework\TestCase; +use function time; +use function uniqid; + class DeviceCodeGrantTest extends TestCase { const DEFAULT_SCOPE = 'basic'; @@ -42,12 +47,10 @@ public function setUp(): void public function testGetIdentifier() { $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, - $requestRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M'), 5 @@ -60,7 +63,6 @@ public function testCanRespondToDeviceAuthorizationRequest() { $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -81,26 +83,32 @@ public function testValidateDeviceAuthorizationRequest() $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeRepository->method('getNewDeviceCode')->willReturn(new DeviceCodeEntity()); + $scope = new ScopeEntity(); + $scope->setIdentifier('basic'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $grant = new DeviceCodeGrant( - $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), // TODO: Does this have a refersh token? new DateInterval('PT10M') ); + $grant->setClientRepository($clientRepositoryMock); - $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setVerificationUri('http://foo/bar'); $request = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'scope' => 'basic', ]); - $this->assertInstanceOf(DeviceAuthorizationRequest::class, $grant->validateDeviceAuthorizationRequest($request)); + $this->assertInstanceOf(DeviceCodeResponse::class, $grant->respondToDeviceAuthorizationRequest($request)); } public function testValidateDeviceAuthorizationRequestMissingClient() @@ -117,7 +125,6 @@ public function testValidateDeviceAuthorizationRequestMissingClient() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -131,7 +138,37 @@ public function testValidateDeviceAuthorizationRequestMissingClient() $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); - $grant->validateDeviceAuthorizationRequest($request); + $grant->respondToDeviceAuthorizationRequest($request); + } + + public function testValidateDeviceAuthorizationRequestEmptyScope() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new DeviceCodeGrant( + $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + + $request = (new ServerRequest())->withParsedBody([ + 'scope' => '', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->respondToDeviceAuthorizationRequest($request); } public function testValidateDeviceAuthorizationRequestClientMismatch() @@ -145,7 +182,6 @@ public function testValidateDeviceAuthorizationRequestClientMismatch() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); @@ -160,30 +196,92 @@ public function testValidateDeviceAuthorizationRequestClientMismatch() $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); - $grant->validateDeviceAuthorizationRequest($request); + $grant->respondToDeviceAuthorizationRequest($request); } public function testCompleteDeviceAuthorizationRequest() { - $deviceAuthRequest = new DeviceAuthorizationRequest(); - $deviceAuthRequest->setClient(new ClientEntity()); - $deviceAuthRequest->setGrantTypeId('device_code'); + $deviceCode = new DeviceCodeEntity(); + $deviceCode->setUserCode('foo'); $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $deviceCodeRepository->method('getNewDeviceCode')->willReturn(new DeviceCodeEntity()); + $deviceCodeRepository->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); $grant = new DeviceCodeGrant( $deviceCodeRepository, - $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M') ); + + $grant->setVerificationUri('http://foo/bar'); $grant->setEncryptionKey($this->cryptStub->getKey()); - $this->assertInstanceOf(DeviceCodeResponse::class, $grant->completeDeviceAuthorizationRequest($deviceAuthRequest)); + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 'userId'); + + $this->assertEquals('userId', $deviceCode->getUserIdentifier()); + } + + public function testDeviceAuthorizationResponse() + { + $client = new ClientEntity(); + $client->setIdentifier('clientId'); + $client->setConfidential(); + $client->setRedirectUri('http://foo/bar'); + + $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepository->method('getClientEntity')->willReturn($client); + + $scopeEntity = new ScopeEntity; + $scopeEntity->setIdentifier('basic'); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $accessRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + + $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeRepository->method('getNewDeviceCode')->willReturn(new DeviceCodeEntity()); + + $server = new AuthorizationServer( + $clientRepository, + $accessRepositoryMock, + $scopeRepositoryMock, + 'file://' . __DIR__ . '/../Stubs/private.key', + \base64_encode(\random_bytes(36)), + new StubResponseType() + ); + + $server->setDefaultScope(self::DEFAULT_SCOPE); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + ]); + + $deviceCodeGrant = new DeviceCodeGrant( + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + + $deviceCodeGrant->setEncryptionKey($this->cryptStub->getKey()); + $deviceCodeGrant->setVerificationUri('http://foo/bar'); + + $server->enableGrantType($deviceCodeGrant); + + $response = $server->respondToDeviceAuthorizationRequest($serverRequest, new Response()); + + $responseObject = json_decode($response->getBody()->__toString()); + + $this->assertObjectHasAttribute('device_code', $responseObject); + $this->assertObjectHasAttribute('user_code', $responseObject); + $this->assertObjectHasAttribute('verification_uri', $responseObject); + // TODO: $this->assertObjectHasAttribute('verification_uri_complete', $responseObject); + $this->assertObjectHasAttribute('expires_in', $responseObject); + // TODO: $this->assertObjectHasAttribute('interval', $responseObject); } - public function testRespondToRequest() + public function testRespondToAccessTokenRequest() { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -199,10 +297,9 @@ public function testRespondToRequest() $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); - $requestRepositoryMock->method('getLast')->willReturn(null); $deviceCodeEntity = new DeviceCodeEntity(); $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeEntity->setIdentifier('deviceCodeEntityIdentifier'); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scope = new ScopeEntity(); @@ -212,7 +309,6 @@ public function testRespondToRequest() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, - $requestRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -225,12 +321,12 @@ public function testRespondToRequest() $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $serverRequest = (new ServerRequest())->withParsedBody([ - 'client_id' => 'foo', + 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', 'device_code' => $this->cryptStub->doEncrypt( \json_encode( [ - 'device_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'device_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_code' => '12345678', 'scopes' => ['foo'], @@ -238,6 +334,7 @@ public function testRespondToRequest() ] ) ), + 'client_id' => 'foo', ]); $responseType = new StubResponseType(); @@ -253,15 +350,11 @@ public function testRespondToRequestMissingClient() $clientRepositoryMock->method('getClientEntity')->willReturn(null); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); - $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, - $requestRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -273,8 +366,8 @@ public function testRespondToRequestMissingClient() 'device_code' => $this->cryptStub->doEncrypt( \json_encode( [ - 'device_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'device_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_code' => '12345678', 'scopes' => ['foo'], @@ -303,7 +396,6 @@ public function testRespondToRequestMissingDeviceCode() $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); $deviceCodeEntity = new DeviceCodeEntity(); $deviceCodeEntity->setUserIdentifier('baz'); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); @@ -315,7 +407,6 @@ public function testRespondToRequestMissingDeviceCode() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, - $requestRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -350,10 +441,8 @@ public function testIssueSlowDownError() $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $requestRepositoryMock = $this->getMockBuilder(DeviceAuthorizationRequestRepository::class)->getMock(); - $requestRepositoryMock->method('getLast')->willReturn(new \DateTimeImmutable()); $deviceCodeEntity = new DeviceCodeEntity(); - $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable()); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scope = new ScopeEntity(); @@ -363,7 +452,6 @@ public function testIssueSlowDownError() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, - $requestRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M') ); @@ -379,8 +467,8 @@ public function testIssueSlowDownError() 'device_code' => $this->cryptStub->doEncrypt( \json_encode( [ - 'device_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'device_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_code' => '12345678', 'scopes' => ['foo'], @@ -397,4 +485,119 @@ public function testIssueSlowDownError() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } + + function testIssueAuthorizationPendingError() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCode = new DeviceCodeEntity(); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'device_code' => $this->cryptStub->doEncrypt( + \json_encode( + [ + 'device_code_id' => uniqid(), + 'expire_time' => time() + 3600, + 'client_id' => 'foo', + 'user_code' => '12345678', + 'scopes' => ['foo'], + 'verification_uri' => 'http://foo/bar', + ] + ) + ), + ]); + + $responseType = new StubResponseType(); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(12); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } + + function testIssueExpiredTokenError() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCode = new DeviceCodeEntity(); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'device_code' => $this->cryptStub->doEncrypt( + \json_encode( + [ + 'device_code_id' => uniqid(), + 'expire_time' => time() - 3600, + 'client_id' => 'foo', + 'user_code' => '12345678', + 'scopes' => ['foo'], + 'verification_uri' => 'http://foo/bar', + ] + ) + ), + ]); + + $responseType = new StubResponseType(); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(11); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + } + + // NEED TO ADD IN TESTS FOR: + // access_denied - for this one, we need to add it to the completeDeviceAuthorizationRequest method } diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 5f69242c7..f27e1b030 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -219,6 +219,7 @@ public function testCompleteAuthorizationRequest() $accessToken = new AccessTokenEntity(); $accessToken->setClient($client); + $accessToken->setUserIdentifier('userIdentifier'); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); @@ -278,6 +279,7 @@ public function testAccessTokenRepositoryUniqueConstraintCheck() $accessToken = new AccessTokenEntity(); $accessToken->setClient($client); + $accessToken->setUserIdentifier('userIdentifier'); /** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index da7242651..1921d3844 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -38,6 +38,7 @@ public function testGenerateHttpResponse() $accessToken->setClient($client); $accessToken->addScope($scope); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setUserIdentifier('userIdentifier'); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -81,6 +82,7 @@ public function testGenerateHttpResponseWithExtraParams() $accessToken->setClient($client); $accessToken->addScope($scope); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setUserIdentifier('userIdentifier'); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index c5a298db5..111f4a7c0 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -33,6 +33,7 @@ public function testGenerateHttpResponse() $deviceCode->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $deviceCode->setClient($client); $deviceCode->addScope($scope); + $deviceCode->setVerificationUri('https://example.com/device'); $responseType->setDeviceCode($deviceCode); From ab39ef6ecda34aebba58d4437777e4d34611c770 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 5 Sep 2023 21:48:17 +0100 Subject: [PATCH 118/212] Tidy up tests and add error handling --- examples/public/device_code.php | 18 +- .../src/Repositories/DeviceCodeRepository.php | 4 +- src/AuthorizationServer.php | 12 +- src/Entities/DeviceCodeEntityInterface.php | 12 ++ src/Entities/Traits/DeviceCodeTrait.php | 33 ++++ src/Grant/AbstractGrant.php | 2 +- src/Grant/DeviceCodeGrant.php | 91 +++++---- src/Grant/GrantTypeInterface.php | 2 +- src/ResponseTypes/DeviceCodeResponse.php | 20 +- tests/Grant/DeviceCodeGrantTest.php | 182 +++++++++++++++--- .../ResponseTypes/BearerResponseTypeTest.php | 14 +- .../DeviceCodeResponseTypeTest.php | 8 +- 12 files changed, 278 insertions(+), 120 deletions(-) diff --git a/examples/public/device_code.php b/examples/public/device_code.php index 503a554ee..b88a67838 100644 --- a/examples/public/device_code.php +++ b/examples/public/device_code.php @@ -51,7 +51,7 @@ $deviceCodeRepository, $refreshTokenRepository, new \DateInterval('PT10M'), - 5 + 'http://foo/bar' ), new \DateInterval('PT1H') ); @@ -65,17 +65,17 @@ $server = $app->getContainer()->get(AuthorizationServer::class); try { - $deviceAuthRequest = $server->validateDeviceAuthorizationRequest($request); + $deviceCodeResponse = $server->respondToDeviceAuthorizationRequest($request, $response); - // TODO: I don't think this is right as the user can't approve the request via the same client... - // Once the user has logged in, set the user on the authorization request - //$deviceAuthRequest->setUserIdentifier(); + return $deviceCodeResponse; - // Once the user has approved or denied the client, update the status - //$deviceAuthRequest->setAuthorizationApproved(true); + // Extract the device code. Usually we would then assign the user ID to + // the device code but for the purposes of this example, we've hard + // coded it in the response above. + // $deviceCode = json_decode((string) $deviceCodeResponse->getBody()); - // Return the HTTP redirect response - return $server->completeDeviceAuthorizationRequest($deviceAuthRequest, $response); + // Once the user has logged in and approved the request, set the user on the device code + // $server->completeDeviceAuthorizationRequest($deviceCode->user_code, 1); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); } catch (\Exception $exception) { diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index e4b76d368..6848d0235 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -35,10 +35,12 @@ public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) /** * {@inheritdoc} */ - public function getDeviceCodeEntityByDeviceCode($deviceCode, $grantType, ClientEntityInterface $clientEntity) + public function getDeviceCodeEntityByDeviceCode($deviceCode) { $deviceCode = new DeviceCodeEntity(); + $deviceCode->setIdentifier('device_code_1234'); + // The user identifier should be set when the user authenticates on the OAuth server $deviceCode->setUserIdentifier(1); diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index eaf4a8fc9..a9042abbb 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -207,17 +207,11 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * Complete a device authorization request - * - * @param DeviceAuthorizationRequest $deviceRequest - * @param ResponseInterface $response - * - * @return ResponseInterface */ - public function completeDeviceAuthorizationRequest(DeviceAuthorizationRequest $deviceRequest, ResponseInterface $response) + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId): ResponseInterface { - // TODO: Check why we aren't just using completeAuthorizationRequest - return $this->enabledGrantTypes[$deviceRequest->getGrantTypeId()] - ->completeDeviceAuthorizationRequest($deviceRequest) + return $this->enabledGrantTypes['urn:ietf:params:oauth:grant-type:device_code'] + ->completeDeviceAuthorizationRequest($deviceCode, $userId) ->generateHttpResponse($response); } diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index 571d7ee26..07a824cea 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -24,4 +24,16 @@ public function setVerificationUri(string $verificationUri); public function getLastPolledAt(): ?DateTimeImmutable; public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void; + + public function getInterval(): int; + + public function setInterval(int $interval): void; + + public function getIntervalInAuthResponse(): bool; + + public function setIntervalInAuthResponse(bool $intervalInAuthResponse): bool; + + public function getUserApproved(): bool; + + public function setUserApproved(bool $userApproved): void; } diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index a9c0873de..0ea5160e3 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -15,6 +15,9 @@ trait DeviceCodeTrait { + private bool $userApproved = false; + private bool $intervalInAuthResponse = false; + private int $interval = 5; private string $userCode; private string $verificationUri; private ?DateTimeImmutable $lastPolledAt = null; @@ -59,4 +62,34 @@ public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void { $this->lastPolledAt = $lastPolledAt; } + + public function getInterval(): int + { + return $this->interval; + } + + public function setInterval(int $interval): void + { + $this->interval = $interval; + } + + public function getIntervalInAuthResponse(): bool + { + return $this->intervalInAuthResponse; + } + + public function setIntervalInAuthResponse(bool $intervalInAuthResponse): bool + { + return $this->intervalInAuthResponse = $intervalInAuthResponse; + } + + public function getUserApproved(): bool + { + return $this->userApproved; + } + + public function setUserApproved(bool $userApproved): void + { + $this->userApproved = $userApproved; + } } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 8162a573d..1671a8202 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -641,7 +641,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId) + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved) { throw new LogicException('This grant cannot complete a device authorization request'); } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 13e0ef634..6fb0d3c42 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -41,37 +41,18 @@ */ class DeviceCodeGrant extends AbstractGrant { - /** - * @var DeviceCodeRepositoryInterface - */ - protected $deviceCodeRepository; - - /** - * @var DateInterval - */ - private $deviceCodeTTL; + protected DeviceCodeRepositoryInterface $deviceCodeRepository; + private DateInterval $deviceCodeTTL; + private bool $intervalVisibility = false; + private int $retryInterval; + private string $verificationUri; - /** - * @var int - */ - private $retryInterval; - - /** - * @var string - */ - private $verificationUri; - - /** - * @param DeviceCodeRepositoryInterface $deviceCodeRepository - * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param DateInterval $deviceCodeTTL - * @param int $retryInterval - */ public function __construct( DeviceCodeRepositoryInterface $deviceCodeRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, DateInterval $deviceCodeTTL, - $retryInterval = 5 + string $verificationUri, + int $retryInterval = 5 ) { $this->setDeviceCodeRepository($deviceCodeRepository); $this->setRefreshTokenRepository($refreshTokenRepository); @@ -79,6 +60,7 @@ public function __construct( $this->refreshTokenTTL = new DateInterval('P1M'); $this->deviceCodeTTL = $deviceCodeTTL; + $this->setVerificationUri($verificationUri); $this->retryInterval = $retryInterval; } @@ -107,14 +89,8 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ $client = $this->getClientEntityOrFail($clientId, $request); - // TODO: Make sure the grant type is set... - $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); - // TODO: Don't think I need the deviceauthorizationrequest any more. Might repurpose it... - // $deviceAuthorizationRequest = new DeviceAuthorizationRequest(); - // $deviceAuthorizationRequest->setGrantTypeId($this->getIdentifier()); - $deviceCode = $this->issueDeviceCode( $this->deviceCodeTTL, $client, @@ -122,6 +98,8 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ $scopes ); + // TODO: Why do we need this? Why not just generate a random number? Is it a security concern? + // TODO: Do I need to set the interval in this? $payload = [ 'device_code_id' => $deviceCode->getIdentifier(), 'user_code' => $deviceCode->getUserCode(), @@ -143,11 +121,12 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId) + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $approved) { $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode($deviceCode); $deviceCode->setUserIdentifier($userId); + $deviceCode->setUserApproved($approved); $this->deviceCodeRepository->persistDeviceCode($deviceCode); } @@ -161,27 +140,22 @@ public function respondToAccessTokenRequest( DateInterval $accessTokenTTL ) { // Validate request - // TODO: Check that the correct grant type has been sent $client = $this->validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); $deviceCode = $this->validateDeviceCode($request, $client); - // TODO: Should the last poll and user check be done in the validateDeviceCode method? - $lastPoll = $deviceCode->getLastPolledAt(); - - if ($lastPoll !== null && $lastPoll->getTimestamp() + $this->retryInterval > time()) { - throw OAuthServerException::slowDown(); - } - $deviceCode->setLastPolledAt(new DateTimeImmutable()); - $this->deviceCodeRepository->persistDeviceCode($deviceCode); - // if device code has no user associated, respond with pending + // If device code has no user associated, respond with pending if (is_null($deviceCode->getUserIdentifier())) { throw OAuthServerException::authorizationPending(); } + if ($deviceCode->getUserApproved() === false) { + throw OAuthServerException::accessDenied(); + } + // Finalize the requested scopes $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, (string) $deviceCode->getUserIdentifier()); @@ -238,10 +212,14 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt } $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( - $deviceCodePayload->device_code_id, - $this->getIdentifier(), - $client - ); + $deviceCodePayload->device_code_id, + $this->getIdentifier(), + $client + ); + + if ($this->deviceCodePolledTooSoon($deviceCode->getLastPolledAt()) === true) { + throw OAuthServerException::slowDown(); + } if ($deviceCode instanceof DeviceCodeEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); @@ -252,6 +230,11 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt return $deviceCode; } + private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll): bool + { + return $lastPoll !== null && $lastPoll->getTimestamp() + $this->retryInterval > time(); + } + /** * @param string $encryptedDeviceCode * @@ -320,6 +303,10 @@ protected function issueDeviceCode( $deviceCode->setClient($client); $deviceCode->setVerificationUri($verificationUri); + if ($this->getIntervalVisibility() === true) { + $deviceCode->setInterval($this->retryInterval); + } + foreach ($scopes as $scope) { $deviceCode->addScope($scope); } @@ -370,4 +357,14 @@ protected function generateUniqueUserCode($length = 8) } // @codeCoverageIgnoreEnd } + + public function setIntervalVisibility(bool $intervalVisibility): void + { + $this->intervalVisibility = $intervalVisibility; + } + + public function getIntervalVisibility(): bool + { + return $this->intervalVisibility; + } } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 2ccb0f1a6..87fee0a8e 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -133,7 +133,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ * * @return ResponseTypeInterface */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId); + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved); /** * Set the client repository. diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index bb3193b9a..33b3282d9 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -15,17 +15,12 @@ use LogicException; use Psr\Http\Message\ResponseInterface; +use function time; + class DeviceCodeResponse extends AbstractResponseType { - /** - * @var DeviceCodeEntityInterface - */ - protected $deviceCode; - - /** - * @var string - */ - protected $payload; + protected DeviceCodeEntityInterface $deviceCode; + protected string $payload; /** * {@inheritdoc} @@ -38,11 +33,14 @@ public function generateHttpResponse(ResponseInterface $response) 'device_code' => $this->payload, 'user_code' => $this->deviceCode->getUserCode(), 'verification_uri' => $this->deviceCode->getVerificationUri(), - 'expires_in' => $expireDateTime - \time(), - // TODO: Add interval in here + 'expires_in' => $expireDateTime - time(), // TODO: Potentially add in verification_uri_complete - it is optional ]; + if ($this->deviceCode->getIntervalInAuthResponse() === true) { + $responseParams['interval'] = $this->deviceCode->getInterval(); + } + $responseParams = \json_encode($responseParams); if ($responseParams === false) { diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index cb82d6111..537019568 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -53,7 +53,7 @@ public function testGetIdentifier() $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M'), - 5 + "http://foo/bar" ); $this->assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); @@ -64,7 +64,8 @@ public function testCanRespondToDeviceAuthorizationRequest() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') + new DateInterval('PT10M'), + "http://foo/bar" ); $request = (new ServerRequest())->withParsedBody([ @@ -75,7 +76,7 @@ public function testCanRespondToDeviceAuthorizationRequest() $this->assertTrue($grant->canRespondToDeviceAuthorizationRequest($request)); } - public function testValidateDeviceAuthorizationRequest() + public function testRespondToDeviceAuthorizationRequest() { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -93,15 +94,15 @@ public function testValidateDeviceAuthorizationRequest() $grant = new DeviceCodeGrant( $deviceCodeRepository, - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), // TODO: Does this have a refersh token? - new DateInterval('PT10M') + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M'), + "http://foo/bar" ); $grant->setClientRepository($clientRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setScopeRepository($scopeRepositoryMock); - $grant->setVerificationUri('http://foo/bar'); $request = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -126,7 +127,8 @@ public function testValidateDeviceAuthorizationRequestMissingClient() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -156,7 +158,8 @@ public function testValidateDeviceAuthorizationRequestEmptyScope() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -183,7 +186,8 @@ public function testValidateDeviceAuthorizationRequestClientMismatch() $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -210,13 +214,13 @@ public function testCompleteDeviceAuthorizationRequest() $grant = new DeviceCodeGrant( $deviceCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar', ); - $grant->setVerificationUri('http://foo/bar'); $grant->setEncryptionKey($this->cryptStub->getKey()); - $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 'userId'); + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 'userId', true); $this->assertEquals('userId', $deviceCode->getUserIdentifier()); } @@ -261,11 +265,11 @@ public function testDeviceAuthorizationResponse() $deviceCodeGrant = new DeviceCodeGrant( $deviceCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $deviceCodeGrant->setEncryptionKey($this->cryptStub->getKey()); - $deviceCodeGrant->setVerificationUri('http://foo/bar'); $server->enableGrantType($deviceCodeGrant); @@ -273,11 +277,11 @@ public function testDeviceAuthorizationResponse() $responseObject = json_decode($response->getBody()->__toString()); - $this->assertObjectHasAttribute('device_code', $responseObject); - $this->assertObjectHasAttribute('user_code', $responseObject); - $this->assertObjectHasAttribute('verification_uri', $responseObject); + $this->assertObjectHasProperty('device_code', $responseObject); + $this->assertObjectHasProperty('user_code', $responseObject); + $this->assertObjectHasProperty('verification_uri', $responseObject); // TODO: $this->assertObjectHasAttribute('verification_uri_complete', $responseObject); - $this->assertObjectHasAttribute('expires_in', $responseObject); + $this->assertObjectHasProperty('expires_in', $responseObject); // TODO: $this->assertObjectHasAttribute('interval', $responseObject); } @@ -297,10 +301,13 @@ public function testRespondToAccessTokenRequest() $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $deviceCodeEntity = new DeviceCodeEntity(); - $deviceCodeEntity->setUserIdentifier('baz'); - $deviceCodeEntity->setIdentifier('deviceCodeEntityIdentifier'); - $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); + $deviceCode = new DeviceCodeEntity(); + + $deviceCode->setUserIdentifier('baz'); + $deviceCode->setIdentifier('deviceCodeEntityIdentifier'); + $deviceCode->setUserCode('123456'); + + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -310,7 +317,8 @@ public function testRespondToAccessTokenRequest() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -320,6 +328,8 @@ public function testRespondToAccessTokenRequest() $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 1, true); + $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', 'device_code' => $this->cryptStub->doEncrypt( @@ -356,7 +366,8 @@ public function testRespondToRequestMissingClient() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -408,7 +419,8 @@ public function testRespondToRequestMissingDeviceCode() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -453,7 +465,8 @@ public function testIssueSlowDownError() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -509,7 +522,8 @@ function testIssueAuthorizationPendingError() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -565,7 +579,8 @@ function testIssueExpiredTokenError() $grant = new DeviceCodeGrant( $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, - new DateInterval('PT10M') + new DateInterval('PT10M'), + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -598,6 +613,113 @@ function testIssueExpiredTokenError() $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - // NEED TO ADD IN TESTS FOR: - // access_denied - for this one, we need to add it to the completeDeviceAuthorizationRequest method + public function testIntervalVisibility() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $deviceCode = new DeviceCodeEntity(); + + $deviceCode->setIntervalInAuthResponse(true); + + $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeRepository->method('getNewDeviceCode')->willReturn($deviceCode); + + $scope = new ScopeEntity(); + $scope->setIdentifier('basic'); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new DeviceCodeGrant( + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M'), + "http://foo/bar" + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setScopeRepository($scopeRepositoryMock); + + $request = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'scope' => 'basic', + ]); + + $deviceCodeResponse = $grant + ->respondToDeviceAuthorizationRequest($request) + ->generateHttpResponse(new Response()); + + $deviceCode = json_decode((string) $deviceCodeResponse->getBody()); + + $this->assertObjectHasProperty('interval', $deviceCode); + $this->assertEquals(5, $deviceCode->interval); + } + + public function testIssueAccessDeniedError(): void + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + + $deviceCode = new DeviceCodeEntity(); + + $deviceCode->setUserCode('12345678'); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $grant = new DeviceCodeGrant( + $deviceCodeRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M'), + 'http://foo/bar' + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 1, false); + + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'device_code' => $this->cryptStub->doEncrypt( + \json_encode( + [ + 'device_code_id' => uniqid(), + 'expire_time' => time() + 3600, + 'client_id' => 'foo', + 'user_code' => '12345678', + 'scopes' => ['foo'], + 'verification_uri' => 'http://foo/bar', + ] + ) + ), + ]); + + $responseType = new StubResponseType(); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(9); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + + } } diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 1921d3844..0626ddd71 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -59,9 +59,9 @@ public function testGenerateHttpResponse() $response->getBody()->rewind(); $json = \json_decode($response->getBody()->getContents()); $this->assertEquals('Bearer', $json->token_type); - $this->assertObjectHasAttribute('expires_in', $json); - $this->assertObjectHasAttribute('access_token', $json); - $this->assertObjectHasAttribute('refresh_token', $json); + $this->assertObjectHasProperty('expires_in', $json); + $this->assertObjectHasProperty('access_token', $json); + $this->assertObjectHasProperty('refresh_token', $json); } public function testGenerateHttpResponseWithExtraParams() @@ -103,11 +103,11 @@ public function testGenerateHttpResponseWithExtraParams() $response->getBody()->rewind(); $json = \json_decode($response->getBody()->getContents()); $this->assertEquals('Bearer', $json->token_type); - $this->assertObjectHasAttribute('expires_in', $json); - $this->assertObjectHasAttribute('access_token', $json); - $this->assertObjectHasAttribute('refresh_token', $json); + $this->assertObjectHasProperty('expires_in', $json); + $this->assertObjectHasProperty('access_token', $json); + $this->assertObjectHasProperty('refresh_token', $json); - $this->assertObjectHasAttribute('foo', $json); + $this->assertObjectHasProperty('foo', $json); $this->assertEquals('bar', $json->foo); } diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index 111f4a7c0..88044ca68 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -49,10 +49,10 @@ public function testGenerateHttpResponse() $response->getBody()->rewind(); $json = \json_decode($response->getBody()->getContents()); - $this->assertObjectHasAttribute('expires_in', $json); - $this->assertObjectHasAttribute('device_code', $json); + $this->assertObjectHasProperty('expires_in', $json); + $this->assertObjectHasProperty('device_code', $json); $this->assertEquals('test', $json->device_code); - $this->assertObjectHasAttribute('verification_uri', $json); - $this->assertObjectHasAttribute('user_code', $json); + $this->assertObjectHasProperty('verification_uri', $json); + $this->assertObjectHasProperty('user_code', $json); } } From 9433aea26c8fe94352743fcd7b4dadceec722f7e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 5 Sep 2023 22:02:51 +0100 Subject: [PATCH 119/212] Fix deprecation in PHPUnit --- tests/ResponseTypes/BearerResponseTypeTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 7b7c85fa1..6c8c1fd03 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -58,9 +58,9 @@ public function testGenerateHttpResponse(): void $response->getBody()->rewind(); $json = \json_decode($response->getBody()->getContents()); $this->assertEquals('Bearer', $json->token_type); - $this->assertObjectHasAttribute('expires_in', $json); - $this->assertObjectHasAttribute('access_token', $json); - $this->assertObjectHasAttribute('refresh_token', $json); + $this->assertObjectHasProperty('expires_in', $json); + $this->assertObjectHasProperty('access_token', $json); + $this->assertObjectHasProperty('refresh_token', $json); } public function testGenerateHttpResponseWithExtraParams(): void @@ -101,11 +101,11 @@ public function testGenerateHttpResponseWithExtraParams(): void $response->getBody()->rewind(); $json = \json_decode($response->getBody()->getContents()); $this->assertEquals('Bearer', $json->token_type); - $this->assertObjectHasAttribute('expires_in', $json); - $this->assertObjectHasAttribute('access_token', $json); - $this->assertObjectHasAttribute('refresh_token', $json); + $this->assertObjectHasProperty('expires_in', $json); + $this->assertObjectHasProperty('access_token', $json); + $this->assertObjectHasProperty('refresh_token', $json); - $this->assertObjectHasAttribute('foo', $json); + $this->assertObjectHasProperty('foo', $json); $this->assertEquals('bar', $json->foo); } From 2575a3ee0fad42c1a9bf5b93d0fcc304be57684a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 25 Sep 2023 22:43:47 +0100 Subject: [PATCH 120/212] Update comment --- src/Grant/DeviceCodeGrant.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 6fb0d3c42..64a262fac 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -98,8 +98,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ $scopes ); - // TODO: Why do we need this? Why not just generate a random number? Is it a security concern? - // TODO: Do I need to set the interval in this? + // TODO: Check payload generation $payload = [ 'device_code_id' => $deviceCode->getIdentifier(), 'user_code' => $deviceCode->getUserCode(), From 068a13e3e32fc424e9bdd9b5f9ae4c78220be8c3 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Sep 2023 23:44:43 +0100 Subject: [PATCH 121/212] Fix tests --- composer.json | 12 +- src/AuthorizationServer.php | 109 ++----- .../AuthorizationValidatorInterface.php | 12 +- .../BearerTokenValidator.php | 30 +- .../CodeChallengeVerifierInterface.php | 11 +- src/CodeChallengeVerifiers/PlainVerifier.php | 15 +- src/CodeChallengeVerifiers/S256Verifier.php | 21 +- src/CryptKey.php | 63 ++-- src/CryptKeyInterface.php | 9 +- src/CryptTrait.php | 17 +- src/Entities/AccessTokenEntityInterface.php | 5 +- src/Entities/AuthCodeEntityInterface.php | 3 + src/Entities/ClientEntityInterface.php | 14 +- src/Entities/RefreshTokenEntityInterface.php | 6 +- src/Entities/ScopeEntityInterface.php | 6 +- src/Entities/TokenInterface.php | 11 +- src/Entities/Traits/AccessTokenTrait.php | 22 +- src/Entities/Traits/AuthCodeTrait.php | 3 + src/Entities/Traits/ClientTrait.php | 11 +- src/Entities/Traits/EntityTrait.php | 16 +- src/Entities/Traits/RefreshTokenTrait.php | 3 + src/Entities/Traits/ScopeTrait.php | 9 +- src/Entities/Traits/TokenEntityTrait.php | 11 +- src/Entities/UserEntityInterface.php | 6 +- src/Exception/OAuthServerException.php | 91 +++--- ...IdentifierConstraintViolationException.php | 6 +- src/Grant/AbstractAuthorizeGrant.php | 18 +- src/Grant/AbstractGrant.php | 140 +++----- src/Grant/AuthCodeGrant.php | 92 +++--- src/Grant/ClientCredentialsGrant.php | 7 +- src/Grant/GrantTypeInterface.php | 40 +-- src/Grant/ImplicitGrant.php | 41 ++- src/Grant/PasswordGrant.php | 21 +- src/Grant/RefreshTokenGrant.php | 26 +- .../AuthorizationServerMiddleware.php | 10 +- src/Middleware/ResourceServerMiddleware.php | 10 +- .../RedirectUriValidator.php | 33 +- .../RedirectUriValidatorInterface.php | 6 +- .../AccessTokenRepositoryInterface.php | 8 +- .../AuthCodeRepositoryInterface.php | 3 + .../ClientRepositoryInterface.php | 9 +- .../RefreshTokenRepositoryInterface.php | 3 + src/Repositories/RepositoryInterface.php | 3 + src/Repositories/ScopeRepositoryInterface.php | 18 +- src/Repositories/UserRepositoryInterface.php | 15 +- src/RequestAccessTokenEvent.php | 10 +- src/RequestEvent.php | 20 +- src/RequestRefreshTokenEvent.php | 10 +- src/RequestTypes/AuthorizationRequest.php | 3 + .../AuthorizationRequestInterface.php | 3 + src/ResourceServer.php | 14 +- src/ResponseTypes/AbstractResponseType.php | 3 + src/ResponseTypes/BearerTokenResponse.php | 25 +- src/ResponseTypes/RedirectResponse.php | 3 + src/ResponseTypes/ResponseTypeInterface.php | 3 + tests/AuthorizationServerTest.php | 133 +++----- .../BearerTokenValidatorTest.php | 13 +- tests/Bootstrap.php | 2 + .../PlainVerifierTest.php | 8 +- .../S256VerifierTest.php | 15 +- tests/Exception/OAuthServerExceptionTest.php | 25 +- tests/Grant/AbstractGrantTest.php | 147 +++++---- tests/Grant/AuthCodeGrantTest.php | 299 +++++++++++------- tests/Grant/ClientCredentialsGrantTest.php | 12 +- tests/Grant/ImplicitGrantTest.php | 65 ++-- tests/Grant/PasswordGrantTest.php | 21 +- tests/Grant/RefreshTokenGrantTest.php | 121 +++---- .../AuthorizationServerMiddlewareTest.php | 30 +- .../ResourceServerMiddlewareTest.php | 25 +- tests/PHPStan/AbstractGrantExtension.php | 7 +- .../RedirectUriValidatorTest.php | 18 +- tests/ResourceServerTest.php | 3 +- .../ResponseTypes/BearerResponseTypeTest.php | 93 +++--- .../BearerTokenResponseWithParams.php | 3 +- tests/Stubs/AccessTokenEntity.php | 6 +- tests/Stubs/AuthCodeEntity.php | 6 +- tests/Stubs/ClientEntity.php | 5 +- tests/Stubs/CryptTraitStub.php | 7 +- tests/Stubs/GrantType.php | 4 +- tests/Stubs/RefreshTokenEntity.php | 5 +- tests/Stubs/ScopeEntity.php | 7 +- tests/Stubs/StubResponseType.php | 2 + tests/Stubs/UserEntity.php | 4 +- tests/Utils/CryptKeyTest.php | 78 +++-- tests/Utils/CryptTraitTest.php | 11 +- 85 files changed, 1196 insertions(+), 1098 deletions(-) diff --git a/composer.json b/composer.json index 5f1ec4721..6c7d6fc0d 100644 --- a/composer.json +++ b/composer.json @@ -17,9 +17,14 @@ "phpunit/phpunit": "^9.5.11", "laminas/laminas-diactoros": "^2.8.0", "phpstan/phpstan": "^1.10.26", - "phpstan/phpstan-phpunit": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.3.14", "roave/security-advisories": "dev-master", - "phpstan/extension-installer": "^1.3" + "phpstan/extension-installer": "^1.3", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-strict-rules": "^1.5", + "slevomat/coding-standard": "^8.13", + "php-parallel-lint/php-parallel-lint": "^1.3", + "squizlabs/php_codesniffer": "^3.7" }, "repositories": [ { @@ -74,7 +79,8 @@ "config": { "allow-plugins": { "ocramius/package-versions": true, - "phpstan/extension-installer": true + "phpstan/extension-installer": true, + "dealerdirect/phpcodesniffer-composer-installer": false } } } diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 85369a0bc..05e32060a 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server; use DateInterval; @@ -32,75 +35,41 @@ class AuthorizationServer implements EmitterAwareInterface /** * @var GrantTypeInterface[] */ - protected $enabledGrantTypes = []; + protected array $enabledGrantTypes = []; /** * @var DateInterval[] */ - protected $grantTypeAccessTokenTTL = []; + protected array $grantTypeAccessTokenTTL = []; - /** - * @var CryptKeyInterface - */ - protected $privateKey; + protected CryptKeyInterface $privateKey; - /** - * @var CryptKeyInterface - */ - protected $publicKey; + protected CryptKeyInterface $publicKey; - /** - * @var ResponseTypeInterface - */ - protected $responseType; + protected ResponseTypeInterface $responseType; - /** - * @var ClientRepositoryInterface - */ - private $clientRepository; + private ClientRepositoryInterface $clientRepository; - /** - * @var AccessTokenRepositoryInterface - */ - private $accessTokenRepository; + private AccessTokenRepositoryInterface $accessTokenRepository; - /** - * @var ScopeRepositoryInterface - */ - private $scopeRepository; + private ScopeRepositoryInterface $scopeRepository; - /** - * @var string|Key - */ - private $encryptionKey; + private string|Key $encryptionKey; - /** - * @var string - */ - private $defaultScope = ''; + private string $defaultScope = ''; - /** - * @var bool - */ - private $revokeRefreshTokens = true; + private bool $revokeRefreshTokens = true; /** - * New server instance. - * - * @param ClientRepositoryInterface $clientRepository - * @param AccessTokenRepositoryInterface $accessTokenRepository - * @param ScopeRepositoryInterface $scopeRepository - * @param CryptKeyInterface|string $privateKey - * @param string|Key $encryptionKey - * @param null|ResponseTypeInterface $responseType + * New server instance */ public function __construct( ClientRepositoryInterface $clientRepository, AccessTokenRepositoryInterface $accessTokenRepository, ScopeRepositoryInterface $scopeRepository, - $privateKey, - $encryptionKey, - ResponseTypeInterface $responseType = null + CryptKeyInterface|string $privateKey, + Key|string $encryptionKey, + ResponseTypeInterface|null $responseType = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; @@ -123,12 +92,9 @@ public function __construct( } /** - * Enable a grant type on the server. - * - * @param GrantTypeInterface $grantType - * @param null|DateInterval $accessTokenTTL + * Enable a grant type on the server */ - public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null): void + public function enableGrantType(GrantTypeInterface $grantType, DateInterval|null $accessTokenTTL = null): void { if ($accessTokenTTL === null) { $accessTokenTTL = new DateInterval('PT1H'); @@ -150,13 +116,9 @@ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $acc /** * Validate an authorization request * - * @param ServerRequestInterface $request - * * @throws OAuthServerException - * - * @return AuthorizationRequestInterface */ - public function validateAuthorizationRequest(ServerRequestInterface $request) + public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface { foreach ($this->enabledGrantTypes as $grantType) { if ($grantType->canRespondToAuthorizationRequest($request)) { @@ -169,16 +131,11 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) /** * Complete an authorization request - * - * @param AuthorizationRequestInterface $authRequest - * @param ResponseInterface $response - * - * @return ResponseInterface */ public function completeAuthorizationRequest( AuthorizationRequestInterface $authRequest, ResponseInterface $response - ) { + ): ResponseInterface { return $this->enabledGrantTypes[$authRequest->getGrantTypeId()] ->completeAuthorizationRequest($authRequest) ->generateHttpResponse($response); @@ -187,28 +144,22 @@ public function completeAuthorizationRequest( /** * Return an access token response. * - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * * @throws OAuthServerException - * - * @return ResponseInterface */ - public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseInterface $response) + public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { foreach ($this->enabledGrantTypes as $grantType) { if (!$grantType->canRespondToAccessTokenRequest($request)) { continue; } + $tokenResponse = $grantType->respondToAccessTokenRequest( $request, $this->getResponseType(), $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] ); - if ($tokenResponse instanceof ResponseTypeInterface) { - return $tokenResponse->generateHttpResponse($response); - } + return $tokenResponse->generateHttpResponse($response); } throw OAuthServerException::unsupportedGrantType(); @@ -216,10 +167,8 @@ public function respondToAccessTokenRequest(ServerRequestInterface $request, Res /** * Get the token type that grants will return in the HTTP response. - * - * @return ResponseTypeInterface */ - protected function getResponseType() + protected function getResponseType(): ResponseTypeInterface { $responseType = clone $this->responseType; @@ -234,18 +183,14 @@ protected function getResponseType() /** * Set the default scope for the authorization server. - * - * @param string $defaultScope */ - public function setDefaultScope($defaultScope): void + public function setDefaultScope(string $defaultScope): void { $this->defaultScope = $defaultScope; } /** * Sets whether to revoke refresh tokens or not (for all grant types). - * - * @param bool $revokeRefreshTokens */ public function revokeRefreshTokens(bool $revokeRefreshTokens): void { diff --git a/src/AuthorizationValidators/AuthorizationValidatorInterface.php b/src/AuthorizationValidators/AuthorizationValidatorInterface.php index 7e49f8477..275853409 100644 --- a/src/AuthorizationValidators/AuthorizationValidatorInterface.php +++ b/src/AuthorizationValidators/AuthorizationValidatorInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\AuthorizationValidators; use Psr\Http\Message\ServerRequestInterface; @@ -14,12 +17,9 @@ interface AuthorizationValidatorInterface { /** - * Determine the access token in the authorization header and append OAUth properties to the request - * as attributes. - * - * @param ServerRequestInterface $request + * Determine the access token in the authorization header and append OAUth + * properties to the request as attributes. * - * @return ServerRequestInterface */ - public function validateAuthorization(ServerRequestInterface $request); + public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface; } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index d8854efb5..e7e32700d 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,17 +8,19 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\AuthorizationValidators; use DateTimeZone; use Lcobucci\Clock\SystemClock; use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Exception; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\UnencryptedToken; -use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\Constraint\LooseValidAt; -use Lcobucci\JWT\Validation\Constraint\ValidAt; +use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\CryptTrait; @@ -26,6 +29,12 @@ use Psr\Http\Message\ServerRequestInterface; use RuntimeException; +use function count; +use function date_default_timezone_get; +use function is_array; +use function preg_replace; +use function trim; + class BearerTokenValidator implements AuthorizationValidatorInterface { use CryptTrait; @@ -46,7 +55,6 @@ class BearerTokenValidator implements AuthorizationValidatorInterface private $jwtConfiguration; /** - * @param AccessTokenRepositoryInterface $accessTokenRepository */ public function __construct(AccessTokenRepositoryInterface $accessTokenRepository) { @@ -56,7 +64,6 @@ public function __construct(AccessTokenRepositoryInterface $accessTokenRepositor /** * Set the public key * - * @param CryptKeyInterface $key */ public function setPublicKey(CryptKeyInterface $key): void { @@ -82,9 +89,7 @@ private function initJwtConfiguration(): void } $this->jwtConfiguration->setValidationConstraints( - \class_exists(LooseValidAt::class) - ? new LooseValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))) - : new ValidAt(new SystemClock(new DateTimeZone(\date_default_timezone_get()))), + new LooseValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))), new SignedWith( new Sha256(), InMemory::plainText($publicKeyContents, $this->publicKey->getPassPhrase() ?? '') @@ -95,19 +100,19 @@ private function initJwtConfiguration(): void /** * {@inheritdoc} */ - public function validateAuthorization(ServerRequestInterface $request) + public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface { if ($request->hasHeader('authorization') === false) { throw OAuthServerException::accessDenied('Missing "Authorization" header'); } $header = $request->getHeader('authorization'); - $jwt = \trim((string) \preg_replace('/^\s*Bearer\s/', '', $header[0])); + $jwt = trim((string) preg_replace('/^\s*Bearer\s/', '', $header[0])); try { // Attempt to parse the JWT $token = $this->jwtConfiguration->parser()->parse($jwt); - } catch (\Lcobucci\JWT\Exception $exception) { + } catch (Exception $exception) { throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); } @@ -141,12 +146,11 @@ public function validateAuthorization(ServerRequestInterface $request) /** * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt * - * @param mixed $aud * * @return array|string */ - private function convertSingleRecordAudToString($aud): array|string + private function convertSingleRecordAudToString(mixed $aud): array|string { - return \is_array($aud) && \count($aud) === 1 ? $aud[0] : $aud; + return is_array($aud) && count($aud) === 1 ? $aud[0] : $aud; } } diff --git a/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php b/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php index 3d7ad59c5..f358b5c17 100644 --- a/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php +++ b/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Lukáš Unger @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\CodeChallengeVerifiers; interface CodeChallengeVerifierInterface @@ -14,17 +17,13 @@ interface CodeChallengeVerifierInterface /** * Return code challenge method. * - * @return string */ - public function getMethod(); + public function getMethod(): string; /** * Verify the code challenge. * - * @param string $codeVerifier - * @param string $codeChallenge * - * @return bool */ - public function verifyCodeChallenge($codeVerifier, $codeChallenge); + public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool; } diff --git a/src/CodeChallengeVerifiers/PlainVerifier.php b/src/CodeChallengeVerifiers/PlainVerifier.php index cf6b70af1..34a60615c 100644 --- a/src/CodeChallengeVerifiers/PlainVerifier.php +++ b/src/CodeChallengeVerifiers/PlainVerifier.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Lukáš Unger @@ -7,16 +8,19 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\CodeChallengeVerifiers; +use function hash_equals; + class PlainVerifier implements CodeChallengeVerifierInterface { /** * Return code challenge method. * - * @return string */ - public function getMethod() + public function getMethod(): string { return 'plain'; } @@ -24,13 +28,10 @@ public function getMethod() /** * Verify the code challenge. * - * @param string $codeVerifier - * @param string $codeChallenge * - * @return bool */ - public function verifyCodeChallenge($codeVerifier, $codeChallenge) + public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool { - return \hash_equals($codeVerifier, $codeChallenge); + return hash_equals($codeVerifier, $codeChallenge); } } diff --git a/src/CodeChallengeVerifiers/S256Verifier.php b/src/CodeChallengeVerifiers/S256Verifier.php index f70790a3c..e05464f40 100644 --- a/src/CodeChallengeVerifiers/S256Verifier.php +++ b/src/CodeChallengeVerifiers/S256Verifier.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Lukáš Unger @@ -7,16 +8,23 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\CodeChallengeVerifiers; +use function base64_encode; +use function hash; +use function hash_equals; +use function rtrim; +use function strtr; + class S256Verifier implements CodeChallengeVerifierInterface { /** * Return code challenge method. * - * @return string */ - public function getMethod() + public function getMethod(): string { return 'S256'; } @@ -24,15 +32,12 @@ public function getMethod() /** * Verify the code challenge. * - * @param string $codeVerifier - * @param string $codeChallenge * - * @return bool */ - public function verifyCodeChallenge($codeVerifier, $codeChallenge) + public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool { - return \hash_equals( - \strtr(\rtrim(\base64_encode(\hash('sha256', $codeVerifier, true)), '='), '+/', '-_'), + return hash_equals( + strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'), $codeChallenge ); } diff --git a/src/CryptKey.php b/src/CryptKey.php index 351bf9cf0..f0a4464ae 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -1,4 +1,5 @@ passPhrase = $passPhrase; - if (\strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { + if (strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { $this->keyContents = $keyPath; $this->keyPath = ''; // There's no file, so no need for permission check. $keyPermissionsCheck = false; - } elseif (\is_file($keyPath)) { - if (\strpos($keyPath, self::FILE_PREFIX) !== 0) { + } elseif (is_file($keyPath)) { + if (strpos($keyPath, self::FILE_PREFIX) !== 0) { $keyPath = self::FILE_PREFIX . $keyPath; } - if (!\is_readable($keyPath)) { - throw new LogicException(\sprintf('Key path "%s" does not exist or is not readable', $keyPath)); + if (!is_readable($keyPath)) { + throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } $keyContents = file_get_contents($keyPath); @@ -79,10 +87,10 @@ public function __construct($keyPath, $passPhrase = null, $keyPermissionsCheck = if ($keyPermissionsCheck === true) { // Verify the permissions of the key - $keyPathPerms = \decoct(\fileperms($this->keyPath) & 0777); - if (\in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) { - \trigger_error( - \sprintf( + $keyPathPerms = decoct(fileperms($this->keyPath) & 0777); + if (in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) { + trigger_error( + sprintf( 'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s', $this->keyPath, $keyPathPerms @@ -104,20 +112,21 @@ public function getKeyContents(): string /** * Validate key contents. * - * @param string $contents - * @param string $passPhrase * - * @return bool */ - private function isValidKey($contents, $passPhrase) + private function isValidKey(string $contents, string $passPhrase): bool { - $pkey = \openssl_pkey_get_private($contents, $passPhrase) ?: \openssl_pkey_get_public($contents); - if ($pkey === false) { + $privateKey = openssl_pkey_get_private($contents, $passPhrase); + + $key = $privateKey instanceof OpenSSLAsymmetricKey ? $privateKey : openssl_pkey_get_public($contents); + + if ($key === false) { return false; } - $details = \openssl_pkey_get_details($pkey); - return $details !== false && \in_array( + $details = openssl_pkey_get_details($key); + + return $details !== false && in_array( $details['type'] ?? -1, [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC], true @@ -127,7 +136,7 @@ private function isValidKey($contents, $passPhrase) /** * {@inheritdoc} */ - public function getKeyPath() + public function getKeyPath(): string { return $this->keyPath; } @@ -135,7 +144,7 @@ public function getKeyPath() /** * {@inheritdoc} */ - public function getPassPhrase() + public function getPassPhrase(): ?string { return $this->passPhrase; } diff --git a/src/CryptKeyInterface.php b/src/CryptKeyInterface.php index 15c34b8d7..993ab4d52 100644 --- a/src/CryptKeyInterface.php +++ b/src/CryptKeyInterface.php @@ -1,5 +1,7 @@ encryptionKey instanceof Key) { return Crypto::encrypt($unencryptedData, $this->encryptionKey); } - if (\is_string($this->encryptionKey)) { + if (is_string($this->encryptionKey)) { return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); } @@ -52,20 +55,18 @@ protected function encrypt($unencryptedData) /** * Decrypt data with encryptionKey. * - * @param string $encryptedData * * @throws LogicException * - * @return string */ - protected function decrypt($encryptedData) + protected function decrypt(string $encryptedData): string { try { if ($this->encryptionKey instanceof Key) { return Crypto::decrypt($encryptedData, $this->encryptionKey); } - if (\is_string($this->encryptionKey)) { + if (is_string($this->encryptionKey)) { return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); } diff --git a/src/Entities/AccessTokenEntityInterface.php b/src/Entities/AccessTokenEntityInterface.php index 33306b174..6eb3e7f6d 100644 --- a/src/Entities/AccessTokenEntityInterface.php +++ b/src/Entities/AccessTokenEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; use League\OAuth2\Server\CryptKeyInterface; @@ -21,5 +24,5 @@ public function setPrivateKey(CryptKeyInterface $privateKey): void; /** * Generate a string representation of the access token. */ - public function __toString(); + public function __toString(): string; } diff --git a/src/Entities/AuthCodeEntityInterface.php b/src/Entities/AuthCodeEntityInterface.php index 40d581245..68c6a2f5b 100644 --- a/src/Entities/AuthCodeEntityInterface.php +++ b/src/Entities/AuthCodeEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; interface AuthCodeEntityInterface extends TokenInterface diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index 971c61244..56aa54dfb 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; interface ClientEntityInterface @@ -14,16 +17,14 @@ interface ClientEntityInterface /** * Get the client's identifier. * - * @return string */ - public function getIdentifier(); + public function getIdentifier(): string; /** * Get the client's name. * - * @return string */ - public function getName(); + public function getName(): string; /** * Returns the registered redirect URI (as a string). @@ -32,12 +33,11 @@ public function getName(); * * @return string|string[] */ - public function getRedirectUri(); + public function getRedirectUri(): string|array; /** * Returns true if the client is confidential. * - * @return bool */ - public function isConfidential(); + public function isConfidential(): bool; } diff --git a/src/Entities/RefreshTokenEntityInterface.php b/src/Entities/RefreshTokenEntityInterface.php index 63f9732c6..9b881216f 100644 --- a/src/Entities/RefreshTokenEntityInterface.php +++ b/src/Entities/RefreshTokenEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; use DateTimeImmutable; @@ -16,9 +19,8 @@ interface RefreshTokenEntityInterface /** * Get the token's identifier. * - * @return string */ - public function getIdentifier(); + public function getIdentifier(): string; /** * Set the token's identifier. diff --git a/src/Entities/ScopeEntityInterface.php b/src/Entities/ScopeEntityInterface.php index 26748e0c0..653d671ea 100644 --- a/src/Entities/ScopeEntityInterface.php +++ b/src/Entities/ScopeEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; use JsonSerializable; @@ -16,7 +19,6 @@ interface ScopeEntityInterface extends JsonSerializable /** * Get the scope's identifier. * - * @return string */ - public function getIdentifier(); + public function getIdentifier(): string; } diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 75aa261af..22e6fab49 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; use DateTimeImmutable; @@ -16,9 +19,8 @@ interface TokenInterface /** * Get the token's identifier. * - * @return string */ - public function getIdentifier(); + public function getIdentifier(): string; /** * Set the token's identifier. @@ -28,9 +30,8 @@ public function setIdentifier(mixed $identifier): void; /** * Get the token's expiry date time. * - * @return DateTimeImmutable */ - public function getExpiryDateTime(); + public function getExpiryDateTime(): DateTimeImmutable; /** * Set the date time when the token expires. @@ -67,5 +68,5 @@ public function addScope(ScopeEntityInterface $scope): void; * * @return ScopeEntityInterface[] */ - public function getScopes(); + public function getScopes(): array; } diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 35300fa4c..48f6f8eca 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; @@ -60,9 +63,8 @@ public function initJwtConfiguration(): void /** * Generate a JWT from the access token * - * @return Token */ - private function convertToJWT() + private function convertToJWT(): Token { $this->initJwtConfiguration(); @@ -80,33 +82,29 @@ private function convertToJWT() /** * Generate a string representation from the access token */ - public function __toString() + public function __toString(): string { return $this->convertToJWT()->toString(); } /** - * @return ClientEntityInterface */ - abstract public function getClient(); + abstract public function getClient(): ClientEntityInterface; /** - * @return DateTimeImmutable */ - abstract public function getExpiryDateTime(); + abstract public function getExpiryDateTime(): DateTimeImmutable; /** - * @return string|int */ - abstract public function getUserIdentifier(); + abstract public function getUserIdentifier(): string|int|null; /** * @return ScopeEntityInterface[] */ - abstract public function getScopes(); + abstract public function getScopes(): array; /** - * @return string */ - abstract public function getIdentifier(); + abstract public function getIdentifier(): string; } diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index 35cbff295..edc667614 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; trait AuthCodeTrait diff --git a/src/Entities/Traits/ClientTrait.php b/src/Entities/Traits/ClientTrait.php index a0078d8d7..c779b15a9 100644 --- a/src/Entities/Traits/ClientTrait.php +++ b/src/Entities/Traits/ClientTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; trait ClientTrait @@ -29,10 +32,9 @@ trait ClientTrait /** * Get the client's name. * - * @return string * @codeCoverageIgnore */ - public function getName() + public function getName(): string { return $this->name; } @@ -44,7 +46,7 @@ public function getName() * * @return string|string[] */ - public function getRedirectUri() + public function getRedirectUri(): string|array { return $this->redirectUri; } @@ -52,9 +54,8 @@ public function getRedirectUri() /** * Returns true if the client is confidential. * - * @return bool */ - public function isConfidential() + public function isConfidential(): bool { return $this->isConfidential; } diff --git a/src/Entities/Traits/EntityTrait.php b/src/Entities/Traits/EntityTrait.php index 6748aea18..0657a49b8 100644 --- a/src/Entities/Traits/EntityTrait.php +++ b/src/Entities/Traits/EntityTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,27 +8,22 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; trait EntityTrait { - /** - * @var string - */ - protected $identifier; + protected string $identifier; - /** - * @return mixed - */ - public function getIdentifier() + public function getIdentifier(): string { return $this->identifier; } /** - * @param mixed $identifier */ - public function setIdentifier($identifier): void + public function setIdentifier(mixed $identifier): void { $this->identifier = $identifier; } diff --git a/src/Entities/Traits/RefreshTokenTrait.php b/src/Entities/Traits/RefreshTokenTrait.php index 568b2a733..a0d4c8885 100644 --- a/src/Entities/Traits/RefreshTokenTrait.php +++ b/src/Entities/Traits/RefreshTokenTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; diff --git a/src/Entities/Traits/ScopeTrait.php b/src/Entities/Traits/ScopeTrait.php index a132234fc..aeba339de 100644 --- a/src/Entities/Traits/ScopeTrait.php +++ b/src/Entities/Traits/ScopeTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Andrew Millington @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; trait ScopeTrait @@ -14,15 +17,13 @@ trait ScopeTrait /** * Serialize the object to the scopes string identifier when using json_encode(). * - * @return string */ - public function jsonSerialize() + public function jsonSerialize(): string { return $this->getIdentifier(); } /** - * @return string */ - abstract public function getIdentifier(); + abstract public function getIdentifier(): string; } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index d9cfa6fa2..d4a1aabe0 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,12 +8,16 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; +use function array_values; + trait TokenEntityTrait { /** @@ -38,7 +43,6 @@ trait TokenEntityTrait /** * Associate a scope with the token. * - * @param ScopeEntityInterface $scope */ public function addScope(ScopeEntityInterface $scope): void { @@ -52,7 +56,7 @@ public function addScope(ScopeEntityInterface $scope): void */ public function getScopes(): array { - return \array_values($this->scopes); + return array_values($this->scopes); } /** @@ -76,7 +80,7 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void * * @param string|int|null $identifier The identifier of the user */ - public function setUserIdentifier($identifier): void + public function setUserIdentifier(string|int|null $identifier): void { $this->userIdentifier = $identifier; } @@ -84,7 +88,6 @@ public function setUserIdentifier($identifier): void /** * Get the token user's identifier. * - * @return string|int|null */ public function getUserIdentifier(): string|int|null { diff --git a/src/Entities/UserEntityInterface.php b/src/Entities/UserEntityInterface.php index c71cb9c52..28eed197e 100644 --- a/src/Entities/UserEntityInterface.php +++ b/src/Entities/UserEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; interface UserEntityInterface @@ -14,7 +17,6 @@ interface UserEntityInterface /** * Return the user's identifier. * - * @return mixed */ - public function getIdentifier(); + public function getIdentifier(): mixed; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 32104ef0d..f033fdeb0 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Exception; use Exception; @@ -14,6 +17,12 @@ use Psr\Http\Message\ServerRequestInterface; use Throwable; +use function htmlspecialchars; +use function http_build_query; +use function sprintf; +use function strpos; +use function strstr; + class OAuthServerException extends Exception { /** @@ -57,7 +66,7 @@ class OAuthServerException extends Exception * @param null|string $redirectUri A HTTP URI to redirect the user back to * @param Throwable $previous Previous exception */ - final public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) + final public function __construct(string $message, int $code, string $errorType, int $httpStatusCode = 400, ?string $hint = null, ?string $redirectUri = null, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; @@ -104,7 +113,6 @@ public function setPayload(array $payload): void /** * Set the server request that is responsible for generating the exception * - * @param ServerRequestInterface $serverRequest */ public function setServerRequest(ServerRequestInterface $serverRequest): void { @@ -116,7 +124,7 @@ public function setServerRequest(ServerRequestInterface $serverRequest): void * * @return static */ - public static function unsupportedGrantType() + public static function unsupportedGrantType(): static { $errorMessage = 'The authorization grant type is not supported by the authorization server.'; $hint = 'Check that all required parameters have been provided'; @@ -128,16 +136,15 @@ public static function unsupportedGrantType() * Invalid request error. * * @param string $parameter The invalid parameter - * @param null|string $hint * @param Throwable $previous Previous exception * * @return static */ - public static function invalidRequest($parameter, $hint = null, Throwable $previous = null) + public static function invalidRequest(string $parameter, ?string $hint = null, Throwable $previous = null): static { $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' . 'includes a parameter more than once, or is otherwise malformed.'; - $hint = ($hint === null) ? \sprintf('Check the `%s` parameter', $parameter) : $hint; + $hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint; return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous); } @@ -145,11 +152,10 @@ public static function invalidRequest($parameter, $hint = null, Throwable $previ /** * Invalid client error. * - * @param ServerRequestInterface $serverRequest * * @return static */ - public static function invalidClient(ServerRequestInterface $serverRequest) + public static function invalidClient(ServerRequestInterface $serverRequest): static { $exception = new static('Client authentication failed', 4, 'invalid_client', 401); @@ -159,23 +165,18 @@ public static function invalidClient(ServerRequestInterface $serverRequest) } /** - * Invalid scope error. - * - * @param string $scope The bad scope - * @param null|string $redirectUri A HTTP URI to redirect the user back to - * - * @return static + * Invalid scope error */ - public static function invalidScope($scope, $redirectUri = null) + public static function invalidScope(string $scope, string|null $redirectUri = null): static { $errorMessage = 'The requested scope is invalid, unknown, or malformed'; - if (empty($scope)) { + if ($scope === '') { $hint = 'Specify a scope in the request or set a default scope'; } else { - $hint = \sprintf( + $hint = sprintf( 'Check the `%s` scope', - \htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false) + htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false) ); } @@ -187,7 +188,7 @@ public static function invalidScope($scope, $redirectUri = null) * * @return static */ - public static function invalidCredentials() + public static function invalidCredentials(): static { return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400); } @@ -195,14 +196,12 @@ public static function invalidCredentials() /** * Server error. * - * @param string $hint - * @param Throwable $previous * * @return static * * @codeCoverageIgnore */ - public static function serverError($hint, Throwable $previous = null) + public static function serverError(string $hint, Throwable $previous = null): static { return new static( 'The authorization server encountered an unexpected condition which prevented it from fulfilling' @@ -219,12 +218,10 @@ public static function serverError($hint, Throwable $previous = null) /** * Invalid refresh token. * - * @param null|string $hint - * @param Throwable $previous * * @return static */ - public static function invalidRefreshToken($hint = null, Throwable $previous = null) + public static function invalidRefreshToken(?string $hint = null, Throwable $previous = null): static { return new static('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous); } @@ -232,13 +229,10 @@ public static function invalidRefreshToken($hint = null, Throwable $previous = n /** * Access denied. * - * @param null|string $hint - * @param null|string $redirectUri - * @param Throwable $previous * * @return static */ - public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null) + public static function accessDenied(?string $hint = null, ?string $redirectUri = null, Throwable $previous = null): static { return new static( 'The resource owner or authorization server denied the request.', @@ -254,11 +248,10 @@ public static function accessDenied($hint = null, $redirectUri = null, Throwable /** * Invalid grant. * - * @param string $hint * * @return static */ - public static function invalidGrant($hint = '') + public static function invalidGrant(string $hint = ''): static { return new static( 'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token ' @@ -272,9 +265,8 @@ public static function invalidGrant($hint = '') } /** - * @return string */ - public function getErrorType() + public function getErrorType(): string { return $this->errorType; } @@ -282,13 +274,11 @@ public function getErrorType() /** * Generate a HTTP response. * - * @param ResponseInterface $response * @param bool $useFragment True if errors should be in the URI fragment instead of query string * @param int $jsonOptions options passed to json_encode * - * @return ResponseInterface */ - public function generateHttpResponse(ResponseInterface $response, $useFragment = false, $jsonOptions = 0) + public function generateHttpResponse(ResponseInterface $response, bool $useFragment = false, int $jsonOptions = 0): ResponseInterface { $headers = $this->getHttpHeaders(); @@ -296,19 +286,21 @@ public function generateHttpResponse(ResponseInterface $response, $useFragment = if ($this->redirectUri !== null) { if ($useFragment === true) { - $this->redirectUri .= (\strstr($this->redirectUri, '#') === false) ? '#' : '&'; + $this->redirectUri .= (strstr($this->redirectUri, '#') === false) ? '#' : '&'; } else { - $this->redirectUri .= (\strstr($this->redirectUri, '?') === false) ? '?' : '&'; + $this->redirectUri .= (strstr($this->redirectUri, '?') === false) ? '?' : '&'; } - return $response->withStatus(302)->withHeader('Location', $this->redirectUri . \http_build_query($payload)); + return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload)); } foreach ($headers as $header => $content) { $response = $response->withHeader($header, $content); } - $responseBody = \json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed'; + $jsonEncodedPayload = json_encode($payload, $jsonOptions); + + $responseBody = $jsonEncodedPayload === false ? 'JSON encoding of payload failed' : $jsonEncodedPayload; $response->getBody()->write($responseBody); @@ -335,7 +327,7 @@ public function getHttpHeaders(): array // include the "WWW-Authenticate" response header field // matching the authentication scheme used by the client. if ($this->errorType === 'invalid_client' && $this->requestHasAuthorizationHeader()) { - $authScheme = \strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic'; + $authScheme = strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic'; $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"'; } @@ -351,9 +343,8 @@ public function getHttpHeaders(): array * redirect enabled. This helps when you want to override local * error pages but want to let redirects through. * - * @return bool */ - public function hasRedirect() + public function hasRedirect(): bool { return $this->redirectUri !== null; } @@ -361,9 +352,8 @@ public function hasRedirect() /** * Returns the Redirect URI used for redirecting. * - * @return string|null */ - public function getRedirectUri() + public function getRedirectUri(): ?string { return $this->redirectUri; } @@ -371,17 +361,15 @@ public function getRedirectUri() /** * Returns the HTTP status code to send when the exceptions is output. * - * @return int */ - public function getHttpStatusCode() + public function getHttpStatusCode(): int { return $this->httpStatusCode; } /** - * @return null|string */ - public function getHint() + public function getHint(): ?string { return $this->hint; } @@ -392,9 +380,8 @@ public function getHint() * Returns true if the header is present and not an empty string, false * otherwise. * - * @return bool */ - private function requestHasAuthorizationHeader() + private function requestHasAuthorizationHeader(): bool { if (!$this->serverRequest->hasHeader('Authorization')) { return false; @@ -407,7 +394,7 @@ private function requestHasAuthorizationHeader() // For practical purposes that case should be treated as though the // header isn't present. // See https://github.com/thephpleague/oauth2-server/issues/1162 - if (empty($authorizationHeader) || empty($authorizationHeader[0])) { + if ($authorizationHeader === [] || $authorizationHeader[0] === '') { return false; } diff --git a/src/Exception/UniqueTokenIdentifierConstraintViolationException.php b/src/Exception/UniqueTokenIdentifierConstraintViolationException.php index 175ab3a88..21f9a28ab 100644 --- a/src/Exception/UniqueTokenIdentifierConstraintViolationException.php +++ b/src/Exception/UniqueTokenIdentifierConstraintViolationException.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,14 +8,15 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Exception; class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException { /** - * @return UniqueTokenIdentifierConstraintViolationException */ - public static function create() + public static function create(): UniqueTokenIdentifierConstraintViolationException { $errorMessage = 'Could not create unique access token identifier'; diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index 9a78ae562..d9f2d3f3e 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -1,4 +1,5 @@ defaultScope = $scope; } /** - * @param bool $revokeRefreshTokens */ - public function revokeRefreshTokens(bool $revokeRefreshTokens) + public function revokeRefreshTokens(bool $revokeRefreshTokens): void { $this->revokeRefreshTokens = $revokeRefreshTokens; } @@ -188,13 +193,11 @@ public function revokeRefreshTokens(bool $revokeRefreshTokens) /** * Validate the client. * - * @param ServerRequestInterface $request * * @throws OAuthServerException * - * @return ClientEntityInterface */ - protected function validateClient(ServerRequestInterface $request) + protected function validateClient(ServerRequestInterface $request): ClientEntityInterface { [$clientId, $clientSecret] = $this->getClientCredentials($request); @@ -210,7 +213,7 @@ protected function validateClient(ServerRequestInterface $request) $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); if ($redirectUri !== null) { - if (!\is_string($redirectUri)) { + if (!is_string($redirectUri)) { throw OAuthServerException::invalidRequest('redirect_uri'); } @@ -230,12 +233,9 @@ protected function validateClient(ServerRequestInterface $request) * getClientEntity might return null. By contrast, this method will * always either return a ClientEntityInterface or throw. * - * @param string $clientId - * @param ServerRequestInterface $request * - * @return ClientEntityInterface */ - protected function getClientEntityOrFail($clientId, ServerRequestInterface $request) + protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface { $client = $this->clientRepository->getClientEntity($clientId); @@ -251,7 +251,6 @@ protected function getClientEntityOrFail($clientId, ServerRequestInterface $requ * Gets the client credentials from the request from the request body or * the Http Basic Authorization header * - * @param ServerRequestInterface $request * * @return string[] */ @@ -261,13 +260,13 @@ protected function getClientCredentials(ServerRequestInterface $request): array $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); - if (\is_null($clientId)) { + if (is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); - if ($clientSecret !== null && !\is_string($clientSecret)) { + if ($clientSecret !== null && !is_string($clientSecret)) { throw OAuthServerException::invalidRequest('client_secret'); } @@ -278,9 +277,6 @@ protected function getClientCredentials(ServerRequestInterface $request): array * Validate redirectUri from the request. * If a redirect URI is provided ensure it matches what is pre-registered * - * @param string $redirectUri - * @param ClientEntityInterface $client - * @param ServerRequestInterface $request * * @throws OAuthServerException */ @@ -301,24 +297,19 @@ protected function validateRedirectUri( * Validate scopes in the request. * * @param null|string|string[] $scopes - * @param string $redirectUri * * @throws OAuthServerException * * @return ScopeEntityInterface[] */ - public function validateScopes($scopes, $redirectUri = null): array + public function validateScopes(string|array|null $scopes, string $redirectUri = null): array { if ($scopes === null) { $scopes = []; - } elseif (\is_string($scopes)) { + } elseif (is_string($scopes)) { $scopes = $this->convertScopesQueryStringToArray($scopes); } - if (!\is_array($scopes)) { - throw OAuthServerException::invalidRequest('scope'); - } - $validScopes = []; foreach ($scopes as $scopeItem) { @@ -337,13 +328,12 @@ public function validateScopes($scopes, $redirectUri = null): array /** * Converts a scopes query string to an array to easily iterate for validation. * - * @param string $scopes * * @return string[] */ private function convertScopesQueryStringToArray(string $scopes): array { - return \array_filter(\explode(self::SCOPE_DELIMITER_STRING, \trim($scopes)), function ($scope) { + return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) { return $scope !== ''; }); } @@ -351,13 +341,9 @@ private function convertScopesQueryStringToArray(string $scopes): array /** * Retrieve request parameter. * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default * - * @return mixed */ - protected function getRequestParameter($parameter, ServerRequestInterface $request, $default = null) + protected function getRequestParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): mixed { $requestParameters = (array) $request->getParsedBody(); @@ -371,42 +357,39 @@ protected function getRequestParameter($parameter, ServerRequestInterface $reque * not exist, or is otherwise an invalid HTTP Basic header, return * [null, null]. * - * @param ServerRequestInterface $request * * @return string[]|null[] */ - protected function getBasicAuthCredentials(ServerRequestInterface $request) + protected function getBasicAuthCredentials(ServerRequestInterface $request): array { if (!$request->hasHeader('Authorization')) { return [null, null]; } $header = $request->getHeader('Authorization')[0]; - if (\strpos($header, 'Basic ') !== 0) { + if (strpos($header, 'Basic ') !== 0) { return [null, null]; } - if (!($decoded = \base64_decode(\substr($header, 6)))) { + $decoded = base64_decode(substr($header, 6), true); + + if ($decoded === false) { return [null, null]; } - if (\strpos($decoded, ':') === false) { + if (strpos($decoded, ':') === false) { return [null, null]; // HTTP Basic header without colon isn't valid } - return \explode(':', $decoded, 2); + return explode(':', $decoded, 2); } /** * Retrieve query string parameter. * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default * - * @return null|string */ - protected function getQueryStringParameter($parameter, ServerRequestInterface $request, $default = null) + protected function getQueryStringParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): ?string { return isset($request->getQueryParams()[$parameter]) ? $request->getQueryParams()[$parameter] : $default; } @@ -414,13 +397,9 @@ protected function getQueryStringParameter($parameter, ServerRequestInterface $r /** * Retrieve cookie parameter. * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default * - * @return null|string */ - protected function getCookieParameter($parameter, ServerRequestInterface $request, $default = null) + protected function getCookieParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): ?string { return isset($request->getCookieParams()[$parameter]) ? $request->getCookieParams()[$parameter] : $default; } @@ -428,13 +407,9 @@ protected function getCookieParameter($parameter, ServerRequestInterface $reques /** * Retrieve server parameter. * - * @param string $parameter - * @param ServerRequestInterface $request - * @param mixed $default * - * @return null|string */ - protected function getServerParameter($parameter, ServerRequestInterface $request, $default = null) + protected function getServerParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): ?string { return isset($request->getServerParams()[$parameter]) ? $request->getServerParams()[$parameter] : $default; } @@ -442,22 +417,18 @@ protected function getServerParameter($parameter, ServerRequestInterface $reques /** * Issue an access token. * - * @param DateInterval $accessTokenTTL - * @param ClientEntityInterface $client - * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * - * @return AccessTokenEntityInterface */ protected function issueAccessToken( DateInterval $accessTokenTTL, ClientEntityInterface $client, - $userIdentifier, + string|int|null $userIdentifier, array $scopes = [] - ) { + ): AccessTokenEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); @@ -481,24 +452,19 @@ protected function issueAccessToken( /** * Issue an auth code. * - * @param DateInterval $authCodeTTL - * @param ClientEntityInterface $client - * @param string $userIdentifier - * @param string|null $redirectUri * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * - * @return AuthCodeEntityInterface */ protected function issueAuthCode( DateInterval $authCodeTTL, ClientEntityInterface $client, - $userIdentifier, - $redirectUri, + string $userIdentifier, + ?string $redirectUri, array $scopes = [] - ) { + ): AuthCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $authCode = $this->authCodeRepository->getNewAuthCode(); @@ -529,14 +495,12 @@ protected function issueAuthCode( } /** - * @param AccessTokenEntityInterface $accessToken * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * - * @return RefreshTokenEntityInterface|null */ - protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) + protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); @@ -566,13 +530,11 @@ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) /** * Generate a new unique identifier. * - * @param int $length * * @throws OAuthServerException * - * @return string */ - protected function generateUniqueIdentifier($length = 40) + protected function generateUniqueIdentifier(int $length = 40): string { try { if ($length < 1) { @@ -581,7 +543,7 @@ protected function generateUniqueIdentifier($length = 40) return bin2hex(random_bytes($length)); // @codeCoverageIgnoreStart - } catch (TypeError|Error $e) { + } catch (TypeError | Error $e) { throw OAuthServerException::serverError('An unexpected error has occurred', $e); } catch (Exception $e) { // If you get this message, the CSPRNG failed hard. @@ -593,12 +555,12 @@ protected function generateUniqueIdentifier($length = 40) /** * {@inheritdoc} */ - public function canRespondToAccessTokenRequest(ServerRequestInterface $request) + public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool { $requestParameters = (array) $request->getParsedBody(); return ( - \array_key_exists('grant_type', $requestParameters) + array_key_exists('grant_type', $requestParameters) && $requestParameters['grant_type'] === $this->getIdentifier() ); } @@ -606,7 +568,7 @@ public function canRespondToAccessTokenRequest(ServerRequestInterface $request) /** * {@inheritdoc} */ - public function canRespondToAuthorizationRequest(ServerRequestInterface $request) + public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool { return false; } @@ -614,7 +576,7 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request /** * {@inheritdoc} */ - public function validateAuthorizationRequest(ServerRequestInterface $request) + public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface { throw new LogicException('This grant cannot validate an authorization request'); } @@ -622,7 +584,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) /** * {@inheritdoc} */ - public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): ResponseTypeInterface { throw new LogicException('This grant cannot complete an authorization request'); } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 6791f96e8..d940c4038 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Grant; use DateInterval; @@ -30,6 +33,22 @@ use Psr\Http\Message\ServerRequestInterface; use stdClass; +use function array_key_exists; +use function array_keys; +use function array_map; +use function count; +use function hash_algos; +use function implode; +use function in_array; +use function is_array; +use function is_string; +use function json_decode; +use function json_encode; +use function preg_match; +use function property_exists; +use function sprintf; +use function time; + class AuthCodeGrant extends AbstractAuthorizeGrant { /** @@ -48,9 +67,6 @@ class AuthCodeGrant extends AbstractAuthorizeGrant private $codeChallengeVerifiers = []; /** - * @param AuthCodeRepositoryInterface $authCodeRepository - * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param DateInterval $authCodeTTL * * @throws Exception */ @@ -64,7 +80,7 @@ public function __construct( $this->authCodeTTL = $authCodeTTL; $this->refreshTokenTTL = new DateInterval('P1M'); - if (\in_array('sha256', \hash_algos(), true)) { + if (in_array('sha256', hash_algos(), true)) { $s256Verifier = new S256Verifier(); $this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier; } @@ -84,19 +100,15 @@ public function disableRequireCodeChallengeForPublicClients(): void /** * Respond to an access token request. * - * @param ServerRequestInterface $request - * @param ResponseTypeInterface $responseType - * @param DateInterval $accessTokenTTL * * @throws OAuthServerException * - * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL - ) { + ): ResponseTypeInterface { list($clientId) = $this->getClientCredentials($request); $client = $this->getClientEntityOrFail($clientId, $request); @@ -108,12 +120,12 @@ public function respondToAccessTokenRequest( $encryptedAuthCode = $this->getRequestParameter('code', $request, null); - if (!\is_string($encryptedAuthCode)) { + if (!is_string($encryptedAuthCode)) { throw OAuthServerException::invalidRequest('code'); } try { - $authCodePayload = \json_decode($this->decrypt($encryptedAuthCode)); + $authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); $this->validateAuthorizationCode($authCodePayload, $client, $request); @@ -129,7 +141,7 @@ public function respondToAccessTokenRequest( } // Validate code challenge - if (!empty($authCodePayload->code_challenge)) { + if (isset($authCodePayload->code_challenge) && $authCodePayload->code_challenge !== '') { $codeVerifier = $this->getRequestParameter('code_verifier', $request, null); if ($codeVerifier === null) { @@ -138,14 +150,14 @@ public function respondToAccessTokenRequest( // Validate code_verifier according to RFC-7636 // @see: https://tools.ietf.org/html/rfc7636#section-4.1 - if (\preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) { + if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) { throw OAuthServerException::invalidRequest( 'code_verifier', 'Code Verifier must follow the specifications of RFC-7636.' ); } - if (\property_exists($authCodePayload, 'code_challenge_method')) { + if (property_exists($authCodePayload, 'code_challenge_method')) { if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) { $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method]; @@ -154,7 +166,7 @@ public function respondToAccessTokenRequest( } } else { throw OAuthServerException::serverError( - \sprintf( + sprintf( 'Unsupported code challenge method `%s`', $authCodePayload->code_challenge_method ) @@ -185,20 +197,17 @@ public function respondToAccessTokenRequest( /** * Validate the authorization code. * - * @param stdClass $authCodePayload - * @param ClientEntityInterface $client - * @param ServerRequestInterface $request */ private function validateAuthorizationCode( - $authCodePayload, + stdClass $authCodePayload, ClientEntityInterface $client, ServerRequestInterface $request ): void { - if (!\property_exists($authCodePayload, 'auth_code_id')) { + if (!property_exists($authCodePayload, 'auth_code_id')) { throw OAuthServerException::invalidRequest('code', 'Authorization code malformed'); } - if (\time() > $authCodePayload->expire_time) { + if (time() > $authCodePayload->expire_time) { throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); } @@ -212,7 +221,7 @@ private function validateAuthorizationCode( // The redirect URI is required in this request $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); - if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) { + if ($authCodePayload->redirect_uri !== '' && $redirectUri === null) { throw OAuthServerException::invalidRequest('redirect_uri'); } @@ -224,9 +233,8 @@ private function validateAuthorizationCode( /** * Return the grant identifier that can be used in matching up requests. * - * @return string */ - public function getIdentifier() + public function getIdentifier(): string { return 'authorization_code'; } @@ -234,10 +242,10 @@ public function getIdentifier() /** * {@inheritdoc} */ - public function canRespondToAuthorizationRequest(ServerRequestInterface $request) + public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool { return ( - \array_key_exists('response_type', $request->getQueryParams()) + array_key_exists('response_type', $request->getQueryParams()) && $request->getQueryParams()['response_type'] === 'code' && isset($request->getQueryParams()['client_id']) ); @@ -246,7 +254,7 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request /** * {@inheritdoc} */ - public function validateAuthorizationRequest(ServerRequestInterface $request) + public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface { $clientId = $this->getQueryStringParameter( 'client_id', @@ -263,19 +271,17 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); if ($redirectUri !== null) { - if (!\is_string($redirectUri)) { - throw OAuthServerException::invalidRequest('redirect_uri'); - } - $this->validateRedirectUri($redirectUri, $client, $request); - } elseif (empty($client->getRedirectUri()) || - (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { + } elseif ( + $client->getRedirectUri() === '' || $client->getRedirectUri() === null || + (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1) + ) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } - $defaultClientRedirectUri = \is_array($client->getRedirectUri()) + $defaultClientRedirectUri = is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] : $client->getRedirectUri(); @@ -309,21 +315,21 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) ); } - if (\array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) { + if (array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) { throw OAuthServerException::invalidRequest( 'code_challenge_method', - 'Code challenge method must be one of ' . \implode(', ', \array_map( + 'Code challenge method must be one of ' . implode(', ', array_map( function ($method) { return '`' . $method . '`'; }, - \array_keys($this->codeChallengeVerifiers) + array_keys($this->codeChallengeVerifiers) )) ); } // Validate code_challenge according to RFC-7636 // @see: https://tools.ietf.org/html/rfc7636#section-4.2 - if (\preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) { + if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) { throw OAuthServerException::invalidRequest( 'code_challenge', 'Code challenge must follow the specifications of RFC-7636.' @@ -342,7 +348,7 @@ function ($method) { /** * {@inheritdoc} */ - public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): ResponseTypeInterface { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); @@ -372,7 +378,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), ]; - $jsonPayload = \json_encode($payload); + $jsonPayload = json_encode($payload); if ($jsonPayload === false) { throw new LogicException('An error was encountered when JSON encoding the authorization request response'); @@ -407,13 +413,11 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth /** * Get the client redirect URI if not set in the request. * - * @param AuthorizationRequestInterface $authorizationRequest * - * @return string */ - private function getClientRedirectUri(AuthorizationRequestInterface $authorizationRequest) + private function getClientRedirectUri(AuthorizationRequestInterface $authorizationRequest): string { - return \is_array($authorizationRequest->getClient()->getRedirectUri()) + return is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] : $authorizationRequest->getClient()->getRedirectUri(); } diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index d342b269f..bee6abaa1 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -1,4 +1,5 @@ getClientCredentials($request); $client = $this->getClientEntityOrFail($clientId, $request); @@ -64,7 +67,7 @@ public function respondToAccessTokenRequest( /** * {@inheritdoc} */ - public function getIdentifier() + public function getIdentifier(): string { return 'client_credentials'; } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index f8dffd578..36f2f6050 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Grant; use DateInterval; @@ -20,6 +23,11 @@ use LogicException; use Psr\Http\Message\ServerRequestInterface; +use function count; +use function is_array; +use function is_null; +use function time; + class ImplicitGrant extends AbstractAuthorizeGrant { /** @@ -33,17 +41,14 @@ class ImplicitGrant extends AbstractAuthorizeGrant private $queryDelimiter; /** - * @param DateInterval $accessTokenTTL - * @param string $queryDelimiter */ - public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#') + public function __construct(DateInterval $accessTokenTTL, string $queryDelimiter = '#') { $this->accessTokenTTL = $accessTokenTTL; $this->queryDelimiter = $queryDelimiter; } /** - * @param DateInterval $refreshTokenTTL * * @throw LogicException */ @@ -53,7 +58,6 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void } /** - * @param RefreshTokenRepositoryInterface $refreshTokenRepository * * @throw LogicException */ @@ -73,9 +77,8 @@ public function canRespondToAccessTokenRequest(ServerRequestInterface $request) /** * Return the grant identifier that can be used in matching up requests. * - * @return string */ - public function getIdentifier() + public function getIdentifier(): string { return 'implicit'; } @@ -83,17 +86,13 @@ public function getIdentifier() /** * Respond to an incoming request. * - * @param ServerRequestInterface $request - * @param ResponseTypeInterface $responseType - * @param DateInterval $accessTokenTTL * - * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL - ) { + ): ResponseTypeInterface { throw new LogicException('This grant does not used this method'); } @@ -120,7 +119,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $this->getServerParameter('PHP_AUTH_USER', $request) ); - if (\is_null($clientId)) { + if (is_null($clientId)) { throw OAuthServerException::invalidRequest('client_id'); } @@ -129,17 +128,15 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); if ($redirectUri !== null) { - if (!\is_string($redirectUri)) { - throw OAuthServerException::invalidRequest('redirect_uri'); - } - $this->validateRedirectUri($redirectUri, $client, $request); - } elseif (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1 - || empty($client->getRedirectUri())) { + } elseif ( + is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1 + || $client->getRedirectUri() === '' + ) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient($request); } else { - $redirectUri = \is_array($client->getRedirectUri()) + $redirectUri = is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] : $client->getRedirectUri(); } @@ -175,7 +172,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth } $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) - ? \is_array($authorizationRequest->getClient()->getRedirectUri()) + ? is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] : $authorizationRequest->getClient()->getRedirectUri() : $authorizationRequest->getRedirectUri(); @@ -204,7 +201,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth [ 'access_token' => (string) $accessToken, 'token_type' => 'Bearer', - 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - \time(), + 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - time(), 'state' => $authorizationRequest->getState(), ], $this->queryDelimiter diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 80f442616..e492d1e61 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -1,4 +1,5 @@ validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); @@ -59,7 +62,8 @@ public function respondToAccessTokenRequest( $scopes, $this->getIdentifier(), $client, - $user->getIdentifier()); + $user->getIdentifier() + ); // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); @@ -78,24 +82,21 @@ public function respondToAccessTokenRequest( } /** - * @param ServerRequestInterface $request - * @param ClientEntityInterface $client * * @throws OAuthServerException * - * @return UserEntityInterface */ - protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client) + protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client): UserEntityInterface { $username = $this->getRequestParameter('username', $request); - if (!\is_string($username)) { + if (!is_string($username)) { throw OAuthServerException::invalidRequest('username'); } $password = $this->getRequestParameter('password', $request); - if (!\is_string($password)) { + if (!is_string($password)) { throw OAuthServerException::invalidRequest('password'); } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 541faa4ab..8cf4b3272 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -1,4 +1,5 @@ validateClient($request); $oldRefreshToken = $this->validateOldRefreshToken($request, $client->getIdentifier()); @@ -51,14 +59,14 @@ public function respondToAccessTokenRequest( $this->getRequestParameter( 'scope', $request, - \implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken['scopes']) + implode(self::SCOPE_DELIMITER_STRING, $oldRefreshToken['scopes']) ) ); // 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(), $oldRefreshToken['scopes'], true) === false) { throw OAuthServerException::invalidScope($scope->getIdentifier()); } } @@ -90,17 +98,15 @@ public function respondToAccessTokenRequest( } /** - * @param ServerRequestInterface $request - * @param string $clientId * * @throws OAuthServerException * * @return array */ - protected function validateOldRefreshToken(ServerRequestInterface $request, $clientId): array + protected function validateOldRefreshToken(ServerRequestInterface $request, string $clientId): array { $encryptedRefreshToken = $this->getRequestParameter('refresh_token', $request); - if (!\is_string($encryptedRefreshToken)) { + if (!is_string($encryptedRefreshToken)) { throw OAuthServerException::invalidRequest('refresh_token'); } @@ -111,13 +117,13 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, $cli throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); } - $refreshTokenData = \json_decode($refreshToken, true); + $refreshTokenData = json_decode($refreshToken, true); if ($refreshTokenData['client_id'] !== $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 ($refreshTokenData['expire_time'] < time()) { throw OAuthServerException::invalidRefreshToken('Token has expired'); } diff --git a/src/Middleware/AuthorizationServerMiddleware.php b/src/Middleware/AuthorizationServerMiddleware.php index f1a743af5..913bb9afd 100644 --- a/src/Middleware/AuthorizationServerMiddleware.php +++ b/src/Middleware/AuthorizationServerMiddleware.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Middleware; use Exception; @@ -23,7 +26,6 @@ class AuthorizationServerMiddleware private $server; /** - * @param AuthorizationServer $server */ public function __construct(AuthorizationServer $server) { @@ -31,13 +33,9 @@ public function __construct(AuthorizationServer $server) } /** - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @param callable $next * - * @return ResponseInterface */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface { try { $response = $this->server->respondToAccessTokenRequest($request, $response); diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 8a5103f3e..61c8dcab0 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Middleware; use Exception; @@ -23,7 +26,6 @@ class ResourceServerMiddleware private $server; /** - * @param ResourceServer $server */ public function __construct(ResourceServer $server) { @@ -31,13 +33,9 @@ public function __construct(ResourceServer $server) } /** - * @param ServerRequestInterface $request - * @param ResponseInterface $response - * @param callable $next * - * @return ResponseInterface */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface { try { $request = $this->server->validateAuthenticatedRequest($request); diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index 3efa356d3..af20765ff 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,11 +8,16 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\RedirectUriValidators; use League\Uri\Exceptions\SyntaxError; use League\Uri\Uri; +use function in_array; +use function is_string; + class RedirectUriValidator implements RedirectUriValidatorInterface { /** @@ -26,23 +32,20 @@ class RedirectUriValidator implements RedirectUriValidatorInterface */ public function __construct(array|string $allowedRedirectUris) { - if (\is_string($allowedRedirectUris)) { + if (is_string($allowedRedirectUris)) { $this->allowedRedirectUris = [$allowedRedirectUris]; - } elseif (\is_array($allowedRedirectUris)) { - $this->allowedRedirectUris = $allowedRedirectUris; } else { - $this->allowedRedirectUris = []; + $this->allowedRedirectUris = $allowedRedirectUris; } } /** * Validates the redirect uri. * - * @param string $redirectUri * * @return bool Return true if valid, false otherwise */ - public function validateRedirectUri($redirectUri) + public function validateRedirectUri(string $redirectUri): bool { if ($this->isLoopbackUri($redirectUri)) { return $this->matchUriExcludingPort($redirectUri); @@ -56,11 +59,9 @@ public function validateRedirectUri($redirectUri) * - "http://127.0.0.1:{port}/{path}" for IPv4 * - "http://[::1]:{port}/{path}" for IPv6 * - * @param string $redirectUri * - * @return bool */ - private function isLoopbackUri($redirectUri) + private function isLoopbackUri(string $redirectUri): bool { try { $uri = Uri::createFromString($redirectUri); @@ -69,29 +70,27 @@ private function isLoopbackUri($redirectUri) } return $uri->getScheme() === 'http' - && (\in_array($uri->getHost(), ['127.0.0.1', '[::1]'], true)); + && (in_array($uri->getHost(), ['127.0.0.1', '[::1]'], true)); } /** * Find an exact match among allowed uris * - * @param string $redirectUri * * @return bool Return true if an exact match is found, false otherwise */ - private function matchExactUri($redirectUri) + private function matchExactUri(string $redirectUri): bool { - return \in_array($redirectUri, $this->allowedRedirectUris, true); + return in_array($redirectUri, $this->allowedRedirectUris, true); } /** * Find a match among allowed uris, allowing for different port numbers * - * @param string $redirectUri * * @return bool Return true if a match is found, false otherwise */ - private function matchUriExcludingPort($redirectUri) + private function matchUriExcludingPort(string $redirectUri): bool { $parsedUrl = $this->parseUrlAndRemovePort($redirectUri); @@ -107,11 +106,9 @@ private function matchUriExcludingPort($redirectUri) /** * Parse an url like \parse_url, excluding the port * - * @param string $url * - * @return string */ - private function parseUrlAndRemovePort($url) + private function parseUrlAndRemovePort(string $url): string { $uri = Uri::createFromString($url); diff --git a/src/RedirectUriValidators/RedirectUriValidatorInterface.php b/src/RedirectUriValidators/RedirectUriValidatorInterface.php index d039085ab..0e44830f7 100644 --- a/src/RedirectUriValidators/RedirectUriValidatorInterface.php +++ b/src/RedirectUriValidators/RedirectUriValidatorInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\RedirectUriValidators; interface RedirectUriValidatorInterface @@ -14,9 +17,8 @@ interface RedirectUriValidatorInterface /** * Validates the redirect uri. * - * @param string $redirectUri * * @return bool Return true if valid, false otherwise */ - public function validateRedirectUri($redirectUri); + public function validateRedirectUri(string $redirectUri): bool; } diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index fb1c967ce..e5a4b0c98 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; @@ -23,13 +26,12 @@ interface AccessTokenRepositoryInterface extends RepositoryInterface * Create a new access token * * @param ScopeEntityInterface[] $scopes - * @param mixed $userIdentifier * - * @return AccessTokenEntityInterface */ public function getNewToken( ClientEntityInterface $clientEntity, - array $scopes, $userIdentifier = null + array $scopes, + mixed $userIdentifier = null ): AccessTokenEntityInterface; /** diff --git a/src/Repositories/AuthCodeRepositoryInterface.php b/src/Repositories/AuthCodeRepositoryInterface.php index 64954aaf5..89ff86b87 100644 --- a/src/Repositories/AuthCodeRepositoryInterface.php +++ b/src/Repositories/AuthCodeRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; diff --git a/src/Repositories/ClientRepositoryInterface.php b/src/Repositories/ClientRepositoryInterface.php index 7eef494f4..092a627f2 100644 --- a/src/Repositories/ClientRepositoryInterface.php +++ b/src/Repositories/ClientRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -21,9 +24,8 @@ interface ClientRepositoryInterface extends RepositoryInterface * * @param string $clientIdentifier The client's identifier * - * @return ClientEntityInterface|null */ - public function getClientEntity($clientIdentifier); + public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface; /** * Validate a client's secret. @@ -32,7 +34,6 @@ public function getClientEntity($clientIdentifier); * @param null|string $clientSecret The client's secret (if sent) * @param null|string $grantType The type of grant the client is using (if sent) * - * @return bool */ - public function validateClient($clientIdentifier, $clientSecret, $grantType); + public function validateClient(string $clientIdentifier, ?string $clientSecret, ?string $grantType): bool; } diff --git a/src/Repositories/RefreshTokenRepositoryInterface.php b/src/Repositories/RefreshTokenRepositoryInterface.php index 106a2ef7d..a25e50133 100644 --- a/src/Repositories/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/RefreshTokenRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; diff --git a/src/Repositories/RepositoryInterface.php b/src/Repositories/RepositoryInterface.php index 9c27b4b0a..00b1dc106 100644 --- a/src/Repositories/RepositoryInterface.php +++ b/src/Repositories/RepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; /** diff --git a/src/Repositories/ScopeRepositoryInterface.php b/src/Repositories/ScopeRepositoryInterface.php index 9dbc0a896..2ee699ccf 100644 --- a/src/Repositories/ScopeRepositoryInterface.php +++ b/src/Repositories/ScopeRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -22,27 +25,22 @@ interface ScopeRepositoryInterface extends RepositoryInterface * * @param string $identifier The scope identifier * - * @return ScopeEntityInterface|null */ - public function getScopeEntityByIdentifier($identifier); + public function getScopeEntityByIdentifier(string $identifier): ?ScopeEntityInterface; /** * Given a client, grant type and optional user identifier validate the set of scopes requested are valid and optionally * append additional scopes or remove requested scopes. * * @param ScopeEntityInterface[] $scopes - * @param string $grantType - * @param ClientEntityInterface $clientEntity - * @param null|string $userIdentifier - * @param null|string $authCodeId * * @return ScopeEntityInterface[] */ public function finalizeScopes( array $scopes, - $grantType, + string $grantType, ClientEntityInterface $clientEntity, - $userIdentifier = null, - $authCodeId = null - ); + string|int|null $userIdentifier = null, + ?string $authCodeId = null + ): array; } diff --git a/src/Repositories/UserRepositoryInterface.php b/src/Repositories/UserRepositoryInterface.php index 8ad49aa7c..644e700ad 100644 --- a/src/Repositories/UserRepositoryInterface.php +++ b/src/Repositories/UserRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -17,17 +20,13 @@ interface UserRepositoryInterface extends RepositoryInterface /** * Get a user entity. * - * @param string $username - * @param string $password * @param string $grantType The grant type used - * @param ClientEntityInterface $clientEntity * - * @return UserEntityInterface|null */ public function getUserEntityByUserCredentials( - $username, - $password, - $grantType, + string $username, + string $password, + string $grantType, ClientEntityInterface $clientEntity - ); + ): ?UserEntityInterface; } diff --git a/src/RequestAccessTokenEvent.php b/src/RequestAccessTokenEvent.php index 99d17bf36..fe723857f 100644 --- a/src/RequestAccessTokenEvent.php +++ b/src/RequestAccessTokenEvent.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; @@ -20,20 +23,17 @@ class RequestAccessTokenEvent extends RequestEvent private $accessToken; /** - * @param string $name - * @param ServerRequestInterface $request */ - public function __construct($name, ServerRequestInterface $request, AccessTokenEntityInterface $accessToken) + public function __construct(string $name, ServerRequestInterface $request, AccessTokenEntityInterface $accessToken) { parent::__construct($name, $request); $this->accessToken = $accessToken; } /** - * @return AccessTokenEntityInterface * @codeCoverageIgnore */ - public function getAccessToken() + public function getAccessToken(): AccessTokenEntityInterface { return $this->accessToken; } diff --git a/src/RequestEvent.php b/src/RequestEvent.php index b1ca3f6b8..11c5bd0fc 100644 --- a/src/RequestEvent.php +++ b/src/RequestEvent.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server; use League\Event\Event; @@ -14,12 +17,12 @@ class RequestEvent extends Event { - const CLIENT_AUTHENTICATION_FAILED = 'client.authentication.failed'; - const USER_AUTHENTICATION_FAILED = 'user.authentication.failed'; - const REFRESH_TOKEN_CLIENT_FAILED = 'refresh_token.client.failed'; + public const CLIENT_AUTHENTICATION_FAILED = 'client.authentication.failed'; + public const USER_AUTHENTICATION_FAILED = 'user.authentication.failed'; + public const REFRESH_TOKEN_CLIENT_FAILED = 'refresh_token.client.failed'; - const REFRESH_TOKEN_ISSUED = 'refresh_token.issued'; - const ACCESS_TOKEN_ISSUED = 'access_token.issued'; + public const REFRESH_TOKEN_ISSUED = 'refresh_token.issued'; + public const ACCESS_TOKEN_ISSUED = 'access_token.issued'; /** * @var ServerRequestInterface @@ -29,20 +32,17 @@ class RequestEvent extends Event /** * RequestEvent constructor. * - * @param string $name - * @param ServerRequestInterface $request */ - public function __construct($name, ServerRequestInterface $request) + public function __construct(string $name, ServerRequestInterface $request) { parent::__construct($name); $this->request = $request; } /** - * @return ServerRequestInterface * @codeCoverageIgnore */ - public function getRequest() + public function getRequest(): ServerRequestInterface { return $this->request; } diff --git a/src/RequestRefreshTokenEvent.php b/src/RequestRefreshTokenEvent.php index 0682e57f5..d5348b961 100644 --- a/src/RequestRefreshTokenEvent.php +++ b/src/RequestRefreshTokenEvent.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; @@ -20,20 +23,17 @@ class RequestRefreshTokenEvent extends RequestEvent private $refreshToken; /** - * @param string $name - * @param ServerRequestInterface $request */ - public function __construct($name, ServerRequestInterface $request, RefreshTokenEntityInterface $refreshToken) + public function __construct(string $name, ServerRequestInterface $request, RefreshTokenEntityInterface $refreshToken) { parent::__construct($name, $request); $this->refreshToken = $refreshToken; } /** - * @return RefreshTokenEntityInterface * @codeCoverageIgnore */ - public function getRefreshToken() + public function getRefreshToken(): RefreshTokenEntityInterface { return $this->refreshToken; } diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index f9460d0f5..8231ebbfb 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\RequestTypes; use League\OAuth2\Server\Entities\ClientEntityInterface; diff --git a/src/RequestTypes/AuthorizationRequestInterface.php b/src/RequestTypes/AuthorizationRequestInterface.php index 10ea08ace..6ae358439 100644 --- a/src/RequestTypes/AuthorizationRequestInterface.php +++ b/src/RequestTypes/AuthorizationRequestInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\RequestTypes; use League\OAuth2\Server\Entities\ClientEntityInterface; diff --git a/src/ResourceServer.php b/src/ResourceServer.php index 92a72763e..844d39be5 100644 --- a/src/ResourceServer.php +++ b/src/ResourceServer.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server; use League\OAuth2\Server\AuthorizationValidators\AuthorizationValidatorInterface; @@ -35,13 +38,11 @@ class ResourceServer /** * New server instance. * - * @param AccessTokenRepositoryInterface $accessTokenRepository - * @param CryptKeyInterface|string $publicKey * @param null|AuthorizationValidatorInterface $authorizationValidator */ public function __construct( AccessTokenRepositoryInterface $accessTokenRepository, - $publicKey, + CryptKeyInterface|string $publicKey, AuthorizationValidatorInterface $authorizationValidator = null ) { $this->accessTokenRepository = $accessTokenRepository; @@ -55,9 +56,8 @@ public function __construct( } /** - * @return AuthorizationValidatorInterface */ - protected function getAuthorizationValidator() + protected function getAuthorizationValidator(): AuthorizationValidatorInterface { if ($this->authorizationValidator instanceof AuthorizationValidatorInterface === false) { $this->authorizationValidator = new BearerTokenValidator($this->accessTokenRepository); @@ -73,13 +73,11 @@ protected function getAuthorizationValidator() /** * Determine the access token validity. * - * @param ServerRequestInterface $request * * @throws OAuthServerException * - * @return ServerRequestInterface */ - public function validateAuthenticatedRequest(ServerRequestInterface $request) + public function validateAuthenticatedRequest(ServerRequestInterface $request): ServerRequestInterface { return $this->getAuthorizationValidator()->validateAuthorization($request); } diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index dd2c15041..1144b59d5 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -1,4 +1,5 @@ 'Bearer', - 'expires_in' => $expireDateTime - \time(), + 'expires_in' => $expireDateTime - time(), 'access_token' => (string) $this->accessToken, ]; - if ($this->refreshToken instanceof RefreshTokenEntityInterface) { - $refreshTokenPayload = \json_encode([ + $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); + if ($refreshTokenPayload === false) { + throw new LogicException('Error encountered JSON encoding the refresh token payload'); } - $responseParams = \json_encode(\array_merge($this->getExtraParams($this->accessToken), $responseParams)); + $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); + + $responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams)); if ($responseParams === false) { throw new LogicException('Error encountered JSON encoding response parameters'); diff --git a/src/ResponseTypes/RedirectResponse.php b/src/ResponseTypes/RedirectResponse.php index 2006a8f0d..cff6af6eb 100644 --- a/src/ResponseTypes/RedirectResponse.php +++ b/src/ResponseTypes/RedirectResponse.php @@ -1,4 +1,5 @@ enableGrantType(new GrantType(), new DateInterval('PT1M')); $authRequest = $server->validateAuthorizationRequest($this->createMock(ServerRequestInterface::class)); - $this->assertSame(GrantType::class, $authRequest->getGrantTypeId()); + self::assertSame(GrantType::class, $authRequest->getGrantTypeId()); } */ @@ -66,17 +73,17 @@ public function testRespondToRequestInvalidGrantType(): void $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), 'file://' . __DIR__ . '/Stubs/private.key', - \base64_encode(\random_bytes(36)), + base64_encode(random_bytes(36)), new StubResponseType() ); $server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M')); try { - $server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response); + $server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response()); } catch (OAuthServerException $e) { - $this->assertEquals('unsupported_grant_type', $e->getErrorType()); - $this->assertEquals(400, $e->getHttpStatusCode()); + self::assertEquals('unsupported_grant_type', $e->getErrorType()); + self::assertEquals(400, $e->getHttpStatusCode()); } } @@ -89,6 +96,7 @@ public function testRespondToRequest(): void $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepository->method('getClientEntity')->willReturn($client); + $clientRepository->method('validateClient')->willReturn(true); $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -103,7 +111,7 @@ public function testRespondToRequest(): void $accessTokenRepositoryMock, $scopeRepositoryMock, 'file://' . __DIR__ . '/Stubs/private.key', - \base64_encode(\random_bytes(36)), + base64_encode(random_bytes(36)), new StubResponseType() ); @@ -113,8 +121,8 @@ public function testRespondToRequest(): void $_POST['grant_type'] = 'client_credentials'; $_POST['client_id'] = 'foo'; $_POST['client_secret'] = 'bar'; - $response = $server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response); - $this->assertEquals(200, $response->getStatusCode()); + $response = $server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response()); + self::assertEquals(200, $response->getStatusCode()); } public function testGetResponseType(): void @@ -129,11 +137,11 @@ public function testGetResponseType(): void 'file://' . __DIR__ . '/Stubs/public.key' ); - $abstractGrantReflection = new \ReflectionClass($server); + $abstractGrantReflection = new ReflectionClass($server); $method = $abstractGrantReflection->getMethod('getResponseType'); $method->setAccessible(true); - $this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); + self::assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); } public function testGetResponseTypeExtended(): void @@ -150,13 +158,13 @@ public function testGetResponseTypeExtended(): void 'file://' . __DIR__ . '/Stubs/public.key' ); - $abstractGrantReflection = new \ReflectionClass($server); + $abstractGrantReflection = new ReflectionClass($server); $method = $abstractGrantReflection->getMethod('getResponseType'); $method->setAccessible(true); $responseType = $method->invoke($server); - $responseTypeReflection = new \ReflectionClass($responseType); + $responseTypeReflection = new ReflectionClass($responseType); $privateKeyProperty = $responseTypeReflection->getProperty('privateKey'); $privateKeyProperty->setAccessible(true); @@ -165,8 +173,8 @@ public function testGetResponseTypeExtended(): void $encryptionKeyProperty->setAccessible(true); // generated instances should have keys setup - $this->assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath()); - $this->assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType)); + self::assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath()); + self::assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType)); } public function testMultipleRequestsGetDifferentResponseTypeInstances(): void @@ -197,7 +205,7 @@ public function getEncryptionKey(): Key|string|null $responseTypePrototype ); - $abstractGrantReflection = new \ReflectionClass($server); + $abstractGrantReflection = new ReflectionClass($server); $method = $abstractGrantReflection->getMethod('getResponseType'); $method->setAccessible(true); @@ -205,19 +213,19 @@ public function getEncryptionKey(): Key|string|null $responseTypeB = $method->invoke($server); // prototype should not get changed - $this->assertNull($responseTypePrototype->getPrivateKey()); - $this->assertNull($responseTypePrototype->getEncryptionKey()); + self::assertNull($responseTypePrototype->getPrivateKey()); + self::assertNull($responseTypePrototype->getEncryptionKey()); // generated instances should have keys setup - $this->assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath()); - $this->assertSame($encryptionKey, $responseTypeA->getEncryptionKey()); + self::assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath()); + self::assertSame($encryptionKey, $responseTypeA->getEncryptionKey()); // all instances should be different but based on the same prototype - $this->assertSame(\get_class($responseTypePrototype), \get_class($responseTypeA)); - $this->assertSame(\get_class($responseTypePrototype), \get_class($responseTypeB)); - $this->assertNotSame($responseTypePrototype, $responseTypeA); - $this->assertNotSame($responseTypePrototype, $responseTypeB); - $this->assertNotSame($responseTypeA, $responseTypeB); + self::assertSame(get_class($responseTypePrototype), get_class($responseTypeA)); + self::assertSame(get_class($responseTypePrototype), get_class($responseTypeB)); + self::assertNotSame($responseTypePrototype, $responseTypeA); + self::assertNotSame($responseTypePrototype, $responseTypeB); + self::assertNotSame($responseTypeA, $responseTypeB); } public function testCompleteAuthorizationRequest(): void @@ -243,16 +251,23 @@ public function testCompleteAuthorizationRequest(): void $server->enableGrantType($grant); + $client = new ClientEntity(); + + $client->setRedirectUri('http://foo/bar'); + $client->setIdentifier('clientId'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); - $this->assertInstanceOf( - ResponseInterface::class, - $server->completeAuthorizationRequest($authRequest, new Response) - ); + $response = $server->completeAuthorizationRequest($authRequest, new Response()); + + $locationHeader = $response->getHeader('Location')[0]; + + self::assertStringStartsWith('http://foo/bar', $locationHeader); + self::assertStringContainsString('code=', $locationHeader); } public function testValidateAuthorizationRequest(): void @@ -299,53 +314,7 @@ public function testValidateAuthorizationRequest(): void ] ); - $this->assertInstanceOf(AuthorizationRequest::class, $server->validateAuthorizationRequest($request)); - } - - public function testValidateAuthorizationRequestWithMissingRedirectUri(): void - { - $client = new ClientEntity(); - $client->setConfidential(); - - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M') - ); - $grant->setClientRepository($clientRepositoryMock); - - $server = new AuthorizationServer( - $clientRepositoryMock, - $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), - $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), - 'file://' . __DIR__ . '/Stubs/private.key', - 'file://' . __DIR__ . '/Stubs/public.key' - ); - $server->enableGrantType($grant); - - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - ] - ); - - try { - $server->validateAuthorizationRequest($request); - } catch (OAuthServerException $e) { - $this->assertEquals('invalid_client', $e->getErrorType()); - $this->assertEquals(401, $e->getHttpStatusCode()); - } + self::assertInstanceOf(AuthorizationRequest::class, $server->validateAuthorizationRequest($request)); } public function testValidateAuthorizationRequestUnregistered(): void @@ -363,7 +332,7 @@ public function testValidateAuthorizationRequestUnregistered(): void 'client_id' => 'foo', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(2); $server->validateAuthorizationRequest($request); diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 51ab8ecba..ee4f55585 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -1,5 +1,7 @@ withClaim('scopes', 'scope1 scope2 scope3 scope4') ->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $validJwt->toString())); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $validJwt->toString())); $validRequest = $bearerTokenValidator->validateAuthorization($request); - $this->assertArrayHasKey('authorization', $validRequest->getHeaders()); + self::assertArrayHasKey('authorization', $validRequest->getHeaders()); } public function testBearerTokenValidatorRejectsExpiredToken(): void @@ -64,9 +69,9 @@ public function testBearerTokenValidatorRejectsExpiredToken(): void ->withClaim('scopes', 'scope1 scope2 scope3 scope4') ->getToken(new Sha256(), InMemory::file(__DIR__ . '/../Stubs/private.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $expiredJwt->toString())); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $expiredJwt->toString())); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(9); $bearerTokenValidator->validateAuthorization($request); diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php index b02cb7be4..6445d351e 100644 --- a/tests/Bootstrap.php +++ b/tests/Bootstrap.php @@ -1,5 +1,7 @@ assertEquals('plain', $verifier->getMethod()); + self::assertEquals('plain', $verifier->getMethod()); } public function testVerifyCodeChallenge(): void { $verifier = new PlainVerifier(); - $this->assertTrue($verifier->verifyCodeChallenge('foo', 'foo')); - $this->assertFalse($verifier->verifyCodeChallenge('foo', 'bar')); + self::assertTrue($verifier->verifyCodeChallenge('foo', 'foo')); + self::assertFalse($verifier->verifyCodeChallenge('foo', 'bar')); } } diff --git a/tests/CodeChallengeVerifiers/S256VerifierTest.php b/tests/CodeChallengeVerifiers/S256VerifierTest.php index 14ef1957f..4bcbbe0e3 100644 --- a/tests/CodeChallengeVerifiers/S256VerifierTest.php +++ b/tests/CodeChallengeVerifiers/S256VerifierTest.php @@ -1,17 +1,24 @@ assertEquals('S256', $verifier->getMethod()); + self::assertEquals('S256', $verifier->getMethod()); } public function testVerifyCodeChallengeSucceeds(): void @@ -19,7 +26,7 @@ public function testVerifyCodeChallengeSucceeds(): void $codeChallenge = $this->createCodeChallenge('foo'); $verifier = new S256Verifier(); - $this->assertTrue($verifier->verifyCodeChallenge('foo', $codeChallenge)); + self::assertTrue($verifier->verifyCodeChallenge('foo', $codeChallenge)); } public function testVerifyCodeChallengeFails(): void @@ -27,11 +34,11 @@ public function testVerifyCodeChallengeFails(): void $codeChallenge = $this->createCodeChallenge('bar'); $verifier = new S256Verifier(); - $this->assertFalse($verifier->verifyCodeChallenge('foo', $codeChallenge)); + self::assertFalse($verifier->verifyCodeChallenge('foo', $codeChallenge)); } private function createCodeChallenge(string $codeVerifier): string { - return \strtr(\rtrim(\base64_encode(\hash('sha256', $codeVerifier, true)), '='), '+/', '-_'); + return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'); } } diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index d0d6641b6..27922b427 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -1,5 +1,7 @@ generateHttpResponse(new Response()); - $this->assertTrue($response->hasHeader('WWW-Authenticate')); + self::assertTrue($response->hasHeader('WWW-Authenticate')); } } @@ -43,7 +46,7 @@ public function testInvalidClientExceptionSetsBearerAuthenticateHeader(): void } catch (OAuthServerException $e) { $response = $e->generateHttpResponse(new Response()); - $this->assertEquals(['Bearer realm="OAuth"'], $response->getHeader('WWW-Authenticate')); + self::assertEquals(['Bearer realm="OAuth"'], $response->getHeader('WWW-Authenticate')); } } @@ -59,7 +62,7 @@ public function testInvalidClientExceptionOmitsAuthenticateHeader(): void } catch (OAuthServerException $e) { $response = $e->generateHttpResponse(new Response()); - $this->assertFalse($response->hasHeader('WWW-Authenticate')); + self::assertFalse($response->hasHeader('WWW-Authenticate')); } } @@ -76,7 +79,7 @@ public function testInvalidClientExceptionOmitsAuthenticateHeaderGivenEmptyAutho } catch (OAuthServerException $e) { $response = $e->generateHttpResponse(new Response()); - $this->assertFalse($response->hasHeader('WWW-Authenticate')); + self::assertFalse($response->hasHeader('WWW-Authenticate')); } } @@ -93,7 +96,7 @@ private function issueInvalidClientException(ServerRequestInterface $serverReque $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); @@ -105,14 +108,14 @@ public function testHasRedirect(): void { $exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error'); - $this->assertTrue($exceptionWithRedirect->hasRedirect()); + self::assertTrue($exceptionWithRedirect->hasRedirect()); } public function testDoesNotHaveRedirect(): void { $exceptionWithoutRedirect = OAuthServerException::accessDenied('Some hint'); - $this->assertFalse($exceptionWithoutRedirect->hasRedirect()); + self::assertFalse($exceptionWithoutRedirect->hasRedirect()); } public function testHasPrevious(): void @@ -122,27 +125,27 @@ public function testHasPrevious(): void $previousMessage = $exceptionWithPrevious->getPrevious() !== null ? $exceptionWithPrevious->getPrevious()->getMessage() : null; - $this->assertSame('This is the previous', $previousMessage); + self::assertSame('This is the previous', $previousMessage); } public function testDoesNotHavePrevious(): void { $exceptionWithoutPrevious = OAuthServerException::accessDenied(); - $this->assertNull($exceptionWithoutPrevious->getPrevious()); + self::assertNull($exceptionWithoutPrevious->getPrevious()); } public function testCanGetRedirectionUri(): void { $exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error'); - $this->assertSame('https://example.com/error', $exceptionWithRedirect->getRedirectUri()); + self::assertSame('https://example.com/error', $exceptionWithRedirect->getRedirectUri()); } public function testInvalidCredentialsIsInvalidGrant(): void { $exception = OAuthServerException::invalidCredentials(); - $this->assertSame('invalid_grant', $exception->getErrorType()); + self::assertSame('invalid_grant', $exception->getErrorType()); } } diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 9bf569c66..06856f0e3 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -1,5 +1,7 @@ getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); - $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . \base64_encode('Open:Sesame')); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:Sesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); - $this->assertSame(['Open', 'Sesame'], $basicAuthMethod->invoke($grantMock, $serverRequest)); + self::assertSame(['Open', 'Sesame'], $basicAuthMethod->invoke($grantMock, $serverRequest)); } public function testHttpBasicNoPassword(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); - $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . \base64_encode('Open:')); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); - $this->assertSame(['Open', ''], $basicAuthMethod->invoke($grantMock, $serverRequest)); + self::assertSame(['Open', ''], $basicAuthMethod->invoke($grantMock, $serverRequest)); } public function testHttpBasicNotBasic(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); - $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Foo ' . \base64_encode('Open:Sesame')); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Foo ' . base64_encode('Open:Sesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); - $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); + self::assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } public function testHttpBasicNotBase64(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ||'); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); - $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); + self::assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } public function testHttpBasicNoColon(): void { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); - $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . \base64_encode('OpenSesame')); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); - $this->assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); + self::assertSame([null, null], $basicAuthMethod->invoke($grantMock, $serverRequest)); } public function testGetClientCredentialsClientSecretNotAString(): void @@ -97,7 +104,7 @@ public function testGetClientCredentialsClientSecretNotAString(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = new ServerRequest( [], @@ -116,7 +123,7 @@ public function testGetClientCredentialsClientSecretNotAString(): void $getClientCredentialsMethod = $abstractGrantReflection->getMethod('getClientCredentials'); $getClientCredentialsMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $getClientCredentialsMethod->invoke($grantMock, $serverRequest, true, true); } @@ -127,13 +134,15 @@ public function testValidateClientPublic(): void $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -143,7 +152,7 @@ public function testValidateClientPublic(): void $validateClientMethod->setAccessible(true); $result = $validateClientMethod->invoke($grantMock, $serverRequest); - $this->assertEquals($client, $result); + self::assertEquals($client, $result); } public function testValidateClientConfidential(): void @@ -152,13 +161,15 @@ public function testValidateClientConfidential(): void $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -170,12 +181,9 @@ public function testValidateClientConfidential(): void $validateClientMethod->setAccessible(true); $result = $validateClientMethod->invoke($grantMock, $serverRequest, true, true); - $this->assertEquals($client, $result); + self::assertEquals($client, $result); } - - public function testValidateClientMissingClientId(): void - { - $client = new ClientEntity(); +public function testValidateClientMissingClientId(): void { $client = new ClientEntity(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -183,13 +191,13 @@ public function testValidateClientMissingClientId(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = new ServerRequest(); $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } @@ -203,7 +211,7 @@ public function testValidateClientMissingClientSecret(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -212,7 +220,7 @@ public function testValidateClientMissingClientSecret(): void $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } @@ -226,7 +234,7 @@ public function testValidateClientInvalidClientSecret(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -236,7 +244,7 @@ public function testValidateClientInvalidClientSecret(): void $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } @@ -252,7 +260,7 @@ public function testValidateClientInvalidRedirectUri(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -262,7 +270,7 @@ public function testValidateClientInvalidRedirectUri(): void $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } @@ -278,7 +286,7 @@ public function testValidateClientInvalidRedirectUriArray(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -288,7 +296,7 @@ public function testValidateClientInvalidRedirectUriArray(): void $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } @@ -304,7 +312,7 @@ public function testValidateClientMalformedRedirectUri(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -314,7 +322,7 @@ public function testValidateClientMalformedRedirectUri(): void $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } @@ -328,7 +336,7 @@ public function testValidateClientBadClient(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setClientRepository($clientRepositoryMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -338,7 +346,7 @@ public function testValidateClientBadClient(): void $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $validateClientMethod->invoke($grantMock, $serverRequest, true); } @@ -352,14 +360,14 @@ public function testCanRespondToRequest(): void 'grant_type' => 'foobar', ]); - $this->assertTrue($grantMock->canRespondToAccessTokenRequest($serverRequest)); + self::assertTrue($grantMock->canRespondToAccessTokenRequest($serverRequest)); } public function testIssueRefreshToken(): void { $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepoMock - ->expects($this->once()) + ->expects(self::once()) ->method('getNewRefreshToken') ->willReturn(new RefreshTokenEntity()); @@ -368,36 +376,37 @@ public function testIssueRefreshToken(): void $grantMock->setRefreshTokenTTL(new DateInterval('PT1M')); $grantMock->setRefreshTokenRepository($refreshTokenRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken'); $issueRefreshTokenMethod->setAccessible(true); $accessToken = new AccessTokenEntity(); + /** @var RefreshTokenEntityInterface $refreshToken */ $refreshToken = $issueRefreshTokenMethod->invoke($grantMock, $accessToken); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $refreshToken); - $this->assertEquals($accessToken, $refreshToken->getAccessToken()); + + self::assertEquals($accessToken, $refreshToken->getAccessToken()); } public function testIssueNullRefreshToken(): void { $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepoMock - ->expects($this->once()) + ->expects(self::once()) ->method('getNewRefreshToken') ->willReturn(null); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setRefreshTokenTTL(new \DateInterval('PT1M')); + $grantMock->setRefreshTokenTTL(new DateInterval('PT1M')); $grantMock->setRefreshTokenRepository($refreshTokenRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken'); $issueRefreshTokenMethod->setAccessible(true); $accessToken = new AccessTokenEntity(); - $this->assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); + self::assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); } public function testIssueAccessToken(): void @@ -410,7 +419,7 @@ public function testIssueAccessToken(): void $grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grantMock->setAccessTokenRepository($accessTokenRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueAccessTokenMethod = $abstractGrantReflection->getMethod('issueAccessToken'); $issueAccessTokenMethod->setAccessible(true); @@ -422,23 +431,27 @@ public function testIssueAccessToken(): void 123, [new ScopeEntity()] ); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $accessToken); + + self::assertNotEmpty($accessToken->getIdentifier()); } public function testIssueAuthCode(): void { $authCodeRepoMock = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); - $authCodeRepoMock->expects($this->once())->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); + $authCodeRepoMock->expects(self::once())->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setAuthCodeRepository($authCodeRepoMock); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $issueAuthCodeMethod = $abstractGrantReflection->getMethod('issueAuthCode'); $issueAuthCodeMethod->setAccessible(true); - $this->assertInstanceOf( + $scope = new ScopeEntity(); + $scope->setIdentifier('scopeId'); + + self::assertInstanceOf( AuthCodeEntityInterface::class, $issueAuthCodeMethod->invoke( $grantMock, @@ -446,7 +459,7 @@ public function testIssueAuthCode(): void new ClientEntity(), 123, 'http://foo/bar', - [new ScopeEntity()] + [$scope] ) ); } @@ -456,7 +469,7 @@ public function testGetCookieParameter(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $method = $abstractGrantReflection->getMethod('getCookieParameter'); $method->setAccessible(true); @@ -464,8 +477,8 @@ public function testGetCookieParameter(): void 'foo' => 'bar', ]); - $this->assertEquals('bar', $method->invoke($grantMock, 'foo', $serverRequest)); - $this->assertEquals('foo', $method->invoke($grantMock, 'bar', $serverRequest, 'foo')); + self::assertEquals('bar', $method->invoke($grantMock, 'foo', $serverRequest)); + self::assertEquals('foo', $method->invoke($grantMock, 'bar', $serverRequest, 'foo')); } public function testGetQueryStringParameter(): void @@ -473,7 +486,7 @@ public function testGetQueryStringParameter(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $method = $abstractGrantReflection->getMethod('getQueryStringParameter'); $method->setAccessible(true); @@ -481,21 +494,21 @@ public function testGetQueryStringParameter(): void 'foo' => 'bar', ]); - $this->assertEquals('bar', $method->invoke($grantMock, 'foo', $serverRequest)); - $this->assertEquals('foo', $method->invoke($grantMock, 'bar', $serverRequest, 'foo')); + self::assertEquals('bar', $method->invoke($grantMock, 'foo', $serverRequest)); + self::assertEquals('foo', $method->invoke($grantMock, 'bar', $serverRequest, 'foo')); } public function testValidateScopes(): void { $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->expects($this->exactly(3))->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->expects(self::exactly(3))->method('getScopeEntityByIdentifier')->willReturn($scope); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setScopeRepository($scopeRepositoryMock); - $this->assertEquals([$scope, $scope, $scope], $grantMock->validateScopes('basic test 0 ')); + self::assertEquals([$scope, $scope, $scope], $grantMock->validateScopes('basic test 0 ')); } public function testValidateScopesBadScope(): void @@ -507,7 +520,7 @@ public function testValidateScopesBadScope(): void $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setScopeRepository($scopeRepositoryMock); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grantMock->validateScopes('basic '); } @@ -516,24 +529,24 @@ public function testGenerateUniqueIdentifier(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $abstractGrantReflection = new \ReflectionClass($grantMock); + $abstractGrantReflection = new ReflectionClass($grantMock); $method = $abstractGrantReflection->getMethod('generateUniqueIdentifier'); $method->setAccessible(true); - $this->assertIsString($method->invoke($grantMock)); + self::assertIsString($method->invoke($grantMock)); } public function testCanRespondToAuthorizationRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $this->assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest())); + self::assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest())); } public function testValidateAuthorizationRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grantMock->validateAuthorizationRequest(new ServerRequest()); } @@ -542,7 +555,7 @@ public function testCompleteAuthorizationRequest(): void { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grantMock->completeAuthorizationRequest(new AuthorizationRequest()); } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index b5843c29b..efeb6ba59 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1,12 +1,13 @@ assertEquals('authorization_code', $grant->getIdentifier()); + self::assertEquals('authorization_code', $grant->getIdentifier()); } public function testCanRespondToAuthorizationRequest(): void @@ -81,7 +86,7 @@ public function testCanRespondToAuthorizationRequest(): void ] ); - $this->assertTrue($grant->canRespondToAuthorizationRequest($request)); + self::assertTrue($grant->canRespondToAuthorizationRequest($request)); } public function testValidateAuthorizationRequest(): void @@ -121,7 +126,7 @@ public function testValidateAuthorizationRequest(): void ] ); - $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); + self::assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } public function testValidateAuthorizationRequestRedirectUriArray(): void @@ -160,7 +165,7 @@ public function testValidateAuthorizationRequestRedirectUriArray(): void ] ); - $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); + self::assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } public function testValidateAuthorizationRequestWithoutRedirectUri(): void @@ -200,9 +205,9 @@ public function testValidateAuthorizationRequestWithoutRedirectUri(): void ); $authorizationRequest = $grant->validateAuthorizationRequest($request); - $this->assertInstanceOf(AuthorizationRequest::class, $authorizationRequest); + self::assertInstanceOf(AuthorizationRequest::class, $authorizationRequest); - $this->assertEmpty($authorizationRequest->getRedirectUri()); + self::assertEmpty($authorizationRequest->getRedirectUri()); } public function testValidateAuthorizationRequestCodeChallenge(): void @@ -242,7 +247,7 @@ public function testValidateAuthorizationRequestCodeChallenge(): void ] ); - $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); + self::assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooShort(): void @@ -264,10 +269,10 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooSho 'response_type' => 'code', 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', - 'code_challenge' => \str_repeat('A', 42), + 'code_challenge' => str_repeat('A', 42), ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->validateAuthorizationRequest($request); } @@ -291,10 +296,10 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLon 'response_type' => 'code', 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', - 'code_challenge' => \str_repeat('A', 129), + 'code_challenge' => str_repeat('A', 129), ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->validateAuthorizationRequest($request); } @@ -318,10 +323,10 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters() 'response_type' => 'code', 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', - 'code_challenge' => \str_repeat('A', 42) . '!', + 'code_challenge' => str_repeat('A', 42) . '!', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->validateAuthorizationRequest($request); } @@ -341,7 +346,7 @@ public function testValidateAuthorizationRequestMissingClientId(): void 'response_type' => 'code', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); $grant->validateAuthorizationRequest($request); @@ -364,7 +369,7 @@ public function testValidateAuthorizationRequestInvalidClientId(): void 'client_id' => 'foo', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); @@ -390,7 +395,7 @@ public function testValidateAuthorizationRequestBadRedirectUriString(): void 'redirect_uri' => 'http://bar', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); @@ -416,7 +421,7 @@ public function testValidateAuthorizationRequestBadRedirectUriArray(): void 'redirect_uri' => 'http://bar', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); @@ -451,7 +456,7 @@ public function testValidateAuthorizationRequestInvalidCodeChallengeMethod(): vo 'code_challenge_method' => 'foo', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); $grant->validateAuthorizationRequest($request); @@ -459,9 +464,13 @@ public function testValidateAuthorizationRequestInvalidCodeChallengeMethod(): vo public function testCompleteAuthorizationRequest(): void { + $client = new ClientEntity(); + $client->setIdentifier('clientId'); + $client->setRedirectUri('http://foo/bar'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); @@ -475,13 +484,15 @@ public function testCompleteAuthorizationRequest(): void ); $grant->setEncryptionKey($this->cryptStub->getKey()); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient(): void { $client = new ClientEntity(); + $client->setIdentifier('clientId'); $client->setRedirectUri(['uriOne', 'uriTwo']); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); $authRequest->setClient($client); @@ -498,14 +509,17 @@ public function testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient ); $grant->setEncryptionKey($this->cryptStub->getKey()); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testCompleteAuthorizationRequestDenied(): void { + $client = new ClientEntity(); + $client->setRedirectUri('http://foo/bar'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(false); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); @@ -519,7 +533,7 @@ public function testCompleteAuthorizationRequestDenied(): void ); $grant->setEncryptionKey($this->cryptStub->getKey()); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(9); $grant->completeAuthorizationRequest($authRequest); @@ -528,11 +542,15 @@ public function testCompleteAuthorizationRequestDenied(): void public function testRespondToAccessTokenRequest(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -574,8 +592,8 @@ public function testRespondToAccessTokenRequest(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -588,8 +606,7 @@ public function testRespondToAccessTokenRequest(): void /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void @@ -613,7 +630,7 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void $authCodeGrant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $refreshTokenRepositoryMock, - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $authCodeGrant->setClientRepository($clientRepositoryMock); @@ -638,9 +655,9 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), + 'auth_code_id' => uniqid(), 'client_id' => 'foo', - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, 'user_id' => 123, 'scopes' => ['foo'], 'redirect_uri' => 'http://foo/bar', @@ -650,10 +667,9 @@ public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void ); /** @var StubResponseType $response */ - $response = $authCodeGrant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $response = $authCodeGrant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRespondToAccessTokenRequestForPublicClient(): void @@ -704,8 +720,8 @@ public function testRespondToAccessTokenRequestForPublicClient(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -718,8 +734,7 @@ public function testRespondToAccessTokenRequestForPublicClient(): void /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRespondToAccessTokenRequestNullRefreshToken(): void @@ -746,7 +761,7 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $refreshTokenRepositoryMock, - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); @@ -770,8 +785,8 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -782,20 +797,23 @@ public function testRespondToAccessTokenRequestNullRefreshToken(): void ); /** @var StubResponseType $response */ - $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertNull($response->getRefreshToken()); + self::assertNull($response->getRefreshToken()); } public function testRespondToAccessTokenRequestCodeChallengePlain(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -839,8 +857,8 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void 'code_verifier' => self::CODE_VERIFIER, 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -855,18 +873,21 @@ public function testRespondToAccessTokenRequestCodeChallengePlain(): void /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRespondToAccessTokenRequestCodeChallengeS256(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -910,8 +931,8 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void 'code_verifier' => self::CODE_VERIFIER, 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -926,18 +947,21 @@ public function testRespondToAccessTokenRequestCodeChallengeS256(): void /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRespondToAccessTokenRequestMissingRedirectUri(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setConfidential(); $client->setRedirectUri('http://foo/bar'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -961,8 +985,8 @@ public function testRespondToAccessTokenRequestMissingRedirectUri(): void 'grant_type' => 'authorization_code', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', ], JSON_THROW_ON_ERROR) @@ -970,7 +994,7 @@ public function testRespondToAccessTokenRequestMissingRedirectUri(): void ] ); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); @@ -979,11 +1003,15 @@ public function testRespondToAccessTokenRequestMissingRedirectUri(): void public function testRespondToAccessTokenRequestRedirectUriMismatch(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setConfidential(); $client->setRedirectUri('http://bar/foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -1008,8 +1036,8 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void 'redirect_uri' => 'http://bar/foo', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'redirect_uri' => 'http://foo/bar', ], JSON_THROW_ON_ERROR) @@ -1017,7 +1045,7 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void ] ); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); @@ -1026,10 +1054,14 @@ public function testRespondToAccessTokenRequestRedirectUriMismatch(): void public function testRespondToAccessTokenRequestMissingCode(): void { $client = new ClientEntity(); + $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -1061,7 +1093,7 @@ public function testRespondToAccessTokenRequestMissingCode(): void ] ); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); /* @var StubResponseType $response */ @@ -1105,7 +1137,7 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ], JSON_THROW_ON_ERROR) ), ] @@ -1115,7 +1147,7 @@ public function testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals('Authorization code malformed', $e->getHint()); + self::assertEquals('Authorization code malformed', $e->getHint()); } } @@ -1153,7 +1185,7 @@ public function testRespondToAccessTokenRequestWithAuthCodeNotAString(): void ] ); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } @@ -1189,8 +1221,8 @@ public function testRespondToAccessTokenRequestExpiredCode(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() - 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() - 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1204,18 +1236,22 @@ public function testRespondToAccessTokenRequestExpiredCode(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Authorization code has expired'); + self::assertEquals($e->getHint(), 'Authorization code has expired'); } } public function testRespondToAccessTokenRequestRevokedCode(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $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(); @@ -1251,8 +1287,8 @@ public function testRespondToAccessTokenRequestRevokedCode(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1266,19 +1302,23 @@ public function testRespondToAccessTokenRequestRevokedCode(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Authorization code has been revoked'); - $this->assertEquals($e->getErrorType(), 'invalid_grant'); + self::assertEquals($e->getHint(), 'Authorization code has been revoked'); + self::assertEquals($e->getErrorType(), 'invalid_grant'); } } public function testRespondToAccessTokenRequestClientMismatch(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $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(); @@ -1311,8 +1351,8 @@ public function testRespondToAccessTokenRequestClientMismatch(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'bar', 'user_id' => 123, 'scopes' => ['foo'], @@ -1326,18 +1366,22 @@ public function testRespondToAccessTokenRequestClientMismatch(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Authorization code was not issued to this client'); + self::assertEquals($e->getHint(), 'Authorization code was not issued to this client'); } } public function testRespondToAccessTokenRequestBadCodeEncryption(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $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(); @@ -1376,18 +1420,22 @@ public function testRespondToAccessTokenRequestBadCodeEncryption(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Cannot decrypt the authorization code'); + self::assertEquals($e->getHint(), 'Cannot decrypt the authorization code'); } } public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -1430,8 +1478,8 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void 'code_verifier' => self::CODE_VERIFIER, 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1447,18 +1495,22 @@ public function testRespondToAccessTokenRequestBadCodeVerifierPlain(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Failed to verify `code_verifier`.'); + self::assertEquals($e->getHint(), 'Failed to verify `code_verifier`.'); } } public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -1501,8 +1553,8 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void 'code_verifier' => 'nope', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1518,18 +1570,22 @@ public function testRespondToAccessTokenRequestBadCodeVerifierS256(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); + self::assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); } } public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidChars(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -1572,8 +1628,8 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva 'code_verifier' => 'dqX7C-RbqjHYtytmhGTigKdZCXfxq-+xbsk9_GxUcaE', // Malformed code. Contains `+`. 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1589,18 +1645,22 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); + self::assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); } } public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidLength(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -1643,8 +1703,8 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva 'code_verifier' => 'dqX7C-RbqjHY', // Malformed code. Invalid length. 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1660,18 +1720,22 @@ public function testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInva /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); + self::assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); } } public function testRespondToAccessTokenRequestMissingCodeVerifier(): void { $client = new ClientEntity(); + $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); $client->setConfidential(); + $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(); @@ -1713,8 +1777,8 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1730,27 +1794,31 @@ public function testRespondToAccessTokenRequestMissingCodeVerifier(): void /* @var StubResponseType $response */ $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { - $this->assertEquals($e->getHint(), 'Check the `code_verifier` parameter'); + self::assertEquals($e->getHint(), 'Check the `code_verifier` parameter'); } } public function testAuthCodeRepositoryUniqueConstraintCheck(): void { + $client = new ClientEntity(); + $client->setIdentifier('clientId'); + $client->setRedirectUri('http://foo/bar'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); - $matcher = $this->exactly(2); + $matcher = self::exactly(2); $authCodeRepository ->expects($matcher) ->method('persistNewAuthCode') - ->willReturnCallback(function () use ($matcher) { + ->willReturnCallback(function () use ($matcher): void { if ($matcher->getInvocationCount() === 1) { throw UniqueTokenIdentifierConstraintViolationException::create(); } @@ -1764,7 +1832,7 @@ public function testAuthCodeRepositoryUniqueConstraintCheck(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testAuthCodeRepositoryFailToPersist(): void @@ -1786,10 +1854,10 @@ public function testAuthCodeRepositoryFailToPersist(): void ); $grant->setEncryptionKey($this->cryptStub->getKey()); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(7); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop(): void @@ -1810,10 +1878,10 @@ public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop(): void new DateInterval('PT10M') ); - $this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class); + $this->expectException(UniqueTokenIdentifierConstraintViolationException::class); $this->expectExceptionCode(100); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testRefreshTokenRepositoryUniqueConstraintCheck(): void @@ -1836,12 +1904,12 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); - $matcher = $this->exactly(2); + $matcher = self::exactly(2); $refreshTokenRepositoryMock ->expects($matcher) ->method('persistNewRefreshToken') - ->willReturnCallback(function () use ($matcher) { + ->willReturnCallback(function () use ($matcher): void { if ($matcher->getInvocationCount() === 1) { throw UniqueTokenIdentifierConstraintViolationException::create(); } @@ -1874,8 +1942,8 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1888,8 +1956,7 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRefreshTokenRepositoryFailToPersist(): void @@ -1940,8 +2007,8 @@ public function testRefreshTokenRepositoryFailToPersist(): void 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -1951,14 +2018,13 @@ public function testRefreshTokenRepositoryFailToPersist(): void ] ); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(7); /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): void @@ -2009,8 +2075,8 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v 'redirect_uri' => 'http://foo/bar', 'code' => $this->cryptStub->doEncrypt( json_encode([ - 'auth_code_id' => \uniqid(), - 'expire_time' => \time() + 3600, + 'auth_code_id' => uniqid(), + 'expire_time' => time() + 3600, 'client_id' => 'foo', 'user_id' => 123, 'scopes' => ['foo'], @@ -2020,14 +2086,13 @@ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): v ] ); - $this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class); + $this->expectException(UniqueTokenIdentifierConstraintViolationException::class); $this->expectExceptionCode(100); /** @var StubResponseType $response */ $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } public function testCompleteAuthorizationRequestNoUser(): void @@ -2038,7 +2103,7 @@ public function testCompleteAuthorizationRequestNoUser(): void new DateInterval('PT10M') ); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grant->completeAuthorizationRequest(new AuthorizationRequest()); } @@ -2120,7 +2185,7 @@ public function testUseValidRedirectUriIfScopeCheckFails(): void } catch (OAuthServerException $e) { $response = $e->generateHttpResponse(new Response()); - $this->assertStringStartsWith('http://bar/foo', $response->getHeader('Location')[0]); + self::assertStringStartsWith('http://bar/foo', $response->getHeader('Location')[0]); } } } diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index e3d9b9268..e0b67e755 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -1,11 +1,12 @@ assertEquals('client_credentials', $grant->getIdentifier()); + self::assertEquals('client_credentials', $grant->getIdentifier()); } public function testRespondToRequest(): void @@ -57,8 +58,9 @@ public function testRespondToRequest(): void ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + $response = $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); + - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); + self::assertNotEmpty($response->getAccessToken()->getIdentifier()); } } diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 45777b755..64d00de0d 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -1,5 +1,7 @@ assertEquals('implicit', $grant->getIdentifier()); + self::assertEquals('implicit', $grant->getIdentifier()); } public function testCanRespondToAccessTokenRequest(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); - $this->assertFalse( + self::assertFalse( $grant->canRespondToAccessTokenRequest(new ServerRequest()) ); } @@ -52,7 +55,7 @@ public function testRespondToAccessTokenRequest(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grant->respondToAccessTokenRequest( new ServerRequest(), @@ -70,7 +73,7 @@ public function testCanRespondToAuthorizationRequest(): void 'client_id' => 'foo', ]); - $this->assertTrue($grant->canRespondToAuthorizationRequest($request)); + self::assertTrue($grant->canRespondToAuthorizationRequest($request)); } public function testValidateAuthorizationRequest(): void @@ -95,7 +98,7 @@ public function testValidateAuthorizationRequest(): void 'redirect_uri' => 'http://foo/bar', ]); - $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); + self::assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } public function testValidateAuthorizationRequestRedirectUriArray(): void @@ -120,7 +123,7 @@ public function testValidateAuthorizationRequestRedirectUriArray(): void 'redirect_uri' => 'http://foo/bar', ]); - $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); + self::assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } public function testValidateAuthorizationRequestMissingClientId(): void @@ -132,7 +135,7 @@ public function testValidateAuthorizationRequestMissingClientId(): void $request = (new ServerRequest())->withQueryParams(['response_type' => 'code']); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); $grant->validateAuthorizationRequest($request); @@ -151,7 +154,7 @@ public function testValidateAuthorizationRequestInvalidClientId(): void 'client_id' => 'foo', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); @@ -173,7 +176,7 @@ public function testValidateAuthorizationRequestBadRedirectUriString(): void 'redirect_uri' => 'http://bar', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); @@ -195,7 +198,7 @@ public function testValidateAuthorizationRequestBadRedirectUriArray(): void 'redirect_uri' => 'http://bar', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); @@ -205,6 +208,7 @@ public function testCompleteAuthorizationRequest(): void { $client = new ClientEntity(); $client->setIdentifier('identifier'); + $client->setRedirectUri('https://foo/bar'); $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -222,19 +226,23 @@ public function testCompleteAuthorizationRequest(): void $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testCompleteAuthorizationRequestDenied(): void { + $client = new ClientEntity(); + $client->setIdentifier('clientId'); + $client->setRedirectUri('https://foo/bar'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(false); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); @@ -245,12 +253,12 @@ public function testCompleteAuthorizationRequestDenied(): void $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(9); $grant->completeAuthorizationRequest($authRequest); @@ -259,7 +267,8 @@ public function testCompleteAuthorizationRequestDenied(): void public function testAccessTokenRepositoryUniqueConstraintCheck(): void { $client = new ClientEntity(); - $client->setIdentifier('identifier'); + $client->setIdentifier('clientId'); + $client->setRedirectUri('https://foo/bar'); $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); @@ -274,12 +283,12 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); - $matcher = $this->exactly(2); + $matcher = self::exactly(2); $accessTokenRepositoryMock ->expects($matcher) ->method('persistNewAccessToken') - ->willReturnCallback(function () use ($matcher) { + ->willReturnCallback(function () use ($matcher): void { if ($matcher->getInvocationCount() === 1) { throw UniqueTokenIdentifierConstraintViolationException::create(); } @@ -288,12 +297,12 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); + self::assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } public function testAccessTokenRepositoryFailToPersist(): void @@ -312,12 +321,12 @@ public function testAccessTokenRepositoryFailToPersist(): void $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(7); $grant->completeAuthorizationRequest($authRequest); @@ -339,12 +348,12 @@ public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): vo $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); - $this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class); + $this->expectException(UniqueTokenIdentifierConstraintViolationException::class); $this->expectExceptionCode(100); $grant->completeAuthorizationRequest($authRequest); @@ -354,7 +363,7 @@ public function testSetRefreshTokenTTL(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grant->setRefreshTokenTTL(new DateInterval('PT10M')); } @@ -365,7 +374,7 @@ public function testSetRefreshTokenRepository(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); } @@ -374,7 +383,7 @@ public function testCompleteAuthorizationRequestNoUser(): void { $grant = new ImplicitGrant(new DateInterval('PT10M')); - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $grant->completeAuthorizationRequest(new AuthorizationRequest()); } diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 1eb2e0895..42bdc8f95 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -1,5 +1,7 @@ getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $grant = new PasswordGrant($userRepositoryMock, $refreshTokenRepositoryMock); - $this->assertEquals('password', $grant->getIdentifier()); + self::assertEquals('password', $grant->getIdentifier()); } public function testRespondToRequest(): void @@ -76,8 +79,7 @@ public function testRespondToRequest(): void $responseType = new StubResponseType(); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } public function testRespondToRequestNullRefreshToken(): void @@ -119,10 +121,9 @@ public function testRespondToRequestNullRefreshToken(): void ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); - $this->assertNull($responseType->getRefreshToken()); + self::assertNull($responseType->getRefreshToken()); } public function testRespondToRequestMissingUsername(): void @@ -148,7 +149,7 @@ public function testRespondToRequestMissingUsername(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } @@ -177,7 +178,7 @@ public function testRespondToRequestMissingPassword(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } @@ -210,7 +211,7 @@ public function testRespondToRequestBadCredentials(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(6); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index aa2bb101e..0f8e243b5 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -1,12 +1,14 @@ getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); - $this->assertEquals('refresh_token', $grant->getIdentifier()); + self::assertEquals('refresh_token', $grant->getIdentifier()); } public function testRespondToRequest(): void @@ -58,11 +62,11 @@ public function testRespondToRequest(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); - $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); - $refreshTokenRepositoryMock->expects($this->once())->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->expects(self::once())->method('persistNewRefreshToken')->willReturnSelf(); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); @@ -72,19 +76,19 @@ public function testRespondToRequest(): void $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -101,8 +105,7 @@ public function testRespondToRequest(): void $responseType = new StubResponseType(); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } public function testRespondToRequestNullRefreshToken(): void @@ -123,11 +126,11 @@ public function testRespondToRequestNullRefreshToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); - $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); - $refreshTokenRepositoryMock->expects($this->never())->method('persistNewRefreshToken'); + $refreshTokenRepositoryMock->expects(self::never())->method('persistNewRefreshToken'); $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); $grant->setClientRepository($clientRepositoryMock); @@ -136,23 +139,23 @@ public function testRespondToRequestNullRefreshToken(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( - $oldRefreshToken + $oldRefreshToken ); $serverRequest = (new ServerRequest())->withParsedBody([ @@ -163,10 +166,9 @@ public function testRespondToRequestNullRefreshToken(): void ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); - $this->assertNull($responseType->getRefreshToken()); + self::assertNull($responseType->getRefreshToken()); } public function testRespondToReducedScopes(): void @@ -200,19 +202,19 @@ public function testRespondToReducedScopes(): void $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->revokeRefreshTokens(true); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo', 'bar'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -229,8 +231,7 @@ public function testRespondToReducedScopes(): void $responseType = new StubResponseType(); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); + self::assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } public function testRespondToUnexpectedScope(): void @@ -260,19 +261,19 @@ public function testRespondToUnexpectedScope(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo', 'bar'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -288,7 +289,7 @@ public function testRespondToUnexpectedScope(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(5); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); @@ -319,7 +320,7 @@ public function testRespondToRequestMissingOldToken(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(3); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); @@ -353,7 +354,7 @@ public function testRespondToRequestInvalidOldToken(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(8); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); @@ -380,19 +381,19 @@ public function testRespondToRequestClientMismatch(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'bar', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -407,7 +408,7 @@ public function testRespondToRequestClientMismatch(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(8); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); @@ -431,19 +432,19 @@ public function testRespondToRequestExpiredToken(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() - 3600, + 'expire_time' => time() - 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -458,7 +459,7 @@ public function testRespondToRequestExpiredToken(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(8); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); @@ -483,19 +484,19 @@ public function testRespondToRequestRevokedToken(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -510,7 +511,7 @@ public function testRespondToRequestRevokedToken(): void $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(8); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); @@ -554,7 +555,7 @@ public function testRespondToRequestFinalizeScopes(): void $finalizedScopes = [$fooScopeEntity]; $scopeRepositoryMock - ->expects($this->once()) + ->expects(self::once()) ->method('finalizeScopes') ->with($scopes, $grant->getIdentifier(), $client) ->willReturn($finalizedScopes); @@ -564,19 +565,19 @@ public function testRespondToRequestFinalizeScopes(): void ->with($client, $finalizedScopes) ->willReturn(new AccessTokenEntity()); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => 'zyxwvu', 'access_token_id' => 'abcdef', 'scopes' => ['foo', 'bar'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -588,7 +589,7 @@ public function testRespondToRequestFinalizeScopes(): void 'client_secret' => 'bar', 'refresh_token' => $encryptedOldRefreshToken, 'scope' => ['foo', 'bar'], - ]); + ]); $responseType = new StubResponseType(); @@ -615,26 +616,26 @@ public function testRevokedRefreshToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); - $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('isRefreshTokenRevoked') - ->will($this->onConsecutiveCalls(false, true)); - $refreshTokenRepositoryMock->expects($this->once())->method('revokeRefreshToken')->with($this->equalTo($refreshTokenId)); + ->will(self::onConsecutiveCalls(false, true)); + $refreshTokenRepositoryMock->expects(self::once())->method('revokeRefreshToken')->with(self::equalTo($refreshTokenId)); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => $refreshTokenId, 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -657,7 +658,7 @@ public function testRevokedRefreshToken(): void $grant->revokeRefreshTokens(true); $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); - Assert::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); + self::assertTrue($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); } public function testUnrevokedRefreshToken(): void @@ -680,25 +681,25 @@ public function testUnrevokedRefreshToken(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); - $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + $accessTokenRepositoryMock->expects(self::once())->method('persistNewAccessToken')->willReturnSelf(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('isRefreshTokenRevoked')->willReturn(false); - $refreshTokenRepositoryMock->expects($this->never())->method('revokeRefreshToken'); + $refreshTokenRepositoryMock->expects(self::never())->method('revokeRefreshToken'); - $oldRefreshToken = \json_encode( + $oldRefreshToken = json_encode( [ 'client_id' => 'foo', 'refresh_token_id' => $refreshTokenId, 'access_token_id' => 'abcdef', 'scopes' => ['foo'], 'user_id' => 123, - 'expire_time' => \time() + 3600, + 'expire_time' => time() + 3600, ] ); if ($oldRefreshToken === false) { - $this->fail('json_encode failed'); + self::fail('json_encode failed'); } $encryptedOldRefreshToken = $this->cryptStub->doEncrypt( @@ -720,6 +721,6 @@ public function testUnrevokedRefreshToken(): void $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); - Assert::assertFalse($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); + self::assertFalse($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); } } diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index 696b5cfe3..950ace46b 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -1,5 +1,7 @@ getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepository->method('getClientEntity')->willReturn($client); - $scopeEntity = new ScopeEntity; + $scopeEntity = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); @@ -44,7 +50,7 @@ public function testValidResponse(): void $accessRepositoryMock, $scopeRepositoryMock, 'file://' . __DIR__ . '/../Stubs/private.key', - \base64_encode(\random_bytes(36)), + base64_encode(random_bytes(36)), new StubResponseType() ); @@ -62,10 +68,10 @@ public function testValidResponse(): void $request, new Response(), function () { - return \func_get_args()[1]; + return func_get_args()[1]; } ); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); } public function testOAuthErrorResponse(): void @@ -78,7 +84,7 @@ public function testOAuthErrorResponse(): void $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), 'file://' . __DIR__ . '/../Stubs/private.key', - \base64_encode(\random_bytes(36)), + base64_encode(random_bytes(36)), new StubResponseType() ); @@ -96,11 +102,11 @@ public function testOAuthErrorResponse(): void $request, new Response(), function () { - return \func_get_args()[1]; + return func_get_args()[1]; } ); - $this->assertEquals(401, $response->getStatusCode()); + self::assertEquals(401, $response->getStatusCode()); } public function testOAuthErrorResponseRedirectUri(): void @@ -108,8 +114,8 @@ public function testOAuthErrorResponseRedirectUri(): void $exception = OAuthServerException::invalidScope('test', 'http://foo/bar'); $response = $exception->generateHttpResponse(new Response()); - $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals( + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( 'http://foo/bar?error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed', $response->getHeader('location')[0] ); @@ -120,8 +126,8 @@ public function testOAuthErrorResponseRedirectUriFragment(): void $exception = OAuthServerException::invalidScope('test', 'http://foo/bar'); $response = $exception->generateHttpResponse(new Response(), true); - $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals( + self::assertEquals(302, $response->getStatusCode()); + self::assertEquals( 'http://foo/bar#error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed', $response->getHeader('location')[0] ); diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 994d5b5d2..119d43ae0 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -1,5 +1,7 @@ withHeader('authorization', \sprintf('Bearer %s', $token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token)); $middleware = new ResourceServerMiddleware($server); $response = $middleware->__invoke( $request, new Response(), function () { - $this->assertEquals('test', \func_get_args()[0]->getAttribute('oauth_access_token_id')); + self::assertEquals('test', func_get_args()[0]->getAttribute('oauth_access_token_id')); - return \func_get_args()[1]; + return func_get_args()[1]; } ); - $this->assertEquals(200, $response->getStatusCode()); + self::assertEquals(200, $response->getStatusCode()); } public function testValidResponseExpiredToken(): void @@ -70,20 +75,20 @@ public function testValidResponseExpiredToken(): void $token = (string) $accessToken; - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token)); $middleware = new ResourceServerMiddleware($server); $response = $middleware->__invoke( $request, new Response(), function () { - $this->assertEquals('test', \func_get_args()[0]->getAttribute('oauth_access_token_id')); + self::assertEquals('test', func_get_args()[0]->getAttribute('oauth_access_token_id')); - return \func_get_args()[1]; + return func_get_args()[1]; } ); - $this->assertEquals(401, $response->getStatusCode()); + self::assertEquals(401, $response->getStatusCode()); } public function testErrorResponse(): void @@ -100,10 +105,10 @@ public function testErrorResponse(): void $request, new Response(), function () { - return \func_get_args()[1]; + return func_get_args()[1]; } ); - $this->assertEquals(401, $response->getStatusCode()); + self::assertEquals(401, $response->getStatusCode()); } } diff --git a/tests/PHPStan/AbstractGrantExtension.php b/tests/PHPStan/AbstractGrantExtension.php index 121648e13..f23062d5b 100644 --- a/tests/PHPStan/AbstractGrantExtension.php +++ b/tests/PHPStan/AbstractGrantExtension.php @@ -1,5 +1,6 @@ getName(), [ + return in_array($methodReflection->getName(), [ 'getRequestParameter', 'getQueryStringParameter', 'getCookieParameter', diff --git a/tests/RedirectUriValidators/RedirectUriValidatorTest.php b/tests/RedirectUriValidators/RedirectUriValidatorTest.php index 11e7a07e4..c648d7ab1 100644 --- a/tests/RedirectUriValidators/RedirectUriValidatorTest.php +++ b/tests/RedirectUriValidators/RedirectUriValidatorTest.php @@ -1,5 +1,7 @@ assertFalse( + self::assertFalse( $validator->validateRedirectUri($invalidRedirectUri), 'Non loopback URI must match in every part' ); @@ -31,7 +33,7 @@ public function testValidNonLoopbackUri(): void $validRedirectUri = 'https://example.com:8443/endpoint'; - $this->assertTrue( + self::assertTrue( $validator->validateRedirectUri($validRedirectUri), 'Redirect URI must be valid when matching in every part' ); @@ -43,7 +45,7 @@ public function testInvalidLoopbackUri(): void $invalidRedirectUri = 'http://127.0.0.1:8443/different/endpoint'; - $this->assertFalse( + self::assertFalse( $validator->validateRedirectUri($invalidRedirectUri), 'Valid loopback redirect URI can change only the port number' ); @@ -55,7 +57,7 @@ public function testValidLoopbackUri(): void $validRedirectUri = 'http://127.0.0.1:8080/endpoint'; - $this->assertTrue( + self::assertTrue( $validator->validateRedirectUri($validRedirectUri), 'Loopback redirect URI can change the port number' ); @@ -67,7 +69,7 @@ public function testValidIpv6LoopbackUri(): void $validRedirectUri = 'http://[::1]:8080/endpoint'; - $this->assertTrue( + self::assertTrue( $validator->validateRedirectUri($validRedirectUri), 'Loopback redirect URI can change the port number' ); @@ -77,7 +79,7 @@ public function testCanValidateUrn(): void { $validator = new RedirectUriValidator('urn:ietf:wg:oauth:2.0:oob'); - $this->assertTrue( + self::assertTrue( $validator->validateRedirectUri('urn:ietf:wg:oauth:2.0:oob'), 'An invalid pre-registered urn was provided' ); @@ -87,7 +89,7 @@ public function canValidateCustomSchemeHost(): void { $validator = new RedirectUriValidator('msal://redirect'); - $this->assertTrue( + self::assertTrue( $validator->validateRedirectUri('msal://redirect'), 'An invalid, pre-registered, custom scheme uri was provided' ); @@ -97,7 +99,7 @@ public function canValidateCustomSchemePath(): void { $validator = new RedirectUriValidator('com.example.app:/oauth2redirect/example-provider'); - $this->assertTrue( + self::assertTrue( $validator->validateRedirectUri('com.example.app:/oauth2redirect/example-provider'), 'An invalid, pre-registered, custom scheme uri was provided' ); diff --git a/tests/ResourceServerTest.php b/tests/ResourceServerTest.php index cd622c488..41ac2e854 100644 --- a/tests/ResourceServerTest.php +++ b/tests/ResourceServerTest.php @@ -1,5 +1,6 @@ validateAuthenticatedRequest(ServerRequestFactory::fromGlobals()); } catch (OAuthServerException $e) { - $this->assertEquals('Missing "Authorization" header', $e->getHint()); + self::assertEquals('Missing "Authorization" header', $e->getHint()); } } } diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 6c8c1fd03..7f0894936 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -1,5 +1,7 @@ setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -49,25 +56,24 @@ public function testGenerateHttpResponse(): void $response = $responseType->generateHttpResponse(new Response()); - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('no-cache', $response->getHeader('pragma')[0]); - $this->assertEquals('no-store', $response->getHeader('cache-control')[0]); - $this->assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals('no-cache', $response->getHeader('pragma')[0]); + self::assertEquals('no-store', $response->getHeader('cache-control')[0]); + self::assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); $response->getBody()->rewind(); - $json = \json_decode($response->getBody()->getContents()); - $this->assertEquals('Bearer', $json->token_type); - $this->assertObjectHasProperty('expires_in', $json); - $this->assertObjectHasProperty('access_token', $json); - $this->assertObjectHasProperty('refresh_token', $json); + $json = json_decode($response->getBody()->getContents()); + self::assertEquals('Bearer', $json->token_type); + self::assertObjectHasProperty('expires_in', $json); + self::assertObjectHasProperty('access_token', $json); + self::assertObjectHasProperty('refresh_token', $json); } public function testGenerateHttpResponseWithExtraParams(): void { $responseType = new BearerTokenResponseWithParams(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -92,28 +98,27 @@ public function testGenerateHttpResponseWithExtraParams(): void $response = $responseType->generateHttpResponse(new Response()); - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('no-cache', $response->getHeader('pragma')[0]); - $this->assertEquals('no-store', $response->getHeader('cache-control')[0]); - $this->assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); + self::assertEquals(200, $response->getStatusCode()); + self::assertEquals('no-cache', $response->getHeader('pragma')[0]); + self::assertEquals('no-store', $response->getHeader('cache-control')[0]); + self::assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); $response->getBody()->rewind(); - $json = \json_decode($response->getBody()->getContents()); - $this->assertEquals('Bearer', $json->token_type); - $this->assertObjectHasProperty('expires_in', $json); - $this->assertObjectHasProperty('access_token', $json); - $this->assertObjectHasProperty('refresh_token', $json); - - $this->assertObjectHasProperty('foo', $json); - $this->assertEquals('bar', $json->foo); + $json = json_decode($response->getBody()->getContents()); + self::assertEquals('Bearer', $json->token_type); + self::assertObjectHasProperty('expires_in', $json); + self::assertObjectHasProperty('access_token', $json); + self::assertObjectHasProperty('refresh_token', $json); + + self::assertObjectHasProperty('foo', $json); + self::assertEquals('bar', $json->foo); } public function testDetermineAccessTokenInHeaderValidToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -134,7 +139,7 @@ public function testDetermineAccessTokenInHeaderValidToken(): void $responseType->setRefreshToken($refreshToken); $response = $responseType->generateHttpResponse(new Response()); - $json = \json_decode((string) $response->getBody()); + $json = json_decode((string) $response->getBody()); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('isAccessTokenRevoked')->willReturn(false); @@ -142,14 +147,14 @@ public function testDetermineAccessTokenInHeaderValidToken(): void $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $json->access_token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); $request = $authorizationValidator->validateAuthorization($request); - $this->assertEquals('abcdef', $request->getAttribute('oauth_access_token_id')); - $this->assertEquals('clientName', $request->getAttribute('oauth_client_id')); - $this->assertEquals('123', $request->getAttribute('oauth_user_id')); - $this->assertEquals([], $request->getAttribute('oauth_scopes')); + self::assertEquals('abcdef', $request->getAttribute('oauth_access_token_id')); + self::assertEquals('clientName', $request->getAttribute('oauth_client_id')); + self::assertEquals('123', $request->getAttribute('oauth_user_id')); + self::assertEquals([], $request->getAttribute('oauth_scopes')); } public function testDetermineAccessTokenInHeaderInvalidJWT(): void @@ -158,7 +163,7 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -179,17 +184,17 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void $responseType->setRefreshToken($refreshToken); $response = $responseType->generateHttpResponse(new Response()); - $json = \json_decode((string) $response->getBody()); + $json = json_decode((string) $response->getBody()); $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $json->access_token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); try { $authorizationValidator->validateAuthorization($request); } catch (OAuthServerException $e) { - $this->assertEquals( + self::assertEquals( 'Access token could not be verified', $e->getHint() ); @@ -200,7 +205,7 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -221,7 +226,7 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void $responseType->setRefreshToken($refreshToken); $response = $responseType->generateHttpResponse(new Response()); - $json = \json_decode((string) $response->getBody()); + $json = json_decode((string) $response->getBody()); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('isAccessTokenRevoked')->willReturn(true); @@ -229,12 +234,12 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = (new ServerRequest())->withHeader('authorization', \sprintf('Bearer %s', $json->access_token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); try { $authorizationValidator->validateAuthorization($request); } catch (OAuthServerException $e) { - $this->assertEquals( + self::assertEquals( 'Access token has been revoked', $e->getHint() ); @@ -245,7 +250,7 @@ public function testDetermineAccessTokenInHeaderInvalidToken(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -257,7 +262,7 @@ public function testDetermineAccessTokenInHeaderInvalidToken(): void try { $authorizationValidator->validateAuthorization($request); } catch (OAuthServerException $e) { - $this->assertEquals( + self::assertEquals( 'The JWT string must have two dots', $e->getHint() ); @@ -268,7 +273,7 @@ public function testDetermineMissingBearerInHeader(): void { $responseType = new BearerTokenResponse(); $responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -280,7 +285,7 @@ public function testDetermineMissingBearerInHeader(): void try { $authorizationValidator->validateAuthorization($request); } catch (OAuthServerException $e) { - $this->assertEquals( + self::assertEquals( 'Error while decoding from JSON', $e->getHint() ); diff --git a/tests/ResponseTypes/BearerTokenResponseWithParams.php b/tests/ResponseTypes/BearerTokenResponseWithParams.php index 71c1665b2..2be1a3272 100644 --- a/tests/ResponseTypes/BearerTokenResponseWithParams.php +++ b/tests/ResponseTypes/BearerTokenResponseWithParams.php @@ -1,5 +1,7 @@ */ diff --git a/tests/Stubs/AccessTokenEntity.php b/tests/Stubs/AccessTokenEntity.php index 77a4d2231..339b29c8b 100644 --- a/tests/Stubs/AccessTokenEntity.php +++ b/tests/Stubs/AccessTokenEntity.php @@ -1,5 +1,7 @@ setEncryptionKey(\base64_encode(\random_bytes(36))); + $this->setEncryptionKey(base64_encode(random_bytes(36))); } public function getKey(): string|Key|null diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index 37c75fdcd..bd2f95f77 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -47,7 +47,7 @@ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL - ) { + ): ResponseTypeInterface { return $responseType; } @@ -86,7 +86,7 @@ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): v { } - public function setDefaultScope($scope): void + public function setDefaultScope(string $scope): void { } diff --git a/tests/Stubs/RefreshTokenEntity.php b/tests/Stubs/RefreshTokenEntity.php index f145b7065..9d6d79f27 100644 --- a/tests/Stubs/RefreshTokenEntity.php +++ b/tests/Stubs/RefreshTokenEntity.php @@ -1,5 +1,7 @@ getIdentifier(); } diff --git a/tests/Stubs/StubResponseType.php b/tests/Stubs/StubResponseType.php index f04d44395..9a00490d9 100644 --- a/tests/Stubs/StubResponseType.php +++ b/tests/Stubs/StubResponseType.php @@ -1,5 +1,7 @@ setIdentifier(123); + $this->setIdentifier('123'); } } diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 542fdc2b9..a7526c9ea 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -1,15 +1,27 @@ expectException(\LogicException::class); + $this->expectException(LogicException::class); new CryptKey('undefined file'); } @@ -19,34 +31,34 @@ public function testKeyCreation(): void $keyFile = __DIR__ . '/../Stubs/public.key'; $key = new CryptKey($keyFile, 'secret'); - $this->assertEquals('file://' . $keyFile, $key->getKeyPath()); - $this->assertEquals('secret', $key->getPassPhrase()); + self::assertEquals('file://' . $keyFile, $key->getKeyPath()); + self::assertEquals('secret', $key->getPassPhrase()); } public function testKeyString(): void { - $keyContent = \file_get_contents(__DIR__ . '/../Stubs/public.key'); + $keyContent = file_get_contents(__DIR__ . '/../Stubs/public.key'); - if (!\is_string($keyContent)) { - $this->fail('The public key stub is not a string'); + if (!is_string($keyContent)) { + self::fail('The public key stub is not a string'); } $key = new CryptKey($keyContent); - $this->assertEquals( + self::assertEquals( $keyContent, $key->getKeyContents() ); - $keyContent = \file_get_contents(__DIR__ . '/../Stubs/private.key.crlf'); + $keyContent = file_get_contents(__DIR__ . '/../Stubs/private.key.crlf'); - if (!\is_string($keyContent)) { - $this->fail('The private key (crlf) stub is not a string'); + if (!is_string($keyContent)) { + self::fail('The private key (crlf) stub is not a string'); } $key = new CryptKey($keyContent); - $this->assertEquals( + self::assertEquals( $keyContent, $key->getKeyContents() ); @@ -54,29 +66,29 @@ public function testKeyString(): void public function testUnsupportedKeyType(): void { - $this->expectException(\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Unable to read key'); try { // Create the keypair - $res = \openssl_pkey_new([ + $res = openssl_pkey_new([ 'digest_alg' => 'sha512', 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_DSA, ]); if ($res === false) { - $this->fail('The keypair was not created'); + self::fail('The keypair was not created'); } // Get private key - \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); + openssl_pkey_export($res, $keyContent, 'mystrongpassword'); $path = self::generateKeyPath($keyContent); new CryptKey($keyContent, 'mystrongpassword'); } finally { if (isset($path)) { - @\unlink($path); + @unlink($path); } } } @@ -85,25 +97,25 @@ public function testECKeyType(): void { try { // Create the keypair - $res = \openssl_pkey_new([ + $res = openssl_pkey_new([ 'digest_alg' => 'sha512', 'curve_name' => 'prime256v1', 'private_key_type' => OPENSSL_KEYTYPE_EC, ]); if ($res === false) { - $this->fail('The keypair was not created'); + self::fail('The keypair was not created'); } // Get private key - \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); + openssl_pkey_export($res, $keyContent, 'mystrongpassword'); $key = new CryptKey($keyContent, 'mystrongpassword'); - $this->assertEquals('', $key->getKeyPath()); - $this->assertEquals('mystrongpassword', $key->getPassPhrase()); - } catch (\Throwable $e) { - $this->fail('The EC key was not created'); + self::assertEquals('', $key->getKeyPath()); + self::assertEquals('mystrongpassword', $key->getPassPhrase()); + } catch (Throwable $e) { + self::fail('The EC key was not created'); } } @@ -111,35 +123,33 @@ public function testRSAKeyType(): void { try { // Create the keypair - $res = \openssl_pkey_new([ + $res = openssl_pkey_new([ 'digest_alg' => 'sha512', 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ]); if ($res === false) { - $this->fail('The keypair was not created'); + self::fail('The keypair was not created'); } // Get private key - \openssl_pkey_export($res, $keyContent, 'mystrongpassword'); + openssl_pkey_export($res, $keyContent, 'mystrongpassword'); $key = new CryptKey($keyContent, 'mystrongpassword'); - $this->assertEquals('', $key->getKeyPath()); - $this->assertEquals('mystrongpassword', $key->getPassPhrase()); - } catch (\Throwable $e) { - $this->fail('The RSA key was not created'); + self::assertEquals('', $key->getKeyPath()); + self::assertEquals('mystrongpassword', $key->getPassPhrase()); + } catch (Throwable $e) { + self::fail('The RSA key was not created'); } } /** - * @param string $keyContent * - * @return string */ - private static function generateKeyPath($keyContent) + private static function generateKeyPath(string $keyContent): string { - return 'file://' . \sys_get_temp_dir() . '/' . \sha1($keyContent) . '.key'; + return 'file://' . sys_get_temp_dir() . '/' . sha1($keyContent) . '.key'; } } diff --git a/tests/Utils/CryptTraitTest.php b/tests/Utils/CryptTraitTest.php index 912c4e568..b49b0e9e2 100644 --- a/tests/Utils/CryptTraitTest.php +++ b/tests/Utils/CryptTraitTest.php @@ -1,11 +1,16 @@ cryptStub->setEncryptionKey(\base64_encode(\random_bytes(36))); + $this->cryptStub->setEncryptionKey(base64_encode(random_bytes(36))); $this->encryptDecrypt(); } @@ -35,7 +40,7 @@ private function encryptDecrypt(): void $encrypted = $this->cryptStub->doEncrypt($payload); $plainText = $this->cryptStub->doDecrypt($encrypted); - $this->assertNotEquals($payload, $encrypted); - $this->assertEquals($payload, $plainText); + self::assertNotEquals($payload, $encrypted); + self::assertEquals($payload, $plainText); } } From 8582e358d365a20dcd595fd548d98afd6c906d67 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 28 Sep 2023 12:45:45 +0100 Subject: [PATCH 122/212] Fix tests --- src/Grant/ImplicitGrant.php | 8 ++++---- src/Grant/PasswordGrant.php | 2 +- src/Grant/RefreshTokenGrant.php | 2 +- tests/Grant/AuthCodeGrantTest.php | 12 ++++++++++-- tests/Grant/ClientCredentialsGrantTest.php | 1 + tests/Grant/ImplicitGrantTest.php | 14 ++++++++++++-- tests/Grant/PasswordGrantTest.php | 3 +++ tests/Grant/RefreshTokenGrantTest.php | 12 ++++++++++++ .../AuthorizationServerMiddlewareTest.php | 1 + 9 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 124915ceb..2fe7b88f9 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -69,7 +69,7 @@ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refre /** * {@inheritdoc} */ - public function canRespondToAccessTokenRequest(ServerRequestInterface $request) + public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool { return false; } @@ -99,7 +99,7 @@ public function respondToAccessTokenRequest( /** * {@inheritdoc} */ - public function canRespondToAuthorizationRequest(ServerRequestInterface $request) + public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool { return ( isset($request->getQueryParams()['response_type']) @@ -111,7 +111,7 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request /** * {@inheritdoc} */ - public function validateAuthorizationRequest(ServerRequestInterface $request) + public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface { $clientId = $this->getQueryStringParameter( 'client_id', @@ -165,7 +165,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request) /** * {@inheritdoc} */ - public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest) + public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): ResponseTypeInterface { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index e492d1e61..9826ca245 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -119,7 +119,7 @@ protected function validateUser(ServerRequestInterface $request, ClientEntityInt /** * {@inheritdoc} */ - public function getIdentifier() + public function getIdentifier(): string { return 'password'; } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 8cf4b3272..5beb3ccf6 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -137,7 +137,7 @@ protected function validateOldRefreshToken(ServerRequestInterface $request, stri /** * {@inheritdoc} */ - public function getIdentifier() + public function getIdentifier(): string { return 'refresh_token'; } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index efeb6ba59..b84980367 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1837,9 +1837,13 @@ public function testAuthCodeRepositoryUniqueConstraintCheck(): void public function testAuthCodeRepositoryFailToPersist(): void { + $client = new ClientEntity(); + + $client->setRedirectUri('http://foo/bar'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); @@ -1862,9 +1866,13 @@ public function testAuthCodeRepositoryFailToPersist(): void public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop(): void { + $client = new ClientEntity(); + + $client->setRedirectUri('http://foo/bar'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index e0b67e755..fd621bea6 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -35,6 +35,7 @@ public function testRespondToRequest(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 64d00de0d..201e036d6 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -307,9 +307,14 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void public function testAccessTokenRepositoryFailToPersist(): void { + $client = new ClientEntity(); + + $client->setRedirectUri('https://foo/bar'); + $authRequest = new AuthorizationRequest(); + $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); @@ -334,9 +339,14 @@ public function testAccessTokenRepositoryFailToPersist(): void public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop(): void { + $client = new ClientEntity(); + + $client->setRedirectUri('https://foo/bar'); + $authRequest = new AuthorizationRequest(); + $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 42bdc8f95..3439d00c4 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -44,6 +44,7 @@ public function testRespondToRequest(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -89,6 +90,7 @@ public function testRespondToRequestNullRefreshToken(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -190,6 +192,7 @@ public function testRespondToRequestBadCredentials(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 0f8e243b5..20b530192 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -53,6 +53,7 @@ public function testRespondToRequest(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeEntity = new ScopeEntity(); $scopeEntity->setIdentifier('foo'); @@ -116,6 +117,7 @@ public function testRespondToRequestNullRefreshToken(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeEntity = new ScopeEntity(); $scopeEntity->setIdentifier('foo'); @@ -179,6 +181,7 @@ public function testRespondToReducedScopes(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -242,6 +245,7 @@ public function testRespondToUnexpectedScope(): void $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(); @@ -303,6 +307,7 @@ public function testRespondToRequestMissingOldToken(): void $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(); @@ -334,6 +339,7 @@ public function testRespondToRequestInvalidOldToken(): void $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(); @@ -368,6 +374,7 @@ public function testRespondToRequestClientMismatch(): void $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(); @@ -422,6 +429,7 @@ public function testRespondToRequestExpiredToken(): void $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(); @@ -473,6 +481,7 @@ public function testRespondToRequestRevokedToken(): void $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(); @@ -526,6 +535,7 @@ public function testRespondToRequestFinalizeScopes(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $fooScopeEntity = new ScopeEntity(); $fooScopeEntity->setIdentifier('foo'); @@ -606,6 +616,7 @@ public function testRevokedRefreshToken(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeEntity = new ScopeEntity(); $scopeEntity->setIdentifier('foo'); @@ -671,6 +682,7 @@ public function testUnrevokedRefreshToken(): void $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $scopeEntity = new ScopeEntity(); $scopeEntity->setIdentifier('foo'); diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index 950ace46b..3ba692ce2 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -36,6 +36,7 @@ public function testValidResponse(): void $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepository->method('getClientEntity')->willReturn($client); + $clientRepository->method('validateClient')->willReturn(true); $scopeEntity = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); From df850f8504b8d695be396a04d55557e97dd8628c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 1 Oct 2023 23:37:49 +0100 Subject: [PATCH 123/212] Fix tests --- .../BearerTokenValidator.php | 15 ++---- src/CryptKey.php | 12 ++--- src/CryptTrait.php | 5 +- src/Entities/Traits/AccessTokenTrait.php | 10 +--- src/Entities/Traits/AuthCodeTrait.php | 5 +- src/Entities/Traits/ClientTrait.php | 12 ++--- src/Entities/Traits/TokenEntityTrait.php | 17 ++----- src/Exception/OAuthServerException.php | 25 ++-------- src/Grant/AbstractGrant.php | 50 ++++--------------- src/Grant/AuthCodeGrant.php | 12 ++--- src/Grant/ImplicitGrant.php | 10 +--- .../AuthorizationServerMiddleware.php | 5 +- src/Middleware/ResourceServerMiddleware.php | 5 +- src/RequestAccessTokenEvent.php | 5 +- src/RequestEvent.php | 5 +- src/RequestRefreshTokenEvent.php | 5 +- src/RequestTypes/AuthorizationRequest.php | 32 +++++------- src/ResourceServer.php | 15 ++---- src/ResponseTypes/AbstractResponseType.php | 19 ++----- tests/AuthorizationServerTest.php | 9 ++-- tests/Grant/AbstractGrantTest.php | 4 +- tests/Grant/AuthCodeGrantTest.php | 14 ++++-- tests/Grant/PasswordGrantTest.php | 5 ++ tests/Grant/RefreshTokenGrantTest.php | 7 ++- tests/Stubs/StubResponseType.php | 2 +- 25 files changed, 88 insertions(+), 217 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index e7e32700d..8f781824a 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -39,20 +39,11 @@ class BearerTokenValidator implements AuthorizationValidatorInterface { use CryptTrait; - /** - * @var AccessTokenRepositoryInterface - */ - private $accessTokenRepository; + private AccessTokenRepositoryInterface $accessTokenRepository; - /** - * @var CryptKeyInterface - */ - protected $publicKey; + protected CryptKeyInterface $publicKey; - /** - * @var Configuration - */ - private $jwtConfiguration; + private Configuration $jwtConfiguration; /** */ diff --git a/src/CryptKey.php b/src/CryptKey.php index f0a4464ae..2e6b08563 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -37,17 +37,11 @@ class CryptKey implements CryptKeyInterface /** * @var string Key contents */ - protected $keyContents; + protected string $keyContents; - /** - * @var string - */ - protected $keyPath; + protected string $keyPath; - /** - * @var null|string - */ - protected $passPhrase; + protected ?string $passPhrase = null; /** */ diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 6b541a9c3..2119bc34b 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -23,10 +23,7 @@ trait CryptTrait { - /** - * @var string|Key|null - */ - protected $encryptionKey; + protected string|Key|null $encryptionKey = null; /** * Encrypt data with encryptionKey. diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 48f6f8eca..69433fad1 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -24,15 +24,9 @@ trait AccessTokenTrait { - /** - * @var CryptKeyInterface - */ - private $privateKey; + private CryptKeyInterface $privateKey; - /** - * @var Configuration - */ - private $jwtConfiguration; + private Configuration $jwtConfiguration; /** * Set the private key used to encrypt this access token. diff --git a/src/Entities/Traits/AuthCodeTrait.php b/src/Entities/Traits/AuthCodeTrait.php index edc667614..403500b62 100644 --- a/src/Entities/Traits/AuthCodeTrait.php +++ b/src/Entities/Traits/AuthCodeTrait.php @@ -14,10 +14,7 @@ trait AuthCodeTrait { - /** - * @var null|string - */ - protected $redirectUri; + protected ?string $redirectUri = null; public function getRedirectUri(): string|null { diff --git a/src/Entities/Traits/ClientTrait.php b/src/Entities/Traits/ClientTrait.php index c779b15a9..5ae6456b8 100644 --- a/src/Entities/Traits/ClientTrait.php +++ b/src/Entities/Traits/ClientTrait.php @@ -14,20 +14,14 @@ trait ClientTrait { - /** - * @var string - */ - protected $name; + protected string $name; /** * @var string|string[] */ - protected $redirectUri; + protected string|array $redirectUri; - /** - * @var bool - */ - protected $isConfidential = false; + protected bool $isConfidential = false; /** * Get the client's name. diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index d4a1aabe0..73c1d152c 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -23,22 +23,13 @@ trait TokenEntityTrait /** * @var ScopeEntityInterface[] */ - protected $scopes = []; + protected array $scopes = []; - /** - * @var DateTimeImmutable - */ - protected $expiryDateTime; + protected DateTimeImmutable $expiryDateTime; - /** - * @var string|int|null - */ - protected $userIdentifier; + protected string|int|null $userIdentifier = null; - /** - * @var ClientEntityInterface - */ - protected $client; + protected ClientEntityInterface $client; /** * Associate a scope with the token. diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index f033fdeb0..3bae5b3a8 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -25,35 +25,20 @@ class OAuthServerException extends Exception { - /** - * @var int - */ - private $httpStatusCode; + private int $httpStatusCode; - /** - * @var string - */ - private $errorType; + private string $errorType; - /** - * @var null|string - */ - private $hint; + private ?string $hint = null; - /** - * @var null|string - */ - private $redirectUri; + private ?string $redirectUri = null; /** * @var array */ private array $payload; - /** - * @var ServerRequestInterface - */ - private $serverRequest; + private ServerRequestInterface $serverRequest; /** * Throw a new exception. diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index b6d32cefe..5c7103800 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -67,55 +67,25 @@ abstract class AbstractGrant implements GrantTypeInterface private const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; - /** - * @var ClientRepositoryInterface - */ - protected $clientRepository; + protected ClientRepositoryInterface $clientRepository; - /** - * @var AccessTokenRepositoryInterface - */ - protected $accessTokenRepository; + protected AccessTokenRepositoryInterface $accessTokenRepository; - /** - * @var ScopeRepositoryInterface - */ - protected $scopeRepository; + protected ScopeRepositoryInterface $scopeRepository; - /** - * @var AuthCodeRepositoryInterface - */ - protected $authCodeRepository; + protected AuthCodeRepositoryInterface $authCodeRepository; - /** - * @var RefreshTokenRepositoryInterface - */ - protected $refreshTokenRepository; + protected RefreshTokenRepositoryInterface $refreshTokenRepository; - /** - * @var UserRepositoryInterface - */ - protected $userRepository; + protected UserRepositoryInterface $userRepository; - /** - * @var DateInterval - */ - protected $refreshTokenTTL; + protected DateInterval $refreshTokenTTL; - /** - * @var CryptKeyInterface - */ - protected $privateKey; + protected CryptKeyInterface $privateKey; - /** - * @var string - */ - protected $defaultScope; + protected string $defaultScope; - /** - * @var bool - */ - protected $revokeRefreshTokens; + protected bool $revokeRefreshTokens = true; /** */ diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index d940c4038..e9d14e36d 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -51,20 +51,14 @@ class AuthCodeGrant extends AbstractAuthorizeGrant { - /** - * @var DateInterval - */ - private $authCodeTTL; + private DateInterval $authCodeTTL; - /** - * @var bool - */ - private $requireCodeChallengeForPublicClients = true; + private bool $requireCodeChallengeForPublicClients = true; /** * @var CodeChallengeVerifierInterface[] */ - private $codeChallengeVerifiers = []; + private array $codeChallengeVerifiers = []; /** * diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 2fe7b88f9..226621d13 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -30,15 +30,9 @@ class ImplicitGrant extends AbstractAuthorizeGrant { - /** - * @var DateInterval - */ - private $accessTokenTTL; + private DateInterval $accessTokenTTL; - /** - * @var string - */ - private $queryDelimiter; + private string $queryDelimiter; /** */ diff --git a/src/Middleware/AuthorizationServerMiddleware.php b/src/Middleware/AuthorizationServerMiddleware.php index 913bb9afd..95beef721 100644 --- a/src/Middleware/AuthorizationServerMiddleware.php +++ b/src/Middleware/AuthorizationServerMiddleware.php @@ -20,10 +20,7 @@ class AuthorizationServerMiddleware { - /** - * @var AuthorizationServer - */ - private $server; + private AuthorizationServer $server; /** */ diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 61c8dcab0..0607c3e09 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -20,10 +20,7 @@ class ResourceServerMiddleware { - /** - * @var ResourceServer - */ - private $server; + private ResourceServer $server; /** */ diff --git a/src/RequestAccessTokenEvent.php b/src/RequestAccessTokenEvent.php index fe723857f..007252f19 100644 --- a/src/RequestAccessTokenEvent.php +++ b/src/RequestAccessTokenEvent.php @@ -17,10 +17,7 @@ class RequestAccessTokenEvent extends RequestEvent { - /** - * @var AccessTokenEntityInterface - */ - private $accessToken; + private AccessTokenEntityInterface $accessToken; /** */ diff --git a/src/RequestEvent.php b/src/RequestEvent.php index 11c5bd0fc..5c8d09784 100644 --- a/src/RequestEvent.php +++ b/src/RequestEvent.php @@ -24,10 +24,7 @@ class RequestEvent extends Event public const REFRESH_TOKEN_ISSUED = 'refresh_token.issued'; public const ACCESS_TOKEN_ISSUED = 'access_token.issued'; - /** - * @var ServerRequestInterface - */ - private $request; + private ServerRequestInterface $request; /** * RequestEvent constructor. diff --git a/src/RequestRefreshTokenEvent.php b/src/RequestRefreshTokenEvent.php index d5348b961..af67efe3e 100644 --- a/src/RequestRefreshTokenEvent.php +++ b/src/RequestRefreshTokenEvent.php @@ -17,10 +17,7 @@ class RequestRefreshTokenEvent extends RequestEvent { - /** - * @var RefreshTokenEntityInterface - */ - private $refreshToken; + private RefreshTokenEntityInterface $refreshToken; /** */ diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index 8231ebbfb..07ece0f10 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -21,65 +21,57 @@ class AuthorizationRequest implements AuthorizationRequestInterface /** * The grant type identifier * - * @var string */ - protected $grantTypeId; + protected string $grantTypeId; /** * The client identifier * - * @var ClientEntityInterface */ - protected $client; + protected ClientEntityInterface $client; /** * The user identifier * - * @var UserEntityInterface */ - protected $user; + protected UserEntityInterface $user; /** * An array of scope identifiers * * @var ScopeEntityInterface[] */ - protected $scopes = []; + protected array $scopes = []; /** * Has the user authorized the authorization request * - * @var bool */ - protected $authorizationApproved = false; + protected bool $authorizationApproved = false; /** * The redirect URI used in the request * - * @var string|null */ - protected $redirectUri; + protected ?string $redirectUri = null; /** * The state parameter on the authorization request * - * @var string|null */ - protected $state; + protected ?string $state = null; /** * The code challenge (if provided) * - * @var string */ - protected $codeChallenge; + protected string $codeChallenge; /** * The code challenge method (if provided) * - * @var string */ - protected $codeChallengeMethod; + protected string $codeChallengeMethod; public function getGrantTypeId(): string { @@ -103,7 +95,7 @@ public function setClient(ClientEntityInterface $client): void public function getUser(): ?UserEntityInterface { - return $this->user; + return $this->user ?? null; } public function setUser(UserEntityInterface $user): void @@ -159,7 +151,7 @@ public function setState(string $state): void public function getCodeChallenge(): ?string { - return $this->codeChallenge; + return $this->codeChallenge ?? null; } public function setCodeChallenge(string $codeChallenge): void @@ -169,7 +161,7 @@ public function setCodeChallenge(string $codeChallenge): void public function getCodeChallengeMethod(): ?string { - return $this->codeChallengeMethod; + return $this->codeChallengeMethod ?? null; } public function setCodeChallengeMethod(string $codeChallengeMethod): void diff --git a/src/ResourceServer.php b/src/ResourceServer.php index 844d39be5..95833be4f 100644 --- a/src/ResourceServer.php +++ b/src/ResourceServer.php @@ -20,20 +20,11 @@ class ResourceServer { - /** - * @var AccessTokenRepositoryInterface - */ - private $accessTokenRepository; + private AccessTokenRepositoryInterface $accessTokenRepository; - /** - * @var CryptKeyInterface - */ - private $publicKey; + private CryptKeyInterface $publicKey; - /** - * @var null|AuthorizationValidatorInterface - */ - private $authorizationValidator; + private ?AuthorizationValidatorInterface $authorizationValidator = null; /** * New server instance. diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index 1144b59d5..2af00e224 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -23,20 +23,11 @@ abstract class AbstractResponseType implements ResponseTypeInterface { use CryptTrait; - /** - * @var AccessTokenEntityInterface - */ - protected $accessToken; - - /** - * @var RefreshTokenEntityInterface - */ - protected $refreshToken; - - /** - * @var CryptKeyInterface - */ - protected $privateKey; + protected AccessTokenEntityInterface $accessToken; + + protected RefreshTokenEntityInterface $refreshToken; + + protected CryptKeyInterface $privateKey; public function setAccessToken(AccessTokenEntityInterface $accessToken): void { diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 8b35eea6c..fafdd8aae 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -183,7 +183,10 @@ public function testMultipleRequestsGetDifferentResponseTypeInstances(): void $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; $responseTypePrototype = new class extends BearerTokenResponse { - public function getPrivateKey(): CryptKeyInterface|null + protected CryptKeyInterface $privateKey; + protected Key|string|null $encryptionKey = null; + + public function getPrivateKey(): CryptKeyInterface { return $this->privateKey; } @@ -212,10 +215,6 @@ public function getEncryptionKey(): Key|string|null $responseTypeA = $method->invoke($server); $responseTypeB = $method->invoke($server); - // prototype should not get changed - self::assertNull($responseTypePrototype->getPrivateKey()); - self::assertNull($responseTypePrototype->getEncryptionKey()); - // generated instances should have keys setup self::assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath()); self::assertSame($encryptionKey, $responseTypeA->getEncryptionKey()); diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 06856f0e3..37ff51c4d 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -183,7 +183,9 @@ public function testValidateClientConfidential(): void $result = $validateClientMethod->invoke($grantMock, $serverRequest, true, true); self::assertEquals($client, $result); } -public function testValidateClientMissingClientId(): void { $client = new ClientEntity(); + public function testValidateClientMissingClientId(): void + { + $client = new ClientEntity(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index b84980367..536379274 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -39,10 +39,7 @@ class AuthCodeGrantTest extends TestCase { private const DEFAULT_SCOPE = 'basic'; - /** - * @var CryptTraitStub - */ - protected $cryptStub; + protected CryptTraitStub $cryptStub; private const CODE_VERIFIER = 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk'; @@ -256,6 +253,7 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooSho $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -263,7 +261,9 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooSho new DateInterval('PT10M') ); + $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $request = (new ServerRequest())->withQueryParams([ 'response_type' => 'code', @@ -283,6 +283,7 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLon $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -291,6 +292,8 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLon ); $grant->setClientRepository($clientRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setScopeRepository($scopeRepositoryMock); $request = (new ServerRequest())->withQueryParams([ 'response_type' => 'code', @@ -310,6 +313,7 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters() $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), @@ -318,6 +322,8 @@ public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters() ); $grant->setClientRepository($clientRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setScopeRepository($scopeRepositoryMock); $request = (new ServerRequest())->withQueryParams([ 'response_type' => 'code', diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 3439d00c4..494e1d28c 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -201,9 +201,14 @@ public function testRespondToRequestBadCredentials(): 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 20b530192..747ba5ead 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -27,10 +27,7 @@ class RefreshTokenGrantTest extends TestCase { - /** - * @var CryptTraitStub - */ - protected $cryptStub; + protected CryptTraitStub $cryptStub; public function setUp(): void { @@ -731,6 +728,8 @@ public function testUnrevokedRefreshToken(): void $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $grant->revokeRefreshTokens(false); + $grant->respondToAccessTokenRequest($serverRequest, new StubResponseType(), new DateInterval('PT5M')); self::assertFalse($refreshTokenRepositoryMock->isRefreshTokenRevoked($refreshTokenId)); diff --git a/tests/Stubs/StubResponseType.php b/tests/Stubs/StubResponseType.php index 9a00490d9..6acc1562b 100644 --- a/tests/Stubs/StubResponseType.php +++ b/tests/Stubs/StubResponseType.php @@ -25,7 +25,7 @@ public function getAccessToken(): AccessTokenEntityInterface public function getRefreshToken(): RefreshTokenEntityInterface|null { - return $this->refreshToken; + return $this->refreshToken ?? null; } public function setAccessToken(AccessTokenEntityInterface $accessToken): void From 953316901d338dc0803705cc6f03fb9ecba8a4ad Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 2 Oct 2023 11:44:58 +0100 Subject: [PATCH 124/212] PHP CS fixes --- tests/AuthorizationServerTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index fafdd8aae..1f6ea7d59 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -24,11 +24,13 @@ use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; +use LeagueTests\Stubs\GrantType; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; use ReflectionClass; +use Psr\Http\Message\ServerRequestInterface; use function base64_encode; use function chmod; @@ -47,7 +49,6 @@ public function setUp(): void chmod(__DIR__ . '/Stubs/private.key.crlf', 0600); } - /* public function testGrantTypeGetsEnabled() { $server = new AuthorizationServer( @@ -64,7 +65,6 @@ public function testGrantTypeGetsEnabled() $authRequest = $server->validateAuthorizationRequest($this->createMock(ServerRequestInterface::class)); self::assertSame(GrantType::class, $authRequest->getGrantTypeId()); } - */ public function testRespondToRequestInvalidGrantType(): void { From 6720d77487acf96a03692e28ec2328526c315c06 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 2 Oct 2023 11:53:29 +0100 Subject: [PATCH 125/212] Remove empty comments --- .../AuthorizationValidatorInterface.php | 1 - .../BearerTokenValidator.php | 2 -- src/CryptKey.php | 2 -- src/Entities/Traits/AccessTokenTrait.php | 8 -------- src/Entities/Traits/EntityTrait.php | 2 -- src/Entities/Traits/ScopeTrait.php | 2 -- src/Exception/OAuthServerException.php | 4 ---- ...kenIdentifierConstraintViolationException.php | 2 -- src/Grant/AbstractAuthorizeGrant.php | 2 -- src/Grant/AbstractGrant.php | 16 ---------------- src/Grant/ImplicitGrant.php | 2 -- src/Grant/PasswordGrant.php | 2 -- src/Grant/RefreshTokenGrant.php | 2 -- src/Middleware/AuthorizationServerMiddleware.php | 5 ----- src/Middleware/ResourceServerMiddleware.php | 5 ----- src/RequestAccessTokenEvent.php | 2 -- src/RequestRefreshTokenEvent.php | 2 -- src/ResourceServer.php | 2 -- tests/AuthorizationServerTest.php | 6 +++--- tests/Utils/CryptKeyTest.php | 3 --- 20 files changed, 3 insertions(+), 69 deletions(-) diff --git a/src/AuthorizationValidators/AuthorizationValidatorInterface.php b/src/AuthorizationValidators/AuthorizationValidatorInterface.php index 275853409..05d9fb88c 100644 --- a/src/AuthorizationValidators/AuthorizationValidatorInterface.php +++ b/src/AuthorizationValidators/AuthorizationValidatorInterface.php @@ -19,7 +19,6 @@ interface AuthorizationValidatorInterface /** * Determine the access token in the authorization header and append OAUth * properties to the request as attributes. - * */ public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface; } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 8f781824a..c3e9ed719 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -45,8 +45,6 @@ class BearerTokenValidator implements AuthorizationValidatorInterface private Configuration $jwtConfiguration; - /** - */ public function __construct(AccessTokenRepositoryInterface $accessTokenRepository) { $this->accessTokenRepository = $accessTokenRepository; diff --git a/src/CryptKey.php b/src/CryptKey.php index 2e6b08563..cca82adc2 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -43,8 +43,6 @@ class CryptKey implements CryptKeyInterface protected ?string $passPhrase = null; - /** - */ public function __construct(string $keyPath, ?string $passPhrase = null, bool $keyPermissionsCheck = true) { $this->passPhrase = $passPhrase; diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 69433fad1..a3bce4804 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -81,16 +81,10 @@ public function __toString(): string return $this->convertToJWT()->toString(); } - /** - */ abstract public function getClient(): ClientEntityInterface; - /** - */ abstract public function getExpiryDateTime(): DateTimeImmutable; - /** - */ abstract public function getUserIdentifier(): string|int|null; /** @@ -98,7 +92,5 @@ abstract public function getUserIdentifier(): string|int|null; */ abstract public function getScopes(): array; - /** - */ abstract public function getIdentifier(): string; } diff --git a/src/Entities/Traits/EntityTrait.php b/src/Entities/Traits/EntityTrait.php index 0657a49b8..8b3db91d4 100644 --- a/src/Entities/Traits/EntityTrait.php +++ b/src/Entities/Traits/EntityTrait.php @@ -21,8 +21,6 @@ public function getIdentifier(): string return $this->identifier; } - /** - */ public function setIdentifier(mixed $identifier): void { $this->identifier = $identifier; diff --git a/src/Entities/Traits/ScopeTrait.php b/src/Entities/Traits/ScopeTrait.php index aeba339de..790fb02f3 100644 --- a/src/Entities/Traits/ScopeTrait.php +++ b/src/Entities/Traits/ScopeTrait.php @@ -23,7 +23,5 @@ public function jsonSerialize(): string return $this->getIdentifier(); } - /** - */ abstract public function getIdentifier(): string; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 3bae5b3a8..29c195359 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -249,8 +249,6 @@ public static function invalidGrant(string $hint = ''): static ); } - /** - */ public function getErrorType(): string { return $this->errorType; @@ -352,8 +350,6 @@ public function getHttpStatusCode(): int return $this->httpStatusCode; } - /** - */ public function getHint(): ?string { return $this->hint; diff --git a/src/Exception/UniqueTokenIdentifierConstraintViolationException.php b/src/Exception/UniqueTokenIdentifierConstraintViolationException.php index 21f9a28ab..4c2f281f6 100644 --- a/src/Exception/UniqueTokenIdentifierConstraintViolationException.php +++ b/src/Exception/UniqueTokenIdentifierConstraintViolationException.php @@ -14,8 +14,6 @@ class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException { - /** - */ public static function create(): UniqueTokenIdentifierConstraintViolationException { $errorMessage = 'Could not create unique access token identifier'; diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index d9f2d3f3e..2bcf27242 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -33,8 +33,6 @@ public function makeRedirectUri(string $uri, array $params = [], string $queryDe return $uri . http_build_query($params); } - /** - */ protected function createAuthorizationRequest(): AuthorizationRequestInterface { return new AuthorizationRequest(); diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 5c7103800..595f5a097 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -87,43 +87,31 @@ abstract class AbstractGrant implements GrantTypeInterface protected bool $revokeRefreshTokens = true; - /** - */ public function setClientRepository(ClientRepositoryInterface $clientRepository): void { $this->clientRepository = $clientRepository; } - /** - */ public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void { $this->accessTokenRepository = $accessTokenRepository; } - /** - */ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void { $this->scopeRepository = $scopeRepository; } - /** - */ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void { $this->refreshTokenRepository = $refreshTokenRepository; } - /** - */ public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository): void { $this->authCodeRepository = $authCodeRepository; } - /** - */ public function setUserRepository(UserRepositoryInterface $userRepository): void { $this->userRepository = $userRepository; @@ -146,15 +134,11 @@ public function setPrivateKey(CryptKeyInterface $key): void $this->privateKey = $key; } - /** - */ public function setDefaultScope(string $scope): void { $this->defaultScope = $scope; } - /** - */ public function revokeRefreshTokens(bool $revokeRefreshTokens): void { $this->revokeRefreshTokens = $revokeRefreshTokens; diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 226621d13..d071adc40 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -34,8 +34,6 @@ class ImplicitGrant extends AbstractAuthorizeGrant private string $queryDelimiter; - /** - */ public function __construct(DateInterval $accessTokenTTL, string $queryDelimiter = '#') { $this->accessTokenTTL = $accessTokenTTL; diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 9826ca245..1a1759aa0 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -33,8 +33,6 @@ */ class PasswordGrant extends AbstractGrant { - /** - */ public function __construct( UserRepositoryInterface $userRepository, RefreshTokenRepositoryInterface $refreshTokenRepository diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 5beb3ccf6..4cb8ab475 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -35,8 +35,6 @@ */ class RefreshTokenGrant extends AbstractGrant { - /** - */ public function __construct(RefreshTokenRepositoryInterface $refreshTokenRepository) { $this->setRefreshTokenRepository($refreshTokenRepository); diff --git a/src/Middleware/AuthorizationServerMiddleware.php b/src/Middleware/AuthorizationServerMiddleware.php index 95beef721..bf5387c71 100644 --- a/src/Middleware/AuthorizationServerMiddleware.php +++ b/src/Middleware/AuthorizationServerMiddleware.php @@ -22,16 +22,11 @@ class AuthorizationServerMiddleware { private AuthorizationServer $server; - /** - */ public function __construct(AuthorizationServer $server) { $this->server = $server; } - /** - * - */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface { try { diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 0607c3e09..4bed11c66 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -22,16 +22,11 @@ class ResourceServerMiddleware { private ResourceServer $server; - /** - */ public function __construct(ResourceServer $server) { $this->server = $server; } - /** - * - */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface { try { diff --git a/src/RequestAccessTokenEvent.php b/src/RequestAccessTokenEvent.php index 007252f19..ddffa4636 100644 --- a/src/RequestAccessTokenEvent.php +++ b/src/RequestAccessTokenEvent.php @@ -19,8 +19,6 @@ class RequestAccessTokenEvent extends RequestEvent { private AccessTokenEntityInterface $accessToken; - /** - */ public function __construct(string $name, ServerRequestInterface $request, AccessTokenEntityInterface $accessToken) { parent::__construct($name, $request); diff --git a/src/RequestRefreshTokenEvent.php b/src/RequestRefreshTokenEvent.php index af67efe3e..28802951f 100644 --- a/src/RequestRefreshTokenEvent.php +++ b/src/RequestRefreshTokenEvent.php @@ -19,8 +19,6 @@ class RequestRefreshTokenEvent extends RequestEvent { private RefreshTokenEntityInterface $refreshToken; - /** - */ public function __construct(string $name, ServerRequestInterface $request, RefreshTokenEntityInterface $refreshToken) { parent::__construct($name, $request); diff --git a/src/ResourceServer.php b/src/ResourceServer.php index 95833be4f..7ae657715 100644 --- a/src/ResourceServer.php +++ b/src/ResourceServer.php @@ -46,8 +46,6 @@ public function __construct( $this->authorizationValidator = $authorizationValidator; } - /** - */ protected function getAuthorizationValidator(): AuthorizationValidatorInterface { if ($this->authorizationValidator instanceof AuthorizationValidatorInterface === false) { diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 1f6ea7d59..45dd363cc 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -29,8 +29,8 @@ use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; -use ReflectionClass; use Psr\Http\Message\ServerRequestInterface; +use ReflectionClass; use function base64_encode; use function chmod; @@ -49,14 +49,14 @@ public function setUp(): void chmod(__DIR__ . '/Stubs/private.key.crlf', 0600); } - public function testGrantTypeGetsEnabled() + public function testGrantTypeGetsEnabled(): void { $server = new AuthorizationServer( $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(), $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), 'file://' . __DIR__ . '/Stubs/private.key', - \base64_encode(\random_bytes(36)), + base64_encode(random_bytes(36)), new StubResponseType() ); diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index a7526c9ea..3b45e3afc 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -145,9 +145,6 @@ public function testRSAKeyType(): void } } - /** - * - */ private static function generateKeyPath(string $keyContent): string { return 'file://' . sys_get_temp_dir() . '/' . sha1($keyContent) . '.key'; From e19c63c7345357f9191fb50b86a97df76d99d851 Mon Sep 17 00:00:00 2001 From: Serhii Petrov Date: Thu, 5 Oct 2023 22:41:09 +0300 Subject: [PATCH 126/212] Test against php 8.3 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ade0c245b..0b08e41ef 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.1, 8.2] + php: [8.1, 8.2, 8.3] os: [ubuntu-22.04] stability: [prefer-lowest, prefer-stable] include: From abfa828e67388588c17854157065b32b7c702b44 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 13 Oct 2023 22:24:07 +0100 Subject: [PATCH 127/212] Fix styling --- CHANGELOG.md | 3 + phpstan.neon.dist | 4 ++ src/AuthorizationServer.php | 15 +---- .../BearerTokenValidator.php | 9 +-- .../CodeChallengeVerifierInterface.php | 3 - src/CodeChallengeVerifiers/PlainVerifier.php | 3 - src/CodeChallengeVerifiers/S256Verifier.php | 3 - src/CryptKey.php | 7 +- src/CryptTrait.php | 4 -- src/Entities/ClientEntityInterface.php | 8 +-- src/Entities/RefreshTokenEntityInterface.php | 1 - src/Entities/ScopeEntityInterface.php | 1 - src/Entities/TokenInterface.php | 2 - src/Entities/Traits/AccessTokenTrait.php | 3 +- src/Entities/Traits/ClientTrait.php | 6 +- src/Entities/Traits/ScopeTrait.php | 1 - src/Entities/Traits/TokenEntityTrait.php | 2 - src/Entities/UserEntityInterface.php | 1 - src/Exception/OAuthServerException.php | 65 +------------------ src/Grant/AbstractAuthorizeGrant.php | 1 - src/Grant/AbstractGrant.php | 26 +------- src/Grant/AuthCodeGrant.php | 14 +--- src/Grant/GrantTypeInterface.php | 19 ------ src/Grant/ImplicitGrant.php | 18 ++--- src/Grant/PasswordGrant.php | 2 - src/Grant/RefreshTokenGrant.php | 1 - .../AuthorizationServerMiddleware.php | 6 +- src/Middleware/ResourceServerMiddleware.php | 6 +- .../RedirectUriValidator.php | 11 ---- .../RedirectUriValidatorInterface.php | 3 - .../AccessTokenRepositoryInterface.php | 1 - .../ClientRepositoryInterface.php | 8 --- src/Repositories/ScopeRepositoryInterface.php | 1 - src/Repositories/UserRepositoryInterface.php | 3 - src/RequestAccessTokenEvent.php | 5 +- src/RequestEvent.php | 9 +-- src/RequestRefreshTokenEvent.php | 5 +- src/RequestTypes/AuthorizationRequest.php | 9 --- src/ResourceServer.php | 12 +--- src/ResponseTypes/BearerTokenResponse.php | 1 - tests/Grant/ClientCredentialsGrantTest.php | 1 - tests/Grant/PasswordGrantTest.php | 1 - .../ResponseTypes/BearerResponseTypeTest.php | 1 - tests/Stubs/StubResponseType.php | 4 -- 44 files changed, 38 insertions(+), 271 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29548493f..12e305c38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) - Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112) +### Removed +- Removed message property from OAuthException HTTP response. Now just use error_description as per the OAuth 2 spec (PR #1375) + ### [8.3.6] - released 2022-11-14 ### Fixed - Use LooseValidAt instead of StrictValidAt so that users aren't forced to use claims such as NBF in their JWT tokens (PR #1312) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 44d720f86..177d40a90 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -4,6 +4,10 @@ parameters: - src - tests ignoreErrors: + - + message: '#Call to an undefined method League\\OAuth2\\Server\\ResponseTypes\\ResponseTypeInterface::getAccessToken\(\)\.#' + path: tests/Grant/ClientCredentialsGrantTest.php + - '#Return type \(League\\Event\\EmitterInterface\|null\) of method LeagueTests\\Stubs\\GrantType::getEmitter\(\) should be covariant with return type \(League\\Event\\EmitterInterface\) of method League\\Event\\EmitterAwareInterface::getEmitter\(\)#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAccessToken\(\) should return League\\OAuth2\\Server\\Entities\\AccessTokenEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueAuthCode\(\) should return League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface but return statement is missing\.#' - '#Method League\\OAuth2\\Server\\Grant\\AbstractGrant::issueRefreshToken\(\) should return League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInterface\|null but return statement is missing\.#' diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 05e32060a..fbdf99228 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -48,12 +48,6 @@ class AuthorizationServer implements EmitterAwareInterface protected ResponseTypeInterface $responseType; - private ClientRepositoryInterface $clientRepository; - - private AccessTokenRepositoryInterface $accessTokenRepository; - - private ScopeRepositoryInterface $scopeRepository; - private string|Key $encryptionKey; private string $defaultScope = ''; @@ -64,16 +58,13 @@ class AuthorizationServer implements EmitterAwareInterface * New server instance */ public function __construct( - ClientRepositoryInterface $clientRepository, - AccessTokenRepositoryInterface $accessTokenRepository, - ScopeRepositoryInterface $scopeRepository, + private ClientRepositoryInterface $clientRepository, + private AccessTokenRepositoryInterface $accessTokenRepository, + private ScopeRepositoryInterface $scopeRepository, CryptKeyInterface|string $privateKey, Key|string $encryptionKey, ResponseTypeInterface|null $responseType = null ) { - $this->clientRepository = $clientRepository; - $this->accessTokenRepository = $accessTokenRepository; - $this->scopeRepository = $scopeRepository; if ($privateKey instanceof CryptKeyInterface === false) { $privateKey = new CryptKey($privateKey); diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index c3e9ed719..a12e2726f 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -39,20 +39,16 @@ class BearerTokenValidator implements AuthorizationValidatorInterface { use CryptTrait; - private AccessTokenRepositoryInterface $accessTokenRepository; - protected CryptKeyInterface $publicKey; private Configuration $jwtConfiguration; - public function __construct(AccessTokenRepositoryInterface $accessTokenRepository) + public function __construct(private AccessTokenRepositoryInterface $accessTokenRepository) { - $this->accessTokenRepository = $accessTokenRepository; } /** * Set the public key - * */ public function setPublicKey(CryptKeyInterface $key): void { @@ -113,7 +109,7 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe throw OAuthServerException::accessDenied('Access token could not be verified'); } - if (! $token instanceof UnencryptedToken) { + if (!$token instanceof UnencryptedToken) { throw OAuthServerException::accessDenied('Access token is not an instance of UnencryptedToken'); } @@ -135,6 +131,7 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe /** * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt * + * TODO: Investigate as I don't think we need this any more * * @return array|string */ diff --git a/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php b/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php index f358b5c17..1417acf10 100644 --- a/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php +++ b/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php @@ -16,14 +16,11 @@ interface CodeChallengeVerifierInterface { /** * Return code challenge method. - * */ public function getMethod(): string; /** * Verify the code challenge. - * - * */ public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool; } diff --git a/src/CodeChallengeVerifiers/PlainVerifier.php b/src/CodeChallengeVerifiers/PlainVerifier.php index 34a60615c..53d669459 100644 --- a/src/CodeChallengeVerifiers/PlainVerifier.php +++ b/src/CodeChallengeVerifiers/PlainVerifier.php @@ -18,7 +18,6 @@ class PlainVerifier implements CodeChallengeVerifierInterface { /** * Return code challenge method. - * */ public function getMethod(): string { @@ -27,8 +26,6 @@ public function getMethod(): string /** * Verify the code challenge. - * - * */ public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool { diff --git a/src/CodeChallengeVerifiers/S256Verifier.php b/src/CodeChallengeVerifiers/S256Verifier.php index e05464f40..7f99ead5f 100644 --- a/src/CodeChallengeVerifiers/S256Verifier.php +++ b/src/CodeChallengeVerifiers/S256Verifier.php @@ -22,7 +22,6 @@ class S256Verifier implements CodeChallengeVerifierInterface { /** * Return code challenge method. - * */ public function getMethod(): string { @@ -31,8 +30,6 @@ public function getMethod(): string /** * Verify the code challenge. - * - * */ public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool { diff --git a/src/CryptKey.php b/src/CryptKey.php index cca82adc2..206583783 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -41,11 +41,8 @@ class CryptKey implements CryptKeyInterface protected string $keyPath; - protected ?string $passPhrase = null; - - public function __construct(string $keyPath, ?string $passPhrase = null, bool $keyPermissionsCheck = true) + public function __construct(string $keyPath, protected ?string $passPhrase = null, bool $keyPermissionsCheck = true) { - $this->passPhrase = $passPhrase; if (strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { $this->keyContents = $keyPath; @@ -103,8 +100,6 @@ public function getKeyContents(): string /** * Validate key contents. - * - * */ private function isValidKey(string $contents, string $passPhrase): bool { diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 2119bc34b..8b1946fc4 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -28,9 +28,7 @@ trait CryptTrait /** * Encrypt data with encryptionKey. * - * * @throws LogicException - * */ protected function encrypt(string $unencryptedData): string { @@ -52,9 +50,7 @@ protected function encrypt(string $unencryptedData): string /** * Decrypt data with encryptionKey. * - * * @throws LogicException - * */ protected function decrypt(string $encryptedData): string { diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index 56aa54dfb..9b1656679 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -16,20 +16,17 @@ interface ClientEntityInterface { /** * Get the client's identifier. - * */ public function getIdentifier(): string; /** * Get the client's name. - * */ public function getName(): string; /** - * Returns the registered redirect URI (as a string). - * - * Alternatively return an indexed array of redirect URIs. + * Returns the registered redirect URI (as a string). Alternatively return + * an indexed array of redirect URIs. * * @return string|string[] */ @@ -37,7 +34,6 @@ public function getRedirectUri(): string|array; /** * Returns true if the client is confidential. - * */ public function isConfidential(): bool; } diff --git a/src/Entities/RefreshTokenEntityInterface.php b/src/Entities/RefreshTokenEntityInterface.php index 9b881216f..f377cd9e1 100644 --- a/src/Entities/RefreshTokenEntityInterface.php +++ b/src/Entities/RefreshTokenEntityInterface.php @@ -18,7 +18,6 @@ interface RefreshTokenEntityInterface { /** * Get the token's identifier. - * */ public function getIdentifier(): string; diff --git a/src/Entities/ScopeEntityInterface.php b/src/Entities/ScopeEntityInterface.php index 653d671ea..37c40bf55 100644 --- a/src/Entities/ScopeEntityInterface.php +++ b/src/Entities/ScopeEntityInterface.php @@ -18,7 +18,6 @@ interface ScopeEntityInterface extends JsonSerializable { /** * Get the scope's identifier. - * */ public function getIdentifier(): string; } diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 22e6fab49..5434ff00e 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -18,7 +18,6 @@ interface TokenInterface { /** * Get the token's identifier. - * */ public function getIdentifier(): string; @@ -29,7 +28,6 @@ public function setIdentifier(mixed $identifier): void; /** * Get the token's expiry date time. - * */ public function getExpiryDateTime(): DateTimeImmutable; diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index a3bce4804..9d9fe4a65 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -56,7 +56,6 @@ public function initJwtConfiguration(): void /** * Generate a JWT from the access token - * */ private function convertToJWT(): Token { @@ -75,6 +74,8 @@ private function convertToJWT(): Token /** * Generate a string representation from the access token + * + * TODO: Want to remove this function. */ public function __toString(): string { diff --git a/src/Entities/Traits/ClientTrait.php b/src/Entities/Traits/ClientTrait.php index 5ae6456b8..a5a357258 100644 --- a/src/Entities/Traits/ClientTrait.php +++ b/src/Entities/Traits/ClientTrait.php @@ -34,9 +34,8 @@ public function getName(): string } /** - * Returns the registered redirect URI (as a string). - * - * Alternatively return an indexed array of redirect URIs. + * Returns the registered redirect URI (as a string). Alternatively return + * an indexed array of redirect URIs. * * @return string|string[] */ @@ -47,7 +46,6 @@ public function getRedirectUri(): string|array /** * Returns true if the client is confidential. - * */ public function isConfidential(): bool { diff --git a/src/Entities/Traits/ScopeTrait.php b/src/Entities/Traits/ScopeTrait.php index 790fb02f3..71bedac76 100644 --- a/src/Entities/Traits/ScopeTrait.php +++ b/src/Entities/Traits/ScopeTrait.php @@ -16,7 +16,6 @@ trait ScopeTrait { /** * Serialize the object to the scopes string identifier when using json_encode(). - * */ public function jsonSerialize(): string { diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 73c1d152c..06ef15fb1 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -33,7 +33,6 @@ trait TokenEntityTrait /** * Associate a scope with the token. - * */ public function addScope(ScopeEntityInterface $scope): void { @@ -78,7 +77,6 @@ public function setUserIdentifier(string|int|null $identifier): void /** * Get the token user's identifier. - * */ public function getUserIdentifier(): string|int|null { diff --git a/src/Entities/UserEntityInterface.php b/src/Entities/UserEntityInterface.php index 28eed197e..ef88a84d2 100644 --- a/src/Entities/UserEntityInterface.php +++ b/src/Entities/UserEntityInterface.php @@ -16,7 +16,6 @@ interface UserEntityInterface { /** * Return the user's identifier. - * */ public function getIdentifier(): mixed; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 29c195359..08bbb9538 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -25,14 +25,6 @@ class OAuthServerException extends Exception { - private int $httpStatusCode; - - private string $errorType; - - private ?string $hint = null; - - private ?string $redirectUri = null; - /** * @var array */ @@ -42,26 +34,15 @@ class OAuthServerException extends Exception /** * Throw a new exception. - * - * @param string $message Error message - * @param int $code Error code - * @param string $errorType Error type - * @param int $httpStatusCode HTTP status code to send (default = 400) - * @param null|string $hint A helper hint - * @param null|string $redirectUri A HTTP URI to redirect the user back to - * @param Throwable $previous Previous exception */ - final public function __construct(string $message, int $code, string $errorType, int $httpStatusCode = 400, ?string $hint = null, ?string $redirectUri = null, Throwable $previous = null) + final public function __construct(string $message, int $code, private string $errorType, private int $httpStatusCode = 400, private ?string $hint = null, private ?string $redirectUri = null, Throwable $previous = null) { parent::__construct($message, $code, $previous); - $this->httpStatusCode = $httpStatusCode; - $this->errorType = $errorType; - $this->hint = $hint; - $this->redirectUri = $redirectUri; $this->payload = [ 'error' => $errorType, 'error_description' => $message, ]; + if ($hint !== null) { $this->payload['hint'] = $hint; } @@ -74,15 +55,7 @@ final public function __construct(string $message, int $code, string $errorType, */ public function getPayload(): array { - $payload = $this->payload; - - // The "message" property is deprecated and replaced by "error_description" - // TODO: remove "message" property - if (isset($payload['error_description']) && !isset($payload['message'])) { - $payload['message'] = $payload['error_description']; - } - - return $payload; + return $this->payload; } /** @@ -97,7 +70,6 @@ public function setPayload(array $payload): void /** * Set the server request that is responsible for generating the exception - * */ public function setServerRequest(ServerRequestInterface $serverRequest): void { @@ -106,8 +78,6 @@ public function setServerRequest(ServerRequestInterface $serverRequest): void /** * Unsupported grant type error. - * - * @return static */ public static function unsupportedGrantType(): static { @@ -119,11 +89,6 @@ public static function unsupportedGrantType(): static /** * Invalid request error. - * - * @param string $parameter The invalid parameter - * @param Throwable $previous Previous exception - * - * @return static */ public static function invalidRequest(string $parameter, ?string $hint = null, Throwable $previous = null): static { @@ -136,9 +101,6 @@ public static function invalidRequest(string $parameter, ?string $hint = null, T /** * Invalid client error. - * - * - * @return static */ public static function invalidClient(ServerRequestInterface $serverRequest): static { @@ -170,8 +132,6 @@ public static function invalidScope(string $scope, string|null $redirectUri = nu /** * Invalid credentials error. - * - * @return static */ public static function invalidCredentials(): static { @@ -181,9 +141,6 @@ public static function invalidCredentials(): static /** * Server error. * - * - * @return static - * * @codeCoverageIgnore */ public static function serverError(string $hint, Throwable $previous = null): static @@ -202,9 +159,6 @@ public static function serverError(string $hint, Throwable $previous = null): st /** * Invalid refresh token. - * - * - * @return static */ public static function invalidRefreshToken(?string $hint = null, Throwable $previous = null): static { @@ -213,9 +167,6 @@ public static function invalidRefreshToken(?string $hint = null, Throwable $prev /** * Access denied. - * - * - * @return static */ public static function accessDenied(?string $hint = null, ?string $redirectUri = null, Throwable $previous = null): static { @@ -232,9 +183,6 @@ public static function accessDenied(?string $hint = null, ?string $redirectUri = /** * Invalid grant. - * - * - * @return static */ public static function invalidGrant(string $hint = ''): static { @@ -256,10 +204,6 @@ public function getErrorType(): string /** * Generate a HTTP response. - * - * @param bool $useFragment True if errors should be in the URI fragment instead of query string - * @param int $jsonOptions options passed to json_encode - * */ public function generateHttpResponse(ResponseInterface $response, bool $useFragment = false, int $jsonOptions = 0): ResponseInterface { @@ -325,7 +269,6 @@ public function getHttpHeaders(): array * getHttpStatusCode() doesn't return a 302 when there's a * redirect enabled. This helps when you want to override local * error pages but want to let redirects through. - * */ public function hasRedirect(): bool { @@ -334,7 +277,6 @@ public function hasRedirect(): bool /** * Returns the Redirect URI used for redirecting. - * */ public function getRedirectUri(): ?string { @@ -343,7 +285,6 @@ public function getRedirectUri(): ?string /** * Returns the HTTP status code to send when the exceptions is output. - * */ public function getHttpStatusCode(): int { diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index 2bcf27242..27d39982b 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -24,7 +24,6 @@ abstract class AbstractAuthorizeGrant extends AbstractGrant { /** * @param mixed[] $params - * */ public function makeRedirectUri(string $uri, array $params = [], string $queryDelimiter = '?'): string { diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 595f5a097..0bef8209c 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -127,7 +127,6 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void /** * Set the private key - * */ public function setPrivateKey(CryptKeyInterface $key): void { @@ -147,9 +146,7 @@ public function revokeRefreshTokens(bool $revokeRefreshTokens): void /** * Validate the client. * - * * @throws OAuthServerException - * */ protected function validateClient(ServerRequestInterface $request): ClientEntityInterface { @@ -187,7 +184,7 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity * getClientEntity might return null. By contrast, this method will * always either return a ClientEntityInterface or throw. * - * + * TODO: Check if we still need this */ protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface { @@ -205,7 +202,6 @@ protected function getClientEntityOrFail(string $clientId, ServerRequestInterfac * Gets the client credentials from the request from the request body or * the Http Basic Authorization header * - * * @return string[] */ protected function getClientCredentials(ServerRequestInterface $request): array @@ -228,9 +224,8 @@ protected function getClientCredentials(ServerRequestInterface $request): array } /** - * Validate redirectUri from the request. - * If a redirect URI is provided ensure it matches what is pre-registered - * + * Validate redirectUri from the request. If a redirect URI is provided + * ensure it matches what is pre-registered * * @throws OAuthServerException */ @@ -282,7 +277,6 @@ public function validateScopes(string|array|null $scopes, string $redirectUri = /** * Converts a scopes query string to an array to easily iterate for validation. * - * * @return string[] */ private function convertScopesQueryStringToArray(string $scopes): array @@ -294,8 +288,6 @@ private function convertScopesQueryStringToArray(string $scopes): array /** * Retrieve request parameter. - * - * */ protected function getRequestParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): mixed { @@ -311,7 +303,6 @@ protected function getRequestParameter(string $parameter, ServerRequestInterface * not exist, or is otherwise an invalid HTTP Basic header, return * [null, null]. * - * * @return string[]|null[] */ protected function getBasicAuthCredentials(ServerRequestInterface $request): array @@ -340,8 +331,6 @@ protected function getBasicAuthCredentials(ServerRequestInterface $request): arr /** * Retrieve query string parameter. - * - * */ protected function getQueryStringParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): ?string { @@ -350,8 +339,6 @@ protected function getQueryStringParameter(string $parameter, ServerRequestInter /** * Retrieve cookie parameter. - * - * */ protected function getCookieParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): ?string { @@ -360,8 +347,6 @@ protected function getCookieParameter(string $parameter, ServerRequestInterface /** * Retrieve server parameter. - * - * */ protected function getServerParameter(string $parameter, ServerRequestInterface $request, mixed $default = null): ?string { @@ -410,7 +395,6 @@ protected function issueAccessToken( * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException - * */ protected function issueAuthCode( DateInterval $authCodeTTL, @@ -449,10 +433,8 @@ protected function issueAuthCode( } /** - * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException - * */ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface { @@ -484,9 +466,7 @@ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ? /** * Generate a new unique identifier. * - * * @throws OAuthServerException - * */ protected function generateUniqueIdentifier(int $length = 40): string { diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index e9d14e36d..3eb1a8fca 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -51,8 +51,6 @@ class AuthCodeGrant extends AbstractAuthorizeGrant { - private DateInterval $authCodeTTL; - private bool $requireCodeChallengeForPublicClients = true; /** @@ -61,17 +59,15 @@ class AuthCodeGrant extends AbstractAuthorizeGrant private array $codeChallengeVerifiers = []; /** - * * @throws Exception */ public function __construct( AuthCodeRepositoryInterface $authCodeRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, - DateInterval $authCodeTTL + private DateInterval $authCodeTTL ) { $this->setAuthCodeRepository($authCodeRepository); $this->setRefreshTokenRepository($refreshTokenRepository); - $this->authCodeTTL = $authCodeTTL; $this->refreshTokenTTL = new DateInterval('P1M'); if (in_array('sha256', hash_algos(), true)) { @@ -94,9 +90,7 @@ public function disableRequireCodeChallengeForPublicClients(): void /** * Respond to an access token request. * - * * @throws OAuthServerException - * */ public function respondToAccessTokenRequest( ServerRequestInterface $request, @@ -190,7 +184,6 @@ public function respondToAccessTokenRequest( /** * Validate the authorization code. - * */ private function validateAuthorizationCode( stdClass $authCodePayload, @@ -226,7 +219,6 @@ private function validateAuthorizationCode( /** * Return the grant identifier that can be used in matching up requests. - * */ public function getIdentifier(): string { @@ -267,7 +259,7 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): A if ($redirectUri !== null) { $this->validateRedirectUri($redirectUri, $client, $request); } elseif ( - $client->getRedirectUri() === '' || $client->getRedirectUri() === null || + $client->getRedirectUri() === '' || (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1) ) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); @@ -406,8 +398,6 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth /** * Get the client redirect URI if not set in the request. - * - * */ private function getClientRedirectUri(AuthorizationRequestInterface $authorizationRequest): string { diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 36f2f6050..a2fda3734 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -32,20 +32,16 @@ interface GrantTypeInterface extends EmitterAwareInterface { /** * Set refresh token TTL. - * */ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void; /** * Return the grant identifier that can be used in matching up requests. - * */ public function getIdentifier(): string; /** * Respond to an incoming request. - * - * */ public function respondToAccessTokenRequest( ServerRequestInterface $request, @@ -55,8 +51,6 @@ public function respondToAccessTokenRequest( /** * The grant type should return true if it is able to response to an authorization request - * - * */ public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool; @@ -66,8 +60,6 @@ public function canRespondToAuthorizationRequest(ServerRequestInterface $request * * If the validation is successful an AuthorizationRequest object will be returned. This object can be safely * serialized in a user's session, and can be used during user authentication and authorization. - * - * */ public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface; @@ -75,8 +67,6 @@ public function validateAuthorizationRequest(ServerRequestInterface $request): A * Once a user has authenticated and authorized the client the grant can complete the authorization request. * The AuthorizationRequest object's $userId property must be set to the authenticated user and the * $authorizationApproved property must reflect their desire to authorize or deny the client. - * - * */ public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): ResponseTypeInterface; @@ -84,38 +74,31 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth * The grant type should return true if it is able to respond to this request. * * For example most grant types will check that the $_POST['grant_type'] property matches it's identifier property. - * - * */ public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool; /** * Set the client repository. - * */ public function setClientRepository(ClientRepositoryInterface $clientRepository): void; /** * Set the access token repository. - * */ public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void; /** * Set the scope repository. - * */ public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void; /** * Set the default scope. - * */ public function setDefaultScope(string $scope): void; /** * Set the path to the private key. - * */ public function setPrivateKey(CryptKeyInterface $privateKey): void; @@ -123,8 +106,6 @@ public function setEncryptionKey(Key|string|null $key = null): void; /** * Enable or prevent the revocation of refresh tokens upon usage. - * - * */ public function revokeRefreshTokens(bool $willRevoke): void; } diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index d071adc40..d081ba8e5 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -30,19 +30,12 @@ class ImplicitGrant extends AbstractAuthorizeGrant { - private DateInterval $accessTokenTTL; - - private string $queryDelimiter; - - public function __construct(DateInterval $accessTokenTTL, string $queryDelimiter = '#') + public function __construct(private DateInterval $accessTokenTTL, private string $queryDelimiter = '#') { - $this->accessTokenTTL = $accessTokenTTL; - $this->queryDelimiter = $queryDelimiter; } /** - * - * @throw LogicException + * @throws LogicException */ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void { @@ -50,8 +43,7 @@ public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void } /** - * - * @throw LogicException + * @throws LogicException */ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void { @@ -68,7 +60,6 @@ public function canRespondToAccessTokenRequest(ServerRequestInterface $request): /** * Return the grant identifier that can be used in matching up requests. - * */ public function getIdentifier(): string { @@ -77,8 +68,6 @@ public function getIdentifier(): string /** * Respond to an incoming request. - * - * */ public function respondToAccessTokenRequest( ServerRequestInterface $request, @@ -163,6 +152,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } + // TODO: Need to tidy up the nested ternary expressions $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) ? is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 1a1759aa0..1b92dc1da 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -80,9 +80,7 @@ public function respondToAccessTokenRequest( } /** - * * @throws OAuthServerException - * */ protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client): UserEntityInterface { diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 4cb8ab475..5a5b55e30 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -96,7 +96,6 @@ public function respondToAccessTokenRequest( } /** - * * @throws OAuthServerException * * @return array diff --git a/src/Middleware/AuthorizationServerMiddleware.php b/src/Middleware/AuthorizationServerMiddleware.php index bf5387c71..e59d35590 100644 --- a/src/Middleware/AuthorizationServerMiddleware.php +++ b/src/Middleware/AuthorizationServerMiddleware.php @@ -12,7 +12,6 @@ namespace League\OAuth2\Server\Middleware; -use Exception; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; @@ -20,11 +19,8 @@ class AuthorizationServerMiddleware { - private AuthorizationServer $server; - - public function __construct(AuthorizationServer $server) + public function __construct(private AuthorizationServer $server) { - $this->server = $server; } public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 4bed11c66..460e77712 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -12,7 +12,6 @@ namespace League\OAuth2\Server\Middleware; -use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use Psr\Http\Message\ResponseInterface; @@ -20,11 +19,8 @@ class ResourceServerMiddleware { - private ResourceServer $server; - - public function __construct(ResourceServer $server) + public function __construct(private ResourceServer $server) { - $this->server = $server; } public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next): ResponseInterface diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index af20765ff..cbc530adf 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -42,7 +42,6 @@ public function __construct(array|string $allowedRedirectUris) /** * Validates the redirect uri. * - * * @return bool Return true if valid, false otherwise */ public function validateRedirectUri(string $redirectUri): bool @@ -58,8 +57,6 @@ public function validateRedirectUri(string $redirectUri): bool * According to section 7.3 of rfc8252, loopback uris are: * - "http://127.0.0.1:{port}/{path}" for IPv4 * - "http://[::1]:{port}/{path}" for IPv6 - * - * */ private function isLoopbackUri(string $redirectUri): bool { @@ -75,9 +72,6 @@ private function isLoopbackUri(string $redirectUri): bool /** * Find an exact match among allowed uris - * - * - * @return bool Return true if an exact match is found, false otherwise */ private function matchExactUri(string $redirectUri): bool { @@ -86,9 +80,6 @@ private function matchExactUri(string $redirectUri): bool /** * Find a match among allowed uris, allowing for different port numbers - * - * - * @return bool Return true if a match is found, false otherwise */ private function matchUriExcludingPort(string $redirectUri): bool { @@ -105,8 +96,6 @@ private function matchUriExcludingPort(string $redirectUri): bool /** * Parse an url like \parse_url, excluding the port - * - * */ private function parseUrlAndRemovePort(string $url): string { diff --git a/src/RedirectUriValidators/RedirectUriValidatorInterface.php b/src/RedirectUriValidators/RedirectUriValidatorInterface.php index 0e44830f7..df64a219c 100644 --- a/src/RedirectUriValidators/RedirectUriValidatorInterface.php +++ b/src/RedirectUriValidators/RedirectUriValidatorInterface.php @@ -16,9 +16,6 @@ interface RedirectUriValidatorInterface { /** * Validates the redirect uri. - * - * - * @return bool Return true if valid, false otherwise */ public function validateRedirectUri(string $redirectUri): bool; } diff --git a/src/Repositories/AccessTokenRepositoryInterface.php b/src/Repositories/AccessTokenRepositoryInterface.php index e5a4b0c98..d392716ed 100644 --- a/src/Repositories/AccessTokenRepositoryInterface.php +++ b/src/Repositories/AccessTokenRepositoryInterface.php @@ -26,7 +26,6 @@ interface AccessTokenRepositoryInterface extends RepositoryInterface * Create a new access token * * @param ScopeEntityInterface[] $scopes - * */ public function getNewToken( ClientEntityInterface $clientEntity, diff --git a/src/Repositories/ClientRepositoryInterface.php b/src/Repositories/ClientRepositoryInterface.php index 092a627f2..63134ca9d 100644 --- a/src/Repositories/ClientRepositoryInterface.php +++ b/src/Repositories/ClientRepositoryInterface.php @@ -21,19 +21,11 @@ interface ClientRepositoryInterface extends RepositoryInterface { /** * Get a client. - * - * @param string $clientIdentifier The client's identifier - * */ public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface; /** * Validate a client's secret. - * - * @param string $clientIdentifier The client's identifier - * @param null|string $clientSecret The client's secret (if sent) - * @param null|string $grantType The type of grant the client is using (if sent) - * */ public function validateClient(string $clientIdentifier, ?string $clientSecret, ?string $grantType): bool; } diff --git a/src/Repositories/ScopeRepositoryInterface.php b/src/Repositories/ScopeRepositoryInterface.php index 2ee699ccf..95bfdbb9a 100644 --- a/src/Repositories/ScopeRepositoryInterface.php +++ b/src/Repositories/ScopeRepositoryInterface.php @@ -24,7 +24,6 @@ interface ScopeRepositoryInterface extends RepositoryInterface * Return information about a scope. * * @param string $identifier The scope identifier - * */ public function getScopeEntityByIdentifier(string $identifier): ?ScopeEntityInterface; diff --git a/src/Repositories/UserRepositoryInterface.php b/src/Repositories/UserRepositoryInterface.php index 644e700ad..b8cf53d09 100644 --- a/src/Repositories/UserRepositoryInterface.php +++ b/src/Repositories/UserRepositoryInterface.php @@ -19,9 +19,6 @@ interface UserRepositoryInterface extends RepositoryInterface { /** * Get a user entity. - * - * @param string $grantType The grant type used - * */ public function getUserEntityByUserCredentials( string $username, diff --git a/src/RequestAccessTokenEvent.php b/src/RequestAccessTokenEvent.php index ddffa4636..1200c44c5 100644 --- a/src/RequestAccessTokenEvent.php +++ b/src/RequestAccessTokenEvent.php @@ -17,12 +17,9 @@ class RequestAccessTokenEvent extends RequestEvent { - private AccessTokenEntityInterface $accessToken; - - public function __construct(string $name, ServerRequestInterface $request, AccessTokenEntityInterface $accessToken) + public function __construct(string $name, ServerRequestInterface $request, private AccessTokenEntityInterface $accessToken) { parent::__construct($name, $request); - $this->accessToken = $accessToken; } /** diff --git a/src/RequestEvent.php b/src/RequestEvent.php index 5c8d09784..642d9ab60 100644 --- a/src/RequestEvent.php +++ b/src/RequestEvent.php @@ -24,16 +24,9 @@ class RequestEvent extends Event public const REFRESH_TOKEN_ISSUED = 'refresh_token.issued'; public const ACCESS_TOKEN_ISSUED = 'access_token.issued'; - private ServerRequestInterface $request; - - /** - * RequestEvent constructor. - * - */ - public function __construct(string $name, ServerRequestInterface $request) + public function __construct(string $name, private ServerRequestInterface $request) { parent::__construct($name); - $this->request = $request; } /** diff --git a/src/RequestRefreshTokenEvent.php b/src/RequestRefreshTokenEvent.php index 28802951f..f2ac556cd 100644 --- a/src/RequestRefreshTokenEvent.php +++ b/src/RequestRefreshTokenEvent.php @@ -17,12 +17,9 @@ class RequestRefreshTokenEvent extends RequestEvent { - private RefreshTokenEntityInterface $refreshToken; - - public function __construct(string $name, ServerRequestInterface $request, RefreshTokenEntityInterface $refreshToken) + public function __construct(string $name, ServerRequestInterface $request, private RefreshTokenEntityInterface $refreshToken) { parent::__construct($name, $request); - $this->refreshToken = $refreshToken; } /** diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index 07ece0f10..276396722 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -20,56 +20,47 @@ class AuthorizationRequest implements AuthorizationRequestInterface { /** * The grant type identifier - * */ protected string $grantTypeId; /** * The client identifier - * */ protected ClientEntityInterface $client; /** * The user identifier - * */ protected UserEntityInterface $user; /** * An array of scope identifiers - * * @var ScopeEntityInterface[] */ protected array $scopes = []; /** * Has the user authorized the authorization request - * */ protected bool $authorizationApproved = false; /** * The redirect URI used in the request - * */ protected ?string $redirectUri = null; /** * The state parameter on the authorization request - * */ protected ?string $state = null; /** * The code challenge (if provided) - * */ protected string $codeChallenge; /** * The code challenge method (if provided) - * */ protected string $codeChallengeMethod; diff --git a/src/ResourceServer.php b/src/ResourceServer.php index 7ae657715..0ffbf889a 100644 --- a/src/ResourceServer.php +++ b/src/ResourceServer.php @@ -20,23 +20,15 @@ class ResourceServer { - private AccessTokenRepositoryInterface $accessTokenRepository; - private CryptKeyInterface $publicKey; private ?AuthorizationValidatorInterface $authorizationValidator = null; - /** - * New server instance. - * - * @param null|AuthorizationValidatorInterface $authorizationValidator - */ public function __construct( - AccessTokenRepositoryInterface $accessTokenRepository, + private AccessTokenRepositoryInterface $accessTokenRepository, CryptKeyInterface|string $publicKey, AuthorizationValidatorInterface $authorizationValidator = null ) { - $this->accessTokenRepository = $accessTokenRepository; if ($publicKey instanceof CryptKeyInterface === false) { $publicKey = new CryptKey($publicKey); @@ -62,9 +54,7 @@ protected function getAuthorizationValidator(): AuthorizationValidatorInterface /** * Determine the access token validity. * - * * @throws OAuthServerException - * */ public function validateAuthenticatedRequest(ServerRequestInterface $request): ServerRequestInterface { diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 4761d02d6..f738a4e27 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -15,7 +15,6 @@ namespace League\OAuth2\Server\ResponseTypes; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; -use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use LogicException; use Psr\Http\Message\ResponseInterface; diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index fd621bea6..264e026e2 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -61,7 +61,6 @@ public function testRespondToRequest(): void $responseType = new StubResponseType(); $response = $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - self::assertNotEmpty($response->getAccessToken()->getIdentifier()); } } diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 494e1d28c..8c60a8c78 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -7,7 +7,6 @@ use DateInterval; use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\PasswordGrant; diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 7f0894936..64c040f57 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -18,7 +18,6 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use function base64_encode; use function json_decode; diff --git a/tests/Stubs/StubResponseType.php b/tests/Stubs/StubResponseType.php index 6acc1562b..0f8f371a1 100644 --- a/tests/Stubs/StubResponseType.php +++ b/tests/Stubs/StubResponseType.php @@ -14,10 +14,6 @@ class StubResponseType extends AbstractResponseType { - public function __construct() - { - } - public function getAccessToken(): AccessTokenEntityInterface { return $this->accessToken; From d780f13f9a532c4c85151954b30849b98ed5dd1a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 17 Oct 2023 22:40:17 +0100 Subject: [PATCH 128/212] Update minimum PHPUnit version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a1422afad..4f6012f0e 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "lcobucci/clock": "^2.2 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "^9.6.6", + "phpunit/phpunit": "^9.6.11", "laminas/laminas-diactoros": "^3.0.0", "phpstan/phpstan": "^1.10.26", "phpstan/phpstan-phpunit": "^1.3.14", From b0528f7dbf0140422d15e405aa2758bf843b63eb Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Oct 2023 21:04:55 +0100 Subject: [PATCH 129/212] Tidy up ternary operator --- src/Grant/ImplicitGrant.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index d081ba8e5..8c2328b60 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -152,12 +152,11 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } - // TODO: Need to tidy up the nested ternary expressions - $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) - ? is_array($authorizationRequest->getClient()->getRedirectUri()) + $clientRegisteredRedirectUri = is_array($authorizationRequest->getClient()->getRedirectUri()) ? $authorizationRequest->getClient()->getRedirectUri()[0] - : $authorizationRequest->getClient()->getRedirectUri() - : $authorizationRequest->getRedirectUri(); + : $authorizationRequest->getClient()->getRedirectUri(); + + $finalRedirectUri = $authorizationRequest->getRedirectUri() ?? $clientRegisteredRedirectUri; // The user approved the client, redirect them back with an access token if ($authorizationRequest->isAuthorizationApproved() === true) { From a11685608e5b2d485e418996eec78d36f5775d93 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Oct 2023 21:29:34 +0100 Subject: [PATCH 130/212] Update changelog with PR number --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d4ad3ab5..2d4a4dfde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added -- GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #XXXX) +- GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #1375) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) - An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110) -- Added function `getKeyContents()` to the `CryptKeyInterface` (PR #XXXX) +- Added function `getKeyContents()` to the `CryptKeyInterface` (PR #1375) ### Fixed - If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082) From 68cf2173773a9bc69d4704f4486f3c3eedb855b4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Oct 2023 22:12:44 +0100 Subject: [PATCH 131/212] Add a starter PHPCS file --- phpcs.xml.dist | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 phpcs.xml.dist diff --git a/phpcs.xml.dist b/phpcs.xml.dist new file mode 100644 index 000000000..6a33bb3c5 --- /dev/null +++ b/phpcs.xml.dist @@ -0,0 +1,34 @@ + + + + + + + + + + src + tests + + + + + + + + + + + + + + + + + + + + From acef130da0dcbcc1e39a5af4eaf527909f9d4005 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 25 Oct 2023 22:24:08 +0100 Subject: [PATCH 132/212] Fix CS issues --- src/EventEmitting/AbstractEvent.php | 11 +++-------- src/EventEmitting/EmitterAwareInterface.php | 1 + src/EventEmitting/EmitterAwarePolyfill.php | 6 ++---- src/EventEmitting/EventEmitter.php | 1 + 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/EventEmitting/AbstractEvent.php b/src/EventEmitting/AbstractEvent.php index 6e3101397..80c229aaa 100644 --- a/src/EventEmitting/AbstractEvent.php +++ b/src/EventEmitting/AbstractEvent.php @@ -1,4 +1,5 @@ name = $name; } public function eventName(): string diff --git a/src/EventEmitting/EmitterAwareInterface.php b/src/EventEmitting/EmitterAwareInterface.php index 19269ab68..af192ab25 100644 --- a/src/EventEmitting/EmitterAwareInterface.php +++ b/src/EventEmitting/EmitterAwareInterface.php @@ -1,4 +1,5 @@ Date: Wed, 1 Nov 2023 23:06:20 +0000 Subject: [PATCH 133/212] Fix merge errors --- src/Exception/OAuthServerException.php | 5 +++++ src/Grant/AbstractGrant.php | 5 +++-- src/Grant/DeviceCodeGrant.php | 6 +++--- src/Grant/GrantTypeInterface.php | 9 ++++----- src/ResponseTypes/DeviceCodeResponse.php | 2 +- tests/AuthorizationServerTest.php | 2 ++ tests/Grant/AbstractGrantTest.php | 1 - tests/Grant/DeviceCodeGrantTest.php | 5 +++++ tests/Stubs/GrantType.php | 17 +++++++++++++++++ 9 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 333113075..a86453faa 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -198,6 +198,11 @@ public static function invalidGrant(string $hint = ''): static } public function getErrorType(): string + { + return $this->errorType; + } + + /** * Expired token error. * * @param null|string $hint diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index fe78f66a9..0923d8142 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -39,6 +39,7 @@ use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; +use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; use Psr\Http\Message\ServerRequestInterface; @@ -66,7 +67,7 @@ abstract class AbstractGrant implements GrantTypeInterface protected const SCOPE_DELIMITER_STRING = ' '; - private const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; + protected const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; protected ClientRepositoryInterface $clientRepository; @@ -544,7 +545,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r /** * {@inheritdoc} */ - public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request) + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse { throw new LogicException('This grant cannot validate a device authorization request'); } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 64a262fac..0ba5f7583 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -75,7 +75,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r /** * {@inheritdoc} */ - public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request) + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse { $clientId = $this->getRequestParameter( 'client_id', @@ -137,7 +137,7 @@ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, DateInterval $accessTokenTTL - ) { + ): ResponseTypeInterface { // Validate request $client = $this->validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); @@ -263,7 +263,7 @@ public function setVerificationUri($verificationUri) /** * {@inheritdoc} */ - public function getIdentifier() + public function getIdentifier(): string { return 'urn:ietf:params:oauth:grant-type:device_code'; } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 623041ed8..c65190a2e 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -23,6 +23,7 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; +use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -93,12 +94,10 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r * * If the validation is successful a DeviceAuthorizationRequest object will be returned. This object can be safely * serialized in a user's session, and can be used during user authentication and authorization. - * - * @param ServerRequestInterface $request - * - * @return DeviceAuthorizationRequest */ - public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request); + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse; + + // TODO: Check DeviceAuthorizationRequest /** * If the grant can respond to a device authorization request this method should be called to validate the parameters of diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 33b3282d9..e8aa6a8ec 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -25,7 +25,7 @@ class DeviceCodeResponse extends AbstractResponseType /** * {@inheritdoc} */ - public function generateHttpResponse(ResponseInterface $response) + public function generateHttpResponse(ResponseInterface $response): ResponseInterface { $expireDateTime = $this->deviceCode->getExpiryDateTime()->getTimestamp(); diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 3d1c29113..cc5bb374d 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -23,11 +23,13 @@ use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; +use LeagueTests\Stubs\GrantType; use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\ServerRequestInterface; use ReflectionClass; use function base64_encode; diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index d7efd1d37..0cfa10c56 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -5,7 +5,6 @@ namespace LeagueTests\Grant; use DateInterval; -use ReflectionClass; use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 537019568..b9d3469a8 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -291,6 +291,7 @@ public function testRespondToAccessTokenRequest() $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); @@ -447,6 +448,7 @@ public function testIssueSlowDownError() $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); @@ -505,6 +507,7 @@ function testIssueAuthorizationPendingError() $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); @@ -562,6 +565,7 @@ function testIssueExpiredTokenError() $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); @@ -666,6 +670,7 @@ public function testIssueAccessDeniedError(): void $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index bd2f95f77..90ab48489 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -15,7 +15,9 @@ use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; +use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use Psr\Http\Message\ServerRequestInterface; final class GrantType implements GrantTypeInterface @@ -101,4 +103,19 @@ public function setEncryptionKey(Key|string|null $key = null): void public function revokeRefreshTokens(bool $willRevoke): void { } + + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool + { + return true; + } + + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved) + { + return new BearerTokenResponse(); + } + + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse + { + return new DeviceCodeResponse(); + } } From 99835a62c64d9c3880e00c7a98a906ed637d7fa0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 1 Nov 2023 23:52:29 +0000 Subject: [PATCH 134/212] Fix tests and styling --- src/AuthorizationServer.php | 6 +- src/Entities/DeviceCodeEntityInterface.php | 7 +- src/Entities/Traits/DeviceCodeTrait.php | 5 +- src/Entities/Traits/TokenEntityTrait.php | 2 + src/Exception/OAuthServerException.php | 27 +++-- src/Grant/AbstractGrant.php | 4 +- src/Grant/DeviceCodeGrant.php | 59 ++++------ src/Grant/GrantTypeInterface.php | 12 +-- src/Middleware/DeviceGrantMiddleware.php | 15 ++- .../DeviceCodeRepositoryInterface.php | 21 ++-- .../DeviceAuthorizationRequest.php | 35 +++--- src/ResponseTypes/DeviceCodeResponse.php | 16 +-- tests/AuthorizationServerTest.php | 2 +- tests/Grant/DeviceCodeGrantTest.php | 102 +++++++++--------- .../DeviceCodeResponseTypeTest.php | 12 ++- tests/Stubs/DeviceCodeEntity.php | 6 +- tests/Stubs/GrantType.php | 5 +- 17 files changed, 158 insertions(+), 178 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 69242039f..59352c998 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -136,13 +136,9 @@ public function completeAuthorizationRequest( /** * Respond to device authorization request * - * @param ServerRequestInterface $request - * - * @return DeviceAuthorizationRequest - * * @throws OAuthServerException */ - public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request, ResponseInterface $response) + public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface { foreach ($this->enabledGrantTypes as $grantType) { if ($grantType->canRespondToDeviceAuthorizationRequest($request)) { diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index 07a824cea..50338b30d 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities; use DateTimeImmutable; @@ -15,11 +18,11 @@ interface DeviceCodeEntityInterface extends TokenInterface { public function getUserCode(): string; - public function setUserCode(string $userCode); + public function setUserCode(string $userCode): void; public function getVerificationUri(): string; - public function setVerificationUri(string $verificationUri); + public function setVerificationUri(string $verificationUri): void; public function getLastPolledAt(): ?DateTimeImmutable; diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index 0ea5160e3..7bec53027 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Entities\Traits; use DateTimeImmutable; @@ -37,7 +40,7 @@ public function getVerificationUri(): string return $this->verificationUri; } - public function setVerificationUri(string $verificationUri) + public function setVerificationUri(string $verificationUri): void { $this->verificationUri = $verificationUri; } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index f5bb07287..bccab025f 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -41,6 +41,8 @@ public function addScope(ScopeEntityInterface $scope): void /** * Return an array of scopes associated with the token. + * + * @return ScopeEntityInterface[] */ public function getScopes(): array { diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index a86453faa..b0b92fbaa 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -205,12 +205,11 @@ public function getErrorType(): string /** * Expired token error. * - * @param null|string $hint * @param Throwable $previous Previous exception * * @return static */ - public static function expiredToken($hint = null, Throwable $previous = null) + public static function expiredToken(?string $hint = null, Throwable $previous = null): static { $errorMessage = 'The `device_code` has expired and the device ' . 'authorization session has concluded.'; @@ -218,9 +217,9 @@ public static function expiredToken($hint = null, Throwable $previous = null) return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous); } - public static function authorizationPending($hint = '', Throwable $previous = null) + public static function authorizationPending(string $hint = '', Throwable $previous = null): static { - return new static ( + return new static( 'The authorization request is still pending as the end user ' . 'hasn\'t yet completed the user interaction steps. The client ' . 'SHOULD repeat the Access Token Request to the token endpoint', @@ -236,24 +235,22 @@ public static function authorizationPending($hint = '', Throwable $previous = nu /** * Slow down error used with the Device Authorization Grant. * - * @param string $hint - * @param Throwable $previous * * @return static */ - public static function slowDown($hint = '', Throwable $previous = null) + public static function slowDown(string $hint = '', Throwable $previous = null): static { return new static( - 'The authorization request is still pending and polling should ' . + 'The authorization request is still pending and polling should ' . 'continue, but the interval MUST be increased ' . 'by 5 seconds for this and all subsequent requests.', - 13, - 'slow_down', - 400, - $hint, - null, - $previous - ); + 13, + 'slow_down', + 400, + $hint, + null, + $previous + ); } /** diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 0923d8142..e04709b6d 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -537,7 +537,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth /** * {@inheritdoc} */ - public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request) + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool { return false; } @@ -553,7 +553,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved) + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved): void { throw new LogicException('This grant cannot complete a device authorization request'); } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 0ba5f7583..b19d56afc 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -1,4 +1,5 @@ setDeviceCodeRepository($deviceCodeRepository); $this->setRefreshTokenRepository($refreshTokenRepository); $this->refreshTokenTTL = new DateInterval('P1M'); - $this->deviceCodeTTL = $deviceCodeTTL; $this->setVerificationUri($verificationUri); - $this->retryInterval = $retryInterval; } /** * {@inheritdoc} */ - public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request) + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool { return true; } @@ -120,7 +122,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $approved) + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $approved): void { $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode($deviceCode); @@ -177,14 +179,11 @@ public function respondToAccessTokenRequest( } /** - * @param ServerRequestInterface $request - * @param ClientEntityInterface $client * * @throws OAuthServerException * - * @return DeviceCodeEntityInterface */ - protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client) + protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client): DeviceCodeEntityInterface { $encryptedDeviceCode = $this->getRequestParameter('device_code', $request); @@ -211,10 +210,10 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt } $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( - $deviceCodePayload->device_code_id, - $this->getIdentifier(), - $client - ); + $deviceCodePayload->device_code_id, + $this->getIdentifier(), + $client + ); if ($this->deviceCodePolledTooSoon($deviceCode->getLastPolledAt()) === true) { throw OAuthServerException::slowDown(); @@ -235,13 +234,11 @@ private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll): bool } /** - * @param string $encryptedDeviceCode * * @throws OAuthServerException * - * @return \stdClass */ - protected function decodeDeviceCode($encryptedDeviceCode) + protected function decodeDeviceCode(string $encryptedDeviceCode): stdClass { try { return json_decode($this->decrypt($encryptedDeviceCode)); @@ -253,9 +250,8 @@ protected function decodeDeviceCode($encryptedDeviceCode) /** * Set the verification uri * - * @param string $verificationUri */ - public function setVerificationUri($verificationUri) + public function setVerificationUri(string $verificationUri): void { $this->verificationUri = $verificationUri; } @@ -268,10 +264,7 @@ public function getIdentifier(): string return 'urn:ietf:params:oauth:grant-type:device_code'; } - /** - * @param DeviceCodeRepositoryInterface $deviceCodeRepository - */ - private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository) + private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCodeRepository): void { $this->deviceCodeRepository = $deviceCodeRepository; } @@ -279,12 +272,8 @@ private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCo /** * Issue a device code. * - * @param DateInterval $deviceCodeTTL - * @param ClientEntityInterface $client - * @param string $verificationUri * @param ScopeEntityInterface[] $scopes * - * @return DeviceCodeEntityInterface * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException @@ -292,9 +281,9 @@ private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCo protected function issueDeviceCode( DateInterval $deviceCodeTTL, ClientEntityInterface $client, - $verificationUri, + string $verificationUri, array $scopes = [] - ) { + ): DeviceCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $deviceCode = $this->deviceCodeRepository->getNewDeviceCode(); @@ -328,20 +317,18 @@ protected function issueDeviceCode( /** * Generate a new unique user code. * - * @param int $length * - * @return string * * @throws OAuthServerException */ - protected function generateUniqueUserCode($length = 8) + protected function generateUniqueUserCode(int $length = 8): string { try { $userCode = ''; $userCodeCharacters = 'BCDFGHJKLMNPQRSTVWXZ'; - while (\strlen($userCode) < $length) { - $userCode .= $userCodeCharacters[\random_int(0, 19)]; + while (strlen($userCode) < $length) { + $userCode .= $userCodeCharacters[random_int(0, 19)]; } return $userCode; diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index c65190a2e..0da580445 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -82,11 +82,9 @@ public function canRespondToAccessTokenRequest(ServerRequestInterface $request): /** * The grant type should return true if it is able to response to a device authorization request * - * @param ServerRequestInterface $request * - * @return bool */ - public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request); + public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool; /** * If the grant can respond to a device authorization request this method should be called to validate the parameters of @@ -103,13 +101,9 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ * If the grant can respond to a device authorization request this method should be called to validate the parameters of * the request. * - * If the validation is successful a DeviceCode object will be returned. - * - * @param DeviceAuthorizationRequest $deviceAuthorizationRequest - * - * @return ResponseTypeInterface + * If the validation is successful a DeviceCode object is persisted. */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved); + public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved): void; /** * Set the client repository. diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php index 6d1d2a3a4..9ea036b4d 100644 --- a/src/Middleware/DeviceGrantMiddleware.php +++ b/src/Middleware/DeviceGrantMiddleware.php @@ -1,5 +1,7 @@ deviceAuthorizationRequestRepository = $deviceAuthorizationRequestRepository; - $this->responseFactory = $responseFactory; } public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface @@ -32,7 +31,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $lastRequestTimeStamp = $this->deviceAuthorizationRequestRepository->getLast($deviceCode); // If the request is within the last 5 seconds, issue a slowdown notification - if ($lastRequestTimeStamp + 5 > \time()) { + if ($lastRequestTimeStamp + 5 > time()) { return OAuthServerException::slowDown()->generateHttpResponse($this->responseFactory->createResponse()); } diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 7d455f2f1..479ef0456 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -18,43 +21,37 @@ interface DeviceCodeRepositoryInterface extends RepositoryInterface /** * Creates a new DeviceCode * - * @return DeviceCodeEntityInterface */ - public function getNewDeviceCode(); + public function getNewDeviceCode(): DeviceCodeEntityInterface; /** * Persists a device code to permanent storage. * - * @param DeviceCodeEntityInterface $deviceCodeEntity * * @throws UniqueTokenIdentifierConstraintViolationException */ - public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity); + public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity): void; /** * Get a device code entity. * - * @param string $deviceCode * - * @return DeviceCodeEntityInterface|null */ public function getDeviceCodeEntityByDeviceCode( - $deviceCode - ); + string $deviceCode + ): ?DeviceCodeEntityInterface; /** * Revoke a device code. * - * @param string $codeId */ - public function revokeDeviceCode($codeId); + public function revokeDeviceCode(string $codeId): void; /** * Check if the device code has been revoked. * - * @param string $codeId * * @return bool Return true if this code has been revoked */ - public function isDeviceCodeRevoked($codeId); + public function isDeviceCodeRevoked(string $codeId): bool; } diff --git a/src/RequestTypes/DeviceAuthorizationRequest.php b/src/RequestTypes/DeviceAuthorizationRequest.php index 9168829c2..c4bf12197 100644 --- a/src/RequestTypes/DeviceAuthorizationRequest.php +++ b/src/RequestTypes/DeviceAuthorizationRequest.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace League\OAuth2\Server\RequestTypes; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -17,16 +20,14 @@ class DeviceAuthorizationRequest /** * The grant type identifier * - * @var string */ - protected $grantTypeId; + protected string $grantTypeId; /** * The client identifier * - * @var ClientEntityInterface */ - protected $client; + protected ClientEntityInterface $client; private bool $authorizationApproved; @@ -39,36 +40,24 @@ class DeviceAuthorizationRequest * * @var ScopeEntityInterface[] */ - protected $scopes = []; + protected array $scopes = []; - /** - * @return string - */ - public function getGrantTypeId() + public function getGrantTypeId(): string { return $this->grantTypeId; } - /** - * @param string $grantTypeId - */ - public function setGrantTypeId($grantTypeId) + public function setGrantTypeId(string $grantTypeId): void { $this->grantTypeId = $grantTypeId; } - /** - * @return ClientEntityInterface - */ - public function getClient() + public function getClient(): ClientEntityInterface { return $this->client; } - /** - * @param ClientEntityInterface $client - */ - public function setClient(ClientEntityInterface $client) + public function setClient(ClientEntityInterface $client): void { $this->client = $client; } @@ -76,7 +65,7 @@ public function setClient(ClientEntityInterface $client) /** * @return ScopeEntityInterface[] */ - public function getScopes() + public function getScopes(): array { return $this->scopes; } @@ -84,7 +73,7 @@ public function getScopes() /** * @param ScopeEntityInterface[] $scopes */ - public function setScopes(array $scopes) + public function setScopes(array $scopes): void { $this->scopes = $scopes; } diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index e8aa6a8ec..116b9ab01 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -1,4 +1,5 @@ deviceCode->getExpiryDateTime()->getTimestamp(); @@ -41,7 +45,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter $responseParams['interval'] = $this->deviceCode->getInterval(); } - $responseParams = \json_encode($responseParams); + $responseParams = json_encode($responseParams); if ($responseParams === false) { throw new LogicException('Error encountered JSON encoding response parameters'); @@ -58,7 +62,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter return $response; } - public function setPayload($payload) + public function setPayload(string $payload): void { $this->payload = $payload; } @@ -76,11 +80,9 @@ public function setDeviceCode(DeviceCodeEntityInterface $deviceCode) * AuthorizationServer::getResponseType() to pull in your version of * this class rather than the default. * - * @param DeviceCodeEntityInterface $deviceCode - * - * @return array + * @return mixed[] */ - protected function getExtraParams(DeviceCodeEntityInterface $deviceCode) + protected function getExtraParams(DeviceCodeEntityInterface $deviceCode): array { return []; } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index cc5bb374d..2438f9ccd 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -23,8 +23,8 @@ use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\AuthCodeEntity; -use LeagueTests\Stubs\GrantType; use LeagueTests\Stubs\ClientEntity; +use LeagueTests\Stubs\GrantType; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index b9d3469a8..5a933e969 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -1,5 +1,7 @@ cryptStub = new CryptTraitStub(); } - public function testGetIdentifier() + public function testGetIdentifier(): void { $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); @@ -59,7 +62,7 @@ public function testGetIdentifier() $this->assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); } - public function testCanRespondToDeviceAuthorizationRequest() + public function testCanRespondToDeviceAuthorizationRequest(): void { $grant = new DeviceCodeGrant( $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), @@ -76,7 +79,7 @@ public function testCanRespondToDeviceAuthorizationRequest() $this->assertTrue($grant->canRespondToDeviceAuthorizationRequest($request)); } - public function testRespondToDeviceAuthorizationRequest() + public function testRespondToDeviceAuthorizationRequest(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -112,7 +115,7 @@ public function testRespondToDeviceAuthorizationRequest() $this->assertInstanceOf(DeviceCodeResponse::class, $grant->respondToDeviceAuthorizationRequest($request)); } - public function testValidateDeviceAuthorizationRequestMissingClient() + public function testValidateDeviceAuthorizationRequestMissingClient(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -138,12 +141,12 @@ public function testValidateDeviceAuthorizationRequestMissingClient() 'scope' => 'basic', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToDeviceAuthorizationRequest($request); } - public function testValidateDeviceAuthorizationRequestEmptyScope() + public function testValidateDeviceAuthorizationRequestEmptyScope(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -169,12 +172,12 @@ public function testValidateDeviceAuthorizationRequestEmptyScope() 'scope' => '', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToDeviceAuthorizationRequest($request); } - public function testValidateDeviceAuthorizationRequestClientMismatch() + public function testValidateDeviceAuthorizationRequestClientMismatch(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn(null); @@ -198,12 +201,12 @@ public function testValidateDeviceAuthorizationRequestClientMismatch() 'scope' => 'basic', ]); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToDeviceAuthorizationRequest($request); } - public function testCompleteDeviceAuthorizationRequest() + public function testCompleteDeviceAuthorizationRequest(): void { $deviceCode = new DeviceCodeEntity(); $deviceCode->setUserCode('foo'); @@ -225,7 +228,7 @@ public function testCompleteDeviceAuthorizationRequest() $this->assertEquals('userId', $deviceCode->getUserIdentifier()); } - public function testDeviceAuthorizationResponse() + public function testDeviceAuthorizationResponse(): void { $client = new ClientEntity(); $client->setIdentifier('clientId'); @@ -235,7 +238,7 @@ public function testDeviceAuthorizationResponse() $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepository->method('getClientEntity')->willReturn($client); - $scopeEntity = new ScopeEntity; + $scopeEntity = new ScopeEntity(); $scopeEntity->setIdentifier('basic'); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); @@ -252,7 +255,7 @@ public function testDeviceAuthorizationResponse() $accessRepositoryMock, $scopeRepositoryMock, 'file://' . __DIR__ . '/../Stubs/private.key', - \base64_encode(\random_bytes(36)), + base64_encode(random_bytes(36)), new StubResponseType() ); @@ -263,11 +266,11 @@ public function testDeviceAuthorizationResponse() ]); $deviceCodeGrant = new DeviceCodeGrant( - $deviceCodeRepository, - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M'), - 'http://foo/bar' - ); + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M'), + 'http://foo/bar' + ); $deviceCodeGrant->setEncryptionKey($this->cryptStub->getKey()); @@ -285,7 +288,7 @@ public function testDeviceAuthorizationResponse() // TODO: $this->assertObjectHasAttribute('interval', $responseObject); } - public function testRespondToAccessTokenRequest() + public function testRespondToAccessTokenRequest(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -334,7 +337,7 @@ public function testRespondToAccessTokenRequest() $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', 'device_code' => $this->cryptStub->doEncrypt( - \json_encode( + json_encode( [ 'device_code_id' => uniqid(), 'expire_time' => time() + 3600, @@ -355,7 +358,7 @@ public function testRespondToAccessTokenRequest() $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } - public function testRespondToRequestMissingClient() + public function testRespondToRequestMissingClient(): void { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn(null); @@ -376,7 +379,7 @@ public function testRespondToRequestMissingClient() $serverRequest = (new ServerRequest())->withQueryParams([ 'device_code' => $this->cryptStub->doEncrypt( - \json_encode( + json_encode( [ 'device_code_id' => uniqid(), 'expire_time' => time() + 3600, @@ -391,12 +394,12 @@ public function testRespondToRequestMissingClient() $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testRespondToRequestMissingDeviceCode() + public function testRespondToRequestMissingDeviceCode(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -437,12 +440,12 @@ public function testRespondToRequestMissingDeviceCode() $responseType = new StubResponseType(); // TODO: We need to be more specific with this exception - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testIssueSlowDownError() + public function testIssueSlowDownError(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -480,7 +483,7 @@ public function testIssueSlowDownError() $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'device_code' => $this->cryptStub->doEncrypt( - \json_encode( + json_encode( [ 'device_code_id' => uniqid(), 'expire_time' => time() + 3600, @@ -495,13 +498,13 @@ public function testIssueSlowDownError() $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(13); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - function testIssueAuthorizationPendingError() + public function testIssueAuthorizationPendingError(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -538,7 +541,7 @@ function testIssueAuthorizationPendingError() $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'device_code' => $this->cryptStub->doEncrypt( - \json_encode( + json_encode( [ 'device_code_id' => uniqid(), 'expire_time' => time() + 3600, @@ -553,13 +556,13 @@ function testIssueAuthorizationPendingError() $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(12); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - function testIssueExpiredTokenError() + public function testIssueExpiredTokenError(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -596,7 +599,7 @@ function testIssueExpiredTokenError() $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'device_code' => $this->cryptStub->doEncrypt( - \json_encode( + json_encode( [ 'device_code_id' => uniqid(), 'expire_time' => time() - 3600, @@ -611,13 +614,13 @@ function testIssueExpiredTokenError() $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(11); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testIntervalVisibility() + public function testIntervalVisibility(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -689,11 +692,11 @@ public function testIssueAccessDeniedError(): void $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new DeviceCodeGrant( - $deviceCodeRepositoryMock, - $refreshTokenRepositoryMock, - new DateInterval('PT10M'), - 'http://foo/bar' - ); + $deviceCodeRepositoryMock, + $refreshTokenRepositoryMock, + new DateInterval('PT10M'), + 'http://foo/bar' + ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -706,7 +709,7 @@ public function testIssueAccessDeniedError(): void $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'device_code' => $this->cryptStub->doEncrypt( - \json_encode( + json_encode( [ 'device_code_id' => uniqid(), 'expire_time' => time() + 3600, @@ -715,16 +718,15 @@ public function testIssueAccessDeniedError(): void 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', ] - ) - ), + ) + ), ]); $responseType = new StubResponseType(); - $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectException(OAuthServerException::class); $this->expectExceptionCode(9); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - } } diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index 88044ca68..d90bcbbfe 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -1,5 +1,7 @@ setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $responseType->setEncryptionKey(\base64_encode(\random_bytes(36))); + $responseType->setEncryptionKey(base64_encode(random_bytes(36))); $client = new ClientEntity(); $client->setIdentifier('clientName'); @@ -48,7 +54,7 @@ public function testGenerateHttpResponse() $this->assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); $response->getBody()->rewind(); - $json = \json_decode($response->getBody()->getContents()); + $json = json_decode($response->getBody()->getContents()); $this->assertObjectHasProperty('expires_in', $json); $this->assertObjectHasProperty('device_code', $json); $this->assertEquals('test', $json->device_code); diff --git a/tests/Stubs/DeviceCodeEntity.php b/tests/Stubs/DeviceCodeEntity.php index d1d16e775..79a0b7b16 100644 --- a/tests/Stubs/DeviceCodeEntity.php +++ b/tests/Stubs/DeviceCodeEntity.php @@ -1,5 +1,7 @@ Date: Wed, 8 Nov 2023 08:40:01 -0500 Subject: [PATCH 135/212] Use GitHub Actions V4 --- .github/workflows/backwards-compatibility.yml | 4 ++-- .github/workflows/tests.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/backwards-compatibility.yml b/.github/workflows/backwards-compatibility.yml index cc1971dc9..f9da8f578 100644 --- a/.github/workflows/backwards-compatibility.yml +++ b/.github/workflows/backwards-compatibility.yml @@ -11,7 +11,7 @@ jobs: steps: - name: "Checkout" - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" with: fetch-depth: 0 - name: Fix git safe.directory in container @@ -19,4 +19,4 @@ jobs: - name: "Backwards Compatibility Check" uses: docker://nyholm/roave-bc-check-ga with: - args: --from=${{ github.event.pull_request.base.sha }} \ No newline at end of file + args: --from=${{ github.event.pull_request.base.sha }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0b08e41ef..dda52c3c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 From d7045af73b036f6cac68e38c218eef5f8ad68e2f Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 30 Nov 2023 22:22:47 +0000 Subject: [PATCH 136/212] Fix tests --- src/EventEmitting/EmitterAwareInterface.php | 5 +---- src/EventEmitting/EmitterAwarePolyfill.php | 11 ++--------- tests/Stubs/GrantType.php | 8 ++++---- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/EventEmitting/EmitterAwareInterface.php b/src/EventEmitting/EmitterAwareInterface.php index af192ab25..206428a1e 100644 --- a/src/EventEmitting/EmitterAwareInterface.php +++ b/src/EventEmitting/EmitterAwareInterface.php @@ -8,8 +8,5 @@ interface EmitterAwareInterface { public function getEmitter(): EventEmitter; - /** - * @return $this - */ - public function setEmitter(EventEmitter $emitter); + public function setEmitter(EventEmitter $emitter): self; } diff --git a/src/EventEmitting/EmitterAwarePolyfill.php b/src/EventEmitting/EmitterAwarePolyfill.php index 59225d12e..473f49cee 100644 --- a/src/EventEmitting/EmitterAwarePolyfill.php +++ b/src/EventEmitting/EmitterAwarePolyfill.php @@ -13,17 +13,10 @@ trait EmitterAwarePolyfill public function getEmitter(): EventEmitter { - if (!$this->emitter) { - $this->emitter = new EventEmitter(); - } - - return $this->emitter; + return $this->emitter ?? new EventEmitter(); } - /** - * @return $this - */ - public function setEmitter(EventEmitter $emitter) + public function setEmitter(EventEmitter $emitter): self { $this->emitter = $emitter; diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index bd2f95f77..4aa56b870 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -6,7 +6,7 @@ use DateInterval; use Defuse\Crypto\Key; -use League\Event\EmitterInterface; +use League\OAuth2\Server\EventEmitting\EventEmitter; use League\OAuth2\Server\CryptKeyInterface; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -20,16 +20,16 @@ final class GrantType implements GrantTypeInterface { - private ?EmitterInterface $emitter; + private EventEmitter $emitter; - public function setEmitter(EmitterInterface $emitter = null): self + public function setEmitter(EventEmitter $emitter = null): self { $this->emitter = $emitter; return $this; } - public function getEmitter(): ?EmitterInterface + public function getEmitter(): EventEmitter { return $this->emitter; } From 48625162537841d1e35e374aad3be591d562fcd0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 30 Nov 2023 22:23:19 +0000 Subject: [PATCH 137/212] Fix CS issue --- tests/Stubs/GrantType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index 4aa56b870..d81c0c271 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -6,8 +6,8 @@ use DateInterval; use Defuse\Crypto\Key; -use League\OAuth2\Server\EventEmitting\EventEmitter; use League\OAuth2\Server\CryptKeyInterface; +use League\OAuth2\Server\EventEmitting\EventEmitter; use League\OAuth2\Server\Grant\GrantTypeInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; From 637fa67e29a4cb74b5a0ee45da706e9e47b22c30 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 9 Dec 2023 22:58:33 +0000 Subject: [PATCH 138/212] Remove todo --- src/Grant/AbstractGrant.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 340586f44..d87db74fc 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -183,8 +183,6 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity * doesn't actually enforce non-null returns/exception-on-no-client so * getClientEntity might return null. By contrast, this method will * always either return a ClientEntityInterface or throw. - * - * TODO: Check if we still need this */ protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface { From 9cab352523e1a7bcf021a1e3502b3fd62a1235cd Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sat, 9 Dec 2023 23:24:33 +0000 Subject: [PATCH 139/212] Replace __toString in access token entity with non-magic toString --- CHANGELOG.md | 1 + src/Entities/AccessTokenEntityInterface.php | 2 +- src/Entities/Traits/AccessTokenTrait.php | 4 +--- src/Grant/ImplicitGrant.php | 2 +- src/ResponseTypes/BearerTokenResponse.php | 2 +- tests/Middleware/ResourceServerMiddlewareTest.php | 4 ++-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d4a4dfde..bf098c892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) - Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112) +- AccessTokenEntityInterface now requires the implementation of `toString()` instead of the magic method `__toString()` (PR #XXXX) ### Removed - Removed message property from OAuthException HTTP response. Now just use error_description as per the OAuth 2 spec (PR #1375) diff --git a/src/Entities/AccessTokenEntityInterface.php b/src/Entities/AccessTokenEntityInterface.php index 6eb3e7f6d..3c998b4d2 100644 --- a/src/Entities/AccessTokenEntityInterface.php +++ b/src/Entities/AccessTokenEntityInterface.php @@ -24,5 +24,5 @@ public function setPrivateKey(CryptKeyInterface $privateKey): void; /** * Generate a string representation of the access token. */ - public function __toString(): string; + public function toString(): string; } diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 9d9fe4a65..2e1390412 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -74,10 +74,8 @@ private function convertToJWT(): Token /** * Generate a string representation from the access token - * - * TODO: Want to remove this function. */ - public function __toString(): string + public function toString(): string { return $this->convertToJWT()->toString(); } diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 8c2328b60..7723ab8b1 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -180,7 +180,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth $this->makeRedirectUri( $finalRedirectUri, [ - 'access_token' => (string) $accessToken, + 'access_token' => $accessToken->toString(), 'token_type' => 'Bearer', 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - time(), 'state' => $authorizationRequest->getState(), diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index f738a4e27..0c6107b9e 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -31,7 +31,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter $responseParams = [ 'token_type' => 'Bearer', 'expires_in' => $expireDateTime - time(), - 'access_token' => (string) $this->accessToken, + 'access_token' => $this->accessToken->toString(), ]; $refreshTokenPayload = json_encode([ diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 119d43ae0..26198fbc1 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -38,7 +38,7 @@ public function testValidResponse(): void $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $token = (string) $accessToken; + $token = $accessToken->toString(); $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token)); @@ -73,7 +73,7 @@ public function testValidResponseExpiredToken(): void $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $token = (string) $accessToken; + $token = $accessToken->toString(); $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token)); From 7ff6616d108fb2450e9ead5fd9004ec89a22d38e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 2 Jan 2024 23:11:25 +0000 Subject: [PATCH 140/212] Fix errors --- phpstan.neon.dist | 1 - .../BearerTokenValidator.php | 18 +++++------------- src/Entities/ClientEntityInterface.php | 2 ++ src/Entities/TokenInterface.php | 4 +++- src/Entities/Traits/AccessTokenTrait.php | 10 ++++++++-- src/Entities/Traits/EntityTrait.php | 6 ++++++ src/Entities/Traits/TokenEntityTrait.php | 13 +++++++++---- src/Grant/AbstractGrant.php | 1 + .../RedirectUriValidator.php | 4 ++-- tests/Grant/ImplicitGrantTest.php | 2 ++ .../ResourceServerMiddlewareTest.php | 4 ++-- tests/ResponseTypes/BearerResponseTypeTest.php | 8 +++++--- tests/Stubs/GrantType.php | 2 +- 13 files changed, 46 insertions(+), 29 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 2473d6a93..7bef99433 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,4 +7,3 @@ parameters: - message: '#Call to an undefined method League\\OAuth2\\Server\\ResponseTypes\\ResponseTypeInterface::getAccessToken\(\)\.#' path: tests/Grant/ClientCredentialsGrantTest.php - - '#Return type \(League\\Event\\EmitterInterface\|null\) of method LeagueTests\\Stubs\\GrantType::getEmitter\(\) should be covariant with return type \(League\\Event\\EmitterInterface\) of method League\\Event\\EmitterAwareInterface::getEmitter\(\)#' diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 84b7ba484..afe4ee6f9 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -97,6 +97,10 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe $header = $request->getHeader('authorization'); $jwt = trim((string) preg_replace('/^\s*Bearer\s/', '', $header[0])); + if ($jwt === '') { + throw OAuthServerException::accessDenied('Missing "Bearer" token'); + } + try { // Attempt to parse the JWT $token = $this->jwtConfiguration->parser()->parse($jwt); @@ -126,20 +130,8 @@ public function validateAuthorization(ServerRequestInterface $request): ServerRe // Return the request with additional attributes return $request ->withAttribute('oauth_access_token_id', $claims->get('jti')) - ->withAttribute('oauth_client_id', $this->convertSingleRecordAudToString($claims->get('aud'))) + ->withAttribute('oauth_client_id', $claims->get('aud')[0]) ->withAttribute('oauth_user_id', $claims->get('sub')) ->withAttribute('oauth_scopes', $claims->get('scopes')); } - - /** - * Convert single record arrays into strings to ensure backwards compatibility between v4 and v3.x of lcobucci/jwt - * - * TODO: Investigate as I don't think we need this any more - * - * @return array|string - */ - private function convertSingleRecordAudToString(mixed $aud): array|string - { - return is_array($aud) && count($aud) === 1 ? $aud[0] : $aud; - } } diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index 9b1656679..f3838b11c 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -16,6 +16,8 @@ interface ClientEntityInterface { /** * Get the client's identifier. + * + * @return non-empty-string */ public function getIdentifier(): string; diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 5434ff00e..96d2bd418 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -38,8 +38,10 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void; /** * Set the identifier of the user associated with the token. + * + * @param non-empty-string $identifier */ - public function setUserIdentifier(string|int|null $identifier): void; + public function setUserIdentifier(string $identifier): void; /** * Get the token user's identifier. diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 2e1390412..92bbd2d1a 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -67,7 +67,7 @@ private function convertToJWT(): Token ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($this->getExpiryDateTime()) - ->relatedTo((string) $this->getUserIdentifier()) + ->relatedTo($this->getUserIdentifier()) ->withClaim('scopes', $this->getScopes()) ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); } @@ -84,12 +84,18 @@ abstract public function getClient(): ClientEntityInterface; abstract public function getExpiryDateTime(): DateTimeImmutable; - abstract public function getUserIdentifier(): string|int|null; + /** + * @return non-empty-string + */ + abstract public function getUserIdentifier(): string; /** * @return ScopeEntityInterface[] */ abstract public function getScopes(): array; + /** + * @return non-empty-string + */ abstract public function getIdentifier(): string; } diff --git a/src/Entities/Traits/EntityTrait.php b/src/Entities/Traits/EntityTrait.php index 8b3db91d4..87a8f6cf6 100644 --- a/src/Entities/Traits/EntityTrait.php +++ b/src/Entities/Traits/EntityTrait.php @@ -14,8 +14,14 @@ trait EntityTrait { + /** + * @var non-empty-string + */ protected string $identifier; + /** + * @return non-empty-string + */ public function getIdentifier(): string { return $this->identifier; diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 06ef15fb1..0558ea360 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -27,7 +27,10 @@ trait TokenEntityTrait protected DateTimeImmutable $expiryDateTime; - protected string|int|null $userIdentifier = null; + /** + * @var non-empty-string + */ + protected string $userIdentifier; protected ClientEntityInterface $client; @@ -68,17 +71,19 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void /** * Set the identifier of the user associated with the token. * - * @param string|int|null $identifier The identifier of the user + * @param non-empty-string $identifier The identifier of the user */ - public function setUserIdentifier(string|int|null $identifier): void + public function setUserIdentifier(string $identifier): void { $this->userIdentifier = $identifier; } /** * Get the token user's identifier. + * + * @return non-empty-string */ - public function getUserIdentifier(): string|int|null + public function getUserIdentifier(): string { return $this->userIdentifier; } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index d87db74fc..b6f7f0f3a 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -392,6 +392,7 @@ protected function issueAccessToken( /** * Issue an auth code. * + * @param non-empty-string $userIdentifier * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException diff --git a/src/RedirectUriValidators/RedirectUriValidator.php b/src/RedirectUriValidators/RedirectUriValidator.php index cbc530adf..dd4b56834 100644 --- a/src/RedirectUriValidators/RedirectUriValidator.php +++ b/src/RedirectUriValidators/RedirectUriValidator.php @@ -61,7 +61,7 @@ public function validateRedirectUri(string $redirectUri): bool private function isLoopbackUri(string $redirectUri): bool { try { - $uri = Uri::createFromString($redirectUri); + $uri = Uri::new($redirectUri); } catch (SyntaxError $e) { return false; } @@ -99,7 +99,7 @@ private function matchUriExcludingPort(string $redirectUri): bool */ private function parseUrlAndRemovePort(string $url): string { - $uri = Uri::createFromString($url); + $uri = Uri::new($url); return (string) $uri->withPort(null); } diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 36a5294c8..31d85a8aa 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -219,6 +219,7 @@ public function testCompleteAuthorizationRequest(): void $accessToken = new AccessTokenEntity(); $accessToken->setClient($client); + $accessToken->setUserIdentifier('userId'); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); @@ -279,6 +280,7 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void $accessToken = new AccessTokenEntity(); $accessToken->setClient($client); + $accessToken->setUserIdentifier('userId'); /** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 26198fbc1..4a6d3b79e 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -33,7 +33,7 @@ public function testValidResponse(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('test'); - $accessToken->setUserIdentifier(123); + $accessToken->setUserIdentifier('123'); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -68,7 +68,7 @@ public function testValidResponseExpiredToken(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('test'); - $accessToken->setUserIdentifier(123); + $accessToken->setUserIdentifier('123'); $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 64c040f57..386fb628b 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -44,6 +44,7 @@ public function testGenerateHttpResponse(): void $accessToken->setClient($client); $accessToken->addScope($scope); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setUserIdentifier('userId'); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -86,6 +87,7 @@ public function testGenerateHttpResponseWithExtraParams(): void $accessToken->setClient($client); $accessToken->addScope($scope); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $accessToken->setUserIdentifier('userId'); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); @@ -124,7 +126,7 @@ public function testDetermineAccessTokenInHeaderValidToken(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setUserIdentifier(123); + $accessToken->setUserIdentifier('123'); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -169,7 +171,7 @@ public function testDetermineAccessTokenInHeaderInvalidJWT(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setUserIdentifier(123); + $accessToken->setUserIdentifier('123'); $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); @@ -211,7 +213,7 @@ public function testDetermineAccessTokenInHeaderRevokedToken(): void $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setUserIdentifier(123); + $accessToken->setUserIdentifier('123'); $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index d81c0c271..0b0f2125b 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -22,7 +22,7 @@ final class GrantType implements GrantTypeInterface { private EventEmitter $emitter; - public function setEmitter(EventEmitter $emitter = null): self + public function setEmitter(EventEmitter $emitter): self { $this->emitter = $emitter; From 56234530131c6cb08c02133c8e64f0b9f6a0ea56 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 15:17:51 +0000 Subject: [PATCH 141/212] Add static analysis action --- .github/workflows/static-analysis.yml | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/static-analysis.yml diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 000000000..c6c01d4ea --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,37 @@ +name: Static Analysis + +on: + push: + pull_request: + +jobs: + static-analysis: + name: Static Analysis + + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + php-version: [8.1, 8.2, 8.3] + composer-stability: [prefer-lowest, prefer-stable] + operating-system: + - ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + coverage: none + php-version: ${{ matrix.php-version }} + ini-values: memory_limit=-1 + tools: composer:v2, cs2pr + + - name: Install Dependencies + run: composer update --${{ matrix.composer-stability }} --prefer-dist --no-interaction --no-progress + + - name: Run Static Analysis + run: vendor/bin/phpstan analyse + From bf58ebe85556bab5c495521b90a0f269c3c903f5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 15:23:18 +0000 Subject: [PATCH 142/212] Remove dependency on league/uri v6 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 10e321021..32e26daae 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ "php": "^8.0", "ext-openssl": "*", "league/event": "^3.0", - "league/uri": "^6.7 || ^7.0", + "league/uri": "^7.0", "lcobucci/jwt": "^4.3 || ^5.0", "psr/http-message": "^1.0.1 || ^2.0", "defuse/php-encryption": "^2.3.1", From a6e767bd4bd06acbae43220accbe7afa1a57ca0c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 15:47:31 +0000 Subject: [PATCH 143/212] Add a coding standard workflow to gh actions --- .github/workflows/coding-standards.yml | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/coding-standards.yml diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml new file mode 100644 index 000000000..f882b8eaa --- /dev/null +++ b/.github/workflows/coding-standards.yml @@ -0,0 +1,33 @@ +name: Coding Standards + +on: + pull_request: + push: + +jobs: + coding-standards: + name: Coding Standards + + runs-on: ${{ matrix.operating-system }} + + strategy: + matrix: + php-version: + - 8.3 + operating-system: + - ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivamathur/setup-php@v2 + with: + coverage: none + php-version: ${{ matrix.php-version }} + ini-values: memory_limit=-1 + tools: composer:v2, cs2pr + + - name: Run Codesniffer + run: vendor/bin/phpcs From dbec4e5548fa17852f5b40e295da311a398126b4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 15:49:03 +0000 Subject: [PATCH 144/212] Fix typo in gh action repo --- .github/workflows/coding-standards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index f882b8eaa..a158292e1 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Install PHP - uses: shivamathur/setup-php@v2 + uses: shivammathur/setup-php@v2 with: coverage: none php-version: ${{ matrix.php-version }} From 4f85989596e7eaaeb8defef12e1aa53cffcc9a27 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 15:53:07 +0000 Subject: [PATCH 145/212] Install dependencies in workflow --- .github/workflows/coding-standards.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index a158292e1..5818c08ee 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -28,6 +28,9 @@ jobs: php-version: ${{ matrix.php-version }} ini-values: memory_limit=-1 tools: composer:v2, cs2pr + + - name: Install Dependencies + run: composer update --${{ matrix.composer-stability }} --prefer-dist --no-interaction --no-progress - name: Run Codesniffer run: vendor/bin/phpcs From 1006268ca254bd6cf5f0ab82d9a80249b7a83a3e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 15:57:51 +0000 Subject: [PATCH 146/212] Fix reference to non-existent matrix var --- .github/workflows/coding-standards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coding-standards.yml b/.github/workflows/coding-standards.yml index 5818c08ee..e5c9be702 100644 --- a/.github/workflows/coding-standards.yml +++ b/.github/workflows/coding-standards.yml @@ -30,7 +30,7 @@ jobs: tools: composer:v2, cs2pr - name: Install Dependencies - run: composer update --${{ matrix.composer-stability }} --prefer-dist --no-interaction --no-progress + run: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - name: Run Codesniffer run: vendor/bin/phpcs From b620c4391a36bc9cf0c0a4f244e4a3e429c5c1fe Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 16:38:26 +0000 Subject: [PATCH 147/212] Fix phpcs rules --- phpcs.xml.dist | 1 - 1 file changed, 1 deletion(-) diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 6a33bb3c5..f039e3e88 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -20,7 +20,6 @@ - From 4fe0a2c9258ea4c704ca45f75e8dfd991a198140 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 7 Jan 2024 16:52:43 +0000 Subject: [PATCH 148/212] Remove PHP 8.0 test support --- .github/workflows/tests.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ade0c245b..9c6685119 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,16 +11,9 @@ jobs: strategy: fail-fast: false matrix: - php: [8.1, 8.2] + php: [8.1, 8.2, 8.3] os: [ubuntu-22.04] stability: [prefer-lowest, prefer-stable] - include: - - os: ubuntu-20.04 - php: 8.0 - stability: prefer-lowest - - os: ubuntu-20.04 - php: 8.0 - stability: prefer-stable runs-on: ${{ matrix.os }} From b01e6137f5f7678dedfa428f5555573ea9508646 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 8 Jan 2024 21:08:42 +0000 Subject: [PATCH 149/212] Update dependencies --- composer.json | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index 32e26daae..b997d5627 100644 --- a/composer.json +++ b/composer.json @@ -4,27 +4,27 @@ "homepage": "https://oauth2.thephpleague.com/", "license": "MIT", "require": { - "php": "^8.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", "ext-openssl": "*", "league/event": "^3.0", "league/uri": "^7.0", - "lcobucci/jwt": "^4.3 || ^5.0", - "psr/http-message": "^1.0.1 || ^2.0", - "defuse/php-encryption": "^2.3.1", - "lcobucci/clock": "^2.2 || ^3.0" + "lcobucci/jwt": "^5.0", + "psr/http-message": "^2.0", + "defuse/php-encryption": "^2.4", + "lcobucci/clock": "^2.3 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "^9.6.11", - "laminas/laminas-diactoros": "^3.0.0", - "phpstan/phpstan": "^1.10.26", - "phpstan/phpstan-phpunit": "^1.3.14", + "phpunit/phpunit": "^10.5", + "laminas/laminas-diactoros": "^3.3.0", + "phpstan/phpstan": "^1.10.55", + "phpstan/phpstan-phpunit": "^1.3.15", "roave/security-advisories": "dev-master", - "phpstan/extension-installer": "^1.3", - "phpstan/phpstan-deprecation-rules": "^1.1", - "phpstan/phpstan-strict-rules": "^1.5", - "slevomat/coding-standard": "^8.13", - "php-parallel-lint/php-parallel-lint": "^1.3", - "squizlabs/php_codesniffer": "^3.7" + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan-deprecation-rules": "^1.1.4", + "phpstan/phpstan-strict-rules": "^1.5.2", + "slevomat/coding-standard": "^8.14.1", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "squizlabs/php_codesniffer": "^3.8" }, "repositories": [ { From c8a3493f3bbb4f65849e713536c3fc7679e7284e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 8 Jan 2024 21:25:10 +0000 Subject: [PATCH 150/212] Remove verbose mode from PHPUnit --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9c6685119..a1625e132 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -41,7 +41,7 @@ jobs: composer global require scrutinizer/ocular - name: Execute tests - run: vendor/bin/phpunit --verbose --coverage-clover=coverage.clover + run: vendor/bin/phpunit --coverage-clover=coverage.clover - name: Code coverage if: ${{ github.ref == 'refs/heads/master' && github.repository == 'thephpleague/oauth2-server' }} From f5ffc219c05f3b8c7b96e1e720414a60e9a7c69c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 8 Jan 2024 21:43:21 +0000 Subject: [PATCH 151/212] Remove getInvocationCount method --- phpunit.xml.dist | 20 ++++++-------------- tests/Grant/AuthCodeGrantTest.php | 20 ++++++++++---------- tests/Grant/ImplicitGrantTest.php | 10 +++++----- 3 files changed, 21 insertions(+), 29 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9ab509138..52b17a4a7 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,21 +1,13 @@ - - - - src - - + ./tests/ + + + src + + diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 1a58983ae..61b672d78 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -1893,13 +1893,13 @@ public function testAuthCodeRepositoryUniqueConstraintCheck(): void $authCodeRepository = $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(); $authCodeRepository->method('getNewAuthCode')->willReturn(new AuthCodeEntity()); - $matcher = self::exactly(2); - $authCodeRepository - ->expects($matcher) + ->expects(self::exactly(2)) ->method('persistNewAuthCode') - ->willReturnCallback(function () use ($matcher): void { - if ($matcher->getInvocationCount() === 1) { + ->willReturnCallback(function (): void { + static $counter = 0; + + if (1 === ++$counter) { throw UniqueTokenIdentifierConstraintViolationException::create(); } }); @@ -1992,13 +1992,13 @@ public function testRefreshTokenRepositoryUniqueConstraintCheck(): void $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); - $matcher = self::exactly(2); - $refreshTokenRepositoryMock - ->expects($matcher) + ->expects(self::exactly(2)) ->method('persistNewRefreshToken') - ->willReturnCallback(function () use ($matcher): void { - if ($matcher->getInvocationCount() === 1) { + ->willReturnCallback(function (): void { + static $count = 0; + + if (1 === ++$count) { throw UniqueTokenIdentifierConstraintViolationException::create(); } }); diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 31d85a8aa..515629247 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -286,13 +286,13 @@ public function testAccessTokenRepositoryUniqueConstraintCheck(): void $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); - $matcher = self::exactly(2); - $accessTokenRepositoryMock - ->expects($matcher) + ->expects(self::exactly(2)) ->method('persistNewAccessToken') - ->willReturnCallback(function () use ($matcher): void { - if ($matcher->getInvocationCount() === 1) { + ->willReturnCallback(function (): void { + static $count = 0; + + if (1 === ++$count) { throw UniqueTokenIdentifierConstraintViolationException::create(); } }); From 667e4c83fdc332bbdbfa5a9401e65d088de08735 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 8 Jan 2024 22:06:43 +0000 Subject: [PATCH 152/212] Revert upgrade of PHPUnit to 10.x --- composer.json | 2 +- phpunit.xml.dist | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index b997d5627..9b2633e47 100644 --- a/composer.json +++ b/composer.json @@ -14,7 +14,7 @@ "lcobucci/clock": "^2.3 || ^3.0" }, "require-dev": { - "phpunit/phpunit": "^10.5", + "phpunit/phpunit": "^9.6.15", "laminas/laminas-diactoros": "^3.3.0", "phpstan/phpstan": "^1.10.55", "phpstan/phpstan-phpunit": "^1.3.15", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 52b17a4a7..9ab509138 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,13 +1,21 @@ - + + + + src + + ./tests/ - - - src - - From 29122dbc4a145b54b5aad9bb8e02cb72d8e78343 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 10 Jan 2024 22:07:54 +0000 Subject: [PATCH 153/212] Fix composer file --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2d46b380a..c42f302e1 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "psr/http-message": "^2.0", "defuse/php-encryption": "^2.4", "ext-json": "*", - "lcobucci/clock": "^2.3 || ^3.0" + "lcobucci/clock": "^2.3 || ^3.0", "psr/http-server-middleware": "^1.0", }, "require-dev": { From b6e28fa2c2f9ea04d8fa3114b0a0eeeeb1906ee0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 10 Jan 2024 22:08:52 +0000 Subject: [PATCH 154/212] actually fix composer file --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c42f302e1..9376506a2 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "defuse/php-encryption": "^2.4", "ext-json": "*", "lcobucci/clock": "^2.3 || ^3.0", - "psr/http-server-middleware": "^1.0", + "psr/http-server-middleware": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.6.15", From 4aaf7aae40352102c75f892e1521222dad629d53 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 10 Jan 2024 22:18:08 +0000 Subject: [PATCH 155/212] Fix docblock comment merge error --- src/Entities/TokenInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 1916ad2f7..6082e57e1 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -24,8 +24,11 @@ public function getExpiryDateTime(): DateTimeImmutable; public function setExpiryDateTime(DateTimeImmutable $dateTime): void; + /** + * Set the identifier of the user associated with the token. * * @param non-empty-string $identifier + */ public function setUserIdentifier(string $identifier): void; public function getUserIdentifier(): string|int|null; From 93157f4fadf195361102e8d81fec4441f23f25b8 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 10 Jan 2024 23:09:01 +0000 Subject: [PATCH 156/212] Fix typing error --- src/Entities/Traits/AccessTokenTrait.php | 5 +---- src/Entities/Traits/TokenEntityTrait.php | 9 +++------ tests/Grant/DeviceCodeGrantTest.php | 4 ++-- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 92bbd2d1a..ac7e6a29b 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -84,10 +84,7 @@ abstract public function getClient(): ClientEntityInterface; abstract public function getExpiryDateTime(): DateTimeImmutable; - /** - * @return non-empty-string - */ - abstract public function getUserIdentifier(): string; + abstract public function getUserIdentifier(): string|null; /** * @return ScopeEntityInterface[] diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index b1d3bd200..a6daed8ce 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -27,10 +27,7 @@ trait TokenEntityTrait protected DateTimeImmutable $expiryDateTime; - /** - * @var non-empty-string - */ - protected string $userIdentifier; + protected string|null $userIdentifier = null; protected ClientEntityInterface $client; @@ -70,6 +67,7 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void /** * Set the identifier of the user associated with the token. + * * @param non-empty-string $identifier The identifier of the user */ public function setUserIdentifier(string $identifier): void @@ -80,9 +78,8 @@ public function setUserIdentifier(string $identifier): void /** * Get the token user's identifier. * - * @return non-empty-string */ - public function getUserIdentifier(): string + public function getUserIdentifier(): string|null { return $this->userIdentifier; } diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 5a933e969..d0ba9cfb1 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -332,7 +332,7 @@ public function testRespondToAccessTokenRequest(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 1, true); + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), "1", true); $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', @@ -704,7 +704,7 @@ public function testIssueAccessDeniedError(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 1, false); + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), "1", false); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', From 76cb8860ab10403bcfd39239a9cff27947d6e816 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 17 Jan 2024 23:28:47 +0000 Subject: [PATCH 157/212] Fix PHPStan errors --- src/AuthorizationServer.php | 1 - src/Grant/AbstractGrant.php | 3 +- src/Grant/DeviceCodeGrant.php | 28 ++++-- src/Grant/GrantTypeInterface.php | 3 +- src/Middleware/DeviceGrantMiddleware.php | 40 -------- .../DeviceCodeRepositoryInterface.php | 3 - .../DeviceAuthorizationRequest.php | 95 ------------------- src/ResponseTypes/DeviceCodeResponse.php | 2 +- tests/Grant/DeviceCodeGrantTest.php | 53 ++++++----- .../DeviceCodeResponseTypeTest.php | 19 ++-- tests/Stubs/GrantType.php | 1 - 11 files changed, 62 insertions(+), 186 deletions(-) delete mode 100644 src/Middleware/DeviceGrantMiddleware.php delete mode 100644 src/RequestTypes/DeviceAuthorizationRequest.php diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 31c9f731d..308fa2197 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -22,7 +22,6 @@ use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\AbstractResponseType; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index d9fb57b4b..c11be4d77 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -38,7 +38,6 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -552,7 +551,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void { throw new LogicException('This grant cannot complete a device authorization request'); } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index b19d56afc..91f01db67 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -26,7 +26,6 @@ use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -122,10 +121,19 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $approved): void + // TODO: Make sure this cant be abused to try and brute force a device code + public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $approved): void { $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode($deviceCode); + if ($deviceCode instanceof DeviceCodeEntityInterface === false) { + throw OAuthServerException::invalidRequest('device_code', 'Device code does not exist'); + } + + if ($userId === '') { + throw OAuthServerException::invalidRequest('user_id', 'User ID is required'); + } + $deviceCode->setUserIdentifier($userId); $deviceCode->setUserApproved($approved); @@ -210,21 +218,19 @@ protected function validateDeviceCode(ServerRequestInterface $request, ClientEnt } $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( - $deviceCodePayload->device_code_id, - $this->getIdentifier(), - $client + $deviceCodePayload->device_code_id ); - if ($this->deviceCodePolledTooSoon($deviceCode->getLastPolledAt()) === true) { - throw OAuthServerException::slowDown(); - } - if ($deviceCode instanceof DeviceCodeEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidGrant(); } + if ($this->deviceCodePolledTooSoon($deviceCode->getLastPolledAt()) === true) { + throw OAuthServerException::slowDown(); + } + return $deviceCode; } @@ -312,6 +318,10 @@ protected function issueDeviceCode( } } } + + + // This should never be hit. It is here to work around a PHPStan false error + return $deviceCode; } /** diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index c95a0da60..49528945d 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -22,7 +22,6 @@ use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -103,7 +102,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ * * If the validation is successful a DeviceCode object is persisted. */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved): void; + public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void; /** * Set the client repository. diff --git a/src/Middleware/DeviceGrantMiddleware.php b/src/Middleware/DeviceGrantMiddleware.php deleted file mode 100644 index 9ea036b4d..000000000 --- a/src/Middleware/DeviceGrantMiddleware.php +++ /dev/null @@ -1,40 +0,0 @@ -getQueryParams(); - $deviceCode = $queryParameters['device_code']; - - // Get the last timestamp this client requested an access code - $lastRequestTimeStamp = $this->deviceAuthorizationRequestRepository->getLast($deviceCode); - - // If the request is within the last 5 seconds, issue a slowdown notification - if ($lastRequestTimeStamp + 5 > time()) { - return OAuthServerException::slowDown()->generateHttpResponse($this->responseFactory->createResponse()); - } - - return $handler->handle($request); - } -} diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 479ef0456..46e9492ff 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -27,15 +27,12 @@ public function getNewDeviceCode(): DeviceCodeEntityInterface; /** * Persists a device code to permanent storage. * - * * @throws UniqueTokenIdentifierConstraintViolationException */ public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity): void; /** * Get a device code entity. - * - * */ public function getDeviceCodeEntityByDeviceCode( string $deviceCode diff --git a/src/RequestTypes/DeviceAuthorizationRequest.php b/src/RequestTypes/DeviceAuthorizationRequest.php deleted file mode 100644 index c4bf12197..000000000 --- a/src/RequestTypes/DeviceAuthorizationRequest.php +++ /dev/null @@ -1,95 +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\RequestTypes; - -use League\OAuth2\Server\Entities\ClientEntityInterface; -use League\OAuth2\Server\Entities\ScopeEntityInterface; - -class DeviceAuthorizationRequest -{ - /** - * The grant type identifier - * - */ - protected string $grantTypeId; - - /** - * The client identifier - * - */ - protected ClientEntityInterface $client; - - private bool $authorizationApproved; - - private string $userCode; - - private string|int $userIdentifier; - - /** - * An array of scope identifiers - * - * @var ScopeEntityInterface[] - */ - protected array $scopes = []; - - public function getGrantTypeId(): string - { - return $this->grantTypeId; - } - - public function setGrantTypeId(string $grantTypeId): void - { - $this->grantTypeId = $grantTypeId; - } - - public function getClient(): ClientEntityInterface - { - return $this->client; - } - - public function setClient(ClientEntityInterface $client): void - { - $this->client = $client; - } - - /** - * @return ScopeEntityInterface[] - */ - public function getScopes(): array - { - return $this->scopes; - } - - /** - * @param ScopeEntityInterface[] $scopes - */ - public function setScopes(array $scopes): void - { - $this->scopes = $scopes; - } - - public function getUserCode(): string - { - return $this->userCode; - } - - public function getUserIdentifier(): string|int - { - return $this->userIdentifier; - } - - public function authorizationApproved(): bool - { - return $this->authorizationApproved; - } -} diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 116b9ab01..a96403c13 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -70,7 +70,7 @@ public function setPayload(string $payload): void /** * {@inheritdoc} */ - public function setDeviceCode(DeviceCodeEntityInterface $deviceCode) + public function setDeviceCode(DeviceCodeEntityInterface $deviceCode): void { $this->deviceCode = $deviceCode; } diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index d0ba9cfb1..d6dfffba2 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -10,7 +10,6 @@ use Laminas\Diactoros\ServerRequest; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; -use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\DeviceCodeGrant; @@ -19,8 +18,6 @@ use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; -use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use LeagueTests\Stubs\AccessTokenEntity; use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\CryptTraitStub; @@ -59,7 +56,7 @@ public function testGetIdentifier(): void "http://foo/bar" ); - $this->assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); + $this::assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); } public function testCanRespondToDeviceAuthorizationRequest(): void @@ -76,7 +73,7 @@ public function testCanRespondToDeviceAuthorizationRequest(): void 'scope' => 'basic', ]); - $this->assertTrue($grant->canRespondToDeviceAuthorizationRequest($request)); + $this::assertTrue($grant->canRespondToDeviceAuthorizationRequest($request)); } public function testRespondToDeviceAuthorizationRequest(): void @@ -112,7 +109,14 @@ public function testRespondToDeviceAuthorizationRequest(): void 'scope' => 'basic', ]); - $this->assertInstanceOf(DeviceCodeResponse::class, $grant->respondToDeviceAuthorizationRequest($request)); + $deviceCodeResponse = $grant->respondToDeviceAuthorizationRequest($request); + + $responseJson = json_decode($deviceCodeResponse->generateHttpResponse(new Response())->getBody()->__toString()); + + self::assertObjectHasProperty('device_code', $responseJson); + self::assertObjectHasProperty('user_code', $responseJson); + self::assertObjectHasProperty('verification_uri', $responseJson); + self::assertEquals('http://foo/bar', $responseJson->verification_uri); } public function testValidateDeviceAuthorizationRequestMissingClient(): void @@ -225,7 +229,7 @@ public function testCompleteDeviceAuthorizationRequest(): void $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), 'userId', true); - $this->assertEquals('userId', $deviceCode->getUserIdentifier()); + $this::assertEquals('userId', $deviceCode->getUserIdentifier()); } public function testDeviceAuthorizationResponse(): void @@ -280,11 +284,11 @@ public function testDeviceAuthorizationResponse(): void $responseObject = json_decode($response->getBody()->__toString()); - $this->assertObjectHasProperty('device_code', $responseObject); - $this->assertObjectHasProperty('user_code', $responseObject); - $this->assertObjectHasProperty('verification_uri', $responseObject); + $this::assertObjectHasProperty('device_code', $responseObject); + $this::assertObjectHasProperty('user_code', $responseObject); + $this::assertObjectHasProperty('verification_uri', $responseObject); // TODO: $this->assertObjectHasAttribute('verification_uri_complete', $responseObject); - $this->assertObjectHasProperty('expires_in', $responseObject); + $this::assertObjectHasProperty('expires_in', $responseObject); // TODO: $this->assertObjectHasAttribute('interval', $responseObject); } @@ -345,7 +349,8 @@ public function testRespondToAccessTokenRequest(): void 'user_code' => '12345678', 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', - ] + ], + JSON_THROW_ON_ERROR ) ), 'client_id' => 'foo', @@ -354,8 +359,7 @@ public function testRespondToAccessTokenRequest(): void $responseType = new StubResponseType(); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); - $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); - $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); + $this::assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } public function testRespondToRequestMissingClient(): void @@ -387,7 +391,8 @@ public function testRespondToRequestMissingClient(): void 'user_code' => '12345678', 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', - ] + ], + JSON_THROW_ON_ERROR ) ), ]); @@ -491,7 +496,8 @@ public function testIssueSlowDownError(): void 'user_code' => '12345678', 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', - ] + ], + JSON_THROW_ON_ERROR ) ), ]); @@ -549,7 +555,8 @@ public function testIssueAuthorizationPendingError(): void 'user_code' => '12345678', 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', - ] + ], + JSON_THROW_ON_ERROR ) ), ]); @@ -607,7 +614,8 @@ public function testIssueExpiredTokenError(): void 'user_code' => '12345678', 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', - ] + ], + JSON_THROW_ON_ERROR ) ), ]); @@ -663,8 +671,8 @@ public function testIntervalVisibility(): void $deviceCode = json_decode((string) $deviceCodeResponse->getBody()); - $this->assertObjectHasProperty('interval', $deviceCode); - $this->assertEquals(5, $deviceCode->interval); + $this::assertObjectHasProperty('interval', $deviceCode); + $this::assertEquals(5, $deviceCode->interval); } public function testIssueAccessDeniedError(): void @@ -717,8 +725,9 @@ public function testIssueAccessDeniedError(): void 'user_code' => '12345678', 'scopes' => ['foo'], 'verification_uri' => 'http://foo/bar', - ] - ) + ], + JSON_THROW_ON_ERROR + ), ), ]); diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index d90bcbbfe..3d6f960fe 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -47,18 +47,17 @@ public function testGenerateHttpResponse(): void $response = $responseType->generateHttpResponse(new Response()); - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertEquals(200, $response->getStatusCode()); - $this->assertEquals('no-cache', $response->getHeader('pragma')[0]); - $this->assertEquals('no-store', $response->getHeader('cache-control')[0]); - $this->assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); + $this::assertEquals(200, $response->getStatusCode()); + $this::assertEquals('no-cache', $response->getHeader('pragma')[0]); + $this::assertEquals('no-store', $response->getHeader('cache-control')[0]); + $this::assertEquals('application/json; charset=UTF-8', $response->getHeader('content-type')[0]); $response->getBody()->rewind(); $json = json_decode($response->getBody()->getContents()); - $this->assertObjectHasProperty('expires_in', $json); - $this->assertObjectHasProperty('device_code', $json); - $this->assertEquals('test', $json->device_code); - $this->assertObjectHasProperty('verification_uri', $json); - $this->assertObjectHasProperty('user_code', $json); + $this::assertObjectHasProperty('expires_in', $json); + $this::assertObjectHasProperty('device_code', $json); + $this::assertEquals('test', $json->device_code); + $this::assertObjectHasProperty('verification_uri', $json); + $this::assertObjectHasProperty('user_code', $json); } } diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index 6a4d31095..e173a864d 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -14,7 +14,6 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequest; use League\OAuth2\Server\ResponseTypes\BearerTokenResponse; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; From 15c214dc27bbebc80948073eeeabbaf7898d2d5d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 25 Jan 2024 23:51:41 +0000 Subject: [PATCH 158/212] Fix all PHPStan errors --- src/AuthorizationServer.php | 7 +++---- src/Entities/Traits/AccessTokenTrait.php | 11 ++++++++++- src/Entities/Traits/TokenEntityTrait.php | 4 ++++ src/Grant/DeviceCodeGrant.php | 6 +++--- tests/Stubs/GrantType.php | 2 +- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 308fa2197..a52c3cd75 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -153,11 +153,10 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * Complete a device authorization request */ - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId): ResponseInterface + public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void { - return $this->enabledGrantTypes['urn:ietf:params:oauth:grant-type:device_code'] - ->completeDeviceAuthorizationRequest($deviceCode, $userId) - ->generateHttpResponse($response); + $this->enabledGrantTypes['urn:ietf:params:oauth:grant-type:device_code'] + ->completeDeviceAuthorizationRequest($deviceCode, $userId, $userApproved); } /** diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index ac7e6a29b..015002069 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -59,6 +59,12 @@ public function initJwtConfiguration(): void */ private function convertToJWT(): Token { + $userIdentifier = $this->getUserIdentifier(); + + if ($userIdentifier === null) { + throw new RuntimeException('JWT access tokens MUST contain a subject identifier'); + } + $this->initJwtConfiguration(); return $this->jwtConfiguration->builder() @@ -67,7 +73,7 @@ private function convertToJWT(): Token ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($this->getExpiryDateTime()) - ->relatedTo($this->getUserIdentifier()) + ->relatedTo($userIdentifier) ->withClaim('scopes', $this->getScopes()) ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); } @@ -84,6 +90,9 @@ abstract public function getClient(): ClientEntityInterface; abstract public function getExpiryDateTime(): DateTimeImmutable; + /** + * @return non-empty-string|null + */ abstract public function getUserIdentifier(): string|null; /** diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index a6daed8ce..ad472acd9 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -27,6 +27,9 @@ trait TokenEntityTrait protected DateTimeImmutable $expiryDateTime; + /** + * @var non-empty-string|null + */ protected string|null $userIdentifier = null; protected ClientEntityInterface $client; @@ -78,6 +81,7 @@ public function setUserIdentifier(string $identifier): void /** * Get the token user's identifier. * + * @return non-empty-string|null */ public function getUserIdentifier(): string|null { diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 91f01db67..496f9b89b 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -26,6 +26,7 @@ use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; +use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -121,8 +122,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ /** * {@inheritdoc} */ - // TODO: Make sure this cant be abused to try and brute force a device code - public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $approved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void { $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode($deviceCode); @@ -135,7 +135,7 @@ public function completeDeviceAuthorizationRequest(string $deviceCode, string $u } $deviceCode->setUserIdentifier($userId); - $deviceCode->setUserApproved($approved); + $deviceCode->setUserApproved($userApproved); $this->deviceCodeRepository->persistDeviceCode($deviceCode); } diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index e173a864d..6c6ecdcca 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -108,7 +108,7 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r return true; } - public function completeDeviceAuthorizationRequest(string $deviceCode, string|int $userId, bool $userApproved): void + public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void { } From 4e9de6eb0d345294cb95f8c3080ca110623dce4c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 31 Jan 2024 13:55:12 +0000 Subject: [PATCH 159/212] Support complete_verification_uri --- src/Entities/DeviceCodeEntityInterface.php | 4 +- src/Entities/Traits/DeviceCodeTrait.php | 20 ++++++++- src/Grant/DeviceCodeGrant.php | 25 +++++++---- src/Grant/GrantTypeInterface.php | 2 - src/ResponseTypes/DeviceCodeResponse.php | 11 ++++- tests/Grant/DeviceCodeGrantTest.php | 48 +++++++++++++++++++++- 6 files changed, 94 insertions(+), 16 deletions(-) diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index 50338b30d..81834bca9 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -24,6 +24,8 @@ public function getVerificationUri(): string; public function setVerificationUri(string $verificationUri): void; + public function getVerificationUriComplete(): string; + public function getLastPolledAt(): ?DateTimeImmutable; public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void; @@ -34,7 +36,7 @@ public function setInterval(int $interval): void; public function getIntervalInAuthResponse(): bool; - public function setIntervalInAuthResponse(bool $intervalInAuthResponse): bool; + public function setIntervalInAuthResponse(bool $intervalInAuthResponse): void; public function getUserApproved(): bool; diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index 7bec53027..942c6a3ec 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -20,6 +20,7 @@ trait DeviceCodeTrait { private bool $userApproved = false; private bool $intervalInAuthResponse = false; + private bool $includeVerificationUriComplete = false; private int $interval = 5; private string $userCode; private string $verificationUri; @@ -45,6 +46,11 @@ public function setVerificationUri(string $verificationUri): void $this->verificationUri = $verificationUri; } + public function getVerificationUriComplete(): string + { + return $this->verificationUri . '?user_code=' . $this->userCode; + } + abstract public function getClient(): ClientEntityInterface; abstract public function getExpiryDateTime(): DateTimeImmutable; @@ -81,9 +87,9 @@ public function getIntervalInAuthResponse(): bool return $this->intervalInAuthResponse; } - public function setIntervalInAuthResponse(bool $intervalInAuthResponse): bool + public function setIntervalInAuthResponse(bool $intervalInAuthResponse): void { - return $this->intervalInAuthResponse = $intervalInAuthResponse; + $this->intervalInAuthResponse = $intervalInAuthResponse; } public function getUserApproved(): bool @@ -95,4 +101,14 @@ public function setUserApproved(bool $userApproved): void { $this->userApproved = $userApproved; } + + public function getVerificationUriCompleteInAuthResponse(): bool + { + return $this->includeVerificationUriComplete; + } + + public function setVerificationUriCompleteInAuthResponse(bool $includeVerificationUriComplete): void + { + $this->includeVerificationUriComplete = $includeVerificationUriComplete; + } } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index 496f9b89b..bb23c276a 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -26,7 +26,6 @@ use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; -use League\OAuth2\Server\RequestTypes\DeviceAuthorizationRequestInterface; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use LogicException; @@ -48,6 +47,7 @@ class DeviceCodeGrant extends AbstractGrant { protected DeviceCodeRepositoryInterface $deviceCodeRepository; + private bool $includeVerificationUriComplete = false; private bool $intervalVisibility = false; private string $verificationUri; @@ -100,7 +100,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ $scopes ); - // TODO: Check payload generation + // TODO: Check payload generation. Is this the best way to handle things? $payload = [ 'device_code_id' => $deviceCode->getIdentifier(), 'user_code' => $deviceCode->getUserCode(), @@ -110,9 +110,15 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ 'scopes' => $deviceCode->getScopes(), ]; + $response = new DeviceCodeResponse(); + + if ($this->includeVerificationUriComplete === true) { + $response->includeVerificationUriComplete(); + $payload['verification_uri_complete'] = $deviceCode->getVerificationUriComplete(); + } + $jsonPayload = json_encode($payload, JSON_THROW_ON_ERROR); - $response = new DeviceCodeResponse(); $response->setDeviceCode($deviceCode); $response->setPayload($this->encrypt($jsonPayload)); @@ -187,9 +193,7 @@ public function respondToAccessTokenRequest( } /** - * * @throws OAuthServerException - * */ protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client): DeviceCodeEntityInterface { @@ -240,9 +244,7 @@ private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll): bool } /** - * * @throws OAuthServerException - * */ protected function decodeDeviceCode(string $encryptedDeviceCode): stdClass { @@ -255,7 +257,6 @@ protected function decodeDeviceCode(string $encryptedDeviceCode): stdClass /** * Set the verification uri - * */ public function setVerificationUri(string $verificationUri): void { @@ -288,7 +289,7 @@ protected function issueDeviceCode( DateInterval $deviceCodeTTL, ClientEntityInterface $client, string $verificationUri, - array $scopes = [] + array $scopes = [], ): DeviceCodeEntityInterface { $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; @@ -354,6 +355,7 @@ protected function generateUniqueUserCode(int $length = 8): string // @codeCoverageIgnoreEnd } + // TODO: Check interface public function setIntervalVisibility(bool $intervalVisibility): void { $this->intervalVisibility = $intervalVisibility; @@ -363,4 +365,9 @@ public function getIntervalVisibility(): bool { return $this->intervalVisibility; } + + public function setIncludeVerificationUriComplete(bool $includeVerificationUriComplete): void + { + $this->includeVerificationUriComplete = $includeVerificationUriComplete; + } } diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 49528945d..461416773 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -94,8 +94,6 @@ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $r */ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse; - // TODO: Check DeviceAuthorizationRequest - /** * If the grant can respond to a device authorization request this method should be called to validate the parameters of * the request. diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index a96403c13..dfb1c932e 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -25,6 +25,7 @@ class DeviceCodeResponse extends AbstractResponseType { protected DeviceCodeEntityInterface $deviceCode; protected string $payload; + private bool $includeVerificationUriComplete = false; /** * {@inheritdoc} @@ -38,8 +39,11 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter 'user_code' => $this->deviceCode->getUserCode(), 'verification_uri' => $this->deviceCode->getVerificationUri(), 'expires_in' => $expireDateTime - time(), - // TODO: Potentially add in verification_uri_complete - it is optional ]; + + if ($this->includeVerificationUriComplete === true) { + $responseParams['verification_uri_complete'] = $this->deviceCode->getVerificationUriComplete(); + } if ($this->deviceCode->getIntervalInAuthResponse() === true) { $responseParams['interval'] = $this->deviceCode->getInterval(); @@ -75,6 +79,11 @@ public function setDeviceCode(DeviceCodeEntityInterface $deviceCode): void $this->deviceCode = $deviceCode; } + public function includeVerificationUriComplete(): void + { + $this->includeVerificationUriComplete = true; + } + /** * Add custom fields to your Bearer Token response here, then override * AuthorizationServer::getResponseType() to pull in your version of diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index d6dfffba2..730336b9b 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -119,6 +119,53 @@ public function testRespondToDeviceAuthorizationRequest(): void self::assertEquals('http://foo/bar', $responseJson->verification_uri); } + public function testRespondToDeviceAuthorizationRequestWithVerificationUriComplete(): void + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); + $deviceCodeRepository->method('getNewDeviceCode')->willReturn(new DeviceCodeEntity()); + + $scope = new ScopeEntity(); + $scope->setIdentifier('basic'); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new DeviceCodeGrant( + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M'), + "http://foo/bar" + ); + + $grant->setIncludeVerificationUriComplete(true); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setScopeRepository($scopeRepositoryMock); + + $request = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'scope' => 'basic', + ]); + + $deviceCodeResponse = $grant->respondToDeviceAuthorizationRequest($request); + + $responseJson = json_decode($deviceCodeResponse->generateHttpResponse(new Response())->getBody()->__toString()); + + self::assertObjectHasProperty('device_code', $responseJson); + self::assertObjectHasProperty('user_code', $responseJson); + self::assertObjectHasProperty('verification_uri', $responseJson); + self::assertObjectHasProperty('verification_uri_complete', $responseJson); + self::assertEquals('http://foo/bar', $responseJson->verification_uri); + self::assertEquals('http://foo/bar?user_code=' . $responseJson->user_code, $responseJson->verification_uri_complete); + } + public function testValidateDeviceAuthorizationRequestMissingClient(): void { $client = new ClientEntity(); @@ -287,7 +334,6 @@ public function testDeviceAuthorizationResponse(): void $this::assertObjectHasProperty('device_code', $responseObject); $this::assertObjectHasProperty('user_code', $responseObject); $this::assertObjectHasProperty('verification_uri', $responseObject); - // TODO: $this->assertObjectHasAttribute('verification_uri_complete', $responseObject); $this::assertObjectHasProperty('expires_in', $responseObject); // TODO: $this->assertObjectHasAttribute('interval', $responseObject); } From 0636d25dcb5973b93cb274a83483e74dc41e0776 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 5 Mar 2024 22:45:48 +0000 Subject: [PATCH 160/212] add ability to set interval --- src/Entities/DeviceCodeEntityInterface.php | 4 ---- src/Entities/Traits/DeviceCodeTrait.php | 11 ----------- src/Grant/DeviceCodeGrant.php | 3 ++- src/ResponseTypes/DeviceCodeResponse.php | 5 +++-- tests/Grant/DeviceCodeGrantTest.php | 13 ++++++------- 5 files changed, 11 insertions(+), 25 deletions(-) diff --git a/src/Entities/DeviceCodeEntityInterface.php b/src/Entities/DeviceCodeEntityInterface.php index 81834bca9..6969eca01 100644 --- a/src/Entities/DeviceCodeEntityInterface.php +++ b/src/Entities/DeviceCodeEntityInterface.php @@ -34,10 +34,6 @@ public function getInterval(): int; public function setInterval(int $interval): void; - public function getIntervalInAuthResponse(): bool; - - public function setIntervalInAuthResponse(bool $intervalInAuthResponse): void; - public function getUserApproved(): bool; public function setUserApproved(bool $userApproved): void; diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index 942c6a3ec..22af197bb 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -19,7 +19,6 @@ trait DeviceCodeTrait { private bool $userApproved = false; - private bool $intervalInAuthResponse = false; private bool $includeVerificationUriComplete = false; private int $interval = 5; private string $userCode; @@ -82,16 +81,6 @@ public function setInterval(int $interval): void $this->interval = $interval; } - public function getIntervalInAuthResponse(): bool - { - return $this->intervalInAuthResponse; - } - - public function setIntervalInAuthResponse(bool $intervalInAuthResponse): void - { - $this->intervalInAuthResponse = $intervalInAuthResponse; - } - public function getUserApproved(): bool { return $this->userApproved; diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index bb23c276a..d32afd421 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -108,6 +108,7 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ 'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(), 'client_id' => $deviceCode->getClient()->getIdentifier(), 'scopes' => $deviceCode->getScopes(), + 'interval' => $deviceCode->getInterval(), ]; $response = new DeviceCodeResponse(); @@ -355,7 +356,7 @@ protected function generateUniqueUserCode(int $length = 8): string // @codeCoverageIgnoreEnd } - // TODO: Check interface + // TODO: Check interface public function setIntervalVisibility(bool $intervalVisibility): void { $this->intervalVisibility = $intervalVisibility; diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index dfb1c932e..1b0268b8d 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -26,6 +26,7 @@ class DeviceCodeResponse extends AbstractResponseType protected DeviceCodeEntityInterface $deviceCode; protected string $payload; private bool $includeVerificationUriComplete = false; + private const DEFAULT_INTERVAL = 5; /** * {@inheritdoc} @@ -40,12 +41,12 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter 'verification_uri' => $this->deviceCode->getVerificationUri(), 'expires_in' => $expireDateTime - time(), ]; - + if ($this->includeVerificationUriComplete === true) { $responseParams['verification_uri_complete'] = $this->deviceCode->getVerificationUriComplete(); } - if ($this->deviceCode->getIntervalInAuthResponse() === true) { + if ($this->deviceCode->getInterval() !== self::DEFAULT_INTERVAL) { $responseParams['interval'] = $this->deviceCode->getInterval(); } diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 730336b9b..66d7d3024 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -36,6 +36,7 @@ class DeviceCodeGrantTest extends TestCase { private const DEFAULT_SCOPE = 'basic'; + private const INTERVAL_RATE = 10; protected CryptTraitStub $cryptStub; @@ -335,7 +336,6 @@ public function testDeviceAuthorizationResponse(): void $this::assertObjectHasProperty('user_code', $responseObject); $this::assertObjectHasProperty('verification_uri', $responseObject); $this::assertObjectHasProperty('expires_in', $responseObject); - // TODO: $this->assertObjectHasAttribute('interval', $responseObject); } public function testRespondToAccessTokenRequest(): void @@ -674,7 +674,7 @@ public function testIssueExpiredTokenError(): void $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - public function testIntervalVisibility(): void + public function testSettingDeviceCodeIntervalRate(): void { $client = new ClientEntity(); $client->setIdentifier('foo'); @@ -684,8 +684,6 @@ public function testIntervalVisibility(): void $deviceCode = new DeviceCodeEntity(); - $deviceCode->setIntervalInAuthResponse(true); - $deviceCodeRepository = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); $deviceCodeRepository->method('getNewDeviceCode')->willReturn($deviceCode); @@ -698,13 +696,15 @@ public function testIntervalVisibility(): void $deviceCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M'), - "http://foo/bar" + "http://foo/bar", + self::INTERVAL_RATE ); $grant->setClientRepository($clientRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setScopeRepository($scopeRepositoryMock); + $grant->setIntervalVisibility(true); $request = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', @@ -718,9 +718,8 @@ public function testIntervalVisibility(): void $deviceCode = json_decode((string) $deviceCodeResponse->getBody()); $this::assertObjectHasProperty('interval', $deviceCode); - $this::assertEquals(5, $deviceCode->interval); + $this::assertEquals(self::INTERVAL_RATE, $deviceCode->interval); } - public function testIssueAccessDeniedError(): void { $client = new ClientEntity(); From f8f15ef7f8fbc2b07bbdf75a209eae64e2696c8d Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 12:02:38 +0000 Subject: [PATCH 161/212] Simplify device code response - remove payload --- composer.json | 3 +- src/Grant/DeviceCodeGrant.php | 81 +++++-------------- .../DeviceCodeRepositoryInterface.php | 10 +++ src/ResponseTypes/DeviceCodeResponse.php | 26 +++--- tests/Grant/DeviceCodeGrantTest.php | 30 ++++--- .../DeviceCodeResponseTypeTest.php | 7 +- 6 files changed, 65 insertions(+), 92 deletions(-) diff --git a/composer.json b/composer.json index 9376506a2..a3a53b00c 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "defuse/php-encryption": "^2.4", "ext-json": "*", "lcobucci/clock": "^2.3 || ^3.0", - "psr/http-server-middleware": "^1.0" + "psr/http-server-middleware": "^1.0", + "ramsey/uuid": "^4.7" }, "require-dev": { "phpunit/phpunit": "^9.6.15", diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index d32afd421..b48aa946b 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -28,15 +28,11 @@ use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; -use LogicException; use Psr\Http\Message\ServerRequestInterface; -use stdClass; +use Ramsey\Uuid\Uuid; use TypeError; use function is_null; -use function json_decode; -use function json_encode; -use function property_exists; use function random_int; use function strlen; use function time; @@ -93,35 +89,20 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); - $deviceCode = $this->issueDeviceCode( + $deviceCodeEntity = $this->issueDeviceCode( $this->deviceCodeTTL, $client, $this->verificationUri, $scopes ); - // TODO: Check payload generation. Is this the best way to handle things? - $payload = [ - 'device_code_id' => $deviceCode->getIdentifier(), - 'user_code' => $deviceCode->getUserCode(), - 'verification_uri' => $deviceCode->getVerificationUri(), - 'expire_time' => $deviceCode->getExpiryDateTime()->getTimestamp(), - 'client_id' => $deviceCode->getClient()->getIdentifier(), - 'scopes' => $deviceCode->getScopes(), - 'interval' => $deviceCode->getInterval(), - ]; - $response = new DeviceCodeResponse(); if ($this->includeVerificationUriComplete === true) { $response->includeVerificationUriComplete(); - $payload['verification_uri_complete'] = $deviceCode->getVerificationUriComplete(); } - $jsonPayload = json_encode($payload, JSON_THROW_ON_ERROR); - - $response->setDeviceCode($deviceCode); - $response->setPayload($this->encrypt($jsonPayload)); + $response->setDeviceCodeEntity($deviceCodeEntity); return $response; } @@ -198,45 +179,39 @@ public function respondToAccessTokenRequest( */ protected function validateDeviceCode(ServerRequestInterface $request, ClientEntityInterface $client): DeviceCodeEntityInterface { - $encryptedDeviceCode = $this->getRequestParameter('device_code', $request); + $deviceCode = $this->getRequestParameter('device_code', $request); - if (is_null($encryptedDeviceCode)) { + if (is_null($deviceCode)) { throw OAuthServerException::invalidRequest('device_code'); } - $deviceCodePayload = $this->decodeDeviceCode($encryptedDeviceCode); + $deviceCodeEntity = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( + $deviceCode + ); - if (!property_exists($deviceCodePayload, 'device_code_id')) { - throw OAuthServerException::invalidRequest('device_code', 'Device code malformed'); + if ($deviceCodeEntity instanceof DeviceCodeEntityInterface === false) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); + + throw OAuthServerException::invalidGrant(); } - if (time() > $deviceCodePayload->expire_time) { + if (time() > $deviceCodeEntity->getExpiryDateTime()->getTimestamp()) { throw OAuthServerException::expiredToken('device_code'); } - if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCodePayload->device_code_id) === true) { + if ($this->deviceCodeRepository->isDeviceCodeRevoked($deviceCode) === true) { throw OAuthServerException::invalidRequest('device_code', 'Device code has been revoked'); } - if ($deviceCodePayload->client_id !== $client->getIdentifier()) { + if ($deviceCodeEntity->getClient()->getIdentifier() !== $client->getIdentifier()) { throw OAuthServerException::invalidRequest('device_code', 'Device code was not issued to this client'); } - $deviceCode = $this->deviceCodeRepository->getDeviceCodeEntityByDeviceCode( - $deviceCodePayload->device_code_id - ); - - if ($deviceCode instanceof DeviceCodeEntityInterface === false) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); - - throw OAuthServerException::invalidGrant(); - } - - if ($this->deviceCodePolledTooSoon($deviceCode->getLastPolledAt()) === true) { + if ($this->deviceCodePolledTooSoon($deviceCodeEntity->getLastPolledAt()) === true) { throw OAuthServerException::slowDown(); } - return $deviceCode; + return $deviceCodeEntity; } private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll): bool @@ -244,18 +219,6 @@ private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll): bool return $lastPoll !== null && $lastPoll->getTimestamp() + $this->retryInterval > time(); } - /** - * @throws OAuthServerException - */ - protected function decodeDeviceCode(string $encryptedDeviceCode): stdClass - { - try { - return json_decode($this->decrypt($encryptedDeviceCode)); - } catch (LogicException $e) { - throw OAuthServerException::invalidRequest('device_code', 'Cannot decrypt the device code', $e); - } - } - /** * Set the verification uri */ @@ -309,7 +272,8 @@ protected function issueDeviceCode( while ($maxGenerationAttempts-- > 0) { $deviceCode->setIdentifier($this->generateUniqueIdentifier()); - $deviceCode->setUserCode($this->generateUniqueUserCode()); + $deviceCode->setUserCode($this->generateUserCode()); + try { $this->deviceCodeRepository->persistDeviceCode($deviceCode); @@ -321,19 +285,16 @@ protected function issueDeviceCode( } } - // This should never be hit. It is here to work around a PHPStan false error return $deviceCode; } /** - * Generate a new unique user code. - * - * + * Generate a new user code. * * @throws OAuthServerException */ - protected function generateUniqueUserCode(int $length = 8): string + protected function generateUserCode(int $length = 8): string { try { $userCode = ''; diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 46e9492ff..634cf4992 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -51,4 +51,14 @@ public function revokeDeviceCode(string $codeId): void; * @return bool Return true if this code has been revoked */ public function isDeviceCodeRevoked(string $codeId): bool; + + /** + * Get the expiry time of the device code. + */ + public function getDeviceCodeExpiryTime(string $codeId): int; + + /** + * Get the client ID of the device code. + */ + public function getDeviceCodeClientId(string $codeId): string; } diff --git a/src/ResponseTypes/DeviceCodeResponse.php b/src/ResponseTypes/DeviceCodeResponse.php index 1b0268b8d..339c75bd3 100644 --- a/src/ResponseTypes/DeviceCodeResponse.php +++ b/src/ResponseTypes/DeviceCodeResponse.php @@ -23,8 +23,7 @@ class DeviceCodeResponse extends AbstractResponseType { - protected DeviceCodeEntityInterface $deviceCode; - protected string $payload; + protected DeviceCodeEntityInterface $deviceCodeEntity; private bool $includeVerificationUriComplete = false; private const DEFAULT_INTERVAL = 5; @@ -33,21 +32,21 @@ class DeviceCodeResponse extends AbstractResponseType */ public function generateHttpResponse(ResponseInterface $response): ResponseInterface { - $expireDateTime = $this->deviceCode->getExpiryDateTime()->getTimestamp(); + $expireDateTime = $this->deviceCodeEntity->getExpiryDateTime()->getTimestamp(); $responseParams = [ - 'device_code' => $this->payload, - 'user_code' => $this->deviceCode->getUserCode(), - 'verification_uri' => $this->deviceCode->getVerificationUri(), + 'device_code' => $this->deviceCodeEntity->getIdentifier(), + 'user_code' => $this->deviceCodeEntity->getUserCode(), + 'verification_uri' => $this->deviceCodeEntity->getVerificationUri(), 'expires_in' => $expireDateTime - time(), ]; if ($this->includeVerificationUriComplete === true) { - $responseParams['verification_uri_complete'] = $this->deviceCode->getVerificationUriComplete(); + $responseParams['verification_uri_complete'] = $this->deviceCodeEntity->getVerificationUriComplete(); } - if ($this->deviceCode->getInterval() !== self::DEFAULT_INTERVAL) { - $responseParams['interval'] = $this->deviceCode->getInterval(); + if ($this->deviceCodeEntity->getInterval() !== self::DEFAULT_INTERVAL) { + $responseParams['interval'] = $this->deviceCodeEntity->getInterval(); } $responseParams = json_encode($responseParams); @@ -67,17 +66,12 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter return $response; } - public function setPayload(string $payload): void - { - $this->payload = $payload; - } - /** * {@inheritdoc} */ - public function setDeviceCode(DeviceCodeEntityInterface $deviceCode): void + public function setDeviceCodeEntity(DeviceCodeEntityInterface $deviceCodeEntity): void { - $this->deviceCode = $deviceCode; + $this->deviceCodeEntity = $deviceCodeEntity; } public function includeVerificationUriComplete(): void diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 66d7d3024..4383469a7 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -355,13 +355,15 @@ public function testRespondToAccessTokenRequest(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $deviceCode = new DeviceCodeEntity(); + $deviceCodeEntity = new DeviceCodeEntity(); - $deviceCode->setUserIdentifier('baz'); - $deviceCode->setIdentifier('deviceCodeEntityIdentifier'); - $deviceCode->setUserCode('123456'); + $deviceCodeEntity->setUserIdentifier('baz'); + $deviceCodeEntity->setIdentifier('deviceCodeEntityIdentifier'); + $deviceCodeEntity->setUserCode('123456'); + $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('+1 hour')); + $deviceCodeEntity->setClient($client); - $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -382,7 +384,7 @@ public function testRespondToAccessTokenRequest(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), "1", true); + $grant->completeDeviceAuthorizationRequest($deviceCodeEntity->getUserCode(), "1", true); $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', @@ -511,6 +513,8 @@ public function testIssueSlowDownError(): void $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); $deviceCodeEntity = new DeviceCodeEntity(); $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable()); + $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('+1 hour')); + $deviceCodeEntity->setClient($client); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scope = new ScopeEntity(); @@ -569,8 +573,10 @@ public function testIssueAuthorizationPendingError(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $deviceCode = new DeviceCodeEntity(); - $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); + $deviceCodeEntity = new DeviceCodeEntity(); + $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('+1 hour')); + $deviceCodeEntity->setClient($client); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -628,8 +634,10 @@ public function testIssueExpiredTokenError(): void $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); $deviceCodeRepositoryMock = $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(); - $deviceCode = new DeviceCodeEntity(); - $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); + $deviceCodeEntity = new DeviceCodeEntity(); + $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('-1 hour')); + $deviceCodeEntity->setClient($client); + $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCodeEntity); $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -736,6 +744,8 @@ public function testIssueAccessDeniedError(): void $deviceCode = new DeviceCodeEntity(); + $deviceCode->setExpiryDateTime(new DateTimeImmutable('+1 hour')); + $deviceCode->setClient($client); $deviceCode->setUserCode('12345678'); $deviceCodeRepositoryMock->method('getDeviceCodeEntityByDeviceCode')->willReturn($deviceCode); diff --git a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php index 3d6f960fe..93bd9d6b3 100644 --- a/tests/ResponseTypes/DeviceCodeResponseTypeTest.php +++ b/tests/ResponseTypes/DeviceCodeResponseTypeTest.php @@ -13,7 +13,6 @@ use LeagueTests\Stubs\DeviceCodeEntity; use LeagueTests\Stubs\ScopeEntity; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\ResponseInterface; use function base64_encode; use function json_decode; @@ -41,9 +40,7 @@ public function testGenerateHttpResponse(): void $deviceCode->addScope($scope); $deviceCode->setVerificationUri('https://example.com/device'); - - $responseType->setDeviceCode($deviceCode); - $responseType->setPayload('test'); + $responseType->setDeviceCodeEntity($deviceCode); $response = $responseType->generateHttpResponse(new Response()); @@ -56,7 +53,7 @@ public function testGenerateHttpResponse(): void $json = json_decode($response->getBody()->getContents()); $this::assertObjectHasProperty('expires_in', $json); $this::assertObjectHasProperty('device_code', $json); - $this::assertEquals('test', $json->device_code); + $this::assertEquals('abcdef', $json->device_code); $this::assertObjectHasProperty('verification_uri', $json); $this::assertObjectHasProperty('user_code', $json); } From 0bc83e530e0d1888b85e04db7cf92e4a0300a3b5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 12:04:12 +0000 Subject: [PATCH 162/212] Remove uuid lib --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index a3a53b00c..9376506a2 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,7 @@ "defuse/php-encryption": "^2.4", "ext-json": "*", "lcobucci/clock": "^2.3 || ^3.0", - "psr/http-server-middleware": "^1.0", - "ramsey/uuid": "^4.7" + "psr/http-server-middleware": "^1.0" }, "require-dev": { "phpunit/phpunit": "^9.6.15", From 67284deeaddafb54272ba62d4c29ef4094056aa5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 13:54:46 +0000 Subject: [PATCH 163/212] Fix device code examples --- examples/composer.json | 2 +- examples/composer.lock | 365 ++++++++++++------ .../Repositories/AccessTokenRepository.php | 8 +- .../src/Repositories/ClientRepository.php | 5 +- .../src/Repositories/DeviceCodeRepository.php | 42 +- .../Repositories/RefreshTokenRepository.php | 8 +- examples/src/Repositories/ScopeRepository.php | 11 +- src/Grant/DeviceCodeGrant.php | 17 +- .../DeviceCodeRepositoryInterface.php | 2 +- 9 files changed, 310 insertions(+), 150 deletions(-) diff --git a/examples/composer.json b/examples/composer.json index 087caab72..7d2000be5 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -3,7 +3,7 @@ "slim/slim": "^3.12.3" }, "require-dev": { - "league/event": "^2.2", + "league/event": "^3.0", "lcobucci/jwt": "^3.4.6 || ^4.0.4", "psr/http-message": "^1.0.1", "defuse/php-encryption": "^2.2.1", diff --git a/examples/composer.lock b/examples/composer.lock index b937aac99..58f1c60cb 100644 --- a/examples/composer.lock +++ b/examples/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "37792b4ef2ef1a2cf73184749dd182d6", + "content-hash": "ac8c2c0c3717f72036b55ab34445a89d", "packages": [ { "name": "nikic/fast-route", @@ -28,12 +28,12 @@ }, "type": "library", "autoload": { - "psr-4": { - "FastRoute\\": "src/" - }, "files": [ "src/functions.php" - ] + ], + "psr-4": { + "FastRoute\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -50,33 +50,37 @@ "router", "routing" ], + "support": { + "issues": "https://github.com/nikic/FastRoute/issues", + "source": "https://github.com/nikic/FastRoute/tree/master" + }, "time": "2018-02-13T20:26:39+00:00" }, { "name": "pimple/pimple", - "version": "v3.3.1", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "21e45061c3429b1e06233475cc0e1f6fc774d5b0" + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/21e45061c3429b1e06233475cc0e1f6fc774d5b0", - "reference": "21e45061c3429b1e06233475cc0e1f6fc774d5b0", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", "shasum": "" }, "require": { "php": ">=7.2.5", - "psr/container": "^1.0" + "psr/container": "^1.1 || ^2.0" }, "require-dev": { - "symfony/phpunit-bridge": "^5.0" + "symfony/phpunit-bridge": "^5.4@dev" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3.x-dev" + "dev-master": "3.4.x-dev" } }, "autoload": { @@ -100,31 +104,29 @@ "container", "dependency injection" ], - "time": "2020-11-24T20:35:42+00:00" + "support": { + "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" + }, + "time": "2021-10-28T11:13:42+00:00" }, { "name": "psr/container", - "version": "1.0.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.4.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-4": { "Psr\\Container\\": "src/" @@ -137,7 +139,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common Container Interface (PHP FIG PSR-11)", @@ -149,29 +151,33 @@ "container-interop", "psr" ], - "time": "2017-02-14T16:28:37+00:00" + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.2" + }, + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "1.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -200,22 +206,22 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:50:52+00:00" }, { "name": "slim/slim", - "version": "3.12.3", + "version": "3.12.5", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "1c9318a84ffb890900901136d620b4f03a59da38" + "reference": "565632b2d9b64ecedf89546edbbf4f3648089f0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/1c9318a84ffb890900901136d620b4f03a59da38", - "reference": "1c9318a84ffb890900901136d620b4f03a59da38", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/565632b2d9b64ecedf89546edbbf4f3648089f0c", + "reference": "565632b2d9b64ecedf89546edbbf4f3648089f0c", "shasum": "" }, "require": { @@ -233,7 +239,7 @@ }, "require-dev": { "phpunit/phpunit": "^4.0", - "squizlabs/php_codesniffer": "^2.5" + "squizlabs/php_codesniffer": "^3.6.0" }, "type": "library", "autoload": { @@ -275,32 +281,46 @@ "micro", "router" ], - "time": "2019-11-28T17:40:33+00:00" + "support": { + "issues": "https://github.com/slimphp/Slim/issues", + "source": "https://github.com/slimphp/Slim/tree/3.12.5" + }, + "funding": [ + { + "url": "https://opencollective.com/slimphp", + "type": "open_collective" + }, + { + "url": "https://tidelift.com/funding/github/packagist/slim/slim", + "type": "tidelift" + } + ], + "time": "2023-07-23T04:32:51+00:00" } ], "packages-dev": [ { "name": "defuse/php-encryption", - "version": "v2.2.1", + "version": "v2.4.0", "source": { "type": "git", "url": "https://github.com/defuse/php-encryption.git", - "reference": "0f407c43b953d571421e0020ba92082ed5fb7620" + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/defuse/php-encryption/zipball/0f407c43b953d571421e0020ba92082ed5fb7620", - "reference": "0f407c43b953d571421e0020ba92082ed5fb7620", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/f53396c2d34225064647a05ca76c1da9d99e5828", + "reference": "f53396c2d34225064647a05ca76c1da9d99e5828", "shasum": "" }, "require": { "ext-openssl": "*", "paragonie/random_compat": ">= 2", - "php": ">=5.4.0" + "php": ">=5.6.0" }, "require-dev": { - "nikic/php-parser": "^2.0|^3.0|^4.0", - "phpunit/phpunit": "^4|^5" + "phpunit/phpunit": "^5|^6|^7|^8|^9|^10", + "yoast/phpunit-polyfills": "^2.0.0" }, "bin": [ "bin/generate-defuse-key" @@ -340,29 +360,32 @@ "security", "symmetric key cryptography" ], - "time": "2018-07-24T23:27:56+00:00" + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.4.0" + }, + "time": "2023-06-19T06:10:36+00:00" }, { "name": "laminas/laminas-diactoros", - "version": "2.11.1", + "version": "2.26.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "25b11d422c2e5dad868f68619888763b30f91e2d" + "reference": "6584d44eb8e477e89d453313b858daac6183cddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/25b11d422c2e5dad868f68619888763b30f91e2d", - "reference": "25b11d422c2e5dad868f68619888763b30f91e2d", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/6584d44eb8e477e89d453313b858daac6183cddc", + "reference": "6584d44eb8e477e89d453313b858daac6183cddc", "shasum": "" }, "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", + "php": "~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.1" }, "conflict": { - "phpspec/prophecy": "<1.9.0", "zendframework/zend-diactoros": "*" }, "provide": { @@ -374,13 +397,12 @@ "ext-dom": "*", "ext-gd": "*", "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" + "http-interop/http-factory-tests": "^0.9.0", + "laminas/laminas-coding-standard": "^2.5", + "php-http/psr7-integration-tests": "^1.2", + "phpunit/phpunit": "^9.5.28", + "psalm/plugin-phpunit": "^0.18.4", + "vimeo/psalm": "^5.6" }, "type": "library", "extra": { @@ -439,35 +461,38 @@ "type": "community_bridge" } ], - "time": "2022-06-28T21:07:29+00:00" + "time": "2023-10-29T16:17:44+00:00" }, { "name": "lcobucci/clock", - "version": "2.0.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3" + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/353d83fe2e6ae95745b16b3d911813df6a05bfb3", - "reference": "353d83fe2e6ae95745b16b3d911813df6a05bfb3", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/039ef98c6b57b101d10bd11d8fdfda12cbd996dc", + "reference": "039ef98c6b57b101d10bd11d8fdfda12cbd996dc", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "~8.1.0 || ~8.2.0", + "psr/clock": "^1.0" + }, + "provide": { + "psr/clock-implementation": "1.0" }, "require-dev": { - "infection/infection": "^0.17", - "lcobucci/coding-standard": "^6.0", - "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", - "phpunit/php-code-coverage": "9.1.4", - "phpunit/phpunit": "9.3.7" + "infection/infection": "^0.26", + "lcobucci/coding-standard": "^9.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9.4", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.27" }, "type": "library", "autoload": { @@ -488,7 +513,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/2.0.x" + "source": "https://github.com/lcobucci/clock/tree/3.0.0" }, "funding": [ { @@ -500,47 +525,45 @@ "type": "patreon" } ], - "time": "2020-08-27T18:56:02+00:00" + "time": "2022-12-19T15:00:24+00:00" }, { "name": "lcobucci/jwt", - "version": "4.0.4", + "version": "4.3.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "55564265fddf810504110bd68ca311932324b0e9" + "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/55564265fddf810504110bd68ca311932324b0e9", - "reference": "55564265fddf810504110bd68ca311932324b0e9", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/4d7de2fe0d51a96418c0d04004986e410e87f6b4", + "reference": "4d7de2fe0d51a96418c0d04004986e410e87f6b4", "shasum": "" }, "require": { + "ext-hash": "*", + "ext-json": "*", "ext-mbstring": "*", "ext-openssl": "*", - "lcobucci/clock": "^2.0", + "ext-sodium": "*", + "lcobucci/clock": "^2.0 || ^3.0", "php": "^7.4 || ^8.0" }, "require-dev": { - "infection/infection": "^0.20", + "infection/infection": "^0.21", "lcobucci/coding-standard": "^6.0", - "mikey179/vfsstream": "^1.6", - "phpbench/phpbench": "^0.17", + "mikey179/vfsstream": "^1.6.7", + "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-deprecation-rules": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpstan/phpstan-strict-rules": "^0.12", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1.0", "phpunit/php-invoker": "^3.1", - "phpunit/phpunit": "^9.4" + "phpunit/phpunit": "^9.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, "autoload": { "psr-4": { "Lcobucci\\JWT\\": "src" @@ -564,7 +587,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/4.0.4" + "source": "https://github.com/lcobucci/jwt/tree/4.3.0" }, "funding": [ { @@ -576,33 +599,38 @@ "type": "patreon" } ], - "time": "2021-09-28T19:18:28+00:00" + "time": "2023-01-02T13:28:00+00:00" }, { "name": "league/event", - "version": "2.2.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/thephpleague/event.git", - "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119" + "reference": "221867a61087ee265ca07bd39aa757879afca820" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/event/zipball/d2cc124cf9a3fab2bb4ff963307f60361ce4d119", - "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119", + "url": "https://api.github.com/repos/thephpleague/event/zipball/221867a61087ee265ca07bd39aa757879afca820", + "reference": "221867a61087ee265ca07bd39aa757879afca820", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": ">=7.2.0", + "psr/event-dispatcher": "^1.0" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0" }, "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.2" + "friendsofphp/php-cs-fixer": "^2.16", + "phpstan/phpstan": "^0.12.45", + "phpunit/phpunit": "^8.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.2-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -626,7 +654,11 @@ "event", "listener" ], - "time": "2018-11-26T11:52:41+00:00" + "support": { + "issues": "https://github.com/thephpleague/event/issues", + "source": "https://github.com/thephpleague/event/tree/3.0.2" + }, + "time": "2022-10-29T09:31:25+00:00" }, { "name": "paragonie/random_compat", @@ -671,25 +703,128 @@ "pseudorandom", "random" ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, "time": "2020-10-15T08:29:30+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -709,7 +844,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -724,9 +859,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" } ], "aliases": [], diff --git a/examples/src/Repositories/AccessTokenRepository.php b/examples/src/Repositories/AccessTokenRepository.php index d7736c763..b0d3675b8 100644 --- a/examples/src/Repositories/AccessTokenRepository.php +++ b/examples/src/Repositories/AccessTokenRepository.php @@ -19,7 +19,7 @@ class AccessTokenRepository implements AccessTokenRepositoryInterface /** * {@inheritdoc} */ - public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity) + public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void { // Some logic here to save the access token to a database } @@ -27,7 +27,7 @@ public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEnt /** * {@inheritdoc} */ - public function revokeAccessToken($tokenId) + public function revokeAccessToken($tokenId): void { // Some logic here to revoke the access token } @@ -35,7 +35,7 @@ public function revokeAccessToken($tokenId) /** * {@inheritdoc} */ - public function isAccessTokenRevoked($tokenId) + public function isAccessTokenRevoked($tokenId): bool { return false; // Access token hasn't been revoked } @@ -43,7 +43,7 @@ public function isAccessTokenRevoked($tokenId) /** * {@inheritdoc} */ - public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null) + public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null): AccessTokenEntityInterface { $accessToken = new AccessTokenEntity(); $accessToken->setClient($clientEntity); diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 3a398f4ed..ac0b98341 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -9,6 +9,7 @@ namespace OAuth2ServerExamples\Repositories; +use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use OAuth2ServerExamples\Entities\ClientEntity; @@ -20,7 +21,7 @@ class ClientRepository implements ClientRepositoryInterface /** * {@inheritdoc} */ - public function getClientEntity($clientIdentifier) + public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface { $client = new ClientEntity(); @@ -35,7 +36,7 @@ public function getClientEntity($clientIdentifier) /** * {@inheritdoc} */ - public function validateClient($clientIdentifier, $clientSecret, $grantType) + public function validateClient($clientIdentifier, $clientSecret, $grantType): bool { $clients = [ 'myawesomeapp' => [ diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index 6848d0235..053720ffd 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -9,17 +9,19 @@ namespace OAuth2ServerExamples\Repositories; -use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; +use OAuth2ServerExamples\Entities\ClientEntity; use OAuth2ServerExamples\Entities\DeviceCodeEntity; +use DateTimeImmutable; + class DeviceCodeRepository implements DeviceCodeRepositoryInterface { /** * {@inheritdoc} */ - public function getNewDeviceCode() + public function getNewDeviceCode(): DeviceCodeEntityInterface { return new DeviceCodeEntity(); } @@ -27,7 +29,7 @@ public function getNewDeviceCode() /** * {@inheritdoc} */ - public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) + public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity): void { // Some logic to persist a new device code to a database } @@ -35,22 +37,29 @@ public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity) /** * {@inheritdoc} */ - public function getDeviceCodeEntityByDeviceCode($deviceCode) + public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceCodeEntityInterface { - $deviceCode = new DeviceCodeEntity(); + $clientEntity = new ClientEntity(); + $clientEntity->setIdentifier('myawesomeapp'); + + $deviceCodeEntity = new DeviceCodeEntity(); - $deviceCode->setIdentifier('device_code_1234'); + $deviceCodeEntity->setIdentifier($deviceCode); + $deviceCodeEntity->setExpiryDateTime((new DateTimeImmutable)->setTimestamp($this->getDeviceCodeExpiryTime($deviceCode))); + $deviceCodeEntity->setClient($clientEntity); + // TODO: Check if this is still true as it seems we need to set userapproved // The user identifier should be set when the user authenticates on the OAuth server - $deviceCode->setUserIdentifier(1); + $deviceCodeEntity->setUserApproved(true); + $deviceCodeEntity->setUserIdentifier(1); - return $deviceCode; + return $deviceCodeEntity; } /** * {@inheritdoc} */ - public function revokeDeviceCode($codeId) + public function revokeDeviceCode($codeId): void { // Some logic to revoke device code } @@ -58,8 +67,19 @@ public function revokeDeviceCode($codeId) /** * {@inheritdoc} */ - public function isDeviceCodeRevoked($codeId) + public function isDeviceCodeRevoked($codeId): bool + { + return false; + } + + // TODO: This should probably return a datetimeimmutable object to match the setter + public function getDeviceCodeExpiryTime(string $codeId): int + { + return (new DateTimeImmutable('now + 1 hour'))->getTimestamp(); + } + + public function getDeviceCodeClientId(string $codeId): string { - // Some logic to check if a device code has been revoked + // Some logic to get the client ID of the device code } } diff --git a/examples/src/Repositories/RefreshTokenRepository.php b/examples/src/Repositories/RefreshTokenRepository.php index 007529cda..e2a01d73e 100644 --- a/examples/src/Repositories/RefreshTokenRepository.php +++ b/examples/src/Repositories/RefreshTokenRepository.php @@ -18,7 +18,7 @@ class RefreshTokenRepository implements RefreshTokenRepositoryInterface /** * {@inheritdoc} */ - public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity) + public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void { // Some logic to persist the refresh token in a database } @@ -26,7 +26,7 @@ public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshToken /** * {@inheritdoc} */ - public function revokeRefreshToken($tokenId) + public function revokeRefreshToken($tokenId): void { // Some logic to revoke the refresh token in a database } @@ -34,7 +34,7 @@ public function revokeRefreshToken($tokenId) /** * {@inheritdoc} */ - public function isRefreshTokenRevoked($tokenId) + public function isRefreshTokenRevoked($tokenId): bool { return false; // The refresh token has not been revoked } @@ -42,7 +42,7 @@ public function isRefreshTokenRevoked($tokenId) /** * {@inheritdoc} */ - public function getNewRefreshToken() + public function getNewRefreshToken(): ?RefreshTokenEntityInterface { return new RefreshTokenEntity(); } diff --git a/examples/src/Repositories/ScopeRepository.php b/examples/src/Repositories/ScopeRepository.php index f0c6191bb..6b0d72a45 100644 --- a/examples/src/Repositories/ScopeRepository.php +++ b/examples/src/Repositories/ScopeRepository.php @@ -10,6 +10,7 @@ namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use OAuth2ServerExamples\Entities\ScopeEntity; @@ -18,7 +19,7 @@ class ScopeRepository implements ScopeRepositoryInterface /** * {@inheritdoc} */ - public function getScopeEntityByIdentifier($scopeIdentifier) + public function getScopeEntityByIdentifier($scopeIdentifier): ?ScopeEntityInterface { $scopes = [ 'basic' => [ @@ -30,7 +31,7 @@ public function getScopeEntityByIdentifier($scopeIdentifier) ]; if (\array_key_exists($scopeIdentifier, $scopes) === false) { - return; + return null; } $scope = new ScopeEntity(); @@ -39,6 +40,7 @@ public function getScopeEntityByIdentifier($scopeIdentifier) return $scope; } + // TODO: Check why I have added authCodeId here /** * {@inheritdoc} */ @@ -46,8 +48,9 @@ public function finalizeScopes( array $scopes, $grantType, ClientEntityInterface $clientEntity, - $userIdentifier = null - ) { + $userIdentifier = null, + $authCodeId = null + ): array { // Example of programatically modifying the final scope of the access token if ((int) $userIdentifier === 1) { $scope = new ScopeEntity(); diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index b48aa946b..e6921a7ee 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -139,25 +139,26 @@ public function respondToAccessTokenRequest( // Validate request $client = $this->validateClient($request); $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); - $deviceCode = $this->validateDeviceCode($request, $client); + $deviceCodeEntity = $this->validateDeviceCode($request, $client); - $deviceCode->setLastPolledAt(new DateTimeImmutable()); - $this->deviceCodeRepository->persistDeviceCode($deviceCode); + // TODO: This should be set on the repository, not the entity + $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable()); + $this->deviceCodeRepository->persistDeviceCode($deviceCodeEntity); // If device code has no user associated, respond with pending - if (is_null($deviceCode->getUserIdentifier())) { + if (is_null($deviceCodeEntity->getUserIdentifier())) { throw OAuthServerException::authorizationPending(); } - if ($deviceCode->getUserApproved() === false) { + if ($deviceCodeEntity->getUserApproved() === false) { throw OAuthServerException::accessDenied(); } // Finalize the requested scopes - $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, (string) $deviceCode->getUserIdentifier()); + $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, (string) $deviceCodeEntity->getUserIdentifier()); // Issue and persist new access token - $accessToken = $this->issueAccessToken($accessTokenTTL, $client, (string) $deviceCode->getUserIdentifier(), $finalizedScopes); + $accessToken = $this->issueAccessToken($accessTokenTTL, $client, (string) $deviceCodeEntity->getUserIdentifier(), $finalizedScopes); $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $responseType->setAccessToken($accessToken); @@ -169,7 +170,7 @@ public function respondToAccessTokenRequest( $responseType->setRefreshToken($refreshToken); } - $this->deviceCodeRepository->revokeDeviceCode($deviceCode->getIdentifier()); + $this->deviceCodeRepository->revokeDeviceCode($deviceCodeEntity->getIdentifier()); return $responseType; } diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index 634cf4992..e548ab0c5 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -35,7 +35,7 @@ public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity): * Get a device code entity. */ public function getDeviceCodeEntityByDeviceCode( - string $deviceCode + string $deviceCodeEntity ): ?DeviceCodeEntityInterface; /** From e8b5669e6754fc4bd898d3f7993dcd4c54d2dd41 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 14:23:51 +0000 Subject: [PATCH 164/212] Remove unnecessary repository functions --- .../src/Repositories/DeviceCodeRepository.php | 17 +++-------------- examples/src/Repositories/ScopeRepository.php | 1 - src/Grant/DeviceCodeGrant.php | 1 - .../DeviceCodeRepositoryInterface.php | 10 ---------- 4 files changed, 3 insertions(+), 26 deletions(-) diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index 053720ffd..f71141575 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -45,11 +45,11 @@ public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceCodeEntityI $deviceCodeEntity = new DeviceCodeEntity(); $deviceCodeEntity->setIdentifier($deviceCode); - $deviceCodeEntity->setExpiryDateTime((new DateTimeImmutable)->setTimestamp($this->getDeviceCodeExpiryTime($deviceCode))); + $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('now +1 hour')); $deviceCodeEntity->setClient($clientEntity); - // TODO: Check if this is still true as it seems we need to set userapproved - // The user identifier should be set when the user authenticates on the OAuth server + // The user identifier should be set when the user authenticates on the + // OAuth server, along with whether they approved the request $deviceCodeEntity->setUserApproved(true); $deviceCodeEntity->setUserIdentifier(1); @@ -71,15 +71,4 @@ public function isDeviceCodeRevoked($codeId): bool { return false; } - - // TODO: This should probably return a datetimeimmutable object to match the setter - public function getDeviceCodeExpiryTime(string $codeId): int - { - return (new DateTimeImmutable('now + 1 hour'))->getTimestamp(); - } - - public function getDeviceCodeClientId(string $codeId): string - { - // Some logic to get the client ID of the device code - } } diff --git a/examples/src/Repositories/ScopeRepository.php b/examples/src/Repositories/ScopeRepository.php index 6b0d72a45..21ea92ba8 100644 --- a/examples/src/Repositories/ScopeRepository.php +++ b/examples/src/Repositories/ScopeRepository.php @@ -40,7 +40,6 @@ public function getScopeEntityByIdentifier($scopeIdentifier): ?ScopeEntityInterf return $scope; } - // TODO: Check why I have added authCodeId here /** * {@inheritdoc} */ diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index e6921a7ee..bf90d6105 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -141,7 +141,6 @@ public function respondToAccessTokenRequest( $scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope)); $deviceCodeEntity = $this->validateDeviceCode($request, $client); - // TODO: This should be set on the repository, not the entity $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable()); $this->deviceCodeRepository->persistDeviceCode($deviceCodeEntity); diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index e548ab0c5..b25b45d20 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -51,14 +51,4 @@ public function revokeDeviceCode(string $codeId): void; * @return bool Return true if this code has been revoked */ public function isDeviceCodeRevoked(string $codeId): bool; - - /** - * Get the expiry time of the device code. - */ - public function getDeviceCodeExpiryTime(string $codeId): int; - - /** - * Get the client ID of the device code. - */ - public function getDeviceCodeClientId(string $codeId): string; } From 4345dae08285c7791bcc82651a4ab5d2fab31c47 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 14:43:00 +0000 Subject: [PATCH 165/212] remove unusued import --- src/Grant/DeviceCodeGrant.php | 1 - tests/Grant/DeviceCodeGrantTest.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index bf90d6105..bfd326f88 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -29,7 +29,6 @@ use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; -use Ramsey\Uuid\Uuid; use TypeError; use function is_null; diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 4383469a7..3cabffb2d 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -493,6 +493,7 @@ public function testRespondToRequestMissingDeviceCode(): void $responseType = new StubResponseType(); // TODO: We need to be more specific with this exception + // We should add an error that says the device code is missing perhaps? $this->expectException(OAuthServerException::class); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); From 2cf9fbbddf5e2a690d28d00c4a877e01d962f9e3 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 15:00:01 +0000 Subject: [PATCH 166/212] Add new methods to GrantTypeInterface --- src/Grant/AbstractGrant.php | 25 +++++++++++++++++++++++++ src/Grant/DeviceCodeGrant.php | 1 - src/Grant/GrantTypeInterface.php | 19 +++++++++++++++++++ tests/Stubs/GrantType.php | 13 +++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index c11be4d77..2949327ce 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -555,4 +555,29 @@ public function completeDeviceAuthorizationRequest(string $deviceCode, string $u { throw new LogicException('This grant cannot complete a device authorization request'); } + + /** + * {@inheritdoc} + */ + public function setIntervalVisibility(bool $intervalVisibility): void + { + throw new LogicException('This grant does not support the interval parameter'); + } + + /** + * {@inheritdoc} + */ + public function getIntervalVisibility(): bool + { + return false; + } + + /** + * {@inheritdoc} + */ + public function setIncludeVerificationUriComplete(bool $includeVerificationUriComplete): void + { + throw new LogicException('This grant does not support the verification_uri_complete parameter'); + } + } diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index bfd326f88..eb88fe9c7 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -316,7 +316,6 @@ protected function generateUserCode(int $length = 8): string // @codeCoverageIgnoreEnd } - // TODO: Check interface public function setIntervalVisibility(bool $intervalVisibility): void { $this->intervalVisibility = $intervalVisibility; diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 461416773..039982cf1 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -133,4 +133,23 @@ public function setEncryptionKey(Key|string|null $key = null): void; * Enable or prevent the revocation of refresh tokens upon usage. */ public function revokeRefreshTokens(bool $willRevoke): void; + + /** + * If set, the minimum interval between device code polling will be + * returned by the server. + */ + public function setIntervalVisibility(bool $intervalVisibility): void; + + /** + * Checks if the minimum interval between device code polling should be + * returned by the server. + */ + public function getIntervalVisibility(): bool; + + /** + * If set, the server will return a full verification URI to the client. + * This is useful when your device authorization endpoint might not be able + * to enter the user code easily. + */ + public function setIncludeVerificationUriComplete(bool $includeVerificationUriComplete): void; } diff --git a/tests/Stubs/GrantType.php b/tests/Stubs/GrantType.php index 6c6ecdcca..16eab4795 100644 --- a/tests/Stubs/GrantType.php +++ b/tests/Stubs/GrantType.php @@ -116,4 +116,17 @@ public function respondToDeviceAuthorizationRequest(ServerRequestInterface $requ { return new DeviceCodeResponse(); } + + public function setIntervalVisibility(bool $intervalVisibility): void + { + } + + public function getIntervalVisibility(): bool + { + return false; + } + + public function setIncludeVerificationUriComplete(bool $includeVerificationUriComplete): void + { + } } From e080cbff39667001335edafbd93da7c836d1d941 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 15:14:56 +0000 Subject: [PATCH 167/212] Fix a test exception --- src/Grant/AbstractGrant.php | 1 - tests/Grant/DeviceCodeGrantTest.php | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 2949327ce..e9796b185 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -158,7 +158,6 @@ protected function validateClient(ServerRequestInterface $request): ClientEntity throw OAuthServerException::invalidClient($request); } - $client = $this->getClientEntityOrFail($clientId, $request); // If a redirect URI is provided ensure it matches what is pre-registered diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 3cabffb2d..3169f0896 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -458,6 +458,7 @@ public function testRespondToRequestMissingDeviceCode(): void $client->setIdentifier('foo'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); + $clientRepositoryMock->method('validateClient')->willReturn(true); $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); @@ -492,9 +493,8 @@ public function testRespondToRequestMissingDeviceCode(): void $responseType = new StubResponseType(); - // TODO: We need to be more specific with this exception - // We should add an error that says the device code is missing perhaps? $this->expectException(OAuthServerException::class); + $this->expectExceptionCode(3); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } From b4fdc31444455f05135cfadeecc96a2db3bf755b Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 15:52:15 +0000 Subject: [PATCH 168/212] Update changelog --- CHANGELOG.md | 1 + src/Grant/AbstractGrant.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf098c892..0886edb44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added +- Device Code Grant added (PR #1074) - GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #1375) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index e9796b185..9b84e9ed0 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -578,5 +578,4 @@ public function setIncludeVerificationUriComplete(bool $includeVerificationUriCo { throw new LogicException('This grant does not support the verification_uri_complete parameter'); } - } From 6250531b7a797b768fb1669ec3ca19b4e65cfcf7 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 16:23:54 +0000 Subject: [PATCH 169/212] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0886edb44..8c8ebda75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Added -- Device Code Grant added (PR #1074) +- Device Authorization Grant added (PR #1074) - GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #1375) - A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044) - The authorization server can now finalize scopes when a client uses a refresh token (PR #1094) From 616240fbfde62c4f4c4a31fc4b870b370ba48930 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 16:25:08 +0000 Subject: [PATCH 170/212] Update PHP version support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cf973dcc8..091c84f95 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](ht The latest version of this package supports the following versions of PHP: -* PHP 8.0 * PHP 8.1 * PHP 8.2 +* PHP 8.3 The `openssl` and `json` extensions are also required. From dde13a424f2aa28a26f1a429038e97fa032f6107 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 16:35:20 +0000 Subject: [PATCH 171/212] Revert docblock deletions --- src/Entities/TokenInterface.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 6082e57e1..96d2bd418 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -16,12 +16,24 @@ interface TokenInterface { + /** + * Get the token's identifier. + */ public function getIdentifier(): string; + /** + * Set the token's identifier. + */ public function setIdentifier(mixed $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; /** @@ -31,15 +43,29 @@ public function setExpiryDateTime(DateTimeImmutable $dateTime): void; */ public function setUserIdentifier(string $identifier): void; + /** + * Get the token user's identifier. + */ public function getUserIdentifier(): string|int|null; + /** + * Get the client that the token was issued to. + */ public function getClient(): ClientEntityInterface; + /** + * Set the client that the token was issued to. + */ public function setClient(ClientEntityInterface $client): void; + /** + * Associate a scope with the token. + */ public function addScope(ScopeEntityInterface $scope): void; /** + * Return an array of scopes associated with the token. + * * @return ScopeEntityInterface[] */ public function getScopes(): array; From d00fbfcebf7905bbce61f6d1e6d44468fcad0926 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 16:37:26 +0000 Subject: [PATCH 172/212] Update author --- src/Entities/Traits/DeviceCodeTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Entities/Traits/DeviceCodeTrait.php b/src/Entities/Traits/DeviceCodeTrait.php index 22af197bb..ea8b2acc2 100644 --- a/src/Entities/Traits/DeviceCodeTrait.php +++ b/src/Entities/Traits/DeviceCodeTrait.php @@ -1,7 +1,7 @@ + * @author Andrew Millington * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * From 3449fa8a74e9503f7ff3a3a45ee98ee761aae4e2 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 16:41:42 +0000 Subject: [PATCH 173/212] Update author --- src/Grant/AbstractGrant.php | 2 +- src/Grant/DeviceCodeGrant.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 9b84e9ed0..87ac6ae78 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -66,7 +66,7 @@ abstract class AbstractGrant implements GrantTypeInterface protected const SCOPE_DELIMITER_STRING = ' '; - protected const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; + private const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; protected ClientRepositoryInterface $clientRepository; diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index eb88fe9c7..ae0bc2265 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -3,7 +3,7 @@ /** * OAuth 2.0 Device Code grant. * - * @author Alex Bilbie + * @author Andrew Millington * @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * From 571843db9eb3b96924182a6f9b199b3b2be5f878 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 16:49:42 +0000 Subject: [PATCH 174/212] revert erroneous edit --- src/Grant/AbstractGrant.php | 2 +- src/Grant/DeviceCodeGrant.php | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 87ac6ae78..9b84e9ed0 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -66,7 +66,7 @@ abstract class AbstractGrant implements GrantTypeInterface protected const SCOPE_DELIMITER_STRING = ' '; - private const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; + protected const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10; protected ClientRepositoryInterface $clientRepository; diff --git a/src/Grant/DeviceCodeGrant.php b/src/Grant/DeviceCodeGrant.php index ae0bc2265..89568a19b 100644 --- a/src/Grant/DeviceCodeGrant.php +++ b/src/Grant/DeviceCodeGrant.php @@ -244,7 +244,6 @@ private function setDeviceCodeRepository(DeviceCodeRepositoryInterface $deviceCo * * @param ScopeEntityInterface[] $scopes * - * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException */ From 5642455e62ca1412596e70119e85ca972f91007c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:46:08 +0000 Subject: [PATCH 175/212] Update styleci preset --- .styleci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 1caa7b894..7b2a1c0c4 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -1,4 +1,4 @@ -preset: psr2 +preset: psr12 risky: true From 5c47e05ed6f7171000ef16060475db8bc389d167 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:48:16 +0000 Subject: [PATCH 176/212] remove duplicate styleci rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 7b2a1c0c4..a63941961 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -5,7 +5,6 @@ risky: true enabled: - binary_operator_spaces - blank_line_before_return - - concat_with_spaces - fully_qualified_strict_types - function_typehint_space - hash_to_slash_comment From a2f5fca9dd9f6547ccf608f5852e2bd4ccc68c10 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:52:21 +0000 Subject: [PATCH 177/212] remove duplicate styleci rule --- .styleci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.styleci.yml b/.styleci.yml index a63941961..5fb499bc8 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -6,18 +6,13 @@ enabled: - binary_operator_spaces - blank_line_before_return - fully_qualified_strict_types - - function_typehint_space - hash_to_slash_comment - include - - lowercase_cast - method_separation - native_function_casing - native_function_invocation - - no_blank_lines_after_class_opening - no_blank_lines_between_uses - no_duplicate_semicolons - - no_leading_import_slash - - no_leading_namespace_whitespace - no_multiline_whitespace_before_semicolons - no_php4_constructor - no_short_bool_cast @@ -46,7 +41,6 @@ enabled: - single_quote - spaces_cast - standardize_not_equal - - ternary_operator_spaces - trailing_comma_in_multiline_array - trim_array_spaces - unary_operator_spaces From be7eef198c94a1cc1e48ab02f95c737fd1223f1c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:53:05 +0000 Subject: [PATCH 178/212] remove duplicate styleci rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 5fb499bc8..a5787f18c 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -18,7 +18,6 @@ enabled: - no_short_bool_cast - no_singleline_whitespace_before_semicolons - no_trailing_comma_in_singleline_array - - no_unreachable_default_argument_value - no_unused_imports - no_whitespace_before_comma_in_array - ordered_imports From e82d530c267c51b90a0a9981d23e8083d4099021 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:53:46 +0000 Subject: [PATCH 179/212] remove duplicate styleci rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index a5787f18c..49fa06e43 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -36,7 +36,6 @@ enabled: - phpdoc_var_without_name - print_to_echo - short_array_syntax - - short_scalar_cast - single_quote - spaces_cast - standardize_not_equal From d674022e892211443e31cd66a9c98cab29246199 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:54:25 +0000 Subject: [PATCH 180/212] remove duplicate styleci rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 49fa06e43..e04ed1564 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -41,6 +41,5 @@ enabled: - standardize_not_equal - trailing_comma_in_multiline_array - trim_array_spaces - - unary_operator_spaces - whitespace_after_comma_in_array - whitespacy_lines From a94963a5108fe32367c7abfcf32b6ae1d6ca0372 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:55:08 +0000 Subject: [PATCH 181/212] remove duplicate styleci rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index e04ed1564..0341ec6da 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -42,4 +42,3 @@ enabled: - trailing_comma_in_multiline_array - trim_array_spaces - whitespace_after_comma_in_array - - whitespacy_lines From 749f9d06380503acad79669139a752fbdea5c671 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 17:56:36 +0000 Subject: [PATCH 182/212] remove duplicate styleci rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 0341ec6da..a1008ff82 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -3,7 +3,6 @@ preset: psr12 risky: true enabled: - - binary_operator_spaces - blank_line_before_return - fully_qualified_strict_types - hash_to_slash_comment From 3ffbc6704c223f9b97ff83463492718dfccda41e Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:00:22 +0000 Subject: [PATCH 183/212] remove native function invocation rule --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index a1008ff82..742b2ce1f 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -9,7 +9,6 @@ enabled: - include - method_separation - native_function_casing - - native_function_invocation - no_blank_lines_between_uses - no_duplicate_semicolons - no_multiline_whitespace_before_semicolons From 70cce355781b83a1d27c54c694b56b2144aa85a1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:02:47 +0000 Subject: [PATCH 184/212] remove trailing whitespace --- examples/public/device_code.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/public/device_code.php b/examples/public/device_code.php index b88a67838..30ea87e93 100644 --- a/examples/public/device_code.php +++ b/examples/public/device_code.php @@ -70,7 +70,7 @@ return $deviceCodeResponse; // Extract the device code. Usually we would then assign the user ID to - // the device code but for the purposes of this example, we've hard + // the device code but for the purposes of this example, we've hard // coded it in the response above. // $deviceCode = json_decode((string) $deviceCodeResponse->getBody()); From 7474c01cae0979c10e5d58b3cc276d91b372ddec Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:03:51 +0000 Subject: [PATCH 185/212] split use statements over multiple lines --- examples/src/Entities/AccessTokenEntity.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/src/Entities/AccessTokenEntity.php b/examples/src/Entities/AccessTokenEntity.php index f55246b33..f9e70210f 100644 --- a/examples/src/Entities/AccessTokenEntity.php +++ b/examples/src/Entities/AccessTokenEntity.php @@ -16,5 +16,7 @@ class AccessTokenEntity implements AccessTokenEntityInterface { - use AccessTokenTrait, TokenEntityTrait, EntityTrait; + use AccessTokenTrait; + use TokenEntityTrait; + use EntityTrait; } From ec4b6559c0fe4600dcf3f777875b2911f87f05f1 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:04:39 +0000 Subject: [PATCH 186/212] split use statements over multiple lines --- examples/src/Entities/AuthCodeEntity.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/src/Entities/AuthCodeEntity.php b/examples/src/Entities/AuthCodeEntity.php index acfbc3b60..50d2ff479 100644 --- a/examples/src/Entities/AuthCodeEntity.php +++ b/examples/src/Entities/AuthCodeEntity.php @@ -16,5 +16,7 @@ class AuthCodeEntity implements AuthCodeEntityInterface { - use EntityTrait, TokenEntityTrait, AuthCodeTrait; + use EntityTrait; + use TokenEntityTrait; + use AuthCodeTrait; } From 8ef669f19eb6d8774efc7592f794d6b8bb9f1bec Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:06:38 +0000 Subject: [PATCH 187/212] split use statements over multiple lines --- examples/src/Entities/ClientEntity.php | 3 ++- examples/src/Entities/DeviceCodeEntity.php | 4 +++- examples/src/Entities/RefreshTokenEntity.php | 3 ++- examples/src/Entities/ScopeEntity.php | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/src/Entities/ClientEntity.php b/examples/src/Entities/ClientEntity.php index c25b70edc..76bd04085 100644 --- a/examples/src/Entities/ClientEntity.php +++ b/examples/src/Entities/ClientEntity.php @@ -15,7 +15,8 @@ class ClientEntity implements ClientEntityInterface { - use EntityTrait, ClientTrait; + use EntityTrait; + use ClientTrait; public function setName($name) { diff --git a/examples/src/Entities/DeviceCodeEntity.php b/examples/src/Entities/DeviceCodeEntity.php index 92665b421..e3ff398a8 100644 --- a/examples/src/Entities/DeviceCodeEntity.php +++ b/examples/src/Entities/DeviceCodeEntity.php @@ -16,5 +16,7 @@ class DeviceCodeEntity implements DeviceCodeEntityInterface { - use EntityTrait, DeviceCodeTrait, TokenEntityTrait; + use EntityTrait; + use DeviceCodeTrait; + use TokenEntityTrait; } diff --git a/examples/src/Entities/RefreshTokenEntity.php b/examples/src/Entities/RefreshTokenEntity.php index 60109c029..d98709f41 100644 --- a/examples/src/Entities/RefreshTokenEntity.php +++ b/examples/src/Entities/RefreshTokenEntity.php @@ -15,5 +15,6 @@ class RefreshTokenEntity implements RefreshTokenEntityInterface { - use RefreshTokenTrait, EntityTrait; + use RefreshTokenTrait; + use EntityTrait; } diff --git a/examples/src/Entities/ScopeEntity.php b/examples/src/Entities/ScopeEntity.php index 913c0592e..4258ce86f 100644 --- a/examples/src/Entities/ScopeEntity.php +++ b/examples/src/Entities/ScopeEntity.php @@ -15,5 +15,6 @@ class ScopeEntity implements ScopeEntityInterface { - use EntityTrait, ScopeTrait; + use EntityTrait; + use ScopeTrait; } From 1290156723fb25b94bfd196010de7993af62cee2 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:07:43 +0000 Subject: [PATCH 188/212] Add private to consts --- examples/src/Repositories/ClientRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index ac0b98341..d8a74996f 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -15,8 +15,8 @@ class ClientRepository implements ClientRepositoryInterface { - const CLIENT_NAME = 'My Awesome App'; - const REDIRECT_URI = 'http://foo/bar'; + private const CLIENT_NAME = 'My Awesome App'; + private const REDIRECT_URI = 'http://foo/bar'; /** * {@inheritdoc} From 25e45cd1f824cf0742daa3ae2cff0826741b6486 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:08:27 +0000 Subject: [PATCH 189/212] remove unused imports --- src/AuthorizationServer.php | 1 - src/AuthorizationValidators/BearerTokenValidator.php | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index a52c3cd75..c894bbd6b 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -65,7 +65,6 @@ public function __construct( Key|string $encryptionKey, ResponseTypeInterface|null $responseType = null ) { - if ($privateKey instanceof CryptKeyInterface === false) { $privateKey = new CryptKey($privateKey); } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index afe4ee6f9..0442dd48e 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -30,9 +30,7 @@ use Psr\Http\Message\ServerRequestInterface; use RuntimeException; -use function count; use function date_default_timezone_get; -use function is_array; use function preg_replace; use function trim; From 0328f87ae232332753014e129a2336eb552422c4 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:09:14 +0000 Subject: [PATCH 190/212] remove blank line --- src/CryptKey.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CryptKey.php b/src/CryptKey.php index e8ce076f7..944c56a05 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -43,7 +43,6 @@ class CryptKey implements CryptKeyInterface public function __construct(string $keyPath, protected ?string $passPhrase = null, bool $keyPermissionsCheck = true) { - if (strpos($keyPath, self::FILE_PREFIX) !== 0 && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { $this->keyContents = $keyPath; $this->keyPath = ''; From 16e377b64af4f4e0371dcd389fdd071d657c98ea Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:10:37 +0000 Subject: [PATCH 191/212] Remove extra blank line --- src/CryptKeyInterface.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CryptKeyInterface.php b/src/CryptKeyInterface.php index 993ab4d52..7e56d0b76 100644 --- a/src/CryptKeyInterface.php +++ b/src/CryptKeyInterface.php @@ -8,7 +8,6 @@ interface CryptKeyInterface { /** * Retrieve key path. - * */ public function getKeyPath(): string; From b6c44ada661e18b1d639eab7dc695652c1a4cf91 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:11:25 +0000 Subject: [PATCH 192/212] fix docblock formatting --- src/Exception/OAuthServerException.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index b0b92fbaa..03950a381 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -205,7 +205,7 @@ public function getErrorType(): string /** * Expired token error. * - * @param Throwable $previous Previous exception + * @param Throwable $previous Previous exception * * @return static */ @@ -357,7 +357,6 @@ public function getHint(): ?string * * Returns true if the header is present and not an empty string, false * otherwise. - * */ private function requestHasAuthorizationHeader(): bool { From 7d3864d15caffb46219673465f6f58c9948fe3d5 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:11:59 +0000 Subject: [PATCH 193/212] Remove additional whitespace --- src/Grant/AbstractAuthorizeGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/AbstractAuthorizeGrant.php b/src/Grant/AbstractAuthorizeGrant.php index 27d39982b..4fe552c17 100644 --- a/src/Grant/AbstractAuthorizeGrant.php +++ b/src/Grant/AbstractAuthorizeGrant.php @@ -23,7 +23,7 @@ abstract class AbstractAuthorizeGrant extends AbstractGrant { /** - * @param mixed[] $params + * @param mixed[] $params */ public function makeRedirectUri(string $uri, array $params = [], string $queryDelimiter = '?'): string { From aabaf10b888828f29fe83a04eb7cc23d262a6b14 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:26:04 +0000 Subject: [PATCH 194/212] Fix styleci issues --- src/Grant/AbstractGrant.php | 3 +-- src/Grant/GrantTypeInterface.php | 6 ++--- .../DeviceCodeRepositoryInterface.php | 4 --- src/RequestAccessTokenEvent.php | 1 - src/RequestEvent.php | 1 - src/RequestRefreshTokenEvent.php | 1 - src/RequestTypes/AuthorizationRequest.php | 1 + src/ResourceServer.php | 1 - tests/AuthorizationServerTest.php | 2 +- tests/Grant/AbstractGrantTest.php | 1 + tests/Grant/DeviceCodeGrantTest.php | 26 +++++++++---------- 11 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 9b84e9ed0..4a86bceb8 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -358,7 +358,6 @@ protected function getServerParameter(string $parameter, ServerRequestInterface * * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException - * */ protected function issueAccessToken( DateInterval $accessTokenTTL, @@ -392,7 +391,7 @@ protected function issueAccessToken( /** * Issue an auth code. * - * @param non-empty-string $userIdentifier + * @param non-empty-string $userIdentifier * @param ScopeEntityInterface[] $scopes * * @throws OAuthServerException diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 039982cf1..4e7dcf0f0 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -51,7 +51,7 @@ public function respondToAccessTokenRequest( ): ResponseTypeInterface; /** - * The grant type should return true if it is able to response to an authorization request + * The grant type should return true if it is able to respond to an authorization request */ public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool; @@ -79,9 +79,7 @@ public function completeAuthorizationRequest(AuthorizationRequestInterface $auth public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool; /** - * The grant type should return true if it is able to response to a device authorization request - * - * + * The grant type should return true if it is able to respond to a device authorization request */ public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool; diff --git a/src/Repositories/DeviceCodeRepositoryInterface.php b/src/Repositories/DeviceCodeRepositoryInterface.php index b25b45d20..09575ab18 100644 --- a/src/Repositories/DeviceCodeRepositoryInterface.php +++ b/src/Repositories/DeviceCodeRepositoryInterface.php @@ -12,7 +12,6 @@ namespace League\OAuth2\Server\Repositories; -use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; @@ -20,7 +19,6 @@ interface DeviceCodeRepositoryInterface extends RepositoryInterface { /** * Creates a new DeviceCode - * */ public function getNewDeviceCode(): DeviceCodeEntityInterface; @@ -40,14 +38,12 @@ public function getDeviceCodeEntityByDeviceCode( /** * Revoke a device code. - * */ public function revokeDeviceCode(string $codeId): void; /** * Check if the device code has been revoked. * - * * @return bool Return true if this code has been revoked */ public function isDeviceCodeRevoked(string $codeId): bool; diff --git a/src/RequestAccessTokenEvent.php b/src/RequestAccessTokenEvent.php index 9bb0c3c2f..1200c44c5 100644 --- a/src/RequestAccessTokenEvent.php +++ b/src/RequestAccessTokenEvent.php @@ -23,7 +23,6 @@ public function __construct(string $name, ServerRequestInterface $request, priva } /** - * * @codeCoverageIgnore */ public function getAccessToken(): AccessTokenEntityInterface diff --git a/src/RequestEvent.php b/src/RequestEvent.php index 2a1fb0915..e88cd3137 100644 --- a/src/RequestEvent.php +++ b/src/RequestEvent.php @@ -30,7 +30,6 @@ public function __construct(string $name, private ServerRequestInterface $reques } /** - * * @codeCoverageIgnore */ public function getRequest(): ServerRequestInterface diff --git a/src/RequestRefreshTokenEvent.php b/src/RequestRefreshTokenEvent.php index a6d17971c..f2ac556cd 100644 --- a/src/RequestRefreshTokenEvent.php +++ b/src/RequestRefreshTokenEvent.php @@ -23,7 +23,6 @@ public function __construct(string $name, ServerRequestInterface $request, priva } /** - * * @codeCoverageIgnore */ public function getRefreshToken(): RefreshTokenEntityInterface diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index 276396722..09b11562a 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -35,6 +35,7 @@ class AuthorizationRequest implements AuthorizationRequestInterface /** * An array of scope identifiers + * * @var ScopeEntityInterface[] */ protected array $scopes = []; diff --git a/src/ResourceServer.php b/src/ResourceServer.php index 0ffbf889a..e89e8d24a 100644 --- a/src/ResourceServer.php +++ b/src/ResourceServer.php @@ -29,7 +29,6 @@ public function __construct( CryptKeyInterface|string $publicKey, AuthorizationValidatorInterface $authorizationValidator = null ) { - if ($publicKey instanceof CryptKeyInterface === false) { $publicKey = new CryptKey($publicKey); } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 2438f9ccd..6e41a17f3 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -183,7 +183,7 @@ public function testMultipleRequestsGetDifferentResponseTypeInstances(): void $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; - $responseTypePrototype = new class extends BearerTokenResponse { + $responseTypePrototype = new class () extends BearerTokenResponse { protected CryptKeyInterface $privateKey; protected Key|string|null $encryptionKey = null; diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 0cfa10c56..5c80e7d6a 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -183,6 +183,7 @@ public function testValidateClientConfidential(): void $result = $validateClientMethod->invoke($grantMock, $serverRequest, true, true); self::assertEquals($client, $result); } + public function testValidateClientMissingClientId(): void { $client = new ClientEntity(); diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 3169f0896..83baa57d7 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -54,7 +54,7 @@ public function testGetIdentifier(): void $deviceCodeRepositoryMock, $refreshTokenRepositoryMock, new DateInterval('PT10M'), - "http://foo/bar" + 'http://foo/bar' ); $this::assertEquals('urn:ietf:params:oauth:grant-type:device_code', $grant->getIdentifier()); @@ -66,7 +66,7 @@ public function testCanRespondToDeviceAuthorizationRequest(): void $this->getMockBuilder(DeviceCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M'), - "http://foo/bar" + 'http://foo/bar' ); $request = (new ServerRequest())->withParsedBody([ @@ -97,7 +97,7 @@ public function testRespondToDeviceAuthorizationRequest(): void $deviceCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M'), - "http://foo/bar" + 'http://foo/bar' ); $grant->setClientRepository($clientRepositoryMock); @@ -140,7 +140,7 @@ public function testRespondToDeviceAuthorizationRequestWithVerificationUriComple $deviceCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M'), - "http://foo/bar" + 'http://foo/bar' ); $grant->setIncludeVerificationUriComplete(true); @@ -317,12 +317,12 @@ public function testDeviceAuthorizationResponse(): void 'client_id' => 'foo', ]); - $deviceCodeGrant = new DeviceCodeGrant( - $deviceCodeRepository, - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new DateInterval('PT10M'), - 'http://foo/bar' - ); + $deviceCodeGrant = new DeviceCodeGrant( + $deviceCodeRepository, + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M'), + 'http://foo/bar' + ); $deviceCodeGrant->setEncryptionKey($this->cryptStub->getKey()); @@ -384,7 +384,7 @@ public function testRespondToAccessTokenRequest(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCodeEntity->getUserCode(), "1", true); + $grant->completeDeviceAuthorizationRequest($deviceCodeEntity->getUserCode(), '1', true); $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code', @@ -705,7 +705,7 @@ public function testSettingDeviceCodeIntervalRate(): void $deviceCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), new DateInterval('PT10M'), - "http://foo/bar", + 'http://foo/bar', self::INTERVAL_RATE ); @@ -768,7 +768,7 @@ public function testIssueAccessDeniedError(): void $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), "1", false); + $grant->completeDeviceAuthorizationRequest($deviceCode->getUserCode(), '1', false); $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', From 492bc430a10f19f0050fd81d6fcf99ce9dbd8cd7 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:27:24 +0000 Subject: [PATCH 195/212] Fix styles --- .styleci.yml | 1 - tests/Grant/DeviceCodeGrantTest.php | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index 742b2ce1f..3f8b2e454 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -18,7 +18,6 @@ enabled: - no_trailing_comma_in_singleline_array - no_unused_imports - no_whitespace_before_comma_in_array - - ordered_imports - phpdoc_align - phpdoc_indent - phpdoc_inline_tag diff --git a/tests/Grant/DeviceCodeGrantTest.php b/tests/Grant/DeviceCodeGrantTest.php index 83baa57d7..396ea760f 100644 --- a/tests/Grant/DeviceCodeGrantTest.php +++ b/tests/Grant/DeviceCodeGrantTest.php @@ -729,6 +729,7 @@ public function testSettingDeviceCodeIntervalRate(): void $this::assertObjectHasProperty('interval', $deviceCode); $this::assertEquals(self::INTERVAL_RATE, $deviceCode->interval); } + public function testIssueAccessDeniedError(): void { $client = new ClientEntity(); From 06a3b8e820b7f937e26142b957316a8807a5fe40 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Thu, 21 Mar 2024 18:30:03 +0000 Subject: [PATCH 196/212] Fix import order --- .styleci.yml | 1 + examples/src/Repositories/DeviceCodeRepository.php | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.styleci.yml b/.styleci.yml index 3f8b2e454..742b2ce1f 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -18,6 +18,7 @@ enabled: - no_trailing_comma_in_singleline_array - no_unused_imports - no_whitespace_before_comma_in_array + - ordered_imports - phpdoc_align - phpdoc_indent - phpdoc_inline_tag diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index f71141575..838c52e07 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -9,13 +9,12 @@ namespace OAuth2ServerExamples\Repositories; +use DateTimeImmutable; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface; use OAuth2ServerExamples\Entities\ClientEntity; use OAuth2ServerExamples\Entities\DeviceCodeEntity; -use DateTimeImmutable; - class DeviceCodeRepository implements DeviceCodeRepositoryInterface { /** From b47472c95e9c5889b87a1735223a131e47d439f0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 22 Mar 2024 21:54:44 +0000 Subject: [PATCH 197/212] Fix examples styling --- examples/public/api.php | 13 ++++++++----- examples/public/auth_code.php | 11 +++++++---- examples/public/client_credentials.php | 10 +++++++--- examples/public/device_code.php | 11 +++++++---- examples/public/implicit.php | 7 +++++-- examples/public/middleware_use.php | 19 +++++++++++-------- examples/public/password.php | 8 +++++--- examples/public/refresh_token.php | 9 ++++++--- examples/src/Entities/AccessTokenEntity.php | 3 +++ examples/src/Entities/AuthCodeEntity.php | 3 +++ examples/src/Entities/ClientEntity.php | 9 ++++++--- examples/src/Entities/DeviceCodeEntity.php | 3 +++ examples/src/Entities/RefreshTokenEntity.php | 3 +++ examples/src/Entities/ScopeEntity.php | 3 +++ examples/src/Entities/UserEntity.php | 6 ++++-- .../Repositories/AccessTokenRepository.php | 3 +++ .../src/Repositories/AuthCodeRepository.php | 3 +++ .../src/Repositories/ClientRepository.php | 13 ++++++++++--- .../src/Repositories/DeviceCodeRepository.php | 3 +++ .../Repositories/RefreshTokenRepository.php | 3 +++ examples/src/Repositories/ScopeRepository.php | 7 ++++++- examples/src/Repositories/UserRepository.php | 3 +++ phpcs.xml.dist | 1 + 23 files changed, 113 insertions(+), 41 deletions(-) diff --git a/examples/public/api.php b/examples/public/api.php index 1fa9d82dc..08c107be5 100644 --- a/examples/public/api.php +++ b/examples/public/api.php @@ -1,5 +1,8 @@ add( - new \League\OAuth2\Server\Middleware\ResourceServerMiddleware( + new ResourceServerMiddleware( $app->getContainer()->get(ResourceServer::class) ) ); @@ -49,23 +52,23 @@ function (ServerRequestInterface $request, ResponseInterface $response) use ($ap ], ]; - $totalUsers = \count($users); + $totalUsers = count($users); // If the access token doesn't have the `basic` scope hide users' names - if (\in_array('basic', $request->getAttribute('oauth_scopes')) === false) { + if (in_array('basic', $request->getAttribute('oauth_scopes')) === false) { for ($i = 0; $i < $totalUsers; $i++) { unset($users[$i]['name']); } } // If the access token doesn't have the `email` scope hide users' email addresses - if (\in_array('email', $request->getAttribute('oauth_scopes')) === false) { + if (in_array('email', $request->getAttribute('oauth_scopes')) === false) { for ($i = 0; $i < $totalUsers; $i++) { unset($users[$i]['email']); } } - $response->getBody()->write(\json_encode($users)); + $response->getBody()->write(json_encode($users)); return $response->withStatus(200); } diff --git a/examples/public/auth_code.php b/examples/public/auth_code.php index c082e3b3f..815d86dee 100644 --- a/examples/public/auth_code.php +++ b/examples/public/auth_code.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + use Laminas\Diactoros\Stream; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; @@ -51,9 +54,9 @@ new AuthCodeGrant( $authCodeRepository, $refreshTokenRepository, - new \DateInterval('PT10M') + new DateInterval('PT10M') ), - new \DateInterval('PT1H') + new DateInterval('PT1H') ); return $server; @@ -80,7 +83,7 @@ return $server->completeAuthorizationRequest($authRequest, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { $body = new Stream('php://temp', 'r+'); $body->write($exception->getMessage()); @@ -96,7 +99,7 @@ return $server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { $body = new Stream('php://temp', 'r+'); $body->write($exception->getMessage()); diff --git a/examples/public/client_credentials.php b/examples/public/client_credentials.php index 1e5f090d7..12f294484 100644 --- a/examples/public/client_credentials.php +++ b/examples/public/client_credentials.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,9 +8,12 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + use Laminas\Diactoros\Stream; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Grant\ClientCredentialsGrant; use OAuth2ServerExamples\Repositories\AccessTokenRepository; use OAuth2ServerExamples\Repositories\ClientRepository; use OAuth2ServerExamples\Repositories\ScopeRepository; @@ -44,8 +48,8 @@ // Enable the client credentials grant on the server $server->enableGrantType( - new \League\OAuth2\Server\Grant\ClientCredentialsGrant(), - new \DateInterval('PT1H') // access tokens will expire after 1 hour + new ClientCredentialsGrant(), + new DateInterval('PT1H') // access tokens will expire after 1 hour ); return $server; @@ -62,7 +66,7 @@ } catch (OAuthServerException $exception) { // All instances of OAuthServerException can be formatted into a HTTP response return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { // Unknown exception $body = new Stream('php://temp', 'r+'); $body->write($exception->getMessage()); diff --git a/examples/public/device_code.php b/examples/public/device_code.php index 30ea87e93..e63349e76 100644 --- a/examples/public/device_code.php +++ b/examples/public/device_code.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + include __DIR__ . '/../vendor/autoload.php'; use League\OAuth2\Server\AuthorizationServer; @@ -50,10 +53,10 @@ new DeviceCodeGrant( $deviceCodeRepository, $refreshTokenRepository, - new \DateInterval('PT10M'), + new DateInterval('PT10M'), 'http://foo/bar' ), - new \DateInterval('PT1H') + new DateInterval('PT1H') ); return $server; @@ -78,7 +81,7 @@ // $server->completeDeviceAuthorizationRequest($deviceCode->user_code, 1); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { $body = new Stream('php://temp', 'r+'); $body->write($exception->getMessage()); @@ -94,7 +97,7 @@ return $server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { $body = new Stream('php://temp', 'r+'); $body->write($exception->getMessage()); diff --git a/examples/public/implicit.php b/examples/public/implicit.php index ac43f5dd1..6c54b8f2c 100644 --- a/examples/public/implicit.php +++ b/examples/public/implicit.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + use Laminas\Diactoros\Stream; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; @@ -43,7 +46,7 @@ ); // Enable the implicit grant on the server with a token TTL of 1 hour - $server->enableGrantType(new ImplicitGrant(new \DateInterval('PT1H'))); + $server->enableGrantType(new ImplicitGrant(new DateInterval('PT1H'))); return $server; }, @@ -69,7 +72,7 @@ return $server->completeAuthorizationRequest($authRequest, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { $body = new Stream('php://temp', 'r+'); $body->write($exception->getMessage()); diff --git a/examples/public/middleware_use.php b/examples/public/middleware_use.php index 9f958ed26..2bcf9c7b8 100644 --- a/examples/public/middleware_use.php +++ b/examples/public/middleware_use.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + use Laminas\Diactoros\Stream; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Grant\AuthCodeGrant; @@ -53,15 +56,15 @@ new AuthCodeGrant( $authCodeRepository, $refreshTokenRepository, - new \DateInterval('PT10M') + new DateInterval('PT10M') ), - new \DateInterval('PT1H') + new DateInterval('PT1H') ); // Enable the refresh token grant on the server with a token TTL of 1 month $server->enableGrantType( new RefreshTokenGrant($refreshTokenRepository), - new \DateInterval('P1M') + new DateInterval('P1M') ); return $server; @@ -79,15 +82,15 @@ ]); // Access token issuer -$app->post('/access_token', function () { +$app->post('/access_token', function (): void { })->add(new AuthorizationServerMiddleware($app->getContainer()->get(AuthorizationServer::class))); // Secured API -$app->group('/api', function () { +$app->group('/api', function (): void { $this->get('/user', function (ServerRequestInterface $request, ResponseInterface $response) { $params = []; - if (\in_array('basic', $request->getAttribute('oauth_scopes', []))) { + if (in_array('basic', $request->getAttribute('oauth_scopes', []))) { $params = [ 'id' => 1, 'name' => 'Alex', @@ -95,12 +98,12 @@ ]; } - if (\in_array('email', $request->getAttribute('oauth_scopes', []))) { + if (in_array('email', $request->getAttribute('oauth_scopes', []))) { $params['email'] = 'alex@example.com'; } $body = new Stream('php://temp', 'r+'); - $body->write(\json_encode($params)); + $body->write(json_encode($params)); return $response->withBody($body); }); diff --git a/examples/public/password.php b/examples/public/password.php index db65d7840..61096226e 100644 --- a/examples/public/password.php +++ b/examples/public/password.php @@ -1,5 +1,7 @@ setRefreshTokenTTL(new \DateInterval('P1M')); // refresh tokens will expire after 1 month + $grant->setRefreshTokenTTL(new DateInterval('P1M')); // refresh tokens will expire after 1 month // Enable the password grant on the server with a token TTL of 1 hour $server->enableGrantType( $grant, - new \DateInterval('PT1H') // access tokens will expire after 1 hour + new DateInterval('PT1H') // access tokens will expire after 1 hour ); return $server; @@ -54,7 +56,7 @@ function (ServerRequestInterface $request, ResponseInterface $response) use ($ap } catch (OAuthServerException $exception) { // All instances of OAuthServerException can be converted to a PSR-7 response return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { // Catch unexpected exceptions $body = $response->getBody(); $body->write($exception->getMessage()); diff --git a/examples/public/refresh_token.php b/examples/public/refresh_token.php index 39be08262..f534d7483 100644 --- a/examples/public/refresh_token.php +++ b/examples/public/refresh_token.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\RefreshTokenGrant; @@ -44,11 +47,11 @@ // Enable the refresh token grant on the server $grant = new RefreshTokenGrant($refreshTokenRepository); - $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // The refresh token will expire in 1 month + $grant->setRefreshTokenTTL(new DateInterval('P1M')); // The refresh token will expire in 1 month $server->enableGrantType( $grant, - new \DateInterval('PT1H') // The new access token will expire after 1 hour + new DateInterval('PT1H') // The new access token will expire after 1 hour ); return $server; @@ -63,7 +66,7 @@ return $server->respondToAccessTokenRequest($request, $response); } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); - } catch (\Exception $exception) { + } catch (Exception $exception) { $response->getBody()->write($exception->getMessage()); return $response->withStatus(500); diff --git a/examples/src/Entities/AccessTokenEntity.php b/examples/src/Entities/AccessTokenEntity.php index f9e70210f..d08fee8df 100644 --- a/examples/src/Entities/AccessTokenEntity.php +++ b/examples/src/Entities/AccessTokenEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; diff --git a/examples/src/Entities/AuthCodeEntity.php b/examples/src/Entities/AuthCodeEntity.php index 50d2ff479..ed118e019 100644 --- a/examples/src/Entities/AuthCodeEntity.php +++ b/examples/src/Entities/AuthCodeEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; diff --git a/examples/src/Entities/ClientEntity.php b/examples/src/Entities/ClientEntity.php index 76bd04085..69f8a7093 100644 --- a/examples/src/Entities/ClientEntity.php +++ b/examples/src/Entities/ClientEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -18,17 +21,17 @@ class ClientEntity implements ClientEntityInterface use EntityTrait; use ClientTrait; - public function setName($name) + public function setName(string $name): void { $this->name = $name; } - public function setRedirectUri($uri) + public function setRedirectUri(string $uri): void { $this->redirectUri = $uri; } - public function setConfidential() + public function setConfidential(): void { $this->isConfidential = true; } diff --git a/examples/src/Entities/DeviceCodeEntity.php b/examples/src/Entities/DeviceCodeEntity.php index e3ff398a8..aa82d73a0 100644 --- a/examples/src/Entities/DeviceCodeEntity.php +++ b/examples/src/Entities/DeviceCodeEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\DeviceCodeEntityInterface; diff --git a/examples/src/Entities/RefreshTokenEntity.php b/examples/src/Entities/RefreshTokenEntity.php index d98709f41..9083feaa3 100644 --- a/examples/src/Entities/RefreshTokenEntity.php +++ b/examples/src/Entities/RefreshTokenEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; diff --git a/examples/src/Entities/ScopeEntity.php b/examples/src/Entities/ScopeEntity.php index 4258ce86f..5e07084f5 100644 --- a/examples/src/Entities/ScopeEntity.php +++ b/examples/src/Entities/ScopeEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\ScopeEntityInterface; diff --git a/examples/src/Entities/UserEntity.php b/examples/src/Entities/UserEntity.php index 22c1b4e55..eed386016 100644 --- a/examples/src/Entities/UserEntity.php +++ b/examples/src/Entities/UserEntity.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\UserEntityInterface; @@ -16,9 +19,8 @@ class UserEntity implements UserEntityInterface /** * Return the user's identifier. * - * @return mixed */ - public function getIdentifier() + public function getIdentifier(): mixed { return 1; } diff --git a/examples/src/Repositories/AccessTokenRepository.php b/examples/src/Repositories/AccessTokenRepository.php index b0d3675b8..affd9be8a 100644 --- a/examples/src/Repositories/AccessTokenRepository.php +++ b/examples/src/Repositories/AccessTokenRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; diff --git a/examples/src/Repositories/AuthCodeRepository.php b/examples/src/Repositories/AuthCodeRepository.php index d3ca4825c..962ed8da9 100644 --- a/examples/src/Repositories/AuthCodeRepository.php +++ b/examples/src/Repositories/AuthCodeRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index d8a74996f..047fe77f7 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,12 +8,18 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use OAuth2ServerExamples\Entities\ClientEntity; +use function array_key_exists; +use function password_hash; +use function password_verify; + class ClientRepository implements ClientRepositoryInterface { private const CLIENT_NAME = 'My Awesome App'; @@ -40,7 +47,7 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo { $clients = [ 'myawesomeapp' => [ - 'secret' => \password_hash('abc123', PASSWORD_BCRYPT), + 'secret' => password_hash('abc123', PASSWORD_BCRYPT), 'name' => self::CLIENT_NAME, 'redirect_uri' => self::REDIRECT_URI, 'is_confidential' => true, @@ -48,13 +55,13 @@ public function validateClient($clientIdentifier, $clientSecret, $grantType): bo ]; // Check if client is registered - if (\array_key_exists($clientIdentifier, $clients) === false) { + if (array_key_exists($clientIdentifier, $clients) === false) { return false; } if ( $clients[$clientIdentifier]['is_confidential'] === true - && \password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false + && password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false ) { return false; } diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index 838c52e07..2a4adf441 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use DateTimeImmutable; diff --git a/examples/src/Repositories/RefreshTokenRepository.php b/examples/src/Repositories/RefreshTokenRepository.php index e2a01d73e..fadd12ad8 100644 --- a/examples/src/Repositories/RefreshTokenRepository.php +++ b/examples/src/Repositories/RefreshTokenRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; diff --git a/examples/src/Repositories/ScopeRepository.php b/examples/src/Repositories/ScopeRepository.php index 21ea92ba8..bfe5c93be 100644 --- a/examples/src/Repositories/ScopeRepository.php +++ b/examples/src/Repositories/ScopeRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -14,6 +17,8 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use OAuth2ServerExamples\Entities\ScopeEntity; +use function array_key_exists; + class ScopeRepository implements ScopeRepositoryInterface { /** @@ -30,7 +35,7 @@ public function getScopeEntityByIdentifier($scopeIdentifier): ?ScopeEntityInterf ], ]; - if (\array_key_exists($scopeIdentifier, $scopes) === false) { + if (array_key_exists($scopeIdentifier, $scopes) === false) { return null; } diff --git a/examples/src/Repositories/UserRepository.php b/examples/src/Repositories/UserRepository.php index 88836cd6a..f8800a230 100644 --- a/examples/src/Repositories/UserRepository.php +++ b/examples/src/Repositories/UserRepository.php @@ -1,4 +1,5 @@ * @copyright Copyright (c) Alex Bilbie @@ -7,6 +8,8 @@ * @link https://github.com/thephpleague/oauth2-server */ +declare(strict_types=1); + namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; diff --git a/phpcs.xml.dist b/phpcs.xml.dist index f039e3e88..01f95f02b 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -12,6 +12,7 @@ src tests + examples From 5ddd3c4c520aeeda7837a7fec309470cb941c412 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Mon, 25 Mar 2024 22:36:54 +0000 Subject: [PATCH 198/212] fix client credentials example --- .../Repositories/AccessTokenRepository.php | 7 ++++- src/Entities/Traits/AccessTokenTrait.php | 19 +++++++----- src/ResponseTypes/BearerTokenResponse.php | 30 ++++++++++--------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/examples/src/Repositories/AccessTokenRepository.php b/examples/src/Repositories/AccessTokenRepository.php index affd9be8a..1e6da86a4 100644 --- a/examples/src/Repositories/AccessTokenRepository.php +++ b/examples/src/Repositories/AccessTokenRepository.php @@ -49,11 +49,16 @@ public function isAccessTokenRevoked($tokenId): bool public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null): AccessTokenEntityInterface { $accessToken = new AccessTokenEntity(); + $accessToken->setClient($clientEntity); + foreach ($scopes as $scope) { $accessToken->addScope($scope); } - $accessToken->setUserIdentifier($userIdentifier); + + if ($userIdentifier !== null) { + $accessToken->setUserIdentifier($userIdentifier); + } return $accessToken; } diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 015002069..99f3cbc9e 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -59,12 +59,6 @@ public function initJwtConfiguration(): void */ private function convertToJWT(): Token { - $userIdentifier = $this->getUserIdentifier(); - - if ($userIdentifier === null) { - throw new RuntimeException('JWT access tokens MUST contain a subject identifier'); - } - $this->initJwtConfiguration(); return $this->jwtConfiguration->builder() @@ -73,7 +67,7 @@ private function convertToJWT(): Token ->issuedAt(new DateTimeImmutable()) ->canOnlyBeUsedAfter(new DateTimeImmutable()) ->expiresAt($this->getExpiryDateTime()) - ->relatedTo($userIdentifier) + ->relatedTo($this->getSubjectIdentifier()) ->withClaim('scopes', $this->getScopes()) ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey()); } @@ -104,4 +98,15 @@ abstract public function getScopes(): array; * @return non-empty-string */ abstract public function getIdentifier(): string; + + private function getSubjectIdentifier(): string + { + $subjectId = $this->getUserIdentifier() ?? $this->getClient()->getIdentifier(); + + if ($subjectId === null) { + throw new RuntimeException('JWT access tokens MUST contain a subject identifier'); + } + + return $subjectId; + } } diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 0c6107b9e..24ddc8c8e 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -34,21 +34,23 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter 'access_token' => $this->accessToken->toString(), ]; - $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 (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'); + } + + $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); } - - $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); - + $responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams)); if ($responseParams === false) { From f9f73bf2da2742c6df7485d71318c708d7fc4c17 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 09:00:50 +0000 Subject: [PATCH 199/212] Fix password grant example --- examples/src/Repositories/AccessTokenRepository.php | 2 +- examples/src/Repositories/UserRepository.php | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/src/Repositories/AccessTokenRepository.php b/examples/src/Repositories/AccessTokenRepository.php index 1e6da86a4..1eb3e5bdd 100644 --- a/examples/src/Repositories/AccessTokenRepository.php +++ b/examples/src/Repositories/AccessTokenRepository.php @@ -57,7 +57,7 @@ public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, } if ($userIdentifier !== null) { - $accessToken->setUserIdentifier($userIdentifier); + $accessToken->setUserIdentifier((string) $userIdentifier); } return $accessToken; diff --git a/examples/src/Repositories/UserRepository.php b/examples/src/Repositories/UserRepository.php index f8800a230..3fbca9f45 100644 --- a/examples/src/Repositories/UserRepository.php +++ b/examples/src/Repositories/UserRepository.php @@ -13,6 +13,7 @@ namespace OAuth2ServerExamples\Repositories; use League\OAuth2\Server\Entities\ClientEntityInterface; +use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use OAuth2ServerExamples\Entities\UserEntity; @@ -26,11 +27,11 @@ public function getUserEntityByUserCredentials( $password, $grantType, ClientEntityInterface $clientEntity - ) { + ) : ?UserEntityInterface { if ($username === 'alex' && $password === 'whisky') { return new UserEntity(); } - return; + return null; } } From 58cb760c1fa3497fc71f87ac6257f93c487d59b2 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 09:24:03 +0000 Subject: [PATCH 200/212] fix device code examples --- examples/src/Repositories/DeviceCodeRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/Repositories/DeviceCodeRepository.php b/examples/src/Repositories/DeviceCodeRepository.php index 2a4adf441..9495c9a15 100644 --- a/examples/src/Repositories/DeviceCodeRepository.php +++ b/examples/src/Repositories/DeviceCodeRepository.php @@ -53,7 +53,7 @@ public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceCodeEntityI // The user identifier should be set when the user authenticates on the // OAuth server, along with whether they approved the request $deviceCodeEntity->setUserApproved(true); - $deviceCodeEntity->setUserIdentifier(1); + $deviceCodeEntity->setUserIdentifier('1'); return $deviceCodeEntity; } From a22f2b0a965b13574bc2a1b3242b970e720a4bf0 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 09:25:41 +0000 Subject: [PATCH 201/212] Fix styling issues --- examples/src/Entities/UserEntity.php | 1 - examples/src/Repositories/UserRepository.php | 2 +- src/ResponseTypes/BearerTokenResponse.php | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/src/Entities/UserEntity.php b/examples/src/Entities/UserEntity.php index eed386016..00d038462 100644 --- a/examples/src/Entities/UserEntity.php +++ b/examples/src/Entities/UserEntity.php @@ -18,7 +18,6 @@ class UserEntity implements UserEntityInterface { /** * Return the user's identifier. - * */ public function getIdentifier(): mixed { diff --git a/examples/src/Repositories/UserRepository.php b/examples/src/Repositories/UserRepository.php index 3fbca9f45..14bbdcdc6 100644 --- a/examples/src/Repositories/UserRepository.php +++ b/examples/src/Repositories/UserRepository.php @@ -27,7 +27,7 @@ public function getUserEntityByUserCredentials( $password, $grantType, ClientEntityInterface $clientEntity - ) : ?UserEntityInterface { + ): ?UserEntityInterface { if ($username === 'alex' && $password === 'whisky') { return new UserEntity(); } diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 24ddc8c8e..c33bdc712 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -50,7 +50,7 @@ public function generateHttpResponse(ResponseInterface $response): ResponseInter $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); } - + $responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams)); if ($responseParams === false) { From a7dfa4b0552b33dd3e2df8606f4402487871868f Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 23:13:07 +0000 Subject: [PATCH 202/212] Fix phpstan errors --- src/Entities/Traits/AccessTokenTrait.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 99f3cbc9e..6b1387b5f 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -99,14 +99,11 @@ abstract public function getScopes(): array; */ abstract public function getIdentifier(): string; + /** + * @return non-empty-string + */ private function getSubjectIdentifier(): string { - $subjectId = $this->getUserIdentifier() ?? $this->getClient()->getIdentifier(); - - if ($subjectId === null) { - throw new RuntimeException('JWT access tokens MUST contain a subject identifier'); - } - - return $subjectId; + return $this->getUserIdentifier() ?? $this->getClient()->getIdentifier(); } } From 1d0c6e2fe29115e7f93ea5d1aea4fe785625d094 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 23:17:50 +0000 Subject: [PATCH 203/212] Update scrutinizer PHP version --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index d0cb3f2e6..db944c1c8 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,7 +1,7 @@ build: environment: php: - version: 7.4 + version: 8.3 nodes: analysis: tests: From 496df406e3c66f95a07b5a4197c83ad8fd66ee82 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 23:36:11 +0000 Subject: [PATCH 204/212] Be more specific with Scrutinizer PHP version --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index db944c1c8..51d513b91 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,7 +1,7 @@ build: environment: php: - version: 8.3 + version: 8.3.4 nodes: analysis: tests: From c8455615c1645a850d8afa9889246359d92fb645 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 26 Mar 2024 23:39:56 +0000 Subject: [PATCH 205/212] downgrade scrutinizer PHP version --- .scrutinizer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 51d513b91..65631698c 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -1,7 +1,7 @@ build: environment: php: - version: 8.3.4 + version: 8.3.3 nodes: analysis: tests: From 0fe1fe7f22777411cd122f7a9fd2bfbb6cce24dc Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:05:48 +0000 Subject: [PATCH 206/212] Fix reference to $this --- examples/public/middleware_use.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/public/middleware_use.php b/examples/public/middleware_use.php index 2bcf9c7b8..2644f3c20 100644 --- a/examples/public/middleware_use.php +++ b/examples/public/middleware_use.php @@ -87,7 +87,7 @@ // Secured API $app->group('/api', function (): void { - $this->get('/user', function (ServerRequestInterface $request, ResponseInterface $response) { + $app->get('/user', function (ServerRequestInterface $request, ResponseInterface $response) { $params = []; if (in_array('basic', $request->getAttribute('oauth_scopes', []))) { From f4974e66991e3314dd891e7d421d4382f4c674aa Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:07:49 +0000 Subject: [PATCH 207/212] Remove unusued import --- examples/public/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/public/api.php b/examples/public/api.php index 08c107be5..eaaea655c 100644 --- a/examples/public/api.php +++ b/examples/public/api.php @@ -33,7 +33,7 @@ // An example endpoint secured with OAuth 2.0 $app->get( '/users', - function (ServerRequestInterface $request, ResponseInterface $response) use ($app) { + function (ServerRequestInterface $request, ResponseInterface $response) { $users = [ [ 'id' => 123, From 38a67b738f5f954f36a5da8d678b7b35c3e10e7f Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:09:00 +0000 Subject: [PATCH 208/212] Make autoload run earlier --- examples/public/api.php | 4 ++-- examples/public/middleware_use.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/public/api.php b/examples/public/api.php index eaaea655c..8bd5cb6d4 100644 --- a/examples/public/api.php +++ b/examples/public/api.php @@ -2,6 +2,8 @@ declare(strict_types=1); +include __DIR__ . '/../vendor/autoload.php'; + use League\OAuth2\Server\Middleware\ResourceServerMiddleware; use League\OAuth2\Server\ResourceServer; use OAuth2ServerExamples\Repositories\AccessTokenRepository; @@ -9,8 +11,6 @@ use Psr\Http\Message\ServerRequestInterface; use Slim\App; -include __DIR__ . '/../vendor/autoload.php'; - $app = new App([ // Add the resource server to the DI container ResourceServer::class => function () { diff --git a/examples/public/middleware_use.php b/examples/public/middleware_use.php index 2644f3c20..49bb5b5bb 100644 --- a/examples/public/middleware_use.php +++ b/examples/public/middleware_use.php @@ -10,6 +10,8 @@ declare(strict_types=1); +include __DIR__ . '/../vendor/autoload.php'; + use Laminas\Diactoros\Stream; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Grant\AuthCodeGrant; @@ -26,8 +28,6 @@ use Psr\Http\Message\ServerRequestInterface; use Slim\App; -include __DIR__ . '/../vendor/autoload.php'; - $app = new App([ 'settings' => [ 'displayErrorDetails' => true, From 2e5b0553c8d5827815e8ba50dd6443d87eefe9d3 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:09:31 +0000 Subject: [PATCH 209/212] Make autoload run earlier --- examples/public/client_credentials.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/public/client_credentials.php b/examples/public/client_credentials.php index 12f294484..080b06e07 100644 --- a/examples/public/client_credentials.php +++ b/examples/public/client_credentials.php @@ -10,6 +10,8 @@ declare(strict_types=1); +include __DIR__ . '/../vendor/autoload.php'; + use Laminas\Diactoros\Stream; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; @@ -21,8 +23,6 @@ use Psr\Http\Message\ServerRequestInterface; use Slim\App; -include __DIR__ . '/../vendor/autoload.php'; - $app = new App([ 'settings' => [ 'displayErrorDetails' => true, From 6e290827401ad2fbd5a87325d79227f300e2cf49 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:14:12 +0000 Subject: [PATCH 210/212] Remove unneccessary if check --- examples/src/Repositories/ClientRepository.php | 5 +---- 1 file changed, 1 insertion(+), 4 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; } From 0858a2c31999fba0707b3bd9bd7557606e302108 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:47:17 +0000 Subject: [PATCH 211/212] Update changelog for v9 RC1 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c8ebda75..0e73d60df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ 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] + +## [9.0.0-RC1] - released 2024-03-27 ### Added - Device Authorization Grant added (PR #1074) - GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #1375) @@ -623,7 +625,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.5.4...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/9.0.0-RC1...HEAD +[9.0.0-RC1]: https://github.com/thephpleague/oauth2-server/compare/8.5.4...9.0.0-RC1 [8.5.4]: https://github.com/thephpleague/oauth2-server/compare/8.5.3...8.5.4 [8.5.3]: https://github.com/thephpleague/oauth2-server/compare/8.5.2...8.5.3 [8.5.2]: https://github.com/thephpleague/oauth2-server/compare/8.5.1...8.5.2 From ca511c14fca93ffc0f518370492c2476e7b0e4f7 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Wed, 27 Mar 2024 09:52:09 +0000 Subject: [PATCH 212/212] Add PR # for toString change --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e73d60df..2bd3ea6c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111) - Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112) -- AccessTokenEntityInterface now requires the implementation of `toString()` instead of the magic method `__toString()` (PR #XXXX) +- AccessTokenEntityInterface now requires the implementation of `toString()` instead of the magic method `__toString()` (PR #1395) ### Removed - Removed message property from OAuthException HTTP response. Now just use error_description as per the OAuth 2 spec (PR #1375)