diff --git a/.gitignore b/.gitignore index 897c80b1..e22101f2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ phpunit.xml examples/public.key examples/private.key build +*.orig diff --git a/.travis.yml b/.travis.yml index ae8f640c..b773c2aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ env: - DEPENDENCIES="--prefer-lowest --prefer-stable" php: - - 7.0 - 7.1 - 7.2 - 7.3 @@ -31,3 +30,4 @@ after_script: branches: only: - master + - 8.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index e0721bc6..acfe9c1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Added +- Flag, `requireCodeChallengeForPublicClients`, used to reject public clients that do not provide a code challenge for the Auth Code Grant; use AuthCodeGrant::disableRequireCodeCallengeForPublicClients() to turn off this requirement (PR #938) +- Public clients can now use the Auth Code Grant (PR #938) +- `isConfidential` getter added to `ClientEntity` to identify type of client (PR #938) +- Function `validateClient()` added to validate clients which was previously performed by the `getClientEntity()` function (PR #938) +- Add a new function to the AbstractGrant class called `getClientEntityOrFail()`. This is a wrapper around the `getClientEntity()` function that ensures we emit and throw an exception if the repo doesn't return a client entity. (PR #1010) + +### Changed +- Replace `convertToJWT()` interface with a more generic `__toString()` to improve extensibility; AccessTokenEntityInterface now requires `setPrivateKey(CryptKey $privateKey)` so `__toString()` has everything it needs to work (PR #874) +- The `invalidClient()` function accepts a PSR-7 compliant `$serverRequest` argument to avoid accessing the `$_SERVER` global variable and improve testing (PR #899) +- `issueAccessToken()` in the Abstract Grant no longer sets access token client, user ID or scopes. These values should already have been set when calling `getNewToken()` (PR #919) +- No longer need to enable PKCE with `enableCodeExchangeProof` flag. Any client sending a code challenge will initiate PKCE checks. (PR #938) +- Function `getClientEntity()` no longer performs client validation (PR #938) +- Password Grant now returns an invalid_grant error instead of invalid_credentials if a user cannot be validated (PR #967) +- Use `DateTimeImmutable()` instead of `DateTime()`, `time()` instead of `(new DateTime())->getTimeStamp()`, and `DateTime::getTimeStamp()` instead of `DateTime::format('U')` (PR #963) + +### Removed +- `enableCodeExchangeProof` flag (PR #938) +- Support for PHP 7.0 (PR #1014) +- Remove JTI claim from JWT header (PR #1031) + ## [7.4.0] - released 2019-05-05 ### Changed diff --git a/README.md b/README.md index ced17ed8..92a12f3e 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,11 @@ This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](ht The following versions of PHP are supported: -* PHP 7.0 * PHP 7.1 * PHP 7.2 * PHP 7.3 -The `openssl` extension is also required. +The `openssl` and `json` extensions are also required. All HTTP messages passed to the server should be [PSR-7 compliant](https://www.php-fig.org/psr/psr-7/). This ensures interoperability with other packages and frameworks. @@ -86,13 +85,9 @@ Bugs and feature request are tracked on [GitHub](https://github.com/thephpleague If you have any questions about OAuth _please_ open a ticket here; please **don't** email the address below. -## Commercial Support - -If you would like help implementing this library into your existing platform, or would be interested in OAuth advice or training for you and your team please get in touch with [Glynde Labs](https://glyndelabs.com). - ## Security -If you discover any security related issues, please email `hello@alexbilbie.com` instead of using the issue tracker. +If you discover any security related issues, please email `andrew@noexceptions.io` instead of using the issue tracker. ## License @@ -100,7 +95,7 @@ This package is released under the MIT License. See the bundled [LICENSE](https: ## Credits -This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster) and [Simon Hamp](https://twitter.com/simonhamp). +This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster). Between 2012 and 2017 this library was developed and maintained by [Alex Bilbie](https://alexbilbie.com/). diff --git a/composer.json b/composer.json index cb54606c..7991a0cc 100644 --- a/composer.json +++ b/composer.json @@ -4,19 +4,19 @@ "homepage": "https://oauth2.thephpleague.com/", "license": "MIT", "require": { - "php": ">=7.0.0", + "php": ">=7.1.0", "ext-openssl": "*", - "league/event": "^2.1", - "lcobucci/jwt": "^3.2.2", + "league/event": "^2.2", + "lcobucci/jwt": "^3.3.1", "psr/http-message": "^1.0.1", - "defuse/php-encryption": "^2.1" + "defuse/php-encryption": "^2.2.1", + "ext-json": "*" }, "require-dev": { - "phpunit/phpunit": "^6.3 || ^7.0", - "zendframework/zend-diactoros": "^1.3.2", - "phpstan/phpstan": "^0.9.2", - "phpstan/phpstan-phpunit": "^0.9.4", - "phpstan/phpstan-strict-rules": "^0.9.0", + "phpunit/phpunit": "^7.5.13 || ^8.2.3", + "zendframework/zend-diactoros": "^2.1.2", + "phpstan/phpstan": "^0.11.8", + "phpstan/phpstan-phpunit": "^0.11.2", "roave/security-advisories": "dev-master" }, "repositories": [ diff --git a/examples/composer.json b/examples/composer.json index a3ef9b7a..ab0832fe 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -3,11 +3,11 @@ "slim/slim": "^3.0.0" }, "require-dev": { - "league/event": "^2.1", - "lcobucci/jwt": "^3.2", + "league/event": "^2.2", + "lcobucci/jwt": "^3.3", "psr/http-message": "^1.0", "defuse/php-encryption": "^2.2", - "zendframework/zend-diactoros": "^2.0.0" + "zendframework/zend-diactoros": "^2.1.0" }, "autoload": { "psr-4": { diff --git a/examples/composer.lock b/examples/composer.lock index 6ba8ec13..d557b685 100644 --- a/examples/composer.lock +++ b/examples/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "97f2878428e37d1d8e5418cc85cbfa3d", + "content-hash": "a7f5c3fdcadb17399bbd97f15e1b11f1", "packages": [ { "name": "container-interop/container-interop", @@ -234,16 +234,16 @@ }, { "name": "slim/slim", - "version": "3.11.0", + "version": "3.12.1", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a" + "reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a", - "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/eaee12ef8d0750db62b8c548016d82fb33addb6b", + "reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b", "shasum": "" }, "require": { @@ -301,7 +301,7 @@ "micro", "router" ], - "time": "2018-09-16T10:54:21+00:00" + "time": "2019-04-16T16:47:29+00:00" } ], "packages-dev": [ @@ -370,33 +370,30 @@ }, { "name": "lcobucci/jwt", - "version": "3.2.4", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "c9704b751315d21735dc98d78d4f37bd73596da7" + "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c9704b751315d21735dc98d78d4f37bd73596da7", - "reference": "c9704b751315d21735dc98d78d4f37bd73596da7", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", "shasum": "" }, "require": { + "ext-mbstring": "*", "ext-openssl": "*", - "php": ">=5.5" + "php": "^5.6 || ^7.0" }, "require-dev": { - "mdanter/ecc": "~0.3.1", "mikey179/vfsstream": "~1.5", "phpmd/phpmd": "~2.2", "phpunit/php-invoker": "~1.1", - "phpunit/phpunit": "~4.5", + "phpunit/phpunit": "^5.7 || ^7.3", "squizlabs/php_codesniffer": "~2.3" }, - "suggest": { - "mdanter/ecc": "Required to use Elliptic Curves based algorithms." - }, "type": "library", "extra": { "branch-alias": { @@ -424,20 +421,20 @@ "JWS", "jwt" ], - "time": "2018-08-03T11:23:50+00:00" + "time": "2019-05-24T18:30:49+00:00" }, { "name": "league/event", - "version": "2.1.2", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/thephpleague/event.git", - "reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd" + "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/event/zipball/e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd", - "reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd", + "url": "https://api.github.com/repos/thephpleague/event/zipball/d2cc124cf9a3fab2bb4ff963307f60361ce4d119", + "reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119", "shasum": "" }, "require": { @@ -445,7 +442,7 @@ }, "require-dev": { "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "~2.0.0" + "phpspec/phpspec": "^2.2" }, "type": "library", "extra": { @@ -474,7 +471,7 @@ "event", "listener" ], - "time": "2015-05-21T12:24:47+00:00" + "time": "2018-11-26T11:52:41+00:00" }, { "name": "paragonie/random_compat", @@ -523,16 +520,16 @@ }, { "name": "psr/http-factory", - "version": "1.0.0", + "version": "1.0.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c" + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/378bfe27931ecc54ff824a20d6f6bfc303bbd04c", - "reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", "shasum": "" }, "require": { @@ -571,20 +568,20 @@ "request", "response" ], - "time": "2018-07-30T21:54:04+00:00" + "time": "2019-04-30T12:38:16+00:00" }, { "name": "zendframework/zend-diactoros", - "version": "2.0.0", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "0bae78192e634774b5584f0210c1232da82cb1ff" + "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/0bae78192e634774b5584f0210c1232da82cb1ff", - "reference": "0bae78192e634774b5584f0210c1232da82cb1ff", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1", + "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1", "shasum": "" }, "require": { @@ -607,8 +604,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev", - "dev-develop": "2.1.x-dev", + "dev-master": "2.1.x-dev", + "dev-develop": "2.2.x-dev", "dev-release-1.8": "1.8.x-dev" } }, @@ -637,7 +634,7 @@ "psr", "psr-7" ], - "time": "2018-09-27T19:49:04+00:00" + "time": "2019-07-10T16:13:25+00:00" } ], "aliases": [], diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index eaa4e5c2..47db508c 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -14,16 +14,33 @@ use OAuth2ServerExamples\Entities\ClientEntity; class ClientRepository implements ClientRepositoryInterface { + const CLIENT_NAME = 'My Awesome App'; + const REDIRECT_URI = 'http://foo/bar'; + /** * {@inheritdoc} */ - public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true) + public function getClientEntity($clientIdentifier) + { + $client = new ClientEntity(); + + $client->setIdentifier($clientIdentifier); + $client->setName(self::CLIENT_NAME); + $client->setRedirectUri(self::REDIRECT_URI); + + return $client; + } + + /** + * {@inheritdoc} + */ + public function validateClient($clientIdentifier, $clientSecret, $grantType) { $clients = [ 'myawesomeapp' => [ 'secret' => password_hash('abc123', PASSWORD_BCRYPT), - 'name' => 'My Awesome App', - 'redirect_uri' => 'http://foo/bar', + 'name' => self::CLIENT_NAME, + 'redirect_uri' => self::REDIRECT_URI, 'is_confidential' => true, ], ]; @@ -34,18 +51,10 @@ class ClientRepository implements ClientRepositoryInterface } if ( - $mustValidateSecret === true - && $clients[$clientIdentifier]['is_confidential'] === true + $clients[$clientIdentifier]['is_confidential'] === true && password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false ) { return; } - - $client = new ClientEntity(); - $client->setIdentifier($clientIdentifier); - $client->setName($clients[$clientIdentifier]['name']); - $client->setRedirectUri($clients[$clientIdentifier]['redirect_uri']); - - return $client; } } diff --git a/phpstan.neon b/phpstan.neon index 5cd9d80d..ba1fb491 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,6 @@ includes: - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon - - vendor/phpstan/phpstan-phpunit/strictRules.neon - - vendor/phpstan/phpstan-strict-rules/rules.neon services: - class: LeagueTests\PHPStan\AbstractGrantExtension diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index bde97d6e..8b0b2815 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -125,7 +125,7 @@ class AuthorizationServer implements EmitterAwareInterface */ public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null) { - if ($accessTokenTTL instanceof DateInterval === false) { + if ($accessTokenTTL === null) { $accessTokenTTL = new DateInterval('PT1H'); } diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index b2035ccc..7218f413 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -63,7 +63,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface } $header = $request->getHeader('authorization'); - $jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); + $jwt = trim((string) preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0])); try { // Attempt to parse and validate the JWT diff --git a/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php b/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php new file mode 100644 index 00000000..3d7ad59c --- /dev/null +++ b/src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php @@ -0,0 +1,30 @@ + + * @copyright Copyright (c) Lukáš Unger + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\CodeChallengeVerifiers; + +interface CodeChallengeVerifierInterface +{ + /** + * Return code challenge method. + * + * @return string + */ + public function getMethod(); + + /** + * Verify the code challenge. + * + * @param string $codeVerifier + * @param string $codeChallenge + * + * @return bool + */ + public function verifyCodeChallenge($codeVerifier, $codeChallenge); +} diff --git a/src/CodeChallengeVerifiers/PlainVerifier.php b/src/CodeChallengeVerifiers/PlainVerifier.php new file mode 100644 index 00000000..71749c97 --- /dev/null +++ b/src/CodeChallengeVerifiers/PlainVerifier.php @@ -0,0 +1,36 @@ + + * @copyright Copyright (c) Lukáš Unger + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\CodeChallengeVerifiers; + +class PlainVerifier implements CodeChallengeVerifierInterface +{ + /** + * Return code challenge method. + * + * @return string + */ + public function getMethod() + { + return 'plain'; + } + + /** + * Verify the code challenge. + * + * @param string $codeVerifier + * @param string $codeChallenge + * + * @return bool + */ + public function verifyCodeChallenge($codeVerifier, $codeChallenge) + { + return hash_equals($codeVerifier, $codeChallenge); + } +} diff --git a/src/CodeChallengeVerifiers/S256Verifier.php b/src/CodeChallengeVerifiers/S256Verifier.php new file mode 100644 index 00000000..3b37af3a --- /dev/null +++ b/src/CodeChallengeVerifiers/S256Verifier.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) Lukáš Unger + * @license http://mit-license.org/ + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\CodeChallengeVerifiers; + +class S256Verifier implements CodeChallengeVerifierInterface +{ + /** + * Return code challenge method. + * + * @return string + */ + public function getMethod() + { + return 'S256'; + } + + /** + * Verify the code challenge. + * + * @param string $codeVerifier + * @param string $codeChallenge + * + * @return bool + */ + public function verifyCodeChallenge($codeVerifier, $codeChallenge) + { + return hash_equals( + strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'), + $codeChallenge + ); + } +} diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 1196e9dc..5709940d 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -19,7 +19,7 @@ use LogicException; trait CryptTrait { /** - * @var string|Key + * @var string|Key|null */ protected $encryptionKey; @@ -39,9 +39,13 @@ trait CryptTrait return Crypto::encrypt($unencryptedData, $this->encryptionKey); } - return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); + if (is_string($this->encryptionKey)) { + return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); + } + + throw new LogicException('Encryption key not set when attempting to encrypt'); } catch (Exception $e) { - throw new LogicException($e->getMessage(), null, $e); + throw new LogicException($e->getMessage(), 0, $e); } } @@ -61,9 +65,13 @@ trait CryptTrait return Crypto::decrypt($encryptedData, $this->encryptionKey); } - return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); + if (is_string($this->encryptionKey)) { + return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); + } + + throw new LogicException('Encryption key not set when attempting to decrypt'); } catch (Exception $e) { - throw new LogicException($e->getMessage(), null, $e); + throw new LogicException($e->getMessage(), 0, $e); } } diff --git a/src/Entities/AccessTokenEntityInterface.php b/src/Entities/AccessTokenEntityInterface.php index 4da7600e..fd459914 100644 --- a/src/Entities/AccessTokenEntityInterface.php +++ b/src/Entities/AccessTokenEntityInterface.php @@ -9,17 +9,17 @@ namespace League\OAuth2\Server\Entities; -use Lcobucci\JWT\Token; use League\OAuth2\Server\CryptKey; interface AccessTokenEntityInterface extends TokenInterface { /** - * Generate a JWT from the access token - * - * @param CryptKey $privateKey - * - * @return Token + * Set a private key used to encrypt the access token. */ - public function convertToJWT(CryptKey $privateKey); + public function setPrivateKey(CryptKey $privateKey); + + /** + * Generate a string representation of the access token. + */ + public function __toString(); } diff --git a/src/Entities/ClientEntityInterface.php b/src/Entities/ClientEntityInterface.php index 80cc70c8..971c6124 100644 --- a/src/Entities/ClientEntityInterface.php +++ b/src/Entities/ClientEntityInterface.php @@ -33,4 +33,11 @@ interface ClientEntityInterface * @return string|string[] */ public function getRedirectUri(); + + /** + * Returns true if the client is confidential. + * + * @return bool + */ + public function isConfidential(); } diff --git a/src/Entities/RefreshTokenEntityInterface.php b/src/Entities/RefreshTokenEntityInterface.php index c2898ae5..551ad052 100644 --- a/src/Entities/RefreshTokenEntityInterface.php +++ b/src/Entities/RefreshTokenEntityInterface.php @@ -9,7 +9,7 @@ namespace League\OAuth2\Server\Entities; -use DateTime; +use DateTimeImmutable; interface RefreshTokenEntityInterface { @@ -30,16 +30,16 @@ interface RefreshTokenEntityInterface /** * Get the token's expiry date time. * - * @return DateTime + * @return DateTimeImmutable */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * - * @param DateTime $dateTime + * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTime $dateTime); + public function setExpiryDateTime(DateTimeImmutable $dateTime); /** * Set the access token that the refresh token was associated with. diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index d2ca77d3..7b063e13 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -9,7 +9,7 @@ namespace League\OAuth2\Server\Entities; -use DateTime; +use DateTimeImmutable; interface TokenInterface { @@ -30,16 +30,16 @@ interface TokenInterface /** * Get the token's expiry date time. * - * @return DateTime + * @return DateTimeImmutable */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * - * @param DateTime $dateTime + * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTime $dateTime); + public function setExpiryDateTime(DateTimeImmutable $dateTime); /** * Set the identifier of the user associated with the token. diff --git a/src/Entities/Traits/AccessTokenTrait.php b/src/Entities/Traits/AccessTokenTrait.php index 5fa4a9be..44276de1 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -9,7 +9,7 @@ namespace League\OAuth2\Server\Entities\Traits; -use DateTime; +use DateTimeImmutable; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Rsa\Sha256; @@ -20,6 +20,19 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface; trait AccessTokenTrait { + /** + * @var CryptKey + */ + private $privateKey; + + /** + * 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 * @@ -27,27 +40,35 @@ trait AccessTokenTrait * * @return Token */ - public function convertToJWT(CryptKey $privateKey) + private function convertToJWT(CryptKey $privateKey) { return (new Builder()) ->setAudience($this->getClient()->getIdentifier()) - ->setId($this->getIdentifier(), true) + ->setId($this->getIdentifier()) ->setIssuedAt(time()) ->setNotBefore(time()) ->setExpiration($this->getExpiryDateTime()->getTimestamp()) - ->setSubject($this->getUserIdentifier()) + ->setSubject((string) $this->getUserIdentifier()) ->set('scopes', $this->getScopes()) ->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase())) ->getToken(); } + /** + * Generate a string representation from the access token + */ + public function __toString() + { + return (string) $this->convertToJWT($this->privateKey); + } + /** * @return ClientEntityInterface */ abstract public function getClient(); /** - * @return DateTime + * @return DateTimeImmutable */ abstract public function getExpiryDateTime(); diff --git a/src/Entities/Traits/ClientTrait.php b/src/Entities/Traits/ClientTrait.php index fec76a6f..a0078d8d 100644 --- a/src/Entities/Traits/ClientTrait.php +++ b/src/Entities/Traits/ClientTrait.php @@ -21,6 +21,11 @@ trait ClientTrait */ protected $redirectUri; + /** + * @var bool + */ + protected $isConfidential = false; + /** * Get the client's name. * @@ -43,4 +48,14 @@ trait ClientTrait { return $this->redirectUri; } + + /** + * Returns true if the client is confidential. + * + * @return bool + */ + public function isConfidential() + { + return $this->isConfidential; + } } diff --git a/src/Entities/Traits/RefreshTokenTrait.php b/src/Entities/Traits/RefreshTokenTrait.php index 0734daf1..f0f15444 100644 --- a/src/Entities/Traits/RefreshTokenTrait.php +++ b/src/Entities/Traits/RefreshTokenTrait.php @@ -9,7 +9,7 @@ namespace League\OAuth2\Server\Entities\Traits; -use DateTime; +use DateTimeImmutable; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; trait RefreshTokenTrait @@ -20,7 +20,7 @@ trait RefreshTokenTrait protected $accessToken; /** - * @var DateTime + * @var DateTimeImmutable */ protected $expiryDateTime; @@ -43,7 +43,7 @@ trait RefreshTokenTrait /** * Get the token's expiry date time. * - * @return DateTime + * @return DateTimeImmutable */ public function getExpiryDateTime() { @@ -53,9 +53,9 @@ trait RefreshTokenTrait /** * Set the date time when the token expires. * - * @param DateTime $dateTime + * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTime $dateTime) + public function setExpiryDateTime(DateTimeImmutable $dateTime) { $this->expiryDateTime = $dateTime; } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index 34159149..5275c462 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -9,7 +9,7 @@ namespace League\OAuth2\Server\Entities\Traits; -use DateTime; +use DateTimeImmutable; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -21,7 +21,7 @@ trait TokenEntityTrait protected $scopes = []; /** - * @var DateTime + * @var DateTimeImmutable */ protected $expiryDateTime; @@ -58,7 +58,7 @@ trait TokenEntityTrait /** * Get the token's expiry date time. * - * @return DateTime + * @return DateTimeImmutable */ public function getExpiryDateTime() { @@ -68,9 +68,9 @@ trait TokenEntityTrait /** * Set the date time when the token expires. * - * @param DateTime $dateTime + * @param DateTimeImmutable $dateTime */ - public function setExpiryDateTime(DateTime $dateTime) + public function setExpiryDateTime(DateTimeImmutable $dateTime) { $this->expiryDateTime = $dateTime; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index bb448767..8e628baa 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -11,6 +11,7 @@ namespace League\OAuth2\Server\Exception; use Exception; use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\ServerRequestInterface; use Throwable; class OAuthServerException extends Exception @@ -40,6 +41,11 @@ class OAuthServerException extends Exception */ private $payload; + /** + * @var ServerRequestInterface + */ + private $serverRequest; + /** * Throw a new exception. * @@ -95,6 +101,16 @@ class OAuthServerException extends Exception $this->payload = $payload; } + /** + * Set the server request that is responsible for generating the exception + * + * @param ServerRequestInterface $serverRequest + */ + public function setServerRequest(ServerRequestInterface $serverRequest) + { + $this->serverRequest = $serverRequest; + } + /** * Unsupported grant type error. * @@ -129,13 +145,17 @@ class OAuthServerException extends Exception /** * Invalid client error. * + * @param ServerRequestInterface $serverRequest + * * @return static */ - public static function invalidClient() + public static function invalidClient(ServerRequestInterface $serverRequest) { - $errorMessage = 'Client authentication failed'; + $exception = new static('Client authentication failed', 4, 'invalid_client', 401); - return new static($errorMessage, 4, 'invalid_client', 401); + $exception->setServerRequest($serverRequest); + + return $exception; } /** @@ -288,7 +308,9 @@ class OAuthServerException extends Exception $response = $response->withHeader($header, $content); } - $response->getBody()->write(json_encode($payload, $jsonOptions)); + $responseBody = json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed'; + + $response->getBody()->write($responseBody); return $response->withStatus($this->getHttpStatusCode()); } @@ -313,8 +335,8 @@ class OAuthServerException extends Exception // include the "WWW-Authenticate" response header field // matching the authentication scheme used by the client. // @codeCoverageIgnoreStart - if ($this->errorType === 'invalid_client' && array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false) { - $authScheme = strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0 ? 'Bearer' : 'Basic'; + if ($this->errorType === 'invalid_client' && $this->serverRequest->hasHeader('Authorization') === true) { + $authScheme = strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic'; $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"'; } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 6d7ff18d..0ac9e395 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -11,7 +11,7 @@ namespace League\OAuth2\Server\Grant; use DateInterval; -use DateTime; +use DateTimeImmutable; use Error; use Exception; use League\Event\EmitterAwareTrait; @@ -177,28 +177,17 @@ abstract class AbstractGrant implements GrantTypeInterface */ protected function validateClient(ServerRequestInterface $request) { - list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); + list($clientId, $clientSecret) = $this->getClientCredentials($request); - $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); - if ($clientId === null) { - throw OAuthServerException::invalidRequest('client_id'); - } - - // If the client is confidential require the client secret - $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); - - $client = $this->clientRepository->getClientEntity( - $clientId, - $this->getIdentifier(), - $clientSecret, - true - ); - - if ($client instanceof ClientEntityInterface === false) { + if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); + + throw OAuthServerException::invalidClient($request); } + $client = $this->getClientEntityOrFail($clientId, $request); + + // If a redirect URI is provided ensure it matches what is pre-registered $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); if ($redirectUri !== null) { @@ -208,6 +197,56 @@ abstract class AbstractGrant implements GrantTypeInterface return $client; } + /** + * Wrapper around ClientRepository::getClientEntity() that ensures we emit + * an event and throw an exception if the repo doesn't return a client + * entity. + * + * This is a bit of defensive coding because the interface contract + * 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. + * + * @param string $clientId + * @param ServerRequestInterface $request + * + * @return ClientEntityInterface + */ + protected function getClientEntityOrFail($clientId, ServerRequestInterface $request) + { + $client = $this->clientRepository->getClientEntity($clientId); + + if ($client instanceof ClientEntityInterface === false) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); + throw OAuthServerException::invalidClient($request); + } + + return $client; + } + + /** + * Gets the client credentials from the request from the request body or + * the Http Basic Authorization header + * + * @param ServerRequestInterface $request + * + * @return array + */ + protected function getClientCredentials(ServerRequestInterface $request) + { + list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); + + $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); + + if (is_null($clientId)) { + throw OAuthServerException::invalidRequest('client_id'); + } + + $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword); + + return [$clientId, $clientSecret]; + } + /** * Validate redirectUri from the request. * If a redirect URI is provided ensure it matches what is pre-registered @@ -227,12 +266,12 @@ abstract class AbstractGrant implements GrantTypeInterface && (strcmp($client->getRedirectUri(), $redirectUri) !== 0) ) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); + throw OAuthServerException::invalidClient($request); } elseif (\is_array($client->getRedirectUri()) && \in_array($redirectUri, $client->getRedirectUri(), true) === false ) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); + throw OAuthServerException::invalidClient($request); } } @@ -394,13 +433,8 @@ abstract class AbstractGrant implements GrantTypeInterface $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); - $accessToken->setClient($client); - $accessToken->setUserIdentifier($userIdentifier); - $accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL)); - - foreach ($scopes as $scope) { - $accessToken->addScope($scope); - } + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL)); + $accessToken->setPrivateKey($this->privateKey); while ($maxGenerationAttempts-- > 0) { $accessToken->setIdentifier($this->generateUniqueIdentifier()); @@ -440,7 +474,7 @@ abstract class AbstractGrant implements GrantTypeInterface $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $authCode = $this->authCodeRepository->getNewAuthCode(); - $authCode->setExpiryDateTime((new DateTime())->add($authCodeTTL)); + $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL)); $authCode->setClient($client); $authCode->setUserIdentifier($userIdentifier); @@ -482,7 +516,7 @@ abstract class AbstractGrant implements GrantTypeInterface return null; } - $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL)); + $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL)); $refreshToken->setAccessToken($accessToken); $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 350fdde5..f85a0898 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -10,8 +10,11 @@ namespace League\OAuth2\Server\Grant; use DateInterval; -use DateTime; +use DateTimeImmutable; use Exception; +use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface; +use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier; +use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -35,7 +38,12 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * @var bool */ - private $enableCodeExchangeProof = false; + private $requireCodeChallengeForPublicClients = true; + + /** + * @var CodeChallengeVerifierInterface[] + */ + private $codeChallengeVerifiers = []; /** * @param AuthCodeRepositoryInterface $authCodeRepository @@ -53,11 +61,22 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $this->setRefreshTokenRepository($refreshTokenRepository); $this->authCodeTTL = $authCodeTTL; $this->refreshTokenTTL = new DateInterval('P1M'); + + if (in_array('sha256', hash_algos(), true)) { + $s256Verifier = new S256Verifier(); + $this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier; + } + + $plainVerifier = new PlainVerifier(); + $this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier; } - public function enableCodeExchangeProof() + /** + * Disable the requirement for a code challenge for public clients. + */ + public function disableRequireCodeChallengeForPublicClients() { - $this->enableCodeExchangeProof = true; + $this->requireCodeChallengeForPublicClients = false; } /** @@ -76,8 +95,15 @@ class AuthCodeGrant extends AbstractAuthorizeGrant ResponseTypeInterface $responseType, DateInterval $accessTokenTTL ) { - // Validate request - $client = $this->validateClient($request); + list($clientId) = $this->getClientCredentials($request); + + $client = $this->getClientEntityOrFail($clientId, $request); + + // Only validate the client if it is confidential + if ($client->isConfidential()) { + $this->validateClient($request); + } + $encryptedAuthCode = $this->getRequestParameter('code', $request, null); if ($encryptedAuthCode === null) { @@ -100,7 +126,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant } // Validate code challenge - if ($this->enableCodeExchangeProof === true) { + if (!empty($authCodePayload->code_challenge)) { $codeVerifier = $this->getRequestParameter('code_verifier', $request, null); if ($codeVerifier === null) { @@ -116,32 +142,21 @@ class AuthCodeGrant extends AbstractAuthorizeGrant ); } - switch ($authCodePayload->code_challenge_method) { - case 'plain': - if (hash_equals($codeVerifier, $authCodePayload->code_challenge) === false) { - throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); - } + if (property_exists($authCodePayload, 'code_challenge_method')) { + if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) { + $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method]; - break; - case 'S256': - if ( - hash_equals( - strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'), - $authCodePayload->code_challenge - ) === false - ) { + if ($codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) { throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); } - // @codeCoverageIgnoreStart - break; - default: + } else { throw OAuthServerException::serverError( sprintf( 'Unsupported code challenge method `%s`', $authCodePayload->code_challenge_method ) ); - // @codeCoverageIgnoreEnd + } } } @@ -236,17 +251,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant throw OAuthServerException::invalidRequest('client_id'); } - $client = $this->clientRepository->getClientEntity( - $clientId, - $this->getIdentifier(), - null, - false - ); - - if ($client instanceof ClientEntityInterface === false) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); - } + $client = $this->getClientEntityOrFail($clientId, $request); $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); @@ -255,7 +260,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant } elseif (empty($client->getRedirectUri()) || (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); + throw OAuthServerException::invalidClient($request); } else { $redirectUri = \is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] @@ -280,18 +285,20 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $authorizationRequest->setScopes($scopes); - if ($this->enableCodeExchangeProof === true) { - $codeChallenge = $this->getQueryStringParameter('code_challenge', $request); - if ($codeChallenge === null) { - throw OAuthServerException::invalidRequest('code_challenge'); - } + $codeChallenge = $this->getQueryStringParameter('code_challenge', $request); + if ($codeChallenge !== null) { $codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain'); - if (\in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) { + if (array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) { throw OAuthServerException::invalidRequest( 'code_challenge_method', - 'Code challenge method must be `plain` or `S256`' + 'Code challenge method must be one of ' . implode(', ', array_map( + function ($method) { + return '`' . $method . '`'; + }, + array_keys($this->codeChallengeVerifiers) + )) ); } @@ -306,6 +313,8 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $authorizationRequest->setCodeChallenge($codeChallenge); $authorizationRequest->setCodeChallengeMethod($codeChallengeMethod); + } elseif ($this->requireCodeChallengeForPublicClients && !$client->isConfidential()) { + throw OAuthServerException::invalidRequest('code_challenge', 'Code challenge must be provided for public clients'); } return $authorizationRequest; @@ -339,21 +348,23 @@ class AuthCodeGrant extends AbstractAuthorizeGrant 'auth_code_id' => $authCode->getIdentifier(), 'scopes' => $authCode->getScopes(), 'user_id' => $authCode->getUserIdentifier(), - 'expire_time' => (new DateTime())->add($this->authCodeTTL)->format('U'), + 'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(), 'code_challenge' => $authorizationRequest->getCodeChallenge(), 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), ]; + $jsonPayload = json_encode($payload); + + if ($jsonPayload === false) { + throw new LogicException('An error was encountered when JSON encoding the authorization request response'); + } + $response = new RedirectResponse(); $response->setRedirectUri( $this->makeRedirectUri( $finalRedirectUri, [ - 'code' => $this->encrypt( - json_encode( - $payload - ) - ), + 'code' => $this->encrypt($jsonPayload), 'state' => $authorizationRequest->getState(), ] ) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 63a6c0b2..251ea6c2 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -10,8 +10,6 @@ namespace League\OAuth2\Server\Grant; use DateInterval; -use DateTime; -use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; @@ -126,17 +124,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant throw OAuthServerException::invalidRequest('client_id'); } - $client = $this->clientRepository->getClientEntity( - $clientId, - $this->getIdentifier(), - null, - false - ); - - if ($client instanceof ClientEntityInterface === false) { - $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); - } + $client = $this->getClientEntityOrFail($clientId, $request); $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); @@ -145,7 +133,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant } elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1 || empty($client->getRedirectUri())) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidClient(); + throw OAuthServerException::invalidClient($request); } else { $redirectUri = is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] @@ -210,9 +198,9 @@ class ImplicitGrant extends AbstractAuthorizeGrant $this->makeRedirectUri( $finalRedirectUri, [ - 'access_token' => (string) $accessToken->convertToJWT($this->privateKey), + 'access_token' => (string) $accessToken, 'token_type' => 'Bearer', - 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new DateTime())->getTimestamp(), + 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - \time(), 'state' => $authorizationRequest->getState(), ], $this->queryDelimiter diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index cb53ccd8..0dc687b9 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -83,11 +83,13 @@ class PasswordGrant extends AbstractGrant protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client) { $username = $this->getRequestParameter('username', $request); + if (is_null($username)) { throw OAuthServerException::invalidRequest('username'); } $password = $this->getRequestParameter('password', $request); + if (is_null($password)) { throw OAuthServerException::invalidRequest('password'); } @@ -98,10 +100,11 @@ class PasswordGrant extends AbstractGrant $this->getIdentifier(), $client ); + if ($user instanceof UserEntityInterface === false) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request)); - throw OAuthServerException::invalidCredentials(); + throw OAuthServerException::invalidGrant(); } return $user; diff --git a/src/Repositories/ClientRepositoryInterface.php b/src/Repositories/ClientRepositoryInterface.php index ba0610d5..7eef494f 100644 --- a/src/Repositories/ClientRepositoryInterface.php +++ b/src/Repositories/ClientRepositoryInterface.php @@ -19,13 +19,20 @@ interface ClientRepositoryInterface extends RepositoryInterface /** * Get a client. * - * @param string $clientIdentifier The client's identifier - * @param null|string $grantType The grant type used (if sent) - * @param null|string $clientSecret The client's secret (if sent) - * @param bool $mustValidateSecret If true the client must attempt to validate the secret if the client - * is confidential + * @param string $clientIdentifier The client's identifier * - * @return ClientEntityInterface + * @return ClientEntityInterface|null */ - public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true); + public function getClientEntity($clientIdentifier); + + /** + * 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) + * + * @return bool + */ + public function validateClient($clientIdentifier, $clientSecret, $grantType); } diff --git a/src/Repositories/ScopeRepositoryInterface.php b/src/Repositories/ScopeRepositoryInterface.php index 52db05de..997aac2c 100644 --- a/src/Repositories/ScopeRepositoryInterface.php +++ b/src/Repositories/ScopeRepositoryInterface.php @@ -22,7 +22,7 @@ interface ScopeRepositoryInterface extends RepositoryInterface * * @param string $identifier The scope identifier * - * @return ScopeEntityInterface + * @return ScopeEntityInterface|null */ public function getScopeEntityByIdentifier($identifier); diff --git a/src/Repositories/UserRepositoryInterface.php b/src/Repositories/UserRepositoryInterface.php index 0a9efef0..8ad49aa7 100644 --- a/src/Repositories/UserRepositoryInterface.php +++ b/src/Repositories/UserRepositoryInterface.php @@ -22,7 +22,7 @@ interface UserRepositoryInterface extends RepositoryInterface * @param string $grantType The grant type used * @param ClientEntityInterface $clientEntity * - * @return UserEntityInterface + * @return UserEntityInterface|null */ public function getUserEntityByUserCredentials( $username, diff --git a/src/RequestTypes/AuthorizationRequest.php b/src/RequestTypes/AuthorizationRequest.php index 5faa45d4..6441e144 100644 --- a/src/RequestTypes/AuthorizationRequest.php +++ b/src/RequestTypes/AuthorizationRequest.php @@ -111,7 +111,7 @@ class AuthorizationRequest } /** - * @return UserEntityInterface + * @return UserEntityInterface|null */ public function getUser() { diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 4f6e9b47..f24a9485 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -11,9 +11,9 @@ namespace League\OAuth2\Server\ResponseTypes; -use DateTime; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; +use LogicException; use Psr\Http\Message\ResponseInterface; class BearerTokenResponse extends AbstractResponseType @@ -25,32 +25,34 @@ class BearerTokenResponse extends AbstractResponseType { $expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp(); - $jwtAccessToken = $this->accessToken->convertToJWT($this->privateKey); - $responseParams = [ 'token_type' => 'Bearer', - 'expires_in' => $expireDateTime - (new DateTime())->getTimestamp(), - 'access_token' => (string) $jwtAccessToken, + 'expires_in' => $expireDateTime - \time(), + 'access_token' => (string) $this->accessToken, ]; if ($this->refreshToken instanceof RefreshTokenEntityInterface) { - $refreshToken = $this->encrypt( - 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(), - ] - ) - ); + $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(), + ]); - $responseParams['refresh_token'] = $refreshToken; + if ($refreshTokenPayload === false) { + throw new LogicException('Error encountered JSON encoding the refresh token payload'); + } + + $responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload); } - $responseParams = array_merge($this->getExtraParams($this->accessToken), $responseParams); + $responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams)); + + if ($responseParams === false) { + throw new LogicException('Error encountered JSON encoding response parameters'); + } $response = $response ->withStatus(200) @@ -58,7 +60,7 @@ class BearerTokenResponse extends AbstractResponseType ->withHeader('cache-control', 'no-store') ->withHeader('content-type', 'application/json; charset=UTF-8'); - $response->getBody()->write(json_encode($responseParams)); + $response->getBody()->write($responseParams); return $response; } diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 0a8bf6d1..bcd87b5b 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -2,6 +2,7 @@ namespace LeagueTests; +use DateInterval; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Exception\OAuthServerException; @@ -30,7 +31,7 @@ class AuthorizationServerTest extends TestCase { const DEFAULT_SCOPE = 'basic'; - public function setUp() + public function setUp(): void { // Make sure the keys have the correct permissions. chmod(__DIR__ . '/Stubs/private.key', 0600); @@ -49,7 +50,7 @@ class AuthorizationServerTest extends TestCase new StubResponseType() ); - $server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M')); + $server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M')); try { $server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response); @@ -82,7 +83,7 @@ class AuthorizationServerTest extends TestCase ); $server->setDefaultScope(self::DEFAULT_SCOPE); - $server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M')); + $server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M')); $_POST['grant_type'] = 'client_credentials'; $_POST['client_id'] = 'foo'; @@ -116,35 +117,31 @@ class AuthorizationServerTest extends TestCase $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; - $server = new class($clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), $privateKey, $encryptionKey) extends AuthorizationServer { - protected function getResponseType() - { - $this->responseType = new class extends BearerTokenResponse { - /* @return null|CryptKey */ - public function getPrivateKey() - { - return $this->privateKey; - } - - public function getEncryptionKey() - { - return $this->encryptionKey; - } - }; - - return parent::getResponseType(); - } - }; + $server = new AuthorizationServer( + $clientRepository, + $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), + $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), + 'file://' . __DIR__ . '/Stubs/private.key', + 'file://' . __DIR__ . '/Stubs/public.key' + ); $abstractGrantReflection = new \ReflectionClass($server); $method = $abstractGrantReflection->getMethod('getResponseType'); $method->setAccessible(true); + $responseType = $method->invoke($server); - $this->assertInstanceOf(BearerTokenResponse::class, $responseType); + $responseTypeReflection = new \ReflectionClass($responseType); + + $privateKeyProperty = $responseTypeReflection->getProperty('privateKey'); + $privateKeyProperty->setAccessible(true); + + $encryptionKeyProperty = $responseTypeReflection->getProperty('encryptionKey'); + $encryptionKeyProperty->setAccessible(true); + // generated instances should have keys setup - $this->assertSame($privateKey, $responseType->getPrivateKey()->getKeyPath()); - $this->assertSame($encryptionKey, $responseType->getEncryptionKey()); + $this->assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath()); + $this->assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType)); } public function testMultipleRequestsGetDifferentResponseTypeInstances() @@ -217,7 +214,7 @@ class AuthorizationServerTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $server->enableGrantType($grant); @@ -238,6 +235,7 @@ class AuthorizationServerTest extends TestCase { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -248,7 +246,7 @@ class AuthorizationServerTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); @@ -289,7 +287,7 @@ class AuthorizationServerTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); @@ -324,10 +322,6 @@ class AuthorizationServerTest extends TestCase } } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 2 - */ public function testValidateAuthorizationRequestUnregistered() { $server = new AuthorizationServer( @@ -338,19 +332,13 @@ class AuthorizationServerTest extends TestCase 'file://' . __DIR__ . '/Stubs/public.key' ); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(2); $server->validateAuthorizationRequest($request); } diff --git a/tests/AuthorizationValidators/BearerTokenValidatorTest.php b/tests/AuthorizationValidators/BearerTokenValidatorTest.php index 801846cb..69e56995 100644 --- a/tests/AuthorizationValidators/BearerTokenValidatorTest.php +++ b/tests/AuthorizationValidators/BearerTokenValidatorTest.php @@ -11,10 +11,6 @@ use Zend\Diactoros\ServerRequest; class BearerTokenValidatorTest extends TestCase { - /** - * @expectedException League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 9 - */ public function testThrowExceptionWhenAccessTokenIsNotSigned() { $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); @@ -32,8 +28,10 @@ class BearerTokenValidatorTest extends TestCase ->set('scopes', 'scope1 scope2 scope3 scope4') ->getToken(); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt)); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(9); $bearerTokenValidator->validateAuthorization($request); } diff --git a/tests/CodeChallengeVerifiers/PlainVerifierTest.php b/tests/CodeChallengeVerifiers/PlainVerifierTest.php new file mode 100644 index 00000000..437c0c2f --- /dev/null +++ b/tests/CodeChallengeVerifiers/PlainVerifierTest.php @@ -0,0 +1,24 @@ +assertEquals('plain', $verifier->getMethod()); + } + + public function testVerifyCodeChallenge() + { + $verifier = new PlainVerifier(); + + $this->assertTrue($verifier->verifyCodeChallenge('foo', 'foo')); + $this->assertFalse($verifier->verifyCodeChallenge('foo', 'bar')); + } +} diff --git a/tests/CodeChallengeVerifiers/S256VerifierTest.php b/tests/CodeChallengeVerifiers/S256VerifierTest.php new file mode 100644 index 00000000..cb96c000 --- /dev/null +++ b/tests/CodeChallengeVerifiers/S256VerifierTest.php @@ -0,0 +1,37 @@ +assertEquals('S256', $verifier->getMethod()); + } + + public function testVerifyCodeChallengeSucceeds() + { + $codeChallenge = $this->createCodeChallenge('foo'); + $verifier = new S256Verifier(); + + $this->assertTrue($verifier->verifyCodeChallenge('foo', $codeChallenge)); + } + + public function testVerifyCodeChallengeFails() + { + $codeChallenge = $this->createCodeChallenge('bar'); + $verifier = new S256Verifier(); + + $this->assertFalse($verifier->verifyCodeChallenge('foo', $codeChallenge)); + } + + private function createCodeChallenge($codeVerifier) + { + return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'); + } +} diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index 201fb5dd..eb2b2ad3 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -4,10 +4,68 @@ namespace LeagueTests\Exception; use Exception; use League\OAuth2\Server\Exception\OAuthServerException; +use League\OAuth2\Server\Grant\AbstractGrant; +use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use PHPUnit\Framework\TestCase; +use Zend\Diactoros\Response; +use Zend\Diactoros\ServerRequest; class OAuthServerExceptionTest extends TestCase { + public function testInvalidClientExceptionSetsAuthenticateHeader() + { + $serverRequest = (new ServerRequest()) + ->withParsedBody([ + 'client_id' => 'foo', + ]) + ->withAddedHeader('Authorization', 'Basic fakeauthdetails'); + + try { + $this->issueInvalidClientException($serverRequest); + } catch (OAuthServerException $e) { + $response = $e->generateHttpResponse(new Response()); + + $this->assertTrue($response->hasHeader('WWW-Authenticate')); + } + } + + public function testInvalidClientExceptionOmitsAuthenticateHeader() + { + $serverRequest = (new ServerRequest()) + ->withParsedBody([ + 'client_id' => 'foo', + ]); + + try { + $this->issueInvalidClientException($serverRequest); + } catch (OAuthServerException $e) { + $response = $e->generateHttpResponse(new Response()); + + $this->assertFalse($response->hasHeader('WWW-Authenticate')); + } + } + + /** + * Issue an invalid client exception + * + * @throws OAuthServerException + */ + private function issueInvalidClientException($serverRequest) + { + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('validateClient')->willReturn(false); + + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setClientRepository($clientRepositoryMock); + + $abstractGrantReflection = new \ReflectionClass($grantMock); + + $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); + $validateClientMethod->setAccessible(true); + + $validateClientMethod->invoke($grantMock, $serverRequest); + } + public function testHasRedirect() { $exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error'); @@ -27,7 +85,9 @@ class OAuthServerExceptionTest extends TestCase $previous = new Exception('This is the previous'); $exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $previous); - $this->assertSame('This is the previous', $exceptionWithPrevious->getPrevious()->getMessage()); + $previousMessage = $exceptionWithPrevious->getPrevious() !== null ? $exceptionWithPrevious->getPrevious()->getMessage() : null; + + $this->assertSame('This is the previous', $previousMessage); } public function testDoesNotHavePrevious() diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 75d3bcae..a4b94264 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -2,7 +2,8 @@ namespace LeagueTests\Grant; -use League\Event\Emitter; +use DateInterval; +use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; @@ -23,21 +24,13 @@ use Zend\Diactoros\ServerRequest; class AbstractGrantTest extends TestCase { - public function testGetSet() - { - /** @var AbstractGrant $grantMock */ - $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); - $grantMock->setEmitter(new Emitter()); - } - public function testHttpBasicWithPassword() { /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $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); @@ -50,8 +43,7 @@ class AbstractGrantTest extends TestCase $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('Open:')); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); @@ -64,8 +56,7 @@ class AbstractGrantTest extends TestCase $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $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); @@ -78,8 +69,7 @@ class AbstractGrantTest extends TestCase $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withHeader('Authorization', 'Basic ||'); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ||'); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); @@ -92,8 +82,7 @@ class AbstractGrantTest extends TestCase $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame')); + $serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame')); $basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials'); $basicAuthMethod->setAccessible(true); @@ -113,16 +102,14 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + ]); + $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); - $result = $validateClientMethod->invoke($grantMock, $serverRequest, true, true); + $result = $validateClientMethod->invoke($grantMock, $serverRequest); $this->assertEquals($client, $result); } @@ -139,14 +126,12 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'redirect_uri' => 'http://foo/bar', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'redirect_uri' => 'http://foo/bar', + ]); + $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); @@ -154,9 +139,6 @@ class AbstractGrantTest extends TestCase $this->assertEquals($client, $result); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateClientMissingClientId() { $client = new ClientEntity(); @@ -173,16 +155,15 @@ class AbstractGrantTest extends TestCase $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateClientMissingClientSecret() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn(null); + $clientRepositoryMock->method('validateClient')->willReturn(false); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -190,24 +171,22 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', ]); $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateClientInvalidClientSecret() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn(null); + $clientRepositoryMock->method('validateClient')->willReturn(false); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -215,8 +194,7 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'foo', ]); @@ -224,12 +202,11 @@ class AbstractGrantTest extends TestCase $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateClientInvalidRedirectUri() { $client = new ClientEntity(); @@ -243,8 +220,7 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'redirect_uri' => 'http://bar/foo', ]); @@ -252,12 +228,11 @@ class AbstractGrantTest extends TestCase $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateClientInvalidRedirectUriArray() { $client = new ClientEntity(); @@ -271,8 +246,7 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'redirect_uri' => 'http://bar/foo', ]); @@ -280,16 +254,15 @@ class AbstractGrantTest extends TestCase $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $validateClientMethod->invoke($grantMock, $serverRequest, true, true); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateClientBadClient() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn(null); + $clientRepositoryMock->method('validateClient')->willReturn(false); /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); @@ -297,8 +270,7 @@ class AbstractGrantTest extends TestCase $abstractGrantReflection = new \ReflectionClass($grantMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', ]); @@ -306,6 +278,8 @@ class AbstractGrantTest extends TestCase $validateClientMethod = $abstractGrantReflection->getMethod('validateClient'); $validateClientMethod->setAccessible(true); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $validateClientMethod->invoke($grantMock, $serverRequest, true); } @@ -314,8 +288,7 @@ class AbstractGrantTest extends TestCase $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->method('getIdentifier')->willReturn('foobar'); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'grant_type' => 'foobar', ]); @@ -332,7 +305,7 @@ class AbstractGrantTest extends TestCase /** @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); @@ -374,6 +347,7 @@ class AbstractGrantTest extends TestCase /** @var AbstractGrant $grantMock */ $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grantMock->setAccessTokenRepository($accessTokenRepoMock); $abstractGrantReflection = new \ReflectionClass($grantMock); @@ -383,7 +357,7 @@ class AbstractGrantTest extends TestCase /** @var AccessTokenEntityInterface $accessToken */ $accessToken = $issueAccessTokenMethod->invoke( $grantMock, - new \DateInterval('PT1H'), + new DateInterval('PT1H'), new ClientEntity(), 123, [new ScopeEntity()] @@ -408,7 +382,7 @@ class AbstractGrantTest extends TestCase AuthCodeEntityInterface::class, $issueAuthCodeMethod->invoke( $grantMock, - new \DateInterval('PT1H'), + new DateInterval('PT1H'), new ClientEntity(), 123, 'http://foo/bar', @@ -426,8 +400,7 @@ class AbstractGrantTest extends TestCase $method = $abstractGrantReflection->getMethod('getCookieParameter'); $method->setAccessible(true); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withCookieParams([ + $serverRequest = (new ServerRequest())->withCookieParams([ 'foo' => 'bar', ]); @@ -444,8 +417,7 @@ class AbstractGrantTest extends TestCase $method = $abstractGrantReflection->getMethod('getQueryStringParameter'); $method->setAccessible(true); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withQueryParams([ + $serverRequest = (new ServerRequest())->withQueryParams([ 'foo' => 'bar', ]); @@ -466,9 +438,6 @@ class AbstractGrantTest extends TestCase $this->assertEquals([$scope], $grantMock->validateScopes('basic ')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateScopesBadScope() { $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -478,6 +447,8 @@ class AbstractGrantTest extends TestCase $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); $grantMock->setScopeRepository($scopeRepositoryMock); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $grantMock->validateScopes('basic '); } @@ -489,7 +460,7 @@ class AbstractGrantTest extends TestCase $method = $abstractGrantReflection->getMethod('generateUniqueIdentifier'); $method->setAccessible(true); - $this->assertInternalType('string', $method->invoke($grantMock)); + $this->assertIsString($method->invoke($grantMock)); } public function testCanRespondToAuthorizationRequest() @@ -498,21 +469,21 @@ class AbstractGrantTest extends TestCase $this->assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest())); } - /** - * @expectedException \LogicException - */ public function testValidateAuthorizationRequest() { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + + $this->expectException(\LogicException::class); + $grantMock->validateAuthorizationRequest(new ServerRequest()); } - /** - * @expectedException \LogicException - */ public function testCompleteAuthorizationRequest() { $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + + $this->expectException(\LogicException::class); + $grantMock->completeAuthorizationRequest(new AuthorizationRequest()); } } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 04fb60c2..b2dc122a 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -2,6 +2,8 @@ namespace LeagueTests\Grant; +use DateInterval; +use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -38,9 +40,9 @@ class AuthCodeGrantTest extends TestCase const CODE_CHALLENGE = 'E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM'; - public function setUp() + public function setUp(): void { - $this->cryptStub = new CryptTraitStub; + $this->cryptStub = new CryptTraitStub(); } public function testGetIdentifier() @@ -48,7 +50,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $this->assertEquals('authorization_code', $grant->getIdentifier()); @@ -59,7 +61,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $request = new ServerRequest( @@ -83,6 +85,7 @@ class AuthCodeGrantTest extends TestCase { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -94,7 +97,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -122,6 +125,7 @@ class AuthCodeGrantTest extends TestCase { $client = new ClientEntity(); $client->setRedirectUri(['http://foo/bar']); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -132,7 +136,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); @@ -170,9 +174,9 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); @@ -196,9 +200,6 @@ class AuthCodeGrantTest extends TestCase $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooShort() { $client = new ClientEntity(); @@ -209,33 +210,23 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - [], - [], - [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - 'code_challenge' => str_repeat('A', 42), - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => str_repeat('A', 42), + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLong() { $client = new ClientEntity(); @@ -246,33 +237,23 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - [], - [], - [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - 'code_challenge' => str_repeat('A', 129), - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => str_repeat('A', 129), + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testValidateAuthorizationRequestCodeChallengeInvalidCharacters() { $client = new ClientEntity(); @@ -283,34 +264,23 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - [], - [], - [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - 'code_challenge' => str_repeat('A', 42) . '!', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => str_repeat('A', 42) . '!', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testValidateAuthorizationRequestMissingClientId() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -318,30 +288,20 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 4 - */ public function testValidateAuthorizationRequestInvalidClientId() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -350,31 +310,21 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 4 - */ public function testValidateAuthorizationRequestBadRedirectUriString() { $client = new ClientEntity(); @@ -385,32 +335,22 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - [], - [], - [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://bar', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 4 - */ public function testValidateAuthorizationRequestBadRedirectUriArray() { $client = new ClientEntity(); @@ -421,75 +361,22 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - [], - [], - [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://bar', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ - public function testValidateAuthorizationRequestMissingCodeChallenge() - { - $client = new ClientEntity(); - $client->setRedirectUri('http://foo/bar'); - $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - $scope = new ScopeEntity(); - $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); - - $grant = new AuthCodeGrant( - $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), - $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') - ); - $grant->enableCodeExchangeProof(); - $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://foo/bar', - ] - ); - - $grant->validateAuthorizationRequest($request); - } - - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testValidateAuthorizationRequestInvalidCodeChallengeMethod() { $client = new ClientEntity(); @@ -504,29 +391,23 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $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://foo/bar', - 'code_challenge' => 'foobar', - 'code_challenge_method' => 'foo', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + 'code_challenge' => 'foobar', + 'code_challenge_method' => 'foo', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); $grant->validateAuthorizationRequest($request); } @@ -545,17 +426,13 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setEncryptionKey($this->cryptStub->getKey()); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 9 - */ public function testCompleteAuthorizationRequestDenied() { $authRequest = new AuthorizationRequest(); @@ -570,10 +447,13 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setEncryptionKey($this->cryptStub->getKey()); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(9); + $grant->completeAuthorizationRequest($authRequest); } @@ -582,6 +462,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -601,13 +482,14 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -638,7 +520,140 @@ class AuthCodeGrantTest extends TestCase ); /** @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->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + } + + public function testRespondToAccessTokenRequestUsingHttpBasicAuth() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn(new ScopeEntity()); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity()); + + $authCodeGrant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $refreshTokenRepositoryMock, + new \DateInterval('PT10M') + ); + + $authCodeGrant->setClientRepository($clientRepositoryMock); + $authCodeGrant->setScopeRepository($scopeRepositoryMock); + $authCodeGrant->setAccessTokenRepository($accessTokenRepositoryMock); + $authCodeGrant->setEncryptionKey($this->cryptStub->getKey()); + $authCodeGrant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $request = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [ + 'Authorization' => 'Basic Zm9vOmJhcg==', + ], + [], + [], + [ + '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', + ] + ) + ), + ] + ); + + /** @var StubResponseType $response */ + $response = $authCodeGrant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + + $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); + $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); + } + + public function testRespondToAccessTokenRequestForPublicClient() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + $client->setRedirectUri('http://foo/bar'); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeEntity = new ScopeEntity(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + $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()); + + $grant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $request = new ServerRequest( + [], + [], + null, + 'POST', + 'php://input', + [], + [], + [], + [ + 'grant_type' => 'authorization_code', + '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', + ] + ) + ), + ] + ); + + /** @var StubResponseType $response */ + $response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); $this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); @@ -675,6 +690,7 @@ class AuthCodeGrantTest extends TestCase $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -716,6 +732,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -735,14 +752,15 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -776,7 +794,7 @@ class AuthCodeGrantTest extends TestCase ); /** @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->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); @@ -787,6 +805,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -806,14 +825,15 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -847,27 +867,24 @@ class AuthCodeGrantTest extends TestCase ); /** @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->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testRespondToAccessTokenRequestMissingRedirectUri() { $client = new ClientEntity(); $client->setIdentifier('foo'); + $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') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); @@ -897,24 +914,24 @@ class AuthCodeGrantTest extends TestCase ] ); - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); + + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testRespondToAccessTokenRequestRedirectUriMismatch() { $client = new ClientEntity(); $client->setIdentifier('foo'); + $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') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); @@ -945,17 +962,17 @@ class AuthCodeGrantTest extends TestCase ] ); - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); + + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testRespondToAccessTokenRequestMissingCode() { $client = new ClientEntity(); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -965,7 +982,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); @@ -989,32 +1006,25 @@ class AuthCodeGrantTest extends TestCase ] ); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); + /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } public function testRespondToAccessTokenRequestExpiredCode() { - $client = new ClientEntity(); - $client->setIdentifier('foo'); - $client->setRedirectUri('http://foo/bar'); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepositoryMock->method('getClientEntity')->willReturn($client); - - $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); - - $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); - $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $clientRepositoryMock->method('getClientEntity')->willReturn(new ClientEntity()); $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); + $grant->setClientRepository($clientRepositoryMock); - $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); $request = new ServerRequest( @@ -1047,7 +1057,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Authorization code has expired'); } @@ -1058,6 +1068,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1073,7 +1084,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepositoryMock, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); @@ -1110,7 +1121,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Authorization code has been revoked'); } @@ -1121,6 +1132,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1133,7 +1145,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); @@ -1170,7 +1182,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Authorization code was not issued to this client'); } @@ -1181,6 +1193,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1193,7 +1206,7 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); @@ -1219,7 +1232,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Cannot decrypt the authorization code'); } @@ -1230,6 +1243,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1249,9 +1263,9 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); @@ -1291,7 +1305,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Failed to verify `code_verifier`.'); } @@ -1302,6 +1316,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1321,9 +1336,9 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); @@ -1363,7 +1378,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); } @@ -1374,6 +1389,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1393,9 +1409,9 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); @@ -1435,7 +1451,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); } @@ -1446,6 +1462,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1465,9 +1482,9 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); @@ -1507,7 +1524,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Code Verifier must follow the specifications of RFC-7636.'); } @@ -1518,6 +1535,7 @@ class AuthCodeGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); $client->setRedirectUri('http://foo/bar'); + $client->setConfidential(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); @@ -1537,9 +1555,9 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); - $grant->enableCodeExchangeProof(); + $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); @@ -1578,7 +1596,7 @@ class AuthCodeGrantTest extends TestCase try { /* @var StubResponseType $response */ - $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M')); + $grant->respondToAccessTokenRequest($request, new StubResponseType(), new DateInterval('PT10M')); } catch (OAuthServerException $e) { $this->assertEquals($e->getHint(), 'Check the `code_verifier` parameter'); } @@ -1601,17 +1619,14 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 7 - */ public function testAuthCodeRepositoryFailToPersist() { $authRequest = new AuthorizationRequest(); @@ -1627,17 +1642,16 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setEncryptionKey($this->cryptStub->getKey()); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(7); + $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - /** - * @expectedException \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException - * @expectedExceptionCode 100 - */ public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop() { $authRequest = new AuthorizationRequest(); @@ -1653,9 +1667,12 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $authCodeRepository, $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); + $this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class); + $this->expectExceptionCode(100); + $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } @@ -1684,13 +1701,14 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -1721,16 +1739,12 @@ class AuthCodeGrantTest extends TestCase ); /** @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->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 7 - */ public function testRefreshTokenRepositoryFailToPersist() { $client = new ClientEntity(); @@ -1755,13 +1769,14 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -1791,17 +1806,16 @@ class AuthCodeGrantTest extends TestCase ] ); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(7); + /** @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->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - /** - * @expectedException \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException - * @expectedExceptionCode 100 - */ public function testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop() { $client = new ClientEntity(); @@ -1826,13 +1840,14 @@ class AuthCodeGrantTest extends TestCase $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $request = new ServerRequest( [], @@ -1862,24 +1877,60 @@ class AuthCodeGrantTest extends TestCase ] ); + $this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class); + $this->expectExceptionCode(100); + /** @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->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } - /** - * @expectedException \LogicException - */ public function testCompleteAuthorizationRequestNoUser() { $grant = new AuthCodeGrant( $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); + $this->expectException(\LogicException::class); + $grant->completeAuthorizationRequest(new AuthorizationRequest()); } + + public function testPublicClientAuthCodeRequestRejectedWhenCodeChallengeRequiredButNotGiven() + { + $client = new ClientEntity(); + $client->setRedirectUri('http://foo/bar'); + + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + + $grant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(), + new DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + ]); + + $this->expectException(OAuthServerException::class); + $this->expectExceptionCode(3); + + $grant->validateAuthorizationRequest($request); + } } diff --git a/tests/Grant/ClientCredentialsGrantTest.php b/tests/Grant/ClientCredentialsGrantTest.php index 6c7b5a36..54be52fd 100644 --- a/tests/Grant/ClientCredentialsGrantTest.php +++ b/tests/Grant/ClientCredentialsGrantTest.php @@ -2,6 +2,8 @@ namespace LeagueTests\Grant; +use DateInterval; +use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Grant\ClientCredentialsGrant; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -44,17 +46,15 @@ class ClientCredentialsGrantTest extends TestCase $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); } diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 8cfcfff5..e9523669 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -2,6 +2,7 @@ namespace LeagueTests\Grant; +use DateInterval; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException; @@ -30,56 +31,47 @@ class ImplicitGrantTest extends TestCase */ protected $cryptStub; - public function setUp() + public function setUp(): void { $this->cryptStub = new CryptTraitStub(); } public function testGetIdentifier() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $this->assertEquals('implicit', $grant->getIdentifier()); } public function testCanRespondToAccessTokenRequest() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $this->assertFalse( $grant->canRespondToAccessTokenRequest(new ServerRequest()) ); } - /** - * @expectedException \LogicException - */ public function testRespondToAccessTokenRequest() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); + + $this->expectException(\LogicException::class); + $grant->respondToAccessTokenRequest( new ServerRequest(), new StubResponseType(), - new \DateInterval('PT10M') + new DateInterval('PT10M') ); } public function testCanRespondToAuthorizationRequest() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'token', - 'client_id' => 'foo', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'token', + 'client_id' => 'foo', + ]); $this->assertTrue($grant->canRespondToAuthorizationRequest($request)); } @@ -95,25 +87,16 @@ class ImplicitGrantTest extends TestCase $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + ]); $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } @@ -129,89 +112,54 @@ class ImplicitGrantTest extends TestCase $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://foo/bar', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://foo/bar', + ]); $this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request)); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testValidateAuthorizationRequestMissingClientId() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - ] - ); + $request = (new ServerRequest())->withQueryParams(['response_type' => 'code']); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 4 - */ public function testValidateAuthorizationRequestInvalidClientId() { $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn(null); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 4 - */ public function testValidateAuthorizationRequestBadRedirectUriString() { $client = new ClientEntity(); @@ -219,31 +167,21 @@ class ImplicitGrantTest extends TestCase $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://bar', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 4 - */ public function testValidateAuthorizationRequestBadRedirectUriArray() { $client = new ClientEntity(); @@ -251,37 +189,37 @@ class ImplicitGrantTest extends TestCase $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); - $request = new ServerRequest( - [], - [], - null, - null, - 'php://input', - $headers = [], - $cookies = [], - $queryParams = [ - 'response_type' => 'code', - 'client_id' => 'foo', - 'redirect_uri' => 'http://bar', - ] - ); + $request = (new ServerRequest())->withQueryParams([ + 'response_type' => 'code', + 'client_id' => 'foo', + 'redirect_uri' => 'http://bar', + ]); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(4); $grant->validateAuthorizationRequest($request); } public function testCompleteAuthorizationRequest() { + $client = new ClientEntity(); + $client->setIdentifier('identifier'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); @@ -295,10 +233,6 @@ class ImplicitGrantTest extends TestCase $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 9 - */ public function testCompleteAuthorizationRequestDenied() { $authRequest = new AuthorizationRequest(); @@ -319,20 +253,29 @@ class ImplicitGrantTest extends TestCase $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(9); + $grant->completeAuthorizationRequest($authRequest); } public function testAccessTokenRepositoryUniqueConstraintCheck() { + $client = new ClientEntity(); + $client->setIdentifier('identifier'); + $authRequest = new AuthorizationRequest(); $authRequest->setAuthorizationApproved(true); - $authRequest->setClient(new ClientEntity()); + $authRequest->setClient($client); $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); - /** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */ + $accessToken = new AccessTokenEntity(); + $accessToken->setClient($client); + + /** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); - $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken); $accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); $accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf(); @@ -347,10 +290,6 @@ class ImplicitGrantTest extends TestCase $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 7 - */ public function testAccessTokenRepositoryFailToPersist() { $authRequest = new AuthorizationRequest(); @@ -359,7 +298,7 @@ class ImplicitGrantTest extends TestCase $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); - /** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */ + /** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened')); @@ -372,13 +311,12 @@ class ImplicitGrantTest extends TestCase $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(7); + $grant->completeAuthorizationRequest($authRequest); } - /** - * @expectedException \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException - * @expectedExceptionCode 100 - */ public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop() { $authRequest = new AuthorizationRequest(); @@ -387,7 +325,7 @@ class ImplicitGrantTest extends TestCase $authRequest->setGrantTypeId('authorization_code'); $authRequest->setUser(new UserEntity()); - /** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */ + /** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); @@ -400,34 +338,38 @@ class ImplicitGrantTest extends TestCase $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); + $this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class); + $this->expectExceptionCode(100); + $grant->completeAuthorizationRequest($authRequest); } - /** - * @expectedException \LogicException - */ public function testSetRefreshTokenTTL() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); - $grant->setRefreshTokenTTL(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); + + $this->expectException(\LogicException::class); + + $grant->setRefreshTokenTTL(new DateInterval('PT10M')); } - /** - * @expectedException \LogicException - */ public function testSetRefreshTokenRepository() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + + $this->expectException(\LogicException::class); + $grant->setRefreshTokenRepository($refreshTokenRepositoryMock); } - /** - * @expectedException \LogicException - */ public function testCompleteAuthorizationRequestNoUser() { - $grant = new ImplicitGrant(new \DateInterval('PT10M')); + $grant = new ImplicitGrant(new DateInterval('PT10M')); + + $this->expectException(\LogicException::class); + $grant->completeAuthorizationRequest(new AuthorizationRequest()); } } diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 28183a98..6568d06a 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -2,6 +2,8 @@ namespace LeagueTests\Grant; +use DateInterval; +use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Grant\PasswordGrant; @@ -60,19 +62,17 @@ class PasswordGrantTest extends TestCase $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'username' => 'foo', - 'password' => 'bar', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'username' => 'foo', + 'password' => 'bar', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); @@ -105,16 +105,14 @@ class PasswordGrantTest extends TestCase $grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock); $grant->setDefaultScope(self::DEFAULT_SCOPE); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'username' => 'foo', - 'password' => 'bar', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'username' => 'foo', + 'password' => 'bar', + ]); $responseType = new StubResponseType(); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); @@ -123,9 +121,6 @@ class PasswordGrantTest extends TestCase $this->assertNull($responseType->getRefreshToken()); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testRespondToRequestMissingUsername() { $client = new ClientEntity(); @@ -142,21 +137,18 @@ class PasswordGrantTest extends TestCase $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - ] - ); + $serverRequest = (new ServerRequest())->withQueryParams([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testRespondToRequestMissingPassword() { $client = new ClientEntity(); @@ -173,22 +165,19 @@ class PasswordGrantTest extends TestCase $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'username' => 'alex', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'username' => 'alex', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - */ public function testRespondToRequestBadCredentials() { $client = new ClientEntity(); @@ -206,17 +195,18 @@ class PasswordGrantTest extends TestCase $grant->setClientRepository($clientRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'username' => 'alex', - 'password' => 'whisky', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'username' => 'alex', + 'password' => 'whisky', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(10); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } } diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 8f205361..21a5eeb8 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -2,6 +2,7 @@ namespace LeagueTests\Grant; +use DateInterval; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; @@ -26,7 +27,7 @@ class RefreshTokenGrantTest extends TestCase */ protected $cryptStub; - public function setUp() + public function setUp(): void { $this->cryptStub = new CryptTraitStub(); } @@ -79,8 +80,7 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', 'refresh_token' => $oldRefreshToken, @@ -88,7 +88,7 @@ class RefreshTokenGrantTest extends TestCase ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); @@ -136,8 +136,7 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody([ + $serverRequest = (new ServerRequest())->withParsedBody([ 'client_id' => 'foo', 'client_secret' => 'bar', 'refresh_token' => $oldRefreshToken, @@ -191,27 +190,20 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, - 'scope' => 'foo', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + 'scope' => 'foo', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 5 - */ public function testRespondToUnexpectedScope() { $client = new ClientEntity(); @@ -250,24 +242,21 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, - 'scope' => 'foobar', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + 'scope' => 'foobar', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(5); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 3 - */ public function testRespondToRequestMissingOldToken() { $client = new ClientEntity(); @@ -284,22 +273,19 @@ class RefreshTokenGrantTest extends TestCase $grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(3); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 8 - */ public function testRespondToRequestInvalidOldToken() { $client = new ClientEntity(); @@ -318,23 +304,20 @@ class RefreshTokenGrantTest extends TestCase $oldRefreshToken = 'foobar'; - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(8); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 8 - */ public function testRespondToRequestClientMismatch() { $client = new ClientEntity(); @@ -367,23 +350,20 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(8); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 8 - */ public function testRespondToRequestExpiredToken() { $client = new ClientEntity(); @@ -413,23 +393,20 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(8); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } - /** - * @expectedException \League\OAuth2\Server\Exception\OAuthServerException - * @expectedExceptionCode 8 - */ public function testRespondToRequestRevokedToken() { $client = new ClientEntity(); @@ -460,16 +437,17 @@ class RefreshTokenGrantTest extends TestCase ) ); - $serverRequest = new ServerRequest(); - $serverRequest = $serverRequest->withParsedBody( - [ - 'client_id' => 'foo', - 'client_secret' => 'bar', - 'refresh_token' => $oldRefreshToken, - ] - ); + $serverRequest = (new ServerRequest())->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + ]); $responseType = new StubResponseType(); - $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class); + $this->expectExceptionCode(8); + + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M')); } } diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index fb11c483..c8ed7d1a 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -2,6 +2,7 @@ namespace LeagueTests\Middleware; +use DateInterval; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\ClientCredentialsGrant; @@ -66,7 +67,7 @@ class AuthorizationServerMiddlewareTest extends TestCase public function testOAuthErrorResponse() { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); - $clientRepository->method('getClientEntity')->willReturn(null); + $clientRepository->method('validateClient')->willReturn(false); $server = new AuthorizationServer( $clientRepository, @@ -77,7 +78,7 @@ class AuthorizationServerMiddlewareTest extends TestCase new StubResponseType() ); - $server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M')); + $server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M')); $_POST['grant_type'] = 'client_credentials'; $_POST['client_id'] = 'foo'; diff --git a/tests/Middleware/ResourceServerMiddlewareTest.php b/tests/Middleware/ResourceServerMiddlewareTest.php index 2269c45a..4f2d3079 100644 --- a/tests/Middleware/ResourceServerMiddlewareTest.php +++ b/tests/Middleware/ResourceServerMiddlewareTest.php @@ -2,6 +2,8 @@ namespace LeagueTests\Middleware; +use DateInterval; +use DateTimeImmutable; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Middleware\ResourceServerMiddleware; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; @@ -27,13 +29,13 @@ class ResourceServerMiddlewareTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('test'); $accessToken->setUserIdentifier(123); - $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $token = (string) $accessToken; - $request = new ServerRequest(); - $request = $request->withHeader('authorization', sprintf('Bearer %s', $token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token)); $middleware = new ResourceServerMiddleware($server); $response = $middleware->__invoke( @@ -62,13 +64,13 @@ class ResourceServerMiddlewareTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('test'); $accessToken->setUserIdentifier(123); - $accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H'))); $accessToken->setClient($client); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); - $token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + $token = (string) $accessToken; - $request = new ServerRequest(); - $request = $request->withHeader('authorization', sprintf('Bearer %s', $token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token)); $middleware = new ResourceServerMiddleware($server); $response = $middleware->__invoke( @@ -91,8 +93,7 @@ class ResourceServerMiddlewareTest extends TestCase 'file://' . __DIR__ . '/../Stubs/public.key' ); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', ''); + $request = (new ServerRequest())->withHeader('authorization', ''); $middleware = new ResourceServerMiddleware($server); $response = $middleware->__invoke( diff --git a/tests/ResponseTypes/BearerResponseTypeTest.php b/tests/ResponseTypes/BearerResponseTypeTest.php index 31245b07..0c4a46fc 100644 --- a/tests/ResponseTypes/BearerResponseTypeTest.php +++ b/tests/ResponseTypes/BearerResponseTypeTest.php @@ -2,6 +2,8 @@ namespace LeagueTests\ResponseTypes; +use DateInterval; +use DateTimeImmutable; use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Exception\OAuthServerException; @@ -32,14 +34,15 @@ class BearerResponseTypeTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->addScope($scope); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); $refreshToken->setAccessToken($accessToken); - $refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $responseType->setAccessToken($accessToken); $responseType->setRefreshToken($refreshToken); @@ -54,7 +57,7 @@ class BearerResponseTypeTest extends TestCase $response->getBody()->rewind(); $json = json_decode($response->getBody()->getContents()); - $this->assertAttributeEquals('Bearer', 'token_type', $json); + $this->assertEquals('Bearer', $json->token_type); $this->assertObjectHasAttribute('expires_in', $json); $this->assertObjectHasAttribute('access_token', $json); $this->assertObjectHasAttribute('refresh_token', $json); @@ -74,14 +77,15 @@ class BearerResponseTypeTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); - $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); $accessToken->addScope($scope); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); $refreshToken->setAccessToken($accessToken); - $refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $responseType->setAccessToken($accessToken); $responseType->setRefreshToken($refreshToken); @@ -96,13 +100,13 @@ class BearerResponseTypeTest extends TestCase $response->getBody()->rewind(); $json = json_decode($response->getBody()->getContents()); - $this->assertAttributeEquals('Bearer', 'token_type', $json); + $this->assertEquals('Bearer', $json->token_type); $this->assertObjectHasAttribute('expires_in', $json); $this->assertObjectHasAttribute('access_token', $json); $this->assertObjectHasAttribute('refresh_token', $json); $this->assertObjectHasAttribute('foo', $json); - $this->assertAttributeEquals('bar', 'foo', $json); + $this->assertEquals('bar', $json->foo); } public function testDetermineAccessTokenInHeaderValidToken() @@ -117,13 +121,14 @@ class BearerResponseTypeTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); $accessToken->setUserIdentifier(123); - $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); $refreshToken->setAccessToken($accessToken); - $refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $responseType->setAccessToken($accessToken); $responseType->setRefreshToken($refreshToken); @@ -137,8 +142,7 @@ class BearerResponseTypeTest extends TestCase $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); $request = $authorizationValidator->validateAuthorization($request); @@ -162,13 +166,14 @@ class BearerResponseTypeTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); $accessToken->setUserIdentifier(123); - $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); $refreshToken->setAccessToken($accessToken); - $refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $responseType->setAccessToken($accessToken); $responseType->setRefreshToken($refreshToken); @@ -179,8 +184,7 @@ class BearerResponseTypeTest extends TestCase $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token . 'foo')); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token . 'foo')); try { $authorizationValidator->validateAuthorization($request); @@ -204,13 +208,14 @@ class BearerResponseTypeTest extends TestCase $accessToken = new AccessTokenEntity(); $accessToken->setIdentifier('abcdef'); $accessToken->setUserIdentifier(123); - $accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $accessToken->setClient($client); + $accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $refreshToken = new RefreshTokenEntity(); $refreshToken->setIdentifier('abcdef'); $refreshToken->setAccessToken($accessToken); - $refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H'))); + $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H'))); $responseType->setAccessToken($accessToken); $responseType->setRefreshToken($refreshToken); @@ -224,8 +229,7 @@ class BearerResponseTypeTest extends TestCase $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); + $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token)); try { $authorizationValidator->validateAuthorization($request); @@ -248,8 +252,7 @@ class BearerResponseTypeTest extends TestCase $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', 'Bearer blah'); + $request = (new ServerRequest())->withHeader('authorization', 'Bearer blah'); try { $authorizationValidator->validateAuthorization($request); @@ -272,8 +275,7 @@ class BearerResponseTypeTest extends TestCase $authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock); $authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key')); - $request = new ServerRequest(); - $request = $request->withHeader('authorization', 'Bearer blah.blah.blah'); + $request = (new ServerRequest())->withHeader('authorization', 'Bearer blah.blah.blah'); try { $authorizationValidator->validateAuthorization($request); diff --git a/tests/Stubs/ClientEntity.php b/tests/Stubs/ClientEntity.php index 0c6a4f9b..d908248b 100644 --- a/tests/Stubs/ClientEntity.php +++ b/tests/Stubs/ClientEntity.php @@ -15,8 +15,8 @@ class ClientEntity implements ClientEntityInterface $this->redirectUri = $uri; } - public function setName($name) + public function setConfidential() { - $this->name = $name; + $this->isConfidential = true; } } diff --git a/tests/Utils/CryptKeyTest.php b/tests/Utils/CryptKeyTest.php index 9f3f337c..e9799c08 100644 --- a/tests/Utils/CryptKeyTest.php +++ b/tests/Utils/CryptKeyTest.php @@ -7,11 +7,10 @@ use PHPUnit\Framework\TestCase; class CryptKeyTest extends TestCase { - /** - * @expectedException \LogicException - */ public function testNoFile() { + $this->expectException(\LogicException::class); + new CryptKey('undefined file'); } @@ -27,6 +26,11 @@ class CryptKeyTest extends TestCase public function testKeyFileCreation() { $keyContent = file_get_contents(__DIR__ . '/../Stubs/public.key'); + + if (!is_string($keyContent)) { + $this->fail('The public key stub is not a string'); + } + $key = new CryptKey($keyContent); $this->assertEquals( @@ -35,6 +39,11 @@ class CryptKeyTest extends TestCase ); $keyContent = file_get_contents(__DIR__ . '/../Stubs/private.key.crlf'); + + if (!is_string($keyContent)) { + $this->fail('The private key (crlf) stub is not a string'); + } + $key = new CryptKey($keyContent); $this->assertEquals( diff --git a/tests/Utils/CryptTraitTest.php b/tests/Utils/CryptTraitTest.php index c517cec2..06d0b219 100644 --- a/tests/Utils/CryptTraitTest.php +++ b/tests/Utils/CryptTraitTest.php @@ -10,7 +10,7 @@ class CryptTraitTest extends TestCase { protected $cryptStub; - protected function setUp() + protected function setUp(): void { $this->cryptStub = new CryptTraitStub(); }