From a479b5762eac7122ad0af2a600422d431ee36a11 Mon Sep 17 00:00:00 2001 From: Christiaan Goossens Date: Fri, 13 Jul 2018 11:47:32 +0200 Subject: [PATCH 01/66] Fix implicit grant scopes --- src/Grant/ImplicitGrant.php | 17 +++++++++-------- tests/Grant/ImplicitGrantTest.php | 27 +++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 9810c30a..9c053311 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -154,13 +154,6 @@ class ImplicitGrant extends AbstractAuthorizeGrant $redirectUri ); - // Finalize the requested scopes - $finalizedScopes = $this->scopeRepository->finalizeScopes( - $scopes, - $this->getIdentifier(), - $client - ); - $stateParameter = $this->getQueryStringParameter('state', $request); $authorizationRequest = new AuthorizationRequest(); @@ -172,7 +165,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant $authorizationRequest->setState($stateParameter); } - $authorizationRequest->setScopes($finalizedScopes); + $authorizationRequest->setScopes($scopes); return $authorizationRequest; } @@ -194,6 +187,14 @@ class ImplicitGrant extends AbstractAuthorizeGrant // The user approved the client, redirect them back with an access token if ($authorizationRequest->isAuthorizationApproved() === true) { + // Finalize the requested scopes + $finalizedScopes = $this->scopeRepository->finalizeScopes( + $authorizationRequest->getScopes(), + $this->getIdentifier(), + $authorizationRequest->getClient(), + $authorizationRequest->getUser()->getIdentifier() + ); + $accessToken = $this->issueAccessToken( $this->accessTokenTTL, $authorizationRequest->getClient(), diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 0080548f..257ea16d 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -94,7 +94,6 @@ class ImplicitGrantTest extends TestCase $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); - $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); @@ -129,7 +128,6 @@ class ImplicitGrantTest extends TestCase $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); - $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setClientRepository($clientRepositoryMock); @@ -286,9 +284,14 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeEntity = new ScopeEntity(); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } @@ -309,9 +312,14 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeEntity = new ScopeEntity(); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->completeAuthorizationRequest($authRequest); } @@ -330,9 +338,14 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); $accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeEntity = new ScopeEntity(); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); } @@ -354,9 +367,14 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened')); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeEntity = new ScopeEntity(); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->completeAuthorizationRequest($authRequest); } @@ -378,9 +396,14 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeEntity = new ScopeEntity(); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + $grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); $grant->completeAuthorizationRequest($authRequest); } From acf16e924a09ea3d2c86137569cfcf3d59214794 Mon Sep 17 00:00:00 2001 From: Christiaan Goossens Date: Fri, 13 Jul 2018 13:11:18 +0200 Subject: [PATCH 02/66] Actually use finalizedScopes in access token --- src/Grant/ImplicitGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 9c053311..1890a6ba 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -199,7 +199,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant $this->accessTokenTTL, $authorizationRequest->getClient(), $authorizationRequest->getUser()->getIdentifier(), - $authorizationRequest->getScopes() + $finalizedScopes ); $response = new RedirectResponse(); From 6949a007e5939ae99036447de020a576d65d7fd3 Mon Sep 17 00:00:00 2001 From: Jason Adams Date: Sat, 18 Aug 2018 16:57:31 -0700 Subject: [PATCH 03/66] Added docbloc to UniqueTokenIdentifierConstraintViolationException --- .../UniqueTokenIdentifierConstraintViolationException.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Exception/UniqueTokenIdentifierConstraintViolationException.php b/src/Exception/UniqueTokenIdentifierConstraintViolationException.php index a67855b2..175ab3a8 100644 --- a/src/Exception/UniqueTokenIdentifierConstraintViolationException.php +++ b/src/Exception/UniqueTokenIdentifierConstraintViolationException.php @@ -11,6 +11,9 @@ namespace League\OAuth2\Server\Exception; class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException { + /** + * @return UniqueTokenIdentifierConstraintViolationException + */ public static function create() { $errorMessage = 'Could not create unique access token identifier'; From ef864b5cba8eafd508f9847af6ceb28c5e356a40 Mon Sep 17 00:00:00 2001 From: Enrico Zimuel Date: Wed, 19 Sep 2018 12:02:06 +0200 Subject: [PATCH 04/66] Added Expressive in the community integration of README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4d5fd215..5d389591 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci * [Drupal](https://www.drupal.org/project/simple_oauth) * [Laravel Passport](https://github.com/laravel/passport) * [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server) +* [OAuth 2 Server for Expressive](https://github.com/zendframework/zend-expressive-authentication-oauth2) ## Changelog From 6bc6ac09d207998626d7335897465b0708e1fa0a Mon Sep 17 00:00:00 2001 From: sephster Date: Sun, 23 Sep 2018 18:30:14 +0100 Subject: [PATCH 05/66] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9668d0..7dbca52e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Changed +- Moved `finalizeScopes()` call from `validateAuthorizationRequest` method to the `completeAuthorizationRequest` method so it is called just before the access token is issued (PR #923) + ## [7.2.0] - released 2018-06-23 ### Changed From 71c605117a7b2cfc168c572e2bf633837859b00a Mon Sep 17 00:00:00 2001 From: sephster Date: Sun, 23 Sep 2018 18:31:26 +0100 Subject: [PATCH 06/66] Add missing word --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dbca52e..370ab559 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Changed -- Moved `finalizeScopes()` call from `validateAuthorizationRequest` method to the `completeAuthorizationRequest` method so it is called just before the access token is issued (PR #923) +- Moved the `finalizeScopes()` call from `validateAuthorizationRequest` method to the `completeAuthorizationRequest` method so it is called just before the access token is issued (PR #923) ## [7.2.0] - released 2018-06-23 From d6792c1662a523b6df243f48d0581b996875dd97 Mon Sep 17 00:00:00 2001 From: sephster Date: Fri, 12 Oct 2018 23:05:07 +0100 Subject: [PATCH 07/66] Add scope entity trait --- src/Entities/Traits/ScopeTrait.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/Entities/Traits/ScopeTrait.php diff --git a/src/Entities/Traits/ScopeTrait.php b/src/Entities/Traits/ScopeTrait.php new file mode 100644 index 00000000..a132234f --- /dev/null +++ b/src/Entities/Traits/ScopeTrait.php @@ -0,0 +1,28 @@ + + * @copyright Copyright (c) Andrew Millington + * @license http://mit-license.org + * + * @link https://github.com/thephpleague/oauth2-server + */ + +namespace League\OAuth2\Server\Entities\Traits; + +trait ScopeTrait +{ + /** + * Serialize the object to the scopes string identifier when using json_encode(). + * + * @return string + */ + public function jsonSerialize() + { + return $this->getIdentifier(); + } + + /** + * @return string + */ + abstract public function getIdentifier(); +} From d76025d61338f77c8d6dbff2acd3462aaa5ceb9b Mon Sep 17 00:00:00 2001 From: sephster Date: Fri, 12 Oct 2018 23:23:24 +0100 Subject: [PATCH 08/66] Change example to use scope trait --- examples/src/Entities/ScopeEntity.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/src/Entities/ScopeEntity.php b/examples/src/Entities/ScopeEntity.php index ec83cf51..913c0592 100644 --- a/examples/src/Entities/ScopeEntity.php +++ b/examples/src/Entities/ScopeEntity.php @@ -11,13 +11,9 @@ namespace OAuth2ServerExamples\Entities; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\Traits\EntityTrait; +use League\OAuth2\Server\Entities\Traits\ScopeTrait; class ScopeEntity implements ScopeEntityInterface { - use EntityTrait; - - public function jsonSerialize() - { - return $this->getIdentifier(); - } + use EntityTrait, ScopeTrait; } From 7bf7700645be82b3fa82302a6cf6e494426a7142 Mon Sep 17 00:00:00 2001 From: sephster Date: Fri, 12 Oct 2018 23:32:44 +0100 Subject: [PATCH 09/66] Update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370ab559..c21bab59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Moved the `finalizeScopes()` call from `validateAuthorizationRequest` method to the `completeAuthorizationRequest` method so it is called just before the access token is issued (PR #923) +### Added +- Added a ScopeTrait to provide an implementation for jsonSerialize (PR #952) + ## [7.2.0] - released 2018-06-23 ### Changed From 4042a3115978a6dadf0e2533a5d2623ecb5a6f6f Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 13:17:45 +0100 Subject: [PATCH 10/66] Update composer dependency versions --- examples/composer.json | 9 +- examples/composer.lock | 219 ++++++++++++++++++++++++++++++++--------- 2 files changed, 177 insertions(+), 51 deletions(-) diff --git a/examples/composer.json b/examples/composer.json index ec7387cf..a3ef9b7a 100644 --- a/examples/composer.json +++ b/examples/composer.json @@ -1,14 +1,13 @@ { "require": { - "slim/slim": "3.0.*" + "slim/slim": "^3.0.0" }, "require-dev": { "league/event": "^2.1", - "lcobucci/jwt": "^3.1", - "paragonie/random_compat": "^2.0", + "lcobucci/jwt": "^3.2", "psr/http-message": "^1.0", - "defuse/php-encryption": "^2.1", - "zendframework/zend-diactoros": "^1.0" + "defuse/php-encryption": "^2.2", + "zendframework/zend-diactoros": "^2.0.0" }, "autoload": { "psr-4": { diff --git a/examples/composer.lock b/examples/composer.lock index 7210f31e..6ba8ec13 100644 --- a/examples/composer.lock +++ b/examples/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9813ed7c3b6dcf107f44df9392935b8f", + "content-hash": "97f2878428e37d1d8e5418cc85cbfa3d", "packages": [ { "name": "container-interop/container-interop", @@ -39,21 +39,24 @@ }, { "name": "nikic/fast-route", - "version": "v0.6.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nikic/FastRoute.git", - "reference": "31fa86924556b80735f98b294a7ffdfb26789f22" + "reference": "181d480e08d9476e61381e04a71b34dc0432e812" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22", - "reference": "31fa86924556b80735f98b294a7ffdfb26789f22", + "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812", + "reference": "181d480e08d9476e61381e04a71b34dc0432e812", "shasum": "" }, "require": { "php": ">=5.4.0" }, + "require-dev": { + "phpunit/phpunit": "^4.8.35|~5.7" + }, "type": "library", "autoload": { "psr-4": { @@ -78,29 +81,33 @@ "router", "routing" ], - "time": "2015-06-18T19:15:47+00:00" + "time": "2018-02-13T20:26:39+00:00" }, { "name": "pimple/pimple", - "version": "v3.0.2", + "version": "v3.2.3", "source": { "type": "git", "url": "https://github.com/silexphp/Pimple.git", - "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", - "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "3.2.x-dev" } }, "autoload": { @@ -124,7 +131,7 @@ "container", "dependency injection" ], - "time": "2015-09-11T15:10:35+00:00" + "time": "2018-01-21T07:42:36+00:00" }, { "name": "psr/container", @@ -227,27 +234,32 @@ }, { "name": "slim/slim", - "version": "3.0.0", + "version": "3.11.0", "source": { "type": "git", "url": "https://github.com/slimphp/Slim.git", - "reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e" + "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slimphp/Slim/zipball/3b06f0f2d84dabbe81b6cea46ace46a3e883253e", - "reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e", + "url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a", + "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "nikic/fast-route": "^0.6", + "container-interop/container-interop": "^1.2", + "nikic/fast-route": "^1.0", "php": ">=5.5.0", "pimple/pimple": "^3.0", + "psr/container": "^1.0", "psr/http-message": "^1.0" }, + "provide": { + "psr/http-message-implementation": "1.0" + }, "require-dev": { - "phpunit/phpunit": "^4.0" + "phpunit/phpunit": "^4.0", + "squizlabs/php_codesniffer": "^2.5" }, "type": "library", "autoload": { @@ -282,38 +294,38 @@ } ], "description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", - "homepage": "http://slimframework.com", + "homepage": "https://slimframework.com", "keywords": [ "api", "framework", "micro", "router" ], - "time": "2015-12-07T14:11:09+00:00" + "time": "2018-09-16T10:54:21+00:00" } ], "packages-dev": [ { "name": "defuse/php-encryption", - "version": "v2.1.0", + "version": "v2.2.1", "source": { "type": "git", "url": "https://github.com/defuse/php-encryption.git", - "reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689" + "reference": "0f407c43b953d571421e0020ba92082ed5fb7620" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/defuse/php-encryption/zipball/5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689", - "reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/0f407c43b953d571421e0020ba92082ed5fb7620", + "reference": "0f407c43b953d571421e0020ba92082ed5fb7620", "shasum": "" }, "require": { "ext-openssl": "*", - "paragonie/random_compat": "~2.0", + "paragonie/random_compat": ">= 2", "php": ">=5.4.0" }, "require-dev": { - "nikic/php-parser": "^2.0|^3.0", + "nikic/php-parser": "^2.0|^3.0|^4.0", "phpunit/phpunit": "^4|^5" }, "bin": [ @@ -354,20 +366,20 @@ "security", "symmetric key cryptography" ], - "time": "2017-05-18T21:28:48+00:00" + "time": "2018-07-24T23:27:56+00:00" }, { "name": "lcobucci/jwt", - "version": "3.2.1", + "version": "3.2.4", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "ddce703826f9c5229781933b1a39069e38e6a0f3" + "reference": "c9704b751315d21735dc98d78d4f37bd73596da7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/ddce703826f9c5229781933b1a39069e38e6a0f3", - "reference": "ddce703826f9c5229781933b1a39069e38e6a0f3", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c9704b751315d21735dc98d78d4f37bd73596da7", + "reference": "c9704b751315d21735dc98d78d4f37bd73596da7", "shasum": "" }, "require": { @@ -412,7 +424,7 @@ "JWS", "jwt" ], - "time": "2016-10-31T20:09:32+00:00" + "time": "2018-08-03T11:23:50+00:00" }, { "name": "league/event", @@ -466,33 +478,29 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.10", + "version": "v9.99.99", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", - "reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "php": ">=5.2.0" + "php": "^7" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*" + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, "suggest": { "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" @@ -507,10 +515,129 @@ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ "csprng", + "polyfill", "pseudorandom", "random" ], - "time": "2017-03-13T16:27:32+00:00" + "time": "2018-07-02T15:55:56+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/378bfe27931ecc54ff824a20d6f6bfc303bbd04c", + "reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2018-07-30T21:54:04+00:00" + }, + { + "name": "zendframework/zend-diactoros", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-diactoros.git", + "reference": "0bae78192e634774b5584f0210c1232da82cb1ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/0bae78192e634774b5584f0210c1232da82cb1ff", + "reference": "0bae78192e634774b5584f0210c1232da82cb1ff", + "shasum": "" + }, + "require": { + "php": "^7.1", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-dom": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.5.0", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^7.0.2", + "zendframework/zend-coding-standard": "~1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev", + "dev-develop": "2.1.x-dev", + "dev-release-1.8": "1.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Zend\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "keywords": [ + "http", + "psr", + "psr-7" + ], + "time": "2018-09-27T19:49:04+00:00" } ], "aliases": [], From 939c0619d0d959de1b247d08f9a90e92a46fc407 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 13:22:27 +0100 Subject: [PATCH 11/66] Shorten variable name --- examples/src/Repositories/RefreshTokenRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/Repositories/RefreshTokenRepository.php b/examples/src/Repositories/RefreshTokenRepository.php index 39a0b8cd..007529cd 100644 --- a/examples/src/Repositories/RefreshTokenRepository.php +++ b/examples/src/Repositories/RefreshTokenRepository.php @@ -18,7 +18,7 @@ class RefreshTokenRepository implements RefreshTokenRepositoryInterface /** * {@inheritdoc} */ - public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntityInterface) + public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity) { // Some logic to persist the refresh token in a database } From 30ed221481ac29114c946a2507f2085d68e6945f Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 13:27:31 +0100 Subject: [PATCH 12/66] Add Andy to authors list --- composer.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/composer.json b/composer.json index 48a95701..26eace83 100644 --- a/composer.json +++ b/composer.json @@ -46,6 +46,12 @@ "email": "hello@alexbilbie.com", "homepage": "http://www.alexbilbie.com", "role": "Developer" + }, + { + "name": "Andy Millington", + "email": "andrew@noexceptions.io", + "homepage": "https://www.noexceptions.io", + "role": "Developer" } ], "replace": { From 398029be56d6284904f32b196f239a1ffcb5809c Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 13:34:11 +0100 Subject: [PATCH 13/66] Add roave security advisories --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 26eace83..cb54606c 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ "zendframework/zend-diactoros": "^1.3.2", "phpstan/phpstan": "^0.9.2", "phpstan/phpstan-phpunit": "^0.9.4", - "phpstan/phpstan-strict-rules": "^0.9.0" + "phpstan/phpstan-strict-rules": "^0.9.0", + "roave/security-advisories": "dev-master" }, "repositories": [ { From b4d88995de45fa85a69b05094bc43781f861c5de Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 13:42:27 +0100 Subject: [PATCH 14/66] Add throws tag for DateInterval exception --- src/Grant/AuthCodeGrant.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index cfa8309b..552aa7ba 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -34,9 +34,12 @@ class AuthCodeGrant extends AbstractAuthorizeGrant private $enableCodeExchangeProof = false; /** - * @param AuthCodeRepositoryInterface $authCodeRepository + * @param AuthCodeRepositoryInterface $authCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param \DateInterval $authCodeTTL + * @param \DateInterval $authCodeTTL + * + * @return void + * @throws \Exception */ public function __construct( AuthCodeRepositoryInterface $authCodeRepository, From 50566cdc875bf202664b608787ad51a47fe2e079 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 14:34:35 +0100 Subject: [PATCH 15/66] Reduce complexity of respondToAccessTokenRequest --- src/Grant/AuthCodeGrant.php | 134 +++++++++++++++++++++++------------- 1 file changed, 88 insertions(+), 46 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 552aa7ba..53c64a85 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server\Grant; +use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; @@ -81,47 +82,13 @@ class AuthCodeGrant extends AbstractAuthorizeGrant throw OAuthServerException::invalidRequest('code'); } - // Validate the authorization code try { $authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); - if (time() > $authCodePayload->expire_time) { - throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); - } - if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) { - throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked'); - } + $this->validateAuthorizationCode($authCodePayload, $client, $request); - if ($authCodePayload->client_id !== $client->getIdentifier()) { - throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client'); - } - - // The redirect URI is required in this request - $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); - if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) { - throw OAuthServerException::invalidRequest('redirect_uri'); - } - - if ($authCodePayload->redirect_uri !== $redirectUri) { - throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI'); - } - - $scopes = []; - foreach ($authCodePayload->scopes as $scopeId) { - $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId); - - if ($scope instanceof ScopeEntityInterface === false) { - // @codeCoverageIgnoreStart - throw OAuthServerException::invalidScope($scopeId); - // @codeCoverageIgnoreEnd - } - - $scopes[] = $scope; - } - - // Finalize the requested scopes $scopes = $this->scopeRepository->finalizeScopes( - $scopes, + $this->getScopes($authCodePayload), $this->getIdentifier(), $client, $authCodePayload->user_id @@ -133,6 +100,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant // Validate code challenge if ($this->enableCodeExchangeProof === true) { $codeVerifier = $this->getRequestParameter('code_verifier', $request, null); + if ($codeVerifier === null) { throw OAuthServerException::invalidRequest('code_verifier'); } @@ -193,6 +161,69 @@ class AuthCodeGrant extends AbstractAuthorizeGrant return $responseType; } + /** + * Validate the authorization code. + * + * @param AuthCodeEntityInterface $authCodePayload + * @param ClientEntityInterface $client + * @param ServerRequestInterface $request + * + * @return void + */ + private function validateAuthorizationCode( + $authCodePayload, + ClientEntityInterface $client, + ServerRequestInterface $request + ) { + if (time() > $authCodePayload->expire_time) { + throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); + } + + if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) { + throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked'); + } + + if ($authCodePayload->client_id !== $client->getIdentifier()) { + throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client'); + } + + // The redirect URI is required in this request + $redirectUri = $this->getRequestParameter('redirect_uri', $request, null); + if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) { + throw OAuthServerException::invalidRequest('redirect_uri'); + } + + if ($authCodePayload->redirect_uri !== $redirectUri) { + throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI'); + } + } + + /** + * Get scopes from the auth code payload. + * + * @param AuthCodeEntityInterface $authCodePayload + * + * @return array + */ + private function getScopes($authCodePayload) + { + $scopes = []; + + foreach ($authCodePayload->scopes as $scopeId) { + $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId); + + if ($scope instanceof ScopeEntityInterface === false) { + // @codeCoverageIgnoreStart + throw OAuthServerException::invalidScope($scopeId); + // @codeCoverageIgnoreEnd + } + + $scopes[] = $scope; + } + + return $scopes; + } + /** * Return the grant identifier that can be used in matching up requests. * @@ -226,7 +257,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $this->getServerParameter('PHP_AUTH_USER', $request) ); - if (is_null($clientId)) { + if ($clientId === null) { throw OAuthServerException::invalidRequest('client_id'); } @@ -246,12 +277,12 @@ class AuthCodeGrant extends AbstractAuthorizeGrant if ($redirectUri !== null) { $this->validateRedirectUri($redirectUri, $client, $request); - } elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1 - || empty($client->getRedirectUri())) { + } 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(); } else { - $redirectUri = is_array($client->getRedirectUri()) + $redirectUri = \is_array($client->getRedirectUri()) ? $client->getRedirectUri()[0] : $client->getRedirectUri(); } @@ -282,7 +313,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain'); - if (in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) { + if (\in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) { throw OAuthServerException::invalidRequest( 'code_challenge_method', 'Code challenge method must be `plain` or `S256`' @@ -314,11 +345,8 @@ class AuthCodeGrant extends AbstractAuthorizeGrant throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } - $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) - ? is_array($authorizationRequest->getClient()->getRedirectUri()) - ? $authorizationRequest->getClient()->getRedirectUri()[0] - : $authorizationRequest->getClient()->getRedirectUri() - : $authorizationRequest->getRedirectUri(); + $finalRedirectUri = $authorizationRequest->getRedirectUri() + ?? $this->getClientRedirectUri($authorizationRequest); // The user approved the client, redirect them back with an auth code if ($authorizationRequest->isAuthorizationApproved() === true) { @@ -370,4 +398,18 @@ class AuthCodeGrant extends AbstractAuthorizeGrant ) ); } + + /** + * Get the client redirect URI if not set in the request. + * + * @param AuthorizationRequest $authorizationRequest + * + * @return string + */ + private function getClientRedirectUri(AuthorizationRequest $authorizationRequest) + { + return \is_array($authorizationRequest->getClient()->getRedirectUri()) + ? $authorizationRequest->getClient()->getRedirectUri()[0] + : $authorizationRequest->getClient()->getRedirectUri(); + } } From 0515129c9cb9af5d7ee41278d41109fe63f29f29 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 14:37:36 +0100 Subject: [PATCH 16/66] Fix coding standards issues --- src/Grant/AuthCodeGrant.php | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 53c64a85..add63511 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -35,11 +35,10 @@ class AuthCodeGrant extends AbstractAuthorizeGrant private $enableCodeExchangeProof = false; /** - * @param AuthCodeRepositoryInterface $authCodeRepository + * @param AuthCodeRepositoryInterface $authCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param \DateInterval $authCodeTTL + * @param \DateInterval $authCodeTTL * - * @return void * @throws \Exception */ public function __construct( @@ -167,8 +166,6 @@ class AuthCodeGrant extends AbstractAuthorizeGrant * @param AuthCodeEntityInterface $authCodePayload * @param ClientEntityInterface $client * @param ServerRequestInterface $request - * - * @return void */ private function validateAuthorizationCode( $authCodePayload, From b11d628e8b3c98a06118c67c392ccfebe45480bc Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 14:49:29 +0100 Subject: [PATCH 17/66] Change docblock type for --- src/Grant/AuthCodeGrant.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index add63511..e2b92ffd 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -163,7 +163,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * Validate the authorization code. * - * @param AuthCodeEntityInterface $authCodePayload + * @param object $authCodePayload * @param ClientEntityInterface $client * @param ServerRequestInterface $request */ @@ -198,7 +198,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * Get scopes from the auth code payload. * - * @param AuthCodeEntityInterface $authCodePayload + * @param object $authCodePayload * * @return array */ From dbf2b55bc591e5eac06ab8b88e3ac610b896fa8f Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 15:16:50 +0100 Subject: [PATCH 18/66] Fix docblock alignment --- src/Grant/AuthCodeGrant.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index e2b92ffd..14da1f1c 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -163,9 +163,9 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * Validate the authorization code. * - * @param object $authCodePayload - * @param ClientEntityInterface $client - * @param ServerRequestInterface $request + * @param object $authCodePayload + * @param ClientEntityInterface $client + * @param ServerRequestInterface $request */ private function validateAuthorizationCode( $authCodePayload, From b624124d5af0f25714f582a73581016035f7b7c1 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 15:25:49 +0100 Subject: [PATCH 19/66] Chaneg param types to satisfy PHPStan --- src/Grant/AuthCodeGrant.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 14da1f1c..2b7ab787 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -163,7 +163,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * Validate the authorization code. * - * @param object $authCodePayload + * @param \stdClass $authCodePayload * @param ClientEntityInterface $client * @param ServerRequestInterface $request */ @@ -198,7 +198,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * Get scopes from the auth code payload. * - * @param object $authCodePayload + * @param \stdClass $authCodePayload * * @return array */ From 50ab9dd8aca80c13ebba54aa13c8248edb8e1179 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 15:28:39 +0100 Subject: [PATCH 20/66] Remove unused import --- src/Grant/AuthCodeGrant.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 2b7ab787..441dfe48 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -9,7 +9,6 @@ namespace League\OAuth2\Server\Grant; -use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; From 322b55eddf730fe81fbfd7bd2840fff7550a3d01 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 16:11:44 +0100 Subject: [PATCH 21/66] Remove getScopes function and use validateScopes instead --- src/Grant/AbstractGrant.php | 22 ++++++++++++++++++---- src/Grant/AuthCodeGrant.php | 28 +--------------------------- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 99f1626a..b6b14cf0 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -242,13 +242,13 @@ abstract class AbstractGrant implements GrantTypeInterface */ public function validateScopes($scopes, $redirectUri = null) { - $scopesList = array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) { - return !empty($scope); - }); + if (!is_array($scopes)) { + $scopes = $this->convertScopesQueryStringToArray($scopes); + } $validScopes = []; - foreach ($scopesList as $scopeItem) { + foreach ($scopes as $scopeItem) { $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem); if ($scope instanceof ScopeEntityInterface === false) { @@ -261,6 +261,20 @@ abstract class AbstractGrant implements GrantTypeInterface return $validScopes; } + /** + * Converts a scopes query string to an array to easily iterate for validation. + * + * @param string $scopes + * + * @return array + */ + private function convertScopesQueryStringToArray($scopes) + { + return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) { + return !empty($scope); + }); + } + /** * Retrieve request parameter. * diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 441dfe48..16f56481 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -86,7 +86,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $this->validateAuthorizationCode($authCodePayload, $client, $request); $scopes = $this->scopeRepository->finalizeScopes( - $this->getScopes($authCodePayload), + $this->validateScopes($authCodePayload->scopes), $this->getIdentifier(), $client, $authCodePayload->user_id @@ -194,32 +194,6 @@ class AuthCodeGrant extends AbstractAuthorizeGrant } } - /** - * Get scopes from the auth code payload. - * - * @param \stdClass $authCodePayload - * - * @return array - */ - private function getScopes($authCodePayload) - { - $scopes = []; - - foreach ($authCodePayload->scopes as $scopeId) { - $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId); - - if ($scope instanceof ScopeEntityInterface === false) { - // @codeCoverageIgnoreStart - throw OAuthServerException::invalidScope($scopeId); - // @codeCoverageIgnoreEnd - } - - $scopes[] = $scope; - } - - return $scopes; - } - /** * Return the grant identifier that can be used in matching up requests. * From 793f65d3a308a8ffd7925c13b079119963420893 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 16:14:15 +0100 Subject: [PATCH 22/66] Remove unused scope entity interface --- src/Grant/AuthCodeGrant.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 16f56481..db2dee47 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -10,7 +10,6 @@ namespace League\OAuth2\Server\Grant; use League\OAuth2\Server\Entities\ClientEntityInterface; -use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; From 20b355b025be88558bb7a4b295b57343190e2e34 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 16:31:36 +0100 Subject: [PATCH 23/66] Re-order docblock throws --- src/Grant/AbstractGrant.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index b6b14cf0..432cd1a5 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -374,10 +374,9 @@ abstract class AbstractGrant implements GrantTypeInterface * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes * + * @return AccessTokenEntityInterface * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException - * - * @return AccessTokenEntityInterface */ protected function issueAccessToken( \DateInterval $accessTokenTTL, From f96fca3b482c9174fa92e8e17ef402d296d541d6 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 16:44:40 +0100 Subject: [PATCH 24/66] Minor code tidyup --- src/Grant/AbstractGrant.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 432cd1a5..5208d398 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -174,7 +174,7 @@ abstract class AbstractGrant implements GrantTypeInterface list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); - if (is_null($clientId)) { + if ($clientId === null) { throw OAuthServerException::invalidRequest('client_id'); } @@ -217,13 +217,13 @@ abstract class AbstractGrant implements GrantTypeInterface ClientEntityInterface $client, ServerRequestInterface $request ) { - if (is_string($client->getRedirectUri()) + if (\is_string($client->getRedirectUri()) && (strcmp($client->getRedirectUri(), $redirectUri) !== 0) ) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); throw OAuthServerException::invalidClient(); - } elseif (is_array($client->getRedirectUri()) - && in_array($redirectUri, $client->getRedirectUri(), true) === false + } 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(); @@ -233,16 +233,15 @@ abstract class AbstractGrant implements GrantTypeInterface /** * Validate scopes in the request. * - * @param string $scopes - * @param string $redirectUri - * - * @throws OAuthServerException + * @param string|array $scopes + * @param string $redirectUri * * @return ScopeEntityInterface[] + * @throws OAuthServerException */ public function validateScopes($scopes, $redirectUri = null) { - if (!is_array($scopes)) { + if (!\is_array($scopes)) { $scopes = $this->convertScopesQueryStringToArray($scopes); } @@ -288,7 +287,7 @@ abstract class AbstractGrant implements GrantTypeInterface { $requestParameters = (array) $request->getParsedBody(); - return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default; + return $requestParameters[$parameter] ?? $default; } /** @@ -494,9 +493,8 @@ abstract class AbstractGrant implements GrantTypeInterface * * @param int $length * - * @throws OAuthServerException - * * @return string + * @throws OAuthServerException */ protected function generateUniqueIdentifier($length = 40) { From c0efdf0dd071af16586259fb7a00661992588945 Mon Sep 17 00:00:00 2001 From: sephster Date: Sat, 13 Oct 2018 16:54:31 +0100 Subject: [PATCH 25/66] Revert changes to throws and returns ordering --- src/Grant/AbstractGrant.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 5208d398..9b5486c3 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -236,8 +236,9 @@ abstract class AbstractGrant implements GrantTypeInterface * @param string|array $scopes * @param string $redirectUri * - * @return ScopeEntityInterface[] * @throws OAuthServerException + * + * @return ScopeEntityInterface[] */ public function validateScopes($scopes, $redirectUri = null) { @@ -373,9 +374,10 @@ abstract class AbstractGrant implements GrantTypeInterface * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes * - * @return AccessTokenEntityInterface * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException + * + * @return AccessTokenEntityInterface */ protected function issueAccessToken( \DateInterval $accessTokenTTL, @@ -493,8 +495,9 @@ abstract class AbstractGrant implements GrantTypeInterface * * @param int $length * - * @return string * @throws OAuthServerException + * + * @return string */ protected function generateUniqueIdentifier($length = 40) { From d288a2ad8a574d9a1b641d529a79cbc617e03305 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 2 Nov 2018 15:38:07 +0100 Subject: [PATCH 26/66] Make AuthorizationServer stateless --- src/AuthorizationServer.php | 35 ++++++++--------- tests/AuthorizationServerTest.php | 63 +++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index f1e96146..a5ee1b02 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -49,9 +49,9 @@ class AuthorizationServer implements EmitterAwareInterface protected $publicKey; /** - * @var null|ResponseTypeInterface + * @var ResponseTypeInterface */ - protected $responseType; + protected $responseTypePrototype; /** * @var ClientRepositoryInterface @@ -86,7 +86,7 @@ class AuthorizationServer implements EmitterAwareInterface * @param ScopeRepositoryInterface $scopeRepository * @param CryptKey|string $privateKey * @param string|Key $encryptionKey - * @param null|ResponseTypeInterface $responseType + * @param null|ResponseTypeInterface $responseTypePrototype */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -94,7 +94,7 @@ class AuthorizationServer implements EmitterAwareInterface ScopeRepositoryInterface $scopeRepository, $privateKey, $encryptionKey, - ResponseTypeInterface $responseType = null + ResponseTypeInterface $responseTypePrototype = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; @@ -105,7 +105,17 @@ class AuthorizationServer implements EmitterAwareInterface } $this->privateKey = $privateKey; $this->encryptionKey = $encryptionKey; - $this->responseType = $responseType; + + if ($responseTypePrototype === null) { + $responseTypePrototype = new BearerTokenResponse(); + } else { + $responseTypePrototype = clone $responseTypePrototype; + } + if ($responseTypePrototype instanceof AbstractResponseType) { + $responseTypePrototype->setPrivateKey($this->privateKey); + } + $responseTypePrototype->setEncryptionKey($this->encryptionKey); + $this->responseTypePrototype = $responseTypePrototype; } /** @@ -185,7 +195,7 @@ class AuthorizationServer implements EmitterAwareInterface } $tokenResponse = $grantType->respondToAccessTokenRequest( $request, - $this->getResponseType(), + $this->newResponseType(), $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] ); @@ -202,18 +212,9 @@ class AuthorizationServer implements EmitterAwareInterface * * @return ResponseTypeInterface */ - protected function getResponseType() + protected function newResponseType() { - if ($this->responseType instanceof ResponseTypeInterface === false) { - $this->responseType = new BearerTokenResponse(); - } - - if ($this->responseType instanceof AbstractResponseType === true) { - $this->responseType->setPrivateKey($this->privateKey); - } - $this->responseType->setEncryptionKey($this->encryptionKey); - - return $this->responseType; + return clone $this->responseTypePrototype; } /** diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index b003c23f..9e485501 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -3,6 +3,7 @@ namespace LeagueTests; use League\OAuth2\Server\AuthorizationServer; +use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Grant\AuthCodeGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant; @@ -90,7 +91,7 @@ class AuthorizationServerTest extends TestCase $this->assertEquals(200, $response->getStatusCode()); } - public function testGetResponseType() + public function testNewDefaultResponseType() { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -103,10 +104,66 @@ class AuthorizationServerTest extends TestCase ); $abstractGrantReflection = new \ReflectionClass($server); - $method = $abstractGrantReflection->getMethod('getResponseType'); + $method = $abstractGrantReflection->getMethod('newResponseType'); $method->setAccessible(true); - $this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); + $responseTypeA = $method->invoke($server); + $responseTypeB = $method->invoke($server); + $this->assertInstanceOf(BearerTokenResponse::class, $responseTypeA); + $this->assertInstanceOf(BearerTokenResponse::class, $responseTypeB); + $this->assertNotSame($responseTypeA, $responseTypeB); + } + + public function testNewResponseTypeFromPrototype() + { + $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; + $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; + + $responseTypePrototype = new class extends BearerTokenResponse { + /* @return null|CryptKey */ + public function getPrivateKey() + { + return $this->privateKey; + } + + public function getEncryptionKey() + { + return $this->encryptionKey; + } + }; + + $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + + $server = new AuthorizationServer( + $clientRepository, + $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), + $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), + $privateKey, + $encryptionKey, + $responseTypePrototype + ); + + $abstractGrantReflection = new \ReflectionClass($server); + $method = $abstractGrantReflection->getMethod('newResponseType'); + $method->setAccessible(true); + + $responseTypeA = $method->invoke($server); + $responseTypeB = $method->invoke($server); + + // prototype should not get changed + $this->assertNull($responseTypePrototype->getPrivateKey()); + $this->assertNull($responseTypePrototype->getEncryptionKey()); + + // generated instances should have keys setup + $this->assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath()); + $this->assertSame($encryptionKey, $responseTypeA->getEncryptionKey()); + + // all instances should be different but based on the same prototype + $this->assertSame(get_class($responseTypePrototype), get_class($responseTypeA)); + $this->assertSame(get_class($responseTypePrototype), get_class($responseTypeB)); + $this->assertNotSame($responseTypePrototype, $responseTypeA); + $this->assertNotSame($responseTypePrototype, $responseTypeB); + $this->assertNotSame($responseTypeA, $responseTypeB); } public function testCompleteAuthorizationRequest() From 73698e28d944c1a414543ef076fbec1253e75b5c Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Nov 2018 21:38:31 +0000 Subject: [PATCH 27/66] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c21bab59..91ab6b30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added a ScopeTrait to provide an implementation for jsonSerialize (PR #952) +### Fixed +- Fix issue where AuthorizationServer is not stateless as ResponseType could store state of a previous request (PR #960) + ## [7.2.0] - released 2018-06-23 ### Changed From ac818bd9219c3dd84d069f5b67460fb3f8746843 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 6 Nov 2018 21:42:05 +0000 Subject: [PATCH 28/66] Minor formatting adjustment --- src/AuthorizationServer.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index a5ee1b02..416421ec 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -103,6 +103,7 @@ class AuthorizationServer implements EmitterAwareInterface if ($privateKey instanceof CryptKey === false) { $privateKey = new CryptKey($privateKey); } + $this->privateKey = $privateKey; $this->encryptionKey = $encryptionKey; @@ -111,10 +112,13 @@ class AuthorizationServer implements EmitterAwareInterface } else { $responseTypePrototype = clone $responseTypePrototype; } + if ($responseTypePrototype instanceof AbstractResponseType) { $responseTypePrototype->setPrivateKey($this->privateKey); } + $responseTypePrototype->setEncryptionKey($this->encryptionKey); + $this->responseTypePrototype = $responseTypePrototype; } From 34ec35019b2bec0cd2507c9ace83e268d5e58263 Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 8 Nov 2018 13:10:22 +0000 Subject: [PATCH 29/66] Remove additional whitespace --- src/AuthorizationServer.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 416421ec..df079b70 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -103,7 +103,7 @@ class AuthorizationServer implements EmitterAwareInterface if ($privateKey instanceof CryptKey === false) { $privateKey = new CryptKey($privateKey); } - + $this->privateKey = $privateKey; $this->encryptionKey = $encryptionKey; @@ -112,13 +112,13 @@ class AuthorizationServer implements EmitterAwareInterface } else { $responseTypePrototype = clone $responseTypePrototype; } - + if ($responseTypePrototype instanceof AbstractResponseType) { $responseTypePrototype->setPrivateKey($this->privateKey); } - + $responseTypePrototype->setEncryptionKey($this->encryptionKey); - + $this->responseTypePrototype = $responseTypePrototype; } From 3b983ad0b4786206e103d9337c3bf3b186d2e81c Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Mon, 12 Nov 2018 13:47:17 +0100 Subject: [PATCH 30/66] Include previous exception in catch and throw --- .../BearerTokenValidator.php | 6 ++-- src/CryptTrait.php | 4 +-- src/Exception/OAuthServerException.php | 29 ++++++++++++------- src/Grant/AbstractGrant.php | 6 ++-- src/Grant/AuthCodeGrant.php | 2 +- src/Grant/RefreshTokenGrant.php | 2 +- 6 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 2efa3c8e..80e69fdc 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -70,7 +70,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface throw OAuthServerException::accessDenied('Access token could not be verified'); } } catch (\BadMethodCallException $exception) { - throw OAuthServerException::accessDenied('Access token is not signed'); + throw OAuthServerException::accessDenied('Access token is not signed', null, $exception); } // Ensure access token hasn't expired @@ -94,10 +94,10 @@ class BearerTokenValidator implements AuthorizationValidatorInterface ->withAttribute('oauth_scopes', $token->getClaim('scopes')); } catch (\InvalidArgumentException $exception) { // JWT couldn't be parsed so return the request as is - throw OAuthServerException::accessDenied($exception->getMessage()); + throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception); } catch (\RuntimeException $exception) { //JWR couldn't be parsed so return the request as is - throw OAuthServerException::accessDenied('Error while decoding to JSON'); + throw OAuthServerException::accessDenied('Error while decoding to JSON', null, $exception); } } } diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 672c7e2e..db6c8d01 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -39,7 +39,7 @@ trait CryptTrait return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); } catch (\Exception $e) { - throw new \LogicException($e->getMessage()); + throw new \LogicException($e->getMessage(), null, $e); } } @@ -61,7 +61,7 @@ trait CryptTrait return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); } catch (\Exception $e) { - throw new \LogicException($e->getMessage()); + throw new \LogicException($e->getMessage(), null, $e); } } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 2c7bc28b..6bcfa3be 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -47,10 +47,11 @@ class OAuthServerException extends \Exception * @param int $httpStatusCode HTTP status code to send (default = 400) * @param null|string $hint A helper hint * @param null|string $redirectUri A HTTP URI to redirect the user back to + * @param \Throwable $previous Previous exception */ - public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null) + public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, \Throwable $previous = null) { - parent::__construct($message, $code); + parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; $this->errorType = $errorType; $this->hint = $hint; @@ -102,16 +103,17 @@ class OAuthServerException extends \Exception * * @param string $parameter The invalid parameter * @param null|string $hint + * @param \Throwable $previous Previous exception * * @return static */ - public static function invalidRequest($parameter, $hint = null) + public static function invalidRequest($parameter, $hint = null, \Throwable $previous = null) { $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' . 'includes a parameter more than once, or is otherwise malformed.'; $hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint; - return new static($errorMessage, 3, 'invalid_request', 400, $hint); + return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous); } /** @@ -163,20 +165,22 @@ class OAuthServerException extends \Exception /** * Server error. * - * @param string $hint + * @param string $hint + * @param \Throwable $previous * * @return static * * @codeCoverageIgnore */ - public static function serverError($hint) + public static function serverError($hint, \Throwable $previous = null) { return new static( 'The authorization server encountered an unexpected condition which prevented it from fulfilling' . ' the request: ' . $hint, 7, 'server_error', - 500 + 500, + $previous ); } @@ -184,12 +188,13 @@ class OAuthServerException extends \Exception * Invalid refresh token. * * @param null|string $hint + * @param \Throwable $previous * * @return static */ - public static function invalidRefreshToken($hint = null) + public static function invalidRefreshToken($hint = null, \Throwable $previous = null) { - return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint); + return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous); } /** @@ -197,10 +202,11 @@ class OAuthServerException extends \Exception * * @param null|string $hint * @param null|string $redirectUri + * @param \Throwable $previous * * @return static */ - public static function accessDenied($hint = null, $redirectUri = null) + public static function accessDenied($hint = null, $redirectUri = null, \Throwable $previous = null) { return new static( 'The resource owner or authorization server denied the request.', @@ -208,7 +214,8 @@ class OAuthServerException extends \Exception 'access_denied', 401, $hint, - $redirectUri + $redirectUri, + $previous ); } diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 9b5486c3..76c2f8e2 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -505,12 +505,12 @@ abstract class AbstractGrant implements GrantTypeInterface return bin2hex(random_bytes($length)); // @codeCoverageIgnoreStart } catch (\TypeError $e) { - throw OAuthServerException::serverError('An unexpected error has occurred'); + throw OAuthServerException::serverError('An unexpected error has occurred', $e); } catch (\Error $e) { - throw OAuthServerException::serverError('An unexpected error has occurred'); + throw OAuthServerException::serverError('An unexpected error has occurred', $e); } catch (\Exception $e) { // If you get this message, the CSPRNG failed hard. - throw OAuthServerException::serverError('Could not generate a random string'); + throw OAuthServerException::serverError('Could not generate a random string', $e); } // @codeCoverageIgnoreEnd } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index db2dee47..3d963d02 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -91,7 +91,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $authCodePayload->user_id ); } catch (\LogicException $e) { - throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code'); + throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); } // Validate code challenge diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 519954be..d48a9058 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -95,7 +95,7 @@ class RefreshTokenGrant extends AbstractGrant try { $refreshToken = $this->decrypt($encryptedRefreshToken); } catch (\Exception $e) { - throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token'); + throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e); } $refreshTokenData = json_decode($refreshToken, true); From 9542af627eed680df5bf1857758d898ee45ff7ee Mon Sep 17 00:00:00 2001 From: sephster Date: Mon, 12 Nov 2018 19:57:35 +0000 Subject: [PATCH 31/66] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ab6b30..7362fc74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Added a ScopeTrait to provide an implementation for jsonSerialize (PR #952) +- Ability to nest exceptions (PR #965) ### Fixed - Fix issue where AuthorizationServer is not stateless as ResponseType could store state of a previous request (PR #960) From 58689969617ce65d537c855fda2a9dde038656ce Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Tue, 13 Nov 2018 00:25:22 +0100 Subject: [PATCH 32/66] Add test for previous exceptions --- tests/Exception/OAuthServerExceptionTest.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Exception/OAuthServerExceptionTest.php b/tests/Exception/OAuthServerExceptionTest.php index 976362e3..201fb5dd 100644 --- a/tests/Exception/OAuthServerExceptionTest.php +++ b/tests/Exception/OAuthServerExceptionTest.php @@ -2,6 +2,7 @@ namespace LeagueTests\Exception; +use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use PHPUnit\Framework\TestCase; @@ -20,4 +21,19 @@ class OAuthServerExceptionTest extends TestCase $this->assertFalse($exceptionWithoutRedirect->hasRedirect()); } + + public function testHasPrevious() + { + $previous = new Exception('This is the previous'); + $exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $previous); + + $this->assertSame('This is the previous', $exceptionWithPrevious->getPrevious()->getMessage()); + } + + public function testDoesNotHavePrevious() + { + $exceptionWithoutPrevious = OAuthServerException::accessDenied(); + + $this->assertNull($exceptionWithoutPrevious->getPrevious()); + } } From 4bb5b747c15285e5e7fb3cea3dea8f718f74b018 Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Tue, 13 Nov 2018 01:00:23 +0100 Subject: [PATCH 33/66] Replace fqn with unqualified name --- .styleci.yml | 1 + src/AuthorizationServer.php | 11 +++--- .../BearerTokenValidator.php | 13 ++++--- src/CryptKey.php | 13 ++++--- src/CryptTrait.php | 14 ++++--- src/Entities/RefreshTokenEntityInterface.php | 8 ++-- src/Entities/ScopeEntityInterface.php | 4 +- src/Entities/TokenInterface.php | 8 ++-- src/Entities/Traits/AccessTokenTrait.php | 3 +- src/Entities/Traits/RefreshTokenTrait.php | 9 +++-- src/Entities/Traits/TokenEntityTrait.php | 9 +++-- src/Exception/OAuthServerException.php | 3 +- src/Grant/AbstractGrant.php | 38 +++++++++++-------- src/Grant/AuthCodeGrant.php | 27 +++++++------ src/Grant/ClientCredentialsGrant.php | 3 +- src/Grant/GrantTypeInterface.php | 9 +++-- src/Grant/ImplicitGrant.php | 33 ++++++++-------- src/Grant/PasswordGrant.php | 5 ++- src/Grant/RefreshTokenGrant.php | 8 ++-- .../AuthorizationServerMiddleware.php | 3 +- src/Middleware/ResourceServerMiddleware.php | 5 ++- src/ResponseTypes/AbstractResponseType.php | 2 +- src/ResponseTypes/BearerTokenResponse.php | 3 +- 23 files changed, 137 insertions(+), 95 deletions(-) diff --git a/.styleci.yml b/.styleci.yml index d3498157..a77e823b 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -4,6 +4,7 @@ enabled: - binary_operator_spaces - blank_line_before_return - concat_with_spaces + - fully_qualified_strict_types - function_typehint_space - hash_to_slash_comment - include diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index df079b70..35597de1 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server; +use DateInterval; use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareTrait; @@ -34,7 +35,7 @@ class AuthorizationServer implements EmitterAwareInterface protected $enabledGrantTypes = []; /** - * @var \DateInterval[] + * @var DateInterval[] */ protected $grantTypeAccessTokenTTL = []; @@ -126,12 +127,12 @@ class AuthorizationServer implements EmitterAwareInterface * Enable a grant type on the server. * * @param GrantTypeInterface $grantType - * @param null|\DateInterval $accessTokenTTL + * @param null|DateInterval $accessTokenTTL */ - public function enableGrantType(GrantTypeInterface $grantType, \DateInterval $accessTokenTTL = null) + public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null) { - if ($accessTokenTTL instanceof \DateInterval === false) { - $accessTokenTTL = new \DateInterval('PT1H'); + if ($accessTokenTTL instanceof DateInterval === false) { + $accessTokenTTL = new DateInterval('PT1H'); } $grantType->setAccessTokenRepository($this->accessTokenRepository); diff --git a/src/AuthorizationValidators/BearerTokenValidator.php b/src/AuthorizationValidators/BearerTokenValidator.php index 2efa3c8e..924c2fb5 100644 --- a/src/AuthorizationValidators/BearerTokenValidator.php +++ b/src/AuthorizationValidators/BearerTokenValidator.php @@ -9,6 +9,8 @@ namespace League\OAuth2\Server\AuthorizationValidators; +use BadMethodCallException; +use InvalidArgumentException; use Lcobucci\JWT\Parser; use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\ValidationData; @@ -17,6 +19,7 @@ use League\OAuth2\Server\CryptTrait; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use Psr\Http\Message\ServerRequestInterface; +use RuntimeException; class BearerTokenValidator implements AuthorizationValidatorInterface { @@ -28,7 +31,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface private $accessTokenRepository; /** - * @var \League\OAuth2\Server\CryptKey + * @var CryptKey */ protected $publicKey; @@ -43,7 +46,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface /** * Set the public key * - * @param \League\OAuth2\Server\CryptKey $key + * @param CryptKey $key */ public function setPublicKey(CryptKey $key) { @@ -69,7 +72,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { throw OAuthServerException::accessDenied('Access token could not be verified'); } - } catch (\BadMethodCallException $exception) { + } catch (BadMethodCallException $exception) { throw OAuthServerException::accessDenied('Access token is not signed'); } @@ -92,10 +95,10 @@ class BearerTokenValidator implements AuthorizationValidatorInterface ->withAttribute('oauth_client_id', $token->getClaim('aud')) ->withAttribute('oauth_user_id', $token->getClaim('sub')) ->withAttribute('oauth_scopes', $token->getClaim('scopes')); - } catch (\InvalidArgumentException $exception) { + } catch (InvalidArgumentException $exception) { // JWT couldn't be parsed so return the request as is throw OAuthServerException::accessDenied($exception->getMessage()); - } catch (\RuntimeException $exception) { + } catch (RuntimeException $exception) { //JWR couldn't be parsed so return the request as is throw OAuthServerException::accessDenied('Error while decoding to JSON'); } diff --git a/src/CryptKey.php b/src/CryptKey.php index 98b53222..6fc4dff0 100644 --- a/src/CryptKey.php +++ b/src/CryptKey.php @@ -11,6 +11,9 @@ namespace League\OAuth2\Server; +use LogicException; +use RuntimeException; + class CryptKey { const RSA_KEY_PATTERN = @@ -42,7 +45,7 @@ class CryptKey } if (!file_exists($keyPath) || !is_readable($keyPath)) { - throw new \LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath)); + throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } if ($keyPermissionsCheck === true) { @@ -64,7 +67,7 @@ class CryptKey /** * @param string $key * - * @throws \RuntimeException + * @throws RuntimeException * * @return string */ @@ -79,19 +82,19 @@ class CryptKey if (!touch($keyPath)) { // @codeCoverageIgnoreStart - throw new \RuntimeException(sprintf('"%s" key file could not be created', $keyPath)); + throw new RuntimeException(sprintf('"%s" key file could not be created', $keyPath)); // @codeCoverageIgnoreEnd } if (file_put_contents($keyPath, $key) === false) { // @codeCoverageIgnoreStart - throw new \RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir)); + throw new RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir)); // @codeCoverageIgnoreEnd } if (chmod($keyPath, 0600) === false) { // @codeCoverageIgnoreStart - throw new \RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath)); + throw new RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath)); // @codeCoverageIgnoreEnd } diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 672c7e2e..79b8dc55 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -13,6 +13,8 @@ namespace League\OAuth2\Server; use Defuse\Crypto\Crypto; use Defuse\Crypto\Key; +use Exception; +use LogicException; trait CryptTrait { @@ -26,7 +28,7 @@ trait CryptTrait * * @param string $unencryptedData * - * @throws \LogicException + * @throws LogicException * * @return string */ @@ -38,8 +40,8 @@ trait CryptTrait } return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); - } catch (\Exception $e) { - throw new \LogicException($e->getMessage()); + } catch (Exception $e) { + throw new LogicException($e->getMessage()); } } @@ -48,7 +50,7 @@ trait CryptTrait * * @param string $encryptedData * - * @throws \LogicException + * @throws LogicException * * @return string */ @@ -60,8 +62,8 @@ trait CryptTrait } return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); - } catch (\Exception $e) { - throw new \LogicException($e->getMessage()); + } catch (Exception $e) { + throw new LogicException($e->getMessage()); } } diff --git a/src/Entities/RefreshTokenEntityInterface.php b/src/Entities/RefreshTokenEntityInterface.php index e4f10400..c2898ae5 100644 --- a/src/Entities/RefreshTokenEntityInterface.php +++ b/src/Entities/RefreshTokenEntityInterface.php @@ -9,6 +9,8 @@ namespace League\OAuth2\Server\Entities; +use DateTime; + interface RefreshTokenEntityInterface { /** @@ -28,16 +30,16 @@ interface RefreshTokenEntityInterface /** * Get the token's expiry date time. * - * @return \DateTime + * @return DateTime */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * - * @param \DateTime $dateTime + * @param DateTime $dateTime */ - public function setExpiryDateTime(\DateTime $dateTime); + public function setExpiryDateTime(DateTime $dateTime); /** * Set the access token that the refresh token was associated with. diff --git a/src/Entities/ScopeEntityInterface.php b/src/Entities/ScopeEntityInterface.php index 34ef75f0..26748e0c 100644 --- a/src/Entities/ScopeEntityInterface.php +++ b/src/Entities/ScopeEntityInterface.php @@ -9,7 +9,9 @@ namespace League\OAuth2\Server\Entities; -interface ScopeEntityInterface extends \JsonSerializable +use JsonSerializable; + +interface ScopeEntityInterface extends JsonSerializable { /** * Get the scope's identifier. diff --git a/src/Entities/TokenInterface.php b/src/Entities/TokenInterface.php index 378adbdc..d2ca77d3 100644 --- a/src/Entities/TokenInterface.php +++ b/src/Entities/TokenInterface.php @@ -9,6 +9,8 @@ namespace League\OAuth2\Server\Entities; +use DateTime; + interface TokenInterface { /** @@ -28,16 +30,16 @@ interface TokenInterface /** * Get the token's expiry date time. * - * @return \DateTime + * @return DateTime */ public function getExpiryDateTime(); /** * Set the date time when the token expires. * - * @param \DateTime $dateTime + * @param DateTime $dateTime */ - public function setExpiryDateTime(\DateTime $dateTime); + public function setExpiryDateTime(DateTime $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 81fc1bfd..5fa4a9be 100644 --- a/src/Entities/Traits/AccessTokenTrait.php +++ b/src/Entities/Traits/AccessTokenTrait.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server\Entities\Traits; +use DateTime; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Rsa\Sha256; @@ -46,7 +47,7 @@ trait AccessTokenTrait abstract public function getClient(); /** - * @return \DateTime + * @return DateTime */ abstract public function getExpiryDateTime(); diff --git a/src/Entities/Traits/RefreshTokenTrait.php b/src/Entities/Traits/RefreshTokenTrait.php index fb9dbc68..0734daf1 100644 --- a/src/Entities/Traits/RefreshTokenTrait.php +++ b/src/Entities/Traits/RefreshTokenTrait.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server\Entities\Traits; +use DateTime; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; trait RefreshTokenTrait @@ -19,7 +20,7 @@ trait RefreshTokenTrait protected $accessToken; /** - * @var \DateTime + * @var DateTime */ protected $expiryDateTime; @@ -42,7 +43,7 @@ trait RefreshTokenTrait /** * Get the token's expiry date time. * - * @return \DateTime + * @return DateTime */ public function getExpiryDateTime() { @@ -52,9 +53,9 @@ trait RefreshTokenTrait /** * Set the date time when the token expires. * - * @param \DateTime $dateTime + * @param DateTime $dateTime */ - public function setExpiryDateTime(\DateTime $dateTime) + public function setExpiryDateTime(DateTime $dateTime) { $this->expiryDateTime = $dateTime; } diff --git a/src/Entities/Traits/TokenEntityTrait.php b/src/Entities/Traits/TokenEntityTrait.php index c6653cce..34159149 100644 --- a/src/Entities/Traits/TokenEntityTrait.php +++ b/src/Entities/Traits/TokenEntityTrait.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server\Entities\Traits; +use DateTime; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; @@ -20,7 +21,7 @@ trait TokenEntityTrait protected $scopes = []; /** - * @var \DateTime + * @var DateTime */ protected $expiryDateTime; @@ -57,7 +58,7 @@ trait TokenEntityTrait /** * Get the token's expiry date time. * - * @return \DateTime + * @return DateTime */ public function getExpiryDateTime() { @@ -67,9 +68,9 @@ trait TokenEntityTrait /** * Set the date time when the token expires. * - * @param \DateTime $dateTime + * @param DateTime $dateTime */ - public function setExpiryDateTime(\DateTime $dateTime) + public function setExpiryDateTime(DateTime $dateTime) { $this->expiryDateTime = $dateTime; } diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 2c7bc28b..41bb8975 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -9,9 +9,10 @@ namespace League\OAuth2\Server\Exception; +use Exception; use Psr\Http\Message\ResponseInterface; -class OAuthServerException extends \Exception +class OAuthServerException extends Exception { /** * @var int diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 9b5486c3..603c18d0 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -10,6 +10,10 @@ */ namespace League\OAuth2\Server\Grant; +use DateInterval; +use DateTime; +use Error; +use Exception; use League\Event\EmitterAwareTrait; use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptTrait; @@ -28,7 +32,9 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; +use LogicException; use Psr\Http\Message\ServerRequestInterface; +use TypeError; /** * Abstract grant class. @@ -72,12 +78,12 @@ abstract class AbstractGrant implements GrantTypeInterface protected $userRepository; /** - * @var \DateInterval + * @var DateInterval */ protected $refreshTokenTTL; /** - * @var \League\OAuth2\Server\CryptKey + * @var CryptKey */ protected $privateKey; @@ -137,7 +143,7 @@ abstract class AbstractGrant implements GrantTypeInterface /** * {@inheritdoc} */ - public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL) + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) { $this->refreshTokenTTL = $refreshTokenTTL; } @@ -145,7 +151,7 @@ abstract class AbstractGrant implements GrantTypeInterface /** * Set the private key * - * @param \League\OAuth2\Server\CryptKey $key + * @param CryptKey $key */ public function setPrivateKey(CryptKey $key) { @@ -369,7 +375,7 @@ abstract class AbstractGrant implements GrantTypeInterface /** * Issue an access token. * - * @param \DateInterval $accessTokenTTL + * @param DateInterval $accessTokenTTL * @param ClientEntityInterface $client * @param string|null $userIdentifier * @param ScopeEntityInterface[] $scopes @@ -380,7 +386,7 @@ abstract class AbstractGrant implements GrantTypeInterface * @return AccessTokenEntityInterface */ protected function issueAccessToken( - \DateInterval $accessTokenTTL, + DateInterval $accessTokenTTL, ClientEntityInterface $client, $userIdentifier, array $scopes = [] @@ -390,7 +396,7 @@ abstract class AbstractGrant implements GrantTypeInterface $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); $accessToken->setClient($client); $accessToken->setUserIdentifier($userIdentifier); - $accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL)); + $accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL)); foreach ($scopes as $scope) { $accessToken->addScope($scope); @@ -413,7 +419,7 @@ abstract class AbstractGrant implements GrantTypeInterface /** * Issue an auth code. * - * @param \DateInterval $authCodeTTL + * @param DateInterval $authCodeTTL * @param ClientEntityInterface $client * @param string $userIdentifier * @param string|null $redirectUri @@ -425,7 +431,7 @@ abstract class AbstractGrant implements GrantTypeInterface * @return AuthCodeEntityInterface */ protected function issueAuthCode( - \DateInterval $authCodeTTL, + DateInterval $authCodeTTL, ClientEntityInterface $client, $userIdentifier, $redirectUri, @@ -434,7 +440,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 DateTime())->add($authCodeTTL)); $authCode->setClient($client); $authCode->setUserIdentifier($userIdentifier); @@ -473,7 +479,7 @@ abstract class AbstractGrant implements GrantTypeInterface $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); - $refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL)); + $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL)); $refreshToken->setAccessToken($accessToken); while ($maxGenerationAttempts-- > 0) { @@ -504,11 +510,11 @@ abstract class AbstractGrant implements GrantTypeInterface try { return bin2hex(random_bytes($length)); // @codeCoverageIgnoreStart - } catch (\TypeError $e) { + } catch (TypeError $e) { throw OAuthServerException::serverError('An unexpected error has occurred'); - } catch (\Error $e) { + } catch (Error $e) { throw OAuthServerException::serverError('An unexpected error has occurred'); - } catch (\Exception $e) { + } catch (Exception $e) { // If you get this message, the CSPRNG failed hard. throw OAuthServerException::serverError('Could not generate a random string'); } @@ -541,7 +547,7 @@ abstract class AbstractGrant implements GrantTypeInterface */ public function validateAuthorizationRequest(ServerRequestInterface $request) { - throw new \LogicException('This grant cannot validate an authorization request'); + throw new LogicException('This grant cannot validate an authorization request'); } /** @@ -549,6 +555,6 @@ abstract class AbstractGrant implements GrantTypeInterface */ public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) { - throw new \LogicException('This grant cannot complete an authorization request'); + throw new LogicException('This grant cannot complete an authorization request'); } } diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index db2dee47..2dc3c6d7 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -9,6 +9,9 @@ namespace League\OAuth2\Server\Grant; +use DateInterval; +use DateTime; +use Exception; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -18,12 +21,14 @@ use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use LogicException; use Psr\Http\Message\ServerRequestInterface; +use stdClass; class AuthCodeGrant extends AbstractAuthorizeGrant { /** - * @var \DateInterval + * @var DateInterval */ private $authCodeTTL; @@ -35,19 +40,19 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * @param AuthCodeRepositoryInterface $authCodeRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository - * @param \DateInterval $authCodeTTL + * @param DateInterval $authCodeTTL * - * @throws \Exception + * @throws Exception */ public function __construct( AuthCodeRepositoryInterface $authCodeRepository, RefreshTokenRepositoryInterface $refreshTokenRepository, - \DateInterval $authCodeTTL + DateInterval $authCodeTTL ) { $this->setAuthCodeRepository($authCodeRepository); $this->setRefreshTokenRepository($refreshTokenRepository); $this->authCodeTTL = $authCodeTTL; - $this->refreshTokenTTL = new \DateInterval('P1M'); + $this->refreshTokenTTL = new DateInterval('P1M'); } public function enableCodeExchangeProof() @@ -60,7 +65,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant * * @param ServerRequestInterface $request * @param ResponseTypeInterface $responseType - * @param \DateInterval $accessTokenTTL + * @param DateInterval $accessTokenTTL * * @throws OAuthServerException * @@ -69,7 +74,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $accessTokenTTL + DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); @@ -90,7 +95,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant $client, $authCodePayload->user_id ); - } catch (\LogicException $e) { + } catch (LogicException $e) { throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code'); } @@ -161,7 +166,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant /** * Validate the authorization code. * - * @param \stdClass $authCodePayload + * @param stdClass $authCodePayload * @param ClientEntityInterface $client * @param ServerRequestInterface $request */ @@ -311,7 +316,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { - throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); + throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } $finalRedirectUri = $authorizationRequest->getRedirectUri() @@ -333,7 +338,7 @@ 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 DateTime())->add($this->authCodeTTL)->format('U'), 'code_challenge' => $authorizationRequest->getCodeChallenge(), 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), ]; diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index 026ce5e5..9f647965 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -11,6 +11,7 @@ namespace League\OAuth2\Server\Grant; +use DateInterval; use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,7 +27,7 @@ class ClientCredentialsGrant extends AbstractGrant public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $accessTokenTTL + DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 2aee367f..41ebeb5f 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -11,6 +11,7 @@ namespace League\OAuth2\Server\Grant; +use DateInterval; use Defuse\Crypto\Key; use League\Event\EmitterAwareInterface; use League\OAuth2\Server\CryptKey; @@ -29,9 +30,9 @@ interface GrantTypeInterface extends EmitterAwareInterface /** * Set refresh token TTL. * - * @param \DateInterval $refreshTokenTTL + * @param DateInterval $refreshTokenTTL */ - public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL); + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL); /** * Return the grant identifier that can be used in matching up requests. @@ -45,14 +46,14 @@ interface GrantTypeInterface extends EmitterAwareInterface * * @param ServerRequestInterface $request * @param ResponseTypeInterface $responseType - * @param \DateInterval $accessTokenTTL + * @param DateInterval $accessTokenTTL * * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $accessTokenTTL + DateInterval $accessTokenTTL ); /** diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index 1890a6ba..63a6c0b2 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -9,6 +9,8 @@ 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; @@ -17,12 +19,13 @@ use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; +use LogicException; use Psr\Http\Message\ServerRequestInterface; class ImplicitGrant extends AbstractAuthorizeGrant { /** - * @var \DateInterval + * @var DateInterval */ private $accessTokenTTL; @@ -32,33 +35,33 @@ class ImplicitGrant extends AbstractAuthorizeGrant private $queryDelimiter; /** - * @param \DateInterval $accessTokenTTL - * @param string $queryDelimiter + * @param DateInterval $accessTokenTTL + * @param string $queryDelimiter */ - public function __construct(\DateInterval $accessTokenTTL, $queryDelimiter = '#') + public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#') { $this->accessTokenTTL = $accessTokenTTL; $this->queryDelimiter = $queryDelimiter; } /** - * @param \DateInterval $refreshTokenTTL + * @param DateInterval $refreshTokenTTL * - * @throw \LogicException + * @throw LogicException */ - public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL) + public function setRefreshTokenTTL(DateInterval $refreshTokenTTL) { - throw new \LogicException('The Implicit Grant does not return refresh tokens'); + throw new LogicException('The Implicit Grant does not return refresh tokens'); } /** * @param RefreshTokenRepositoryInterface $refreshTokenRepository * - * @throw \LogicException + * @throw LogicException */ public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) { - throw new \LogicException('The Implicit Grant does not return refresh tokens'); + throw new LogicException('The Implicit Grant does not return refresh tokens'); } /** @@ -84,16 +87,16 @@ class ImplicitGrant extends AbstractAuthorizeGrant * * @param ServerRequestInterface $request * @param ResponseTypeInterface $responseType - * @param \DateInterval $accessTokenTTL + * @param DateInterval $accessTokenTTL * * @return ResponseTypeInterface */ public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $accessTokenTTL + DateInterval $accessTokenTTL ) { - throw new \LogicException('This grant does not used this method'); + throw new LogicException('This grant does not used this method'); } /** @@ -176,7 +179,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) { if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { - throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); + throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); } $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) @@ -209,7 +212,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant [ 'access_token' => (string) $accessToken->convertToJWT($this->privateKey), 'token_type' => 'Bearer', - 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(), + 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new DateTime())->getTimestamp(), 'state' => $authorizationRequest->getState(), ], $this->queryDelimiter diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 1d00998b..4b68ad44 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -11,6 +11,7 @@ namespace League\OAuth2\Server\Grant; +use DateInterval; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Exception\OAuthServerException; @@ -36,7 +37,7 @@ class PasswordGrant extends AbstractGrant $this->setUserRepository($userRepository); $this->setRefreshTokenRepository($refreshTokenRepository); - $this->refreshTokenTTL = new \DateInterval('P1M'); + $this->refreshTokenTTL = new DateInterval('P1M'); } /** @@ -45,7 +46,7 @@ class PasswordGrant extends AbstractGrant public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $accessTokenTTL + DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 519954be..2732e5b8 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -11,6 +11,8 @@ namespace League\OAuth2\Server\Grant; +use DateInterval; +use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\RequestEvent; @@ -29,7 +31,7 @@ class RefreshTokenGrant extends AbstractGrant { $this->setRefreshTokenRepository($refreshTokenRepository); - $this->refreshTokenTTL = new \DateInterval('P1M'); + $this->refreshTokenTTL = new DateInterval('P1M'); } /** @@ -38,7 +40,7 @@ class RefreshTokenGrant extends AbstractGrant public function respondToAccessTokenRequest( ServerRequestInterface $request, ResponseTypeInterface $responseType, - \DateInterval $accessTokenTTL + DateInterval $accessTokenTTL ) { // Validate request $client = $this->validateClient($request); @@ -94,7 +96,7 @@ class RefreshTokenGrant extends AbstractGrant // Validate refresh token try { $refreshToken = $this->decrypt($encryptedRefreshToken); - } catch (\Exception $e) { + } catch (Exception $e) { throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token'); } diff --git a/src/Middleware/AuthorizationServerMiddleware.php b/src/Middleware/AuthorizationServerMiddleware.php index a5f102fc..9b78b458 100644 --- a/src/Middleware/AuthorizationServerMiddleware.php +++ b/src/Middleware/AuthorizationServerMiddleware.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server\Middleware; +use Exception; use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\Exception\OAuthServerException; use Psr\Http\Message\ResponseInterface; @@ -43,7 +44,7 @@ class AuthorizationServerMiddleware } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); // @codeCoverageIgnoreStart - } catch (\Exception $exception) { + } catch (Exception $exception) { return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) ->generateHttpResponse($response); // @codeCoverageIgnoreEnd diff --git a/src/Middleware/ResourceServerMiddleware.php b/src/Middleware/ResourceServerMiddleware.php index 56d28aee..e152a999 100644 --- a/src/Middleware/ResourceServerMiddleware.php +++ b/src/Middleware/ResourceServerMiddleware.php @@ -9,6 +9,7 @@ namespace League\OAuth2\Server\Middleware; +use Exception; use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\ResourceServer; use Psr\Http\Message\ResponseInterface; @@ -34,7 +35,7 @@ class ResourceServerMiddleware * @param ResponseInterface $response * @param callable $next * - * @return \Psr\Http\Message\ResponseInterface + * @return ResponseInterface */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) { @@ -43,7 +44,7 @@ class ResourceServerMiddleware } catch (OAuthServerException $exception) { return $exception->generateHttpResponse($response); // @codeCoverageIgnoreStart - } catch (\Exception $exception) { + } catch (Exception $exception) { return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) ->generateHttpResponse($response); // @codeCoverageIgnoreEnd diff --git a/src/ResponseTypes/AbstractResponseType.php b/src/ResponseTypes/AbstractResponseType.php index d013bab0..192f52aa 100644 --- a/src/ResponseTypes/AbstractResponseType.php +++ b/src/ResponseTypes/AbstractResponseType.php @@ -54,7 +54,7 @@ abstract class AbstractResponseType implements ResponseTypeInterface /** * Set the private key * - * @param \League\OAuth2\Server\CryptKey $key + * @param CryptKey $key */ public function setPrivateKey(CryptKey $key) { diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index a57573a0..4f6e9b47 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -11,6 +11,7 @@ namespace League\OAuth2\Server\ResponseTypes; +use DateTime; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use Psr\Http\Message\ResponseInterface; @@ -28,7 +29,7 @@ class BearerTokenResponse extends AbstractResponseType $responseParams = [ 'token_type' => 'Bearer', - 'expires_in' => $expireDateTime - (new \DateTime())->getTimestamp(), + 'expires_in' => $expireDateTime - (new DateTime())->getTimestamp(), 'access_token' => (string) $jwtAccessToken, ]; From f6c1070ccca1d6b0f31dc43b078bb120d775809b Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 13 Nov 2018 12:32:52 +0000 Subject: [PATCH 34/66] Add use Throwable --- src/Exception/OAuthServerException.php | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index b4dc8f14..67de1293 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 Throwable; class OAuthServerException extends Exception { @@ -48,9 +49,9 @@ class OAuthServerException extends Exception * @param int $httpStatusCode HTTP status code to send (default = 400) * @param null|string $hint A helper hint * @param null|string $redirectUri A HTTP URI to redirect the user back to - * @param \Throwable $previous Previous exception + * @param Throwable $previous Previous exception */ - public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, \Throwable $previous = null) + public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->httpStatusCode = $httpStatusCode; @@ -104,11 +105,11 @@ class OAuthServerException extends Exception * * @param string $parameter The invalid parameter * @param null|string $hint - * @param \Throwable $previous Previous exception + * @param Throwable $previous Previous exception * * @return static */ - public static function invalidRequest($parameter, $hint = null, \Throwable $previous = null) + public static function invalidRequest($parameter, $hint = null, Throwable $previous = null) { $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' . 'includes a parameter more than once, or is otherwise malformed.'; @@ -166,14 +167,14 @@ class OAuthServerException extends Exception /** * Server error. * - * @param string $hint - * @param \Throwable $previous + * @param string $hint + * @param Throwable $previous * * @return static * * @codeCoverageIgnore */ - public static function serverError($hint, \Throwable $previous = null) + public static function serverError($hint, Throwable $previous = null) { return new static( 'The authorization server encountered an unexpected condition which prevented it from fulfilling' @@ -189,11 +190,11 @@ class OAuthServerException extends Exception * Invalid refresh token. * * @param null|string $hint - * @param \Throwable $previous + * @param Throwable $previous * * @return static */ - public static function invalidRefreshToken($hint = null, \Throwable $previous = null) + public static function invalidRefreshToken($hint = null, Throwable $previous = null) { return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous); } @@ -203,11 +204,11 @@ class OAuthServerException extends Exception * * @param null|string $hint * @param null|string $redirectUri - * @param \Throwable $previous + * @param Throwable $previous * * @return static */ - public static function accessDenied($hint = null, $redirectUri = null, \Throwable $previous = null) + public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null) { return new static( 'The resource owner or authorization server denied the request.', From 7982275757956f508ac2b21dbab5c9bc4afec9f9 Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 13 Nov 2018 12:34:16 +0000 Subject: [PATCH 35/66] Fix docblock alignment --- src/Exception/OAuthServerException.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 67de1293..ed5f9c78 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -105,7 +105,7 @@ class OAuthServerException extends Exception * * @param string $parameter The invalid parameter * @param null|string $hint - * @param Throwable $previous Previous exception + * @param Throwable $previous Previous exception * * @return static */ @@ -204,7 +204,7 @@ class OAuthServerException extends Exception * * @param null|string $hint * @param null|string $redirectUri - * @param Throwable $previous + * @param Throwable $previous * * @return static */ From 94e75ba6f3716e1dfd87a1e01cee4e91a4ef5ad5 Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 13 Nov 2018 12:56:06 +0000 Subject: [PATCH 36/66] Fix bug --- src/Exception/OAuthServerException.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index ed5f9c78..28cc20be 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -182,6 +182,8 @@ class OAuthServerException extends Exception 7, 'server_error', 500, + null, + null, $previous ); } From a61c6a318acc155769ae9afc693fd18d91475587 Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Tue, 13 Nov 2018 20:17:20 +0000 Subject: [PATCH 37/66] Update changelog for 7.3.0 release --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7362fc74..07a922b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [7.3.0] - released 2018-11-13 + ### Changed - Moved the `finalizeScopes()` call from `validateAuthorizationRequest` method to the `completeAuthorizationRequest` method so it is called just before the access token is issued (PR #923) @@ -420,7 +422,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...HEAD +[7.3.0]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...7.3.0 [7.2.0]: https://github.com/thephpleague/oauth2-server/compare/7.1.1...7.2.0 [7.1.1]: https://github.com/thephpleague/oauth2-server/compare/7.1.0...7.1.1 [7.1.0]: https://github.com/thephpleague/oauth2-server/compare/7.0.0...7.1.0 From 443d7c485a605ebb395ece49e5963666965ec36f Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 15 Nov 2018 22:22:08 +0000 Subject: [PATCH 38/66] Revert interface change so class can be extende --- src/AuthorizationServer.php | 26 +++++++++++++------------- tests/AuthorizationServerTest.php | 14 +++++--------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 35597de1..e96ce04f 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -52,7 +52,7 @@ class AuthorizationServer implements EmitterAwareInterface /** * @var ResponseTypeInterface */ - protected $responseTypePrototype; + protected $responseType; /** * @var ClientRepositoryInterface @@ -87,7 +87,7 @@ class AuthorizationServer implements EmitterAwareInterface * @param ScopeRepositoryInterface $scopeRepository * @param CryptKey|string $privateKey * @param string|Key $encryptionKey - * @param null|ResponseTypeInterface $responseTypePrototype + * @param null|ResponseTypeInterface $responseType */ public function __construct( ClientRepositoryInterface $clientRepository, @@ -95,7 +95,7 @@ class AuthorizationServer implements EmitterAwareInterface ScopeRepositoryInterface $scopeRepository, $privateKey, $encryptionKey, - ResponseTypeInterface $responseTypePrototype = null + ResponseTypeInterface $responseType = null ) { $this->clientRepository = $clientRepository; $this->accessTokenRepository = $accessTokenRepository; @@ -108,19 +108,19 @@ class AuthorizationServer implements EmitterAwareInterface $this->privateKey = $privateKey; $this->encryptionKey = $encryptionKey; - if ($responseTypePrototype === null) { - $responseTypePrototype = new BearerTokenResponse(); + if ($responseType === null) { + $responseType = new BearerTokenResponse(); } else { - $responseTypePrototype = clone $responseTypePrototype; + $responseType = clone $responseType; } - if ($responseTypePrototype instanceof AbstractResponseType) { - $responseTypePrototype->setPrivateKey($this->privateKey); + if ($responseType instanceof AbstractResponseType) { + $responseType->setPrivateKey($this->privateKey); } - $responseTypePrototype->setEncryptionKey($this->encryptionKey); + $responseType->setEncryptionKey($this->encryptionKey); - $this->responseTypePrototype = $responseTypePrototype; + $this->responseType = $responseType; } /** @@ -200,7 +200,7 @@ class AuthorizationServer implements EmitterAwareInterface } $tokenResponse = $grantType->respondToAccessTokenRequest( $request, - $this->newResponseType(), + $this->getResponseType(), $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] ); @@ -217,9 +217,9 @@ class AuthorizationServer implements EmitterAwareInterface * * @return ResponseTypeInterface */ - protected function newResponseType() + protected function getResponseType() { - return clone $this->responseTypePrototype; + return clone $this->responseType; } /** diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 9e485501..73f84a61 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -91,7 +91,7 @@ class AuthorizationServerTest extends TestCase $this->assertEquals(200, $response->getStatusCode()); } - public function testNewDefaultResponseType() + public function testGetResponseType() { $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); @@ -104,17 +104,13 @@ class AuthorizationServerTest extends TestCase ); $abstractGrantReflection = new \ReflectionClass($server); - $method = $abstractGrantReflection->getMethod('newResponseType'); + $method = $abstractGrantReflection->getMethod('getResponseType'); $method->setAccessible(true); - $responseTypeA = $method->invoke($server); - $responseTypeB = $method->invoke($server); - $this->assertInstanceOf(BearerTokenResponse::class, $responseTypeA); - $this->assertInstanceOf(BearerTokenResponse::class, $responseTypeB); - $this->assertNotSame($responseTypeA, $responseTypeB); + $this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); } - public function testNewResponseTypeFromPrototype() + public function testMultipleRequestsGetDifferentResponseTypeInstances() { $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; $encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key'; @@ -144,7 +140,7 @@ class AuthorizationServerTest extends TestCase ); $abstractGrantReflection = new \ReflectionClass($server); - $method = $abstractGrantReflection->getMethod('newResponseType'); + $method = $abstractGrantReflection->getMethod('getResponseType'); $method->setAccessible(true); $responseTypeA = $method->invoke($server); From 7839a61170b2307792c2d889ca17ee7bc34b456d Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 15 Nov 2018 22:33:34 +0000 Subject: [PATCH 39/66] Update changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07a922b6..bceb11f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [7.3.1] - released 2018-11-15 + +### Fixed +- Fix issue with previous release where interface had changed for the AuthorizationServer. Reverted to the previous interface while maintaining functionality changes (PR #970) + ## [7.3.0] - released 2018-11-13 ### Changed @@ -422,7 +427,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.1...HEAD +[7.3.1]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...7.3.1 [7.3.0]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...7.3.0 [7.2.0]: https://github.com/thephpleague/oauth2-server/compare/7.1.1...7.2.0 [7.1.1]: https://github.com/thephpleague/oauth2-server/compare/7.1.0...7.1.1 From f1454cde3648d96c478155d158568ca0ae1dd900 Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Thu, 15 Nov 2018 17:21:27 +0100 Subject: [PATCH 40/66] Fix bc breaking change --- src/AuthorizationServer.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index e96ce04f..ce684d1a 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -114,12 +114,6 @@ class AuthorizationServer implements EmitterAwareInterface $responseType = clone $responseType; } - if ($responseType instanceof AbstractResponseType) { - $responseType->setPrivateKey($this->privateKey); - } - - $responseType->setEncryptionKey($this->encryptionKey); - $this->responseType = $responseType; } @@ -219,7 +213,14 @@ class AuthorizationServer implements EmitterAwareInterface */ protected function getResponseType() { - return clone $this->responseType; + $responseType = clone $this->responseType; + if ($responseType instanceof AbstractResponseType) { + $responseType->setPrivateKey($this->privateKey); + } + + $responseType->setEncryptionKey($this->encryptionKey); + + return $responseType; } /** From b09154af33025d690bd15f9c5ba788568b1b056a Mon Sep 17 00:00:00 2001 From: Marc Ypes Date: Fri, 16 Nov 2018 12:57:37 +0100 Subject: [PATCH 41/66] Add test to prove bc break --- tests/AuthorizationServerTest.php | 37 +++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/AuthorizationServerTest.php b/tests/AuthorizationServerTest.php index 73f84a61..0a8bf6d1 100644 --- a/tests/AuthorizationServerTest.php +++ b/tests/AuthorizationServerTest.php @@ -110,6 +110,43 @@ class AuthorizationServerTest extends TestCase $this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); } + public function testGetResponseTypeExtended() + { + $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $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(); + } + }; + + $abstractGrantReflection = new \ReflectionClass($server); + $method = $abstractGrantReflection->getMethod('getResponseType'); + $method->setAccessible(true); + $responseType = $method->invoke($server); + + $this->assertInstanceOf(BearerTokenResponse::class, $responseType); + // generated instances should have keys setup + $this->assertSame($privateKey, $responseType->getPrivateKey()->getKeyPath()); + $this->assertSame($encryptionKey, $responseType->getEncryptionKey()); + } + public function testMultipleRequestsGetDifferentResponseTypeInstances() { $privateKey = 'file://' . __DIR__ . '/Stubs/private.key'; From 8b421818f2a7f00224a94f2fe67f0c86ac15268b Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 21 Nov 2018 21:26:54 +0000 Subject: [PATCH 42/66] Add blank line to better format --- src/AuthorizationServer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index ce684d1a..bde97d6e 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -214,6 +214,7 @@ class AuthorizationServer implements EmitterAwareInterface protected function getResponseType() { $responseType = clone $this->responseType; + if ($responseType instanceof AbstractResponseType) { $responseType->setPrivateKey($this->privateKey); } From 46493c461e00ee1a941c1f6baac4ce4be88403bb Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 21 Nov 2018 21:29:55 +0000 Subject: [PATCH 43/66] Update changelog for 7.3.2 release --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bceb11f6..f511374a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [7.3.2] - released 2018-11-21 + +- Revert setting keys on response type to be inside `getResponseType()` function instead of AuthorizationServer constructor (PR #969) + ## [7.3.1] - released 2018-11-15 ### Fixed @@ -427,7 +431,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.1...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.2...HEAD +[7.3.2]: https://github.com/thephpleague/oauth2-server/compare/7.3.1...7.3.2 [7.3.1]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...7.3.1 [7.3.0]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...7.3.0 [7.2.0]: https://github.com/thephpleague/oauth2-server/compare/7.1.1...7.2.0 From b71f382cd76e3f6905dfc53ef8148b3eebe1fd41 Mon Sep 17 00:00:00 2001 From: sephster Date: Wed, 21 Nov 2018 21:42:43 +0000 Subject: [PATCH 44/66] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f511374a..2b75288d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [7.3.2] - released 2018-11-21 +### Fixed - Revert setting keys on response type to be inside `getResponseType()` function instead of AuthorizationServer constructor (PR #969) ## [7.3.1] - released 2018-11-15 From 1e3a7adb19d8ac8ffcb6c2a15f42e38678bc02a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20Obradovi=C4=87?= Date: Wed, 28 Nov 2018 12:24:16 +0100 Subject: [PATCH 45/66] Add Symfony community integration to README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5d389591..3d341d39 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci * [Laravel Passport](https://github.com/laravel/passport) * [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server) * [OAuth 2 Server for Expressive](https://github.com/zendframework/zend-expressive-authentication-oauth2) +* [Trikoder OAuth 2 Bundle (Symfony)](https://github.com/trikoder/oauth2-bundle) ## Changelog From faa350792a6a80afa2394caecf100229d2892853 Mon Sep 17 00:00:00 2001 From: sephster Date: Thu, 6 Dec 2018 23:46:28 +0000 Subject: [PATCH 46/66] Add support for PHP 7.3 --- .travis.yml | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index f900228a..ae8f640c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ php: - 7.0 - 7.1 - 7.2 + - 7.3 install: - composer update --no-interaction --prefer-dist $DEPENDENCIES diff --git a/README.md b/README.md index 3d341d39..ced17ed8 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ 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. From fad42a88fdbc5da901061742d18e6459b4c170b6 Mon Sep 17 00:00:00 2001 From: Patrick Rodacker Date: Sun, 20 Jan 2019 22:11:22 +0100 Subject: [PATCH 47/66] removes unused local variable $scopeEntity from ImplicitGrantTest --- tests/Grant/ImplicitGrantTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Grant/ImplicitGrantTest.php b/tests/Grant/ImplicitGrantTest.php index 257ea16d..8cfcfff5 100644 --- a/tests/Grant/ImplicitGrantTest.php +++ b/tests/Grant/ImplicitGrantTest.php @@ -285,7 +285,6 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); @@ -313,7 +312,6 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); @@ -339,7 +337,6 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); @@ -368,7 +365,6 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened')); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); @@ -397,7 +393,6 @@ class ImplicitGrantTest extends TestCase $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); - $scopeEntity = new ScopeEntity(); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); $grant = new ImplicitGrant(new \DateInterval('PT10M')); From b2840474fde964ca55772f84300fafc20e104ed0 Mon Sep 17 00:00:00 2001 From: filecage Date: Fri, 8 Mar 2019 18:16:16 +0100 Subject: [PATCH 48/66] AbstractGrant no longer tries to issue a refresh token if the Repository returned `null` --- src/Grant/AbstractGrant.php | 9 ++++++--- tests/Grant/AbstractGrantTest.php | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 49213331..45269ac7 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -472,16 +472,19 @@ abstract class AbstractGrant implements GrantTypeInterface * @throws OAuthServerException * @throws UniqueTokenIdentifierConstraintViolationException * - * @return RefreshTokenEntityInterface + * @return RefreshTokenEntityInterface|null */ protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) { - $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; - $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); + if ($refreshToken === null) { + return null; + } + $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL)); $refreshToken->setAccessToken($accessToken); + $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; while ($maxGenerationAttempts-- > 0) { $refreshToken->setIdentifier($this->generateUniqueIdentifier()); try { diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 6266df0a..1ed4a935 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -18,6 +18,7 @@ use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -346,6 +347,28 @@ class AbstractGrantTest extends TestCase $this->assertEquals($accessToken, $refreshToken->getAccessToken()); } + public function testIssueNullRefreshToken() + { + /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepoMock */ + $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepoMock + ->expects($this->once()) + ->method('getNewRefreshToken') + ->willReturn(null); + + /** @var AbstractGrant $grantMock */ + $grantMock = $this->getMockForAbstractClass(AbstractGrant::class); + $grantMock->setRefreshTokenTTL(new \DateInterval('PT1M')); + $grantMock->setRefreshTokenRepository($refreshTokenRepoMock); + + $abstractGrantReflection = new \ReflectionClass($grantMock); + $issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken'); + $issueRefreshTokenMethod->setAccessible(true); + + $accessToken = new AccessTokenEntity(); + $this->assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken)); + } + public function testIssueAccessToken() { $accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); From 2ea76ca4fdd6f41393d5185208afb4bddea31146 Mon Sep 17 00:00:00 2001 From: filecage Date: Fri, 8 Mar 2019 18:19:16 +0100 Subject: [PATCH 49/66] Adds handling for `null` issued refresh token to Grant implementations --- src/Grant/AuthCodeGrant.php | 16 +++---- src/Grant/PasswordGrant.php | 21 ++++---- src/Grant/RefreshTokenGrant.php | 16 +++---- tests/Grant/AuthCodeGrantTest.php | 69 +++++++++++++++++++++++++++ tests/Grant/PasswordGrantTest.php | 52 ++++++++++++++++++++ tests/Grant/RefreshTokenGrantTest.php | 62 ++++++++++++++++++++++++ 6 files changed, 211 insertions(+), 25 deletions(-) diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 235a6a96..d7dd6b8f 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -145,17 +145,17 @@ class AuthCodeGrant extends AbstractAuthorizeGrant } } - // Issue and persist access + refresh tokens + // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); - $refreshToken = $this->issueRefreshToken($accessToken); - - // Send events to emitter $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); - - // Inject tokens into response type $responseType->setAccessToken($accessToken); - $responseType->setRefreshToken($refreshToken); + + // Issue and persist new refresh token if given + $refreshToken = $this->issueRefreshToken($accessToken); + if ($refreshToken !== null) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $responseType->setRefreshToken($refreshToken); + } // Revoke used auth code $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 4b68ad44..6dc08958 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -35,7 +35,10 @@ class PasswordGrant extends AbstractGrant RefreshTokenRepositoryInterface $refreshTokenRepository ) { $this->setUserRepository($userRepository); - $this->setRefreshTokenRepository($refreshTokenRepository); + + if ($refreshTokenRepository !== null) { + $this->setRefreshTokenRepository($refreshTokenRepository); + } $this->refreshTokenTTL = new DateInterval('P1M'); } @@ -56,17 +59,17 @@ class PasswordGrant extends AbstractGrant // Finalize the requested scopes $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier()); - // Issue and persist new tokens + // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); - $refreshToken = $this->issueRefreshToken($accessToken); - - // Send events to emitter $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); - - // Inject tokens into response $responseType->setAccessToken($accessToken); - $responseType->setRefreshToken($refreshToken); + + // Issue and persist new refresh token if given + $refreshToken = $this->issueRefreshToken($accessToken); + if ($refreshToken !== null) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $responseType->setRefreshToken($refreshToken); + } return $responseType; } diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 2181af88..24932441 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -63,17 +63,17 @@ class RefreshTokenGrant extends AbstractGrant $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); $this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']); - // Issue and persist new tokens + // Issue and persist new access token $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes); - $refreshToken = $this->issueRefreshToken($accessToken); - - // Send events to emitter $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); - $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); - - // Inject tokens into response $responseType->setAccessToken($accessToken); - $responseType->setRefreshToken($refreshToken); + + // Issue and persist new refresh token if given + $refreshToken = $this->issueRefreshToken($accessToken); + if ($refreshToken !== null) { + $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); + $responseType->setRefreshToken($refreshToken); + } return $responseType; } diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 6a319234..7b81f9fb 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -22,6 +22,7 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -644,6 +645,74 @@ class AuthCodeGrantTest extends TestCase $this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); } + public function testRespondToAccessTokenRequestNullRefreshToken() + { + $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(); + + /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepositoryMock */ + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); + + $grant = new AuthCodeGrant( + $this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(), + $refreshTokenRepositoryMock, + new \DateInterval('PT10M') + ); + + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + + $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->assertNull($response->getRefreshToken()); + } + public function testRespondToAccessTokenRequestCodeChallengePlain() { $client = new ClientEntity(); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 2ee700f8..e3f537ae 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -16,6 +16,8 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; +use OAuth2ServerExamples\Repositories\AccessTokenRepository; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -78,6 +80,56 @@ class PasswordGrantTest extends TestCase $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } + public function testRespondToRequestNullRefreshToken () + { + /** @var ClientRepositoryInterface|MockObject $clientRepositoryMock */ + $client = new ClientEntity(); + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + /** @var AccessTokenRepositoryInterface|MockObject $accessTokenRepositoryMock */ + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); + + /** @var UserRepositoryInterface|MockObject $userRepositoryMock */ + $userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock(); + $userEntity = new UserEntity(); + $userRepositoryMock->method('getUserEntityByUserCredentials')->willReturn($userEntity); + + /** @var ScopeRepositoryInterface|MockObject $scopeRepositoryMock */ + $scope = new ScopeEntity(); + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); + $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); + + /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepositoryMock */ + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); + + $grant = new PasswordGrant($userRepositoryMock, $refreshTokenRepositoryMock); + $grant->setClientRepository($clientRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setDefaultScope(self::DEFAULT_SCOPE); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withParsedBody( + [ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'username' => 'foo', + 'password' => 'bar', + ] + ); + + $responseType = new StubResponseType(); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); + $this->assertNull($responseType->getRefreshToken()); + } + /** * @expectedException \League\OAuth2\Server\Exception\OAuthServerException */ diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 89598115..fc7a2c26 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -16,6 +16,7 @@ use LeagueTests\Stubs\CryptTraitStub; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -94,6 +95,67 @@ class RefreshTokenGrantTest extends TestCase $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } + public function testRespondToRequestNullRefreshToken() + { + $client = new ClientEntity(); + $client->setIdentifier('foo'); + + /** @var ClientRepositoryInterface|MockObject $clientRepositoryMock */ + $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); + $clientRepositoryMock->method('getClientEntity')->willReturn($client); + + $scopeEntity = new ScopeEntity(); + $scopeEntity->setIdentifier('foo'); + + /** @var ScopeRepositoryInterface|MockObject $scopeRepositoryMock */ + $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); + $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); + + /** @var AccessTokenRepositoryInterface|MockObject $accessTokenRepositoryMock */ + $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); + $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); + $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); + + /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepositoryMock */ + $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); + $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); + $refreshTokenRepositoryMock->expects($this->never())->method('persistNewRefreshToken'); + + $grant = new RefreshTokenGrant($refreshTokenRepositoryMock); + $grant->setClientRepository($clientRepositoryMock); + $grant->setScopeRepository($scopeRepositoryMock); + $grant->setAccessTokenRepository($accessTokenRepositoryMock); + $grant->setEncryptionKey($this->cryptStub->getKey()); + $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); + + $oldRefreshToken = $this->cryptStub->doEncrypt( + json_encode( + [ + 'client_id' => 'foo', + 'refresh_token_id' => 'zyxwvu', + 'access_token_id' => 'abcdef', + 'scopes' => ['foo'], + 'user_id' => 123, + 'expire_time' => time() + 3600, + ] + ) + ); + + $serverRequest = new ServerRequest(); + $serverRequest = $serverRequest->withParsedBody([ + 'client_id' => 'foo', + 'client_secret' => 'bar', + 'refresh_token' => $oldRefreshToken, + 'scopes' => ['foo'], + ]); + + $responseType = new StubResponseType(); + $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); + + $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); + $this->assertNull($responseType->getRefreshToken()); + } + public function testRespondToReducedScopes() { $client = new ClientEntity(); From 66d4ce6de80a570194e67cc13a42cef07a5c7641 Mon Sep 17 00:00:00 2001 From: filecage Date: Fri, 8 Mar 2019 18:21:55 +0100 Subject: [PATCH 50/66] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b75288d..c839d29f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Changed +- Refresh Tokens are now optional (#649) + ## [7.3.2] - released 2018-11-21 ### Fixed From aa5bbe5f06a1455ac5f4c2a6d515fb3a8ab4b3a0 Mon Sep 17 00:00:00 2001 From: filecage Date: Mon, 11 Mar 2019 23:26:35 +0100 Subject: [PATCH 51/66] boyscout: style CI tweaks --- src/Grant/AbstractGrant.php | 2 +- tests/Grant/PasswordGrantTest.php | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 45269ac7..90c75d51 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -478,7 +478,7 @@ abstract class AbstractGrant implements GrantTypeInterface { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); if ($refreshToken === null) { - return null; + return; } $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL)); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index e3f537ae..5f8b7e0c 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -16,7 +16,6 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; -use OAuth2ServerExamples\Repositories\AccessTokenRepository; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; @@ -80,7 +79,7 @@ class PasswordGrantTest extends TestCase $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); } - public function testRespondToRequestNullRefreshToken () + public function testRespondToRequestNullRefreshToken() { /** @var ClientRepositoryInterface|MockObject $clientRepositoryMock */ $client = new ClientEntity(); From ebf78132d759014df1c4f492bf65e9c16895e3c5 Mon Sep 17 00:00:00 2001 From: filecage Date: Mon, 11 Mar 2019 23:28:20 +0100 Subject: [PATCH 52/66] `refreshTokenRepository` parameter can not be null, condition is obsolete --- src/Grant/PasswordGrant.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 6dc08958..124bb4f8 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -35,10 +35,7 @@ class PasswordGrant extends AbstractGrant RefreshTokenRepositoryInterface $refreshTokenRepository ) { $this->setUserRepository($userRepository); - - if ($refreshTokenRepository !== null) { - $this->setRefreshTokenRepository($refreshTokenRepository); - } + $this->setRefreshTokenRepository($refreshTokenRepository); $this->refreshTokenTTL = new DateInterval('P1M'); } From 64f0d89fadc80b409a7a106eb7f948d32d24f070 Mon Sep 17 00:00:00 2001 From: filecage Date: Mon, 11 Mar 2019 23:28:47 +0100 Subject: [PATCH 53/66] `getNewRefreshToken()` can also return `NULL` --- src/Repositories/RefreshTokenRepositoryInterface.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Repositories/RefreshTokenRepositoryInterface.php b/src/Repositories/RefreshTokenRepositoryInterface.php index 0c0697bf..a769cf6d 100644 --- a/src/Repositories/RefreshTokenRepositoryInterface.php +++ b/src/Repositories/RefreshTokenRepositoryInterface.php @@ -20,7 +20,7 @@ interface RefreshTokenRepositoryInterface extends RepositoryInterface /** * Creates a new refresh token * - * @return RefreshTokenEntityInterface + * @return RefreshTokenEntityInterface|null */ public function getNewRefreshToken(); From 0742d5150cabc760a052473843d773e9fc472051 Mon Sep 17 00:00:00 2001 From: filecage Date: Wed, 13 Mar 2019 10:08:57 +0100 Subject: [PATCH 54/66] explicit is better than implicit :) --- src/Grant/AbstractGrant.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 90c75d51..45269ac7 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -478,7 +478,7 @@ abstract class AbstractGrant implements GrantTypeInterface { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); if ($refreshToken === null) { - return; + return null; } $refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL)); From 6f6820f629c26b09e7c2cff41feb23d2505d027e Mon Sep 17 00:00:00 2001 From: filecage Date: Sat, 16 Mar 2019 13:12:34 +0100 Subject: [PATCH 55/66] removes @var hints the @var hints make PHP stan fail together with PHPUnit 6.3 --- tests/Grant/AbstractGrantTest.php | 1 - tests/Grant/AuthCodeGrantTest.php | 1 - tests/Grant/PasswordGrantTest.php | 5 ----- tests/Grant/RefreshTokenGrantTest.php | 4 ---- 4 files changed, 11 deletions(-) diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index 1ed4a935..acb519bb 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -349,7 +349,6 @@ class AbstractGrantTest extends TestCase public function testIssueNullRefreshToken() { - /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepoMock */ $refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepoMock ->expects($this->once()) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 7b81f9fb..0abea0c9 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -662,7 +662,6 @@ class AuthCodeGrantTest extends TestCase $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); - /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepositoryMock */ $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 5f8b7e0c..5a9fad0b 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -81,28 +81,23 @@ class PasswordGrantTest extends TestCase public function testRespondToRequestNullRefreshToken() { - /** @var ClientRepositoryInterface|MockObject $clientRepositoryMock */ $client = new ClientEntity(); $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); - /** @var AccessTokenRepositoryInterface|MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); - /** @var UserRepositoryInterface|MockObject $userRepositoryMock */ $userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock(); $userEntity = new UserEntity(); $userRepositoryMock->method('getUserEntityByUserCredentials')->willReturn($userEntity); - /** @var ScopeRepositoryInterface|MockObject $scopeRepositoryMock */ $scope = new ScopeEntity(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope); $scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0); - /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepositoryMock */ $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index fc7a2c26..6743e270 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -100,23 +100,19 @@ class RefreshTokenGrantTest extends TestCase $client = new ClientEntity(); $client->setIdentifier('foo'); - /** @var ClientRepositoryInterface|MockObject $clientRepositoryMock */ $clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepositoryMock->method('getClientEntity')->willReturn($client); $scopeEntity = new ScopeEntity(); $scopeEntity->setIdentifier('foo'); - /** @var ScopeRepositoryInterface|MockObject $scopeRepositoryMock */ $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); - /** @var AccessTokenRepositoryInterface|MockObject $accessTokenRepositoryMock */ $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf(); - /** @var RefreshTokenRepositoryInterface|MockObject $refreshTokenRepositoryMock */ $refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(); $refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null); $refreshTokenRepositoryMock->expects($this->never())->method('persistNewRefreshToken'); From 8cf39fd9cd44c1b6d9c8b98b605cbbf6718ae530 Mon Sep 17 00:00:00 2001 From: filecage Date: Sat, 16 Mar 2019 13:15:38 +0100 Subject: [PATCH 56/66] applies style CI diff --- tests/Grant/AbstractGrantTest.php | 1 - tests/Grant/AuthCodeGrantTest.php | 1 - tests/Grant/PasswordGrantTest.php | 1 - tests/Grant/RefreshTokenGrantTest.php | 1 - 4 files changed, 4 deletions(-) diff --git a/tests/Grant/AbstractGrantTest.php b/tests/Grant/AbstractGrantTest.php index acb519bb..75d3bcae 100644 --- a/tests/Grant/AbstractGrantTest.php +++ b/tests/Grant/AbstractGrantTest.php @@ -18,7 +18,6 @@ use LeagueTests\Stubs\AuthCodeEntity; use LeagueTests\Stubs\ClientEntity; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 0abea0c9..04fb60c2 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -22,7 +22,6 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index 5a9fad0b..28183a98 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -16,7 +16,6 @@ use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; use LeagueTests\Stubs\UserEntity; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; diff --git a/tests/Grant/RefreshTokenGrantTest.php b/tests/Grant/RefreshTokenGrantTest.php index 6743e270..8f205361 100644 --- a/tests/Grant/RefreshTokenGrantTest.php +++ b/tests/Grant/RefreshTokenGrantTest.php @@ -16,7 +16,6 @@ use LeagueTests\Stubs\CryptTraitStub; use LeagueTests\Stubs\RefreshTokenEntity; use LeagueTests\Stubs\ScopeEntity; use LeagueTests\Stubs\StubResponseType; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Zend\Diactoros\ServerRequest; From b88198a9a4d0f58eaf62918564973d75f8ddb2d2 Mon Sep 17 00:00:00 2001 From: Marc Bennewitz Date: Fri, 29 Mar 2019 16:00:26 +0100 Subject: [PATCH 57/66] spec compliant 'error_description' but keep 'message' for BC --- src/Exception/OAuthServerException.php | 14 +++++++++++--- .../AuthorizationServerMiddlewareTest.php | 4 ++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Exception/OAuthServerException.php b/src/Exception/OAuthServerException.php index 28cc20be..bb448767 100644 --- a/src/Exception/OAuthServerException.php +++ b/src/Exception/OAuthServerException.php @@ -59,8 +59,8 @@ class OAuthServerException extends Exception $this->hint = $hint; $this->redirectUri = $redirectUri; $this->payload = [ - 'error' => $errorType, - 'message' => $message, + 'error' => $errorType, + 'error_description' => $message, ]; if ($hint !== null) { $this->payload['hint'] = $hint; @@ -74,7 +74,15 @@ class OAuthServerException extends Exception */ public function getPayload() { - return $this->payload; + $payload = $this->payload; + + // The "message" property is deprecated and replaced by "error_description" + // TODO: remove "message" property + if (isset($payload['error_description']) && !isset($payload['message'])) { + $payload['message'] = $payload['error_description']; + } + + return $payload; } /** diff --git a/tests/Middleware/AuthorizationServerMiddlewareTest.php b/tests/Middleware/AuthorizationServerMiddlewareTest.php index 99118736..fb11c483 100644 --- a/tests/Middleware/AuthorizationServerMiddlewareTest.php +++ b/tests/Middleware/AuthorizationServerMiddlewareTest.php @@ -104,7 +104,7 @@ class AuthorizationServerMiddlewareTest extends TestCase $response = $exception->generateHttpResponse(new Response()); $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals('http://foo/bar?error=invalid_scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope', + $this->assertEquals('http://foo/bar?error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed', $response->getHeader('location')[0]); } @@ -114,7 +114,7 @@ class AuthorizationServerMiddlewareTest extends TestCase $response = $exception->generateHttpResponse(new Response(), true); $this->assertEquals(302, $response->getStatusCode()); - $this->assertEquals('http://foo/bar#error=invalid_scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope', + $this->assertEquals('http://foo/bar#error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed', $response->getHeader('location')[0]); } } From 0a78236f171dc87e2f21b219b0a753b4887549ff Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 29 Mar 2019 18:18:35 +0000 Subject: [PATCH 58/66] Update changelog for version 7.3.3 --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b75288d..fac066b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [7.3.3] - released 2019-03-29 +### Added +- Added `error_description` to the error payload to improve standards compliance. The contents of this are copied from the existing `message` value. (PR #1006) + +### Deprecated +- Error payload will not issue `message` value in the next major release (PR #1006) + ## [7.3.2] - released 2018-11-21 ### Fixed From c7f499849704ebe2c60b45b6d6bb231df5601d4a Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Fri, 29 Mar 2019 18:19:35 +0000 Subject: [PATCH 59/66] Update links --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fac066b5..cefe5953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -439,7 +439,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.2...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.3...HEAD +[7.3.3]: https://github.com/thephpleague/oauth2-server/compare/7.3.2...7.3.3 [7.3.2]: https://github.com/thephpleague/oauth2-server/compare/7.3.1...7.3.2 [7.3.1]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...7.3.1 [7.3.0]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...7.3.0 From 9bc7f6c8c540ae24961aee9909757de68c014036 Mon Sep 17 00:00:00 2001 From: filecage Date: Mon, 29 Apr 2019 19:13:26 +0200 Subject: [PATCH 60/66] removing simplified_null_return --- .styleci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.styleci.yml b/.styleci.yml index a77e823b..41f0dd0a 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -41,7 +41,6 @@ enabled: - print_to_echo - short_array_syntax - short_scalar_cast - - simplified_null_return - single_quote - spaces_cast - standardize_not_equal From 9236e842d9d47510653f5269b61a139bb292351b Mon Sep 17 00:00:00 2001 From: sephster Date: Sun, 5 May 2019 08:58:34 +0100 Subject: [PATCH 61/66] Clarify changelog message --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc6905c..b1539ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] ### Changed -- Refresh Tokens are now optional (#649) +- RefreshTokenRepository can now return null, allowing refresh tokens to be optional. (PR #649) ## [7.3.3] - released 2019-03-29 ### Added From 86869eafbb442ed9423850ffdd1bf366c9d613e1 Mon Sep 17 00:00:00 2001 From: sephster Date: Sun, 5 May 2019 09:03:13 +0100 Subject: [PATCH 62/66] Add whitespace around control blocks --- src/Grant/AbstractGrant.php | 2 ++ src/Grant/AuthCodeGrant.php | 1 + src/Grant/PasswordGrant.php | 1 + src/Grant/RefreshTokenGrant.php | 1 + 4 files changed, 5 insertions(+) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index 45269ac7..6d7ff18d 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -477,6 +477,7 @@ abstract class AbstractGrant implements GrantTypeInterface protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); + if ($refreshToken === null) { return null; } @@ -485,6 +486,7 @@ abstract class AbstractGrant implements GrantTypeInterface $refreshToken->setAccessToken($accessToken); $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; + while ($maxGenerationAttempts-- > 0) { $refreshToken->setIdentifier($this->generateUniqueIdentifier()); try { diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index d7dd6b8f..350fdde5 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -152,6 +152,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); + if ($refreshToken !== null) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); $responseType->setRefreshToken($refreshToken); diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index 124bb4f8..cb53ccd8 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -63,6 +63,7 @@ class PasswordGrant extends AbstractGrant // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); + if ($refreshToken !== null) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); $responseType->setRefreshToken($refreshToken); diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index 24932441..d0794075 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -70,6 +70,7 @@ class RefreshTokenGrant extends AbstractGrant // Issue and persist new refresh token if given $refreshToken = $this->issueRefreshToken($accessToken); + if ($refreshToken !== null) { $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); $responseType->setRefreshToken($refreshToken); From 2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf Mon Sep 17 00:00:00 2001 From: Andrew Millington Date: Sun, 5 May 2019 10:22:01 +0100 Subject: [PATCH 63/66] Update changelog for version 7.4.0 --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1539ef8..e0721bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [7.4.0] - released 2019-05-05 + ### Changed - RefreshTokenRepository can now return null, allowing refresh tokens to be optional. (PR #649) ## [7.3.3] - released 2019-03-29 + ### Added - Added `error_description` to the error payload to improve standards compliance. The contents of this are copied from the existing `message` value. (PR #1006) @@ -441,7 +445,8 @@ Version 5 is a complete code rewrite. - First major release -[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.3.3...HEAD +[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.4.0...HEAD +[7.4.0]: https://github.com/thephpleague/oauth2-server/compare/7.3.3...7.4.0 [7.3.3]: https://github.com/thephpleague/oauth2-server/compare/7.3.2...7.3.3 [7.3.2]: https://github.com/thephpleague/oauth2-server/compare/7.3.1...7.3.2 [7.3.1]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...7.3.1 From 86d1581cd965973665ab2391bfee45d3adfb0732 Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 14 May 2019 15:57:13 +0100 Subject: [PATCH 64/66] Remove unused imports --- src/Grant/ImplicitGrant.php | 1 - src/ResponseTypes/BearerTokenResponse.php | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Grant/ImplicitGrant.php b/src/Grant/ImplicitGrant.php index c84a7c4d..dd6ef406 100644 --- a/src/Grant/ImplicitGrant.php +++ b/src/Grant/ImplicitGrant.php @@ -10,7 +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; diff --git a/src/ResponseTypes/BearerTokenResponse.php b/src/ResponseTypes/BearerTokenResponse.php index 84f8939a..ddcadd63 100644 --- a/src/ResponseTypes/BearerTokenResponse.php +++ b/src/ResponseTypes/BearerTokenResponse.php @@ -11,7 +11,6 @@ namespace League\OAuth2\Server\ResponseTypes; -use DateTime; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use Psr\Http\Message\ResponseInterface; From a1cf22a3a9868b2abe241fc6e874e007a743b1ec Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 14 May 2019 16:11:34 +0100 Subject: [PATCH 65/66] Remove duplicate setting of expirydatetime --- src/Grant/AbstractGrant.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Grant/AbstractGrant.php b/src/Grant/AbstractGrant.php index a3870834..ec317b31 100644 --- a/src/Grant/AbstractGrant.php +++ b/src/Grant/AbstractGrant.php @@ -484,12 +484,12 @@ abstract class AbstractGrant implements GrantTypeInterface protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) { $refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); - $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL)); + if ($refreshToken === null) { 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; From 17923634bf33870b555e520ebede7b689181eff8 Mon Sep 17 00:00:00 2001 From: sephster Date: Tue, 14 May 2019 20:56:54 +0100 Subject: [PATCH 66/66] Set private keys in tests --- tests/Grant/AuthCodeGrantTest.php | 1 + tests/Grant/PasswordGrantTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/Grant/AuthCodeGrantTest.php b/tests/Grant/AuthCodeGrantTest.php index 4ea0a5d0..07b220c5 100644 --- a/tests/Grant/AuthCodeGrantTest.php +++ b/tests/Grant/AuthCodeGrantTest.php @@ -771,6 +771,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( [], diff --git a/tests/Grant/PasswordGrantTest.php b/tests/Grant/PasswordGrantTest.php index e8843c74..7fc99e83 100644 --- a/tests/Grant/PasswordGrantTest.php +++ b/tests/Grant/PasswordGrantTest.php @@ -108,6 +108,7 @@ 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(