Compare commits

...

80 Commits
7.2.0 ... 7.4.0

Author SHA1 Message Date
Andrew Millington
2eb1cf79e5 Update changelog for version 7.4.0 2019-05-05 10:22:01 +01:00
Andrew Millington
382b6f5fbf Merge pull request #1000 from filecage/master
Optional Refresh Tokens
2019-05-05 09:48:53 +01:00
sephster
86869eafbb Add whitespace around control blocks 2019-05-05 09:03:13 +01:00
sephster
9236e842d9 Clarify changelog message 2019-05-05 08:58:34 +01:00
filecage
9bc7f6c8c5 removing simplified_null_return 2019-04-29 19:13:26 +02:00
David
1e9a468e66 Merge branch 'master' into master 2019-04-12 11:17:37 +02:00
Andrew Millington
c7f4998497 Update links 2019-03-29 18:19:35 +00:00
Andrew Millington
0a78236f17 Update changelog for version 7.3.3 2019-03-29 18:18:35 +00:00
Andrew Millington
a68f8001a4 Merge pull request #1006 from marc-mabe/fix-958-error_description
spec compliant 'error_description' but keep 'message' for BC
2019-03-29 16:28:33 +00:00
Marc Bennewitz
b88198a9a4 spec compliant 'error_description' but keep 'message' for BC 2019-03-29 16:00:26 +01:00
filecage
8cf39fd9cd applies style CI diff 2019-03-16 13:15:38 +01:00
filecage
6f6820f629 removes @var hints
the @var hints make PHP stan fail together with PHPUnit 6.3
2019-03-16 13:12:34 +01:00
filecage
0742d5150c explicit is better than implicit :) 2019-03-13 10:08:57 +01:00
filecage
64f0d89fad getNewRefreshToken() can also return NULL 2019-03-11 23:28:47 +01:00
filecage
ebf78132d7 refreshTokenRepository parameter can not be null, condition is obsolete 2019-03-11 23:28:20 +01:00
filecage
aa5bbe5f06 boyscout: style CI tweaks 2019-03-11 23:26:35 +01:00
filecage
66d4ce6de8 Update CHANGELOG.md 2019-03-08 18:21:55 +01:00
filecage
2ea76ca4fd Adds handling for null issued refresh token to Grant implementations 2019-03-08 18:19:16 +01:00
filecage
b2840474fd AbstractGrant no longer tries to issue a refresh token if the Repository returned null 2019-03-08 18:16:16 +01:00
Andrew Millington
0227f14b7b Merge pull request #988 from lordrhodos/feature/test-cleanup
Cleanup: remove unused local variable $scopeEntity from ImplicitGrantTest
2019-01-22 20:59:33 +00:00
Patrick Rodacker
fad42a88fd removes unused local variable $scopeEntity from ImplicitGrantTest 2019-01-20 22:11:22 +01:00
Andrew Millington
2a16dbeb7f Merge pull request #981 from Sephster/support-php-7.3
Add support for PHP 7.3
2018-12-06 23:53:55 +00:00
sephster
faa350792a Add support for PHP 7.3 2018-12-06 23:46:28 +00:00
Andrew Millington
dc3181bbb0 Merge pull request #977 from spideyfusion/symfony-community-integration
Add Symfony community integration to README.md
2018-11-28 12:47:30 +00:00
Petar Obradović
1e3a7adb19 Add Symfony community integration to README.md 2018-11-28 12:24:16 +01:00
sephster
b71f382cd7 Update changelog 2018-11-21 21:42:43 +00:00
Andrew Millington
9783388523 Merge pull request #969 from ceeram/fix-bc-break
Fix bc breaking change
2018-11-21 21:38:37 +00:00
sephster
46493c461e Update changelog for 7.3.2 release 2018-11-21 21:29:55 +00:00
sephster
8b421818f2 Add blank line to better format 2018-11-21 21:26:54 +00:00
Marc Ypes
b09154af33 Add test to prove bc break 2018-11-16 13:29:47 +01:00
Marc Ypes
f1454cde36 Fix bc breaking change 2018-11-16 12:44:41 +01:00
Andrew Millington
f2cd3646ff Merge pull request #970 from Sephster/interface-revert
Revert Interface Change
2018-11-15 22:37:18 +00:00
sephster
7839a61170 Update changelog 2018-11-15 22:33:34 +00:00
sephster
443d7c485a Revert interface change so class can be extende 2018-11-15 22:22:08 +00:00
Andrew Millington
a61c6a318a Update changelog for 7.3.0 release 2018-11-13 20:17:20 +00:00
sephster
94e75ba6f3 Fix bug 2018-11-13 12:56:06 +00:00
Andrew Millington
efa8ef6fce Merge pull request #965 from ceeram/add-previous
Include previous exception in catch and throw
2018-11-13 12:44:43 +00:00
sephster
7982275757 Fix docblock alignment 2018-11-13 12:34:16 +00:00
sephster
f6c1070ccc Add use Throwable 2018-11-13 12:32:52 +00:00
sephster
d64fb3f526 Merge master into this branch 2018-11-13 12:28:39 +00:00
Andrew Millington
95a9f4649d Merge pull request #966 from ceeram/fully_qualified_strict_types
Replace fqn with unqualified name
2018-11-13 11:58:31 +00:00
Marc Ypes
4bb5b747c1 Replace fqn with unqualified name 2018-11-13 01:33:11 +01:00
Marc Ypes
5868996961 Add test for previous exceptions 2018-11-13 00:25:22 +01:00
sephster
9542af627e Update changelog 2018-11-12 19:57:35 +00:00
Marc Ypes
3b983ad0b4 Include previous exception in catch and throw 2018-11-12 13:58:31 +01:00
sephster
34ec35019b Remove additional whitespace 2018-11-08 13:10:22 +00:00
Andrew Millington
ac818bd921 Minor formatting adjustment 2018-11-06 21:42:05 +00:00
Andrew Millington
73698e28d9 Update CHANGELOG.md 2018-11-06 21:38:31 +00:00
Andrew Millington
c87be9477c Merge pull request #960 from marc-mabe/stateless-server
Make AuthorizationServer stateless
2018-11-06 21:34:26 +00:00
Marc Bennewitz
d288a2ad8a Make AuthorizationServer stateless 2018-11-05 09:08:02 +01:00
Andrew Millington
a34f5dd7db Merge pull request #953 from Sephster/code-tidyup
Code Tidyup
2018-10-13 17:06:21 +01:00
sephster
c0efdf0dd0 Revert changes to throws and returns ordering 2018-10-13 16:54:31 +01:00
sephster
f96fca3b48 Minor code tidyup 2018-10-13 16:44:40 +01:00
sephster
20b355b025 Re-order docblock throws 2018-10-13 16:31:36 +01:00
sephster
793f65d3a3 Remove unused scope entity interface 2018-10-13 16:14:15 +01:00
sephster
322b55eddf Remove getScopes function and use validateScopes instead 2018-10-13 16:11:44 +01:00
sephster
50ab9dd8ac Remove unused import 2018-10-13 15:28:39 +01:00
sephster
b624124d5a Chaneg param types to satisfy PHPStan 2018-10-13 15:25:49 +01:00
sephster
dbf2b55bc5 Fix docblock alignment 2018-10-13 15:16:50 +01:00
sephster
b11d628e8b Change docblock type for 2018-10-13 14:49:29 +01:00
sephster
0515129c9c Fix coding standards issues 2018-10-13 14:37:36 +01:00
sephster
50566cdc87 Reduce complexity of respondToAccessTokenRequest 2018-10-13 14:34:35 +01:00
sephster
b4d88995de Add throws tag for DateInterval exception 2018-10-13 13:42:27 +01:00
sephster
398029be56 Add roave security advisories 2018-10-13 13:35:36 +01:00
sephster
30ed221481 Add Andy to authors list 2018-10-13 13:27:31 +01:00
sephster
939c0619d0 Shorten variable name 2018-10-13 13:22:27 +01:00
sephster
4042a31159 Update composer dependency versions 2018-10-13 13:17:45 +01:00
Andrew Millington
0bdd02cdb4 Merge pull request #952 from Sephster/add-scope-entity-trait
Add Scope Entity Trait
2018-10-13 13:14:33 +01:00
sephster
7bf7700645 Update changelog 2018-10-12 23:32:44 +01:00
sephster
d76025d613 Change example to use scope trait 2018-10-12 23:23:24 +01:00
sephster
d6792c1662 Add scope entity trait 2018-10-12 23:05:07 +01:00
Andrew Millington
9882f6716c Merge pull request #923 from christiaangoossens/fix_implicit_grant_scopes
ImplicitGrant finalizes scopes without user identifier
2018-09-23 18:31:53 +01:00
sephster
71c605117a Add missing word 2018-09-23 18:31:26 +01:00
sephster
6bc6ac09d2 Update changelog 2018-09-23 18:30:14 +01:00
Andrew Millington
b7b7dda28c Merge pull request #945 from ezimuel/add-expressive-readme
Added Expressive in the community integration of README
2018-09-19 11:28:31 +01:00
Enrico Zimuel
ef864b5cba Added Expressive in the community integration of README 2018-09-19 12:02:06 +02:00
Andrew Millington
27b956c149 Merge pull request #932 from JasonTheAdams/patch-1
Added docbloc to UniqueTokenIdentifierConstraintViolationException
2018-08-19 11:39:03 +01:00
Jason Adams
6949a007e5 Added docbloc to UniqueTokenIdentifierConstraintViolationException 2018-08-18 16:57:31 -07:00
Christiaan Goossens
acf16e924a Actually use finalizedScopes in access token 2018-07-13 13:11:18 +02:00
Christiaan Goossens
a479b5762e Fix implicit grant scopes 2018-07-13 11:47:32 +02:00
42 changed files with 904 additions and 283 deletions

View File

@@ -4,6 +4,7 @@ enabled:
- binary_operator_spaces - binary_operator_spaces
- blank_line_before_return - blank_line_before_return
- concat_with_spaces - concat_with_spaces
- fully_qualified_strict_types
- function_typehint_space - function_typehint_space
- hash_to_slash_comment - hash_to_slash_comment
- include - include
@@ -40,7 +41,6 @@ enabled:
- print_to_echo - print_to_echo
- short_array_syntax - short_array_syntax
- short_scalar_cast - short_scalar_cast
- simplified_null_return
- single_quote - single_quote
- spaces_cast - spaces_cast
- standardize_not_equal - standardize_not_equal

View File

@@ -15,6 +15,7 @@ php:
- 7.0 - 7.0
- 7.1 - 7.1
- 7.2 - 7.2
- 7.3
install: install:
- composer update --no-interaction --prefer-dist $DEPENDENCIES - composer update --no-interaction --prefer-dist $DEPENDENCIES

View File

@@ -6,6 +6,41 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased] ## [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)
### Deprecated
- Error payload will not issue `message` value in the next major release (PR #1006)
## [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
### 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
- 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)
- 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)
## [7.2.0] - released 2018-06-23 ## [7.2.0] - released 2018-06-23
### Changed ### Changed
@@ -410,7 +445,12 @@ Version 5 is a complete code rewrite.
- First major release - First major release
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...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
[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.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.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 [7.1.0]: https://github.com/thephpleague/oauth2-server/compare/7.0.0...7.1.0

View File

@@ -34,6 +34,7 @@ The following versions of PHP are supported:
* PHP 7.0 * PHP 7.0
* PHP 7.1 * PHP 7.1
* PHP 7.2 * PHP 7.2
* PHP 7.3
The `openssl` extension is also required. The `openssl` extension is also required.
@@ -68,6 +69,8 @@ We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci
* [Drupal](https://www.drupal.org/project/simple_oauth) * [Drupal](https://www.drupal.org/project/simple_oauth)
* [Laravel Passport](https://github.com/laravel/passport) * [Laravel Passport](https://github.com/laravel/passport)
* [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server) * [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 ## Changelog

View File

@@ -16,7 +16,8 @@
"zendframework/zend-diactoros": "^1.3.2", "zendframework/zend-diactoros": "^1.3.2",
"phpstan/phpstan": "^0.9.2", "phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-phpunit": "^0.9.4", "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": [ "repositories": [
{ {
@@ -46,6 +47,12 @@
"email": "hello@alexbilbie.com", "email": "hello@alexbilbie.com",
"homepage": "http://www.alexbilbie.com", "homepage": "http://www.alexbilbie.com",
"role": "Developer" "role": "Developer"
},
{
"name": "Andy Millington",
"email": "andrew@noexceptions.io",
"homepage": "https://www.noexceptions.io",
"role": "Developer"
} }
], ],
"replace": { "replace": {

View File

@@ -1,14 +1,13 @@
{ {
"require": { "require": {
"slim/slim": "3.0.*" "slim/slim": "^3.0.0"
}, },
"require-dev": { "require-dev": {
"league/event": "^2.1", "league/event": "^2.1",
"lcobucci/jwt": "^3.1", "lcobucci/jwt": "^3.2",
"paragonie/random_compat": "^2.0",
"psr/http-message": "^1.0", "psr/http-message": "^1.0",
"defuse/php-encryption": "^2.1", "defuse/php-encryption": "^2.2",
"zendframework/zend-diactoros": "^1.0" "zendframework/zend-diactoros": "^2.0.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

219
examples/composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "9813ed7c3b6dcf107f44df9392935b8f", "content-hash": "97f2878428e37d1d8e5418cc85cbfa3d",
"packages": [ "packages": [
{ {
"name": "container-interop/container-interop", "name": "container-interop/container-interop",
@@ -39,21 +39,24 @@
}, },
{ {
"name": "nikic/fast-route", "name": "nikic/fast-route",
"version": "v0.6.0", "version": "v1.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nikic/FastRoute.git", "url": "https://github.com/nikic/FastRoute.git",
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22" "reference": "181d480e08d9476e61381e04a71b34dc0432e812"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22", "url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22", "reference": "181d480e08d9476e61381e04a71b34dc0432e812",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.4.0" "php": ">=5.4.0"
}, },
"require-dev": {
"phpunit/phpunit": "^4.8.35|~5.7"
},
"type": "library", "type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@@ -78,29 +81,33 @@
"router", "router",
"routing" "routing"
], ],
"time": "2015-06-18T19:15:47+00:00" "time": "2018-02-13T20:26:39+00:00"
}, },
{ {
"name": "pimple/pimple", "name": "pimple/pimple",
"version": "v3.0.2", "version": "v3.2.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/silexphp/Pimple.git", "url": "https://github.com/silexphp/Pimple.git",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.3.0" "php": ">=5.3.0",
"psr/container": "^1.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3.2"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.0.x-dev" "dev-master": "3.2.x-dev"
} }
}, },
"autoload": { "autoload": {
@@ -124,7 +131,7 @@
"container", "container",
"dependency injection" "dependency injection"
], ],
"time": "2015-09-11T15:10:35+00:00" "time": "2018-01-21T07:42:36+00:00"
}, },
{ {
"name": "psr/container", "name": "psr/container",
@@ -227,27 +234,32 @@
}, },
{ {
"name": "slim/slim", "name": "slim/slim",
"version": "3.0.0", "version": "3.11.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/slimphp/Slim.git", "url": "https://github.com/slimphp/Slim.git",
"reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e" "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/3b06f0f2d84dabbe81b6cea46ace46a3e883253e", "url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e", "reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"container-interop/container-interop": "^1.1", "container-interop/container-interop": "^1.2",
"nikic/fast-route": "^0.6", "nikic/fast-route": "^1.0",
"php": ">=5.5.0", "php": ">=5.5.0",
"pimple/pimple": "^3.0", "pimple/pimple": "^3.0",
"psr/container": "^1.0",
"psr/http-message": "^1.0" "psr/http-message": "^1.0"
}, },
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.0" "phpunit/phpunit": "^4.0",
"squizlabs/php_codesniffer": "^2.5"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@@ -282,38 +294,38 @@
} }
], ],
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs", "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": [ "keywords": [
"api", "api",
"framework", "framework",
"micro", "micro",
"router" "router"
], ],
"time": "2015-12-07T14:11:09+00:00" "time": "2018-09-16T10:54:21+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [
{ {
"name": "defuse/php-encryption", "name": "defuse/php-encryption",
"version": "v2.1.0", "version": "v2.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/defuse/php-encryption.git", "url": "https://github.com/defuse/php-encryption.git",
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689" "reference": "0f407c43b953d571421e0020ba92082ed5fb7620"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689", "url": "https://api.github.com/repos/defuse/php-encryption/zipball/0f407c43b953d571421e0020ba92082ed5fb7620",
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689", "reference": "0f407c43b953d571421e0020ba92082ed5fb7620",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-openssl": "*", "ext-openssl": "*",
"paragonie/random_compat": "~2.0", "paragonie/random_compat": ">= 2",
"php": ">=5.4.0" "php": ">=5.4.0"
}, },
"require-dev": { "require-dev": {
"nikic/php-parser": "^2.0|^3.0", "nikic/php-parser": "^2.0|^3.0|^4.0",
"phpunit/phpunit": "^4|^5" "phpunit/phpunit": "^4|^5"
}, },
"bin": [ "bin": [
@@ -354,20 +366,20 @@
"security", "security",
"symmetric key cryptography" "symmetric key cryptography"
], ],
"time": "2017-05-18T21:28:48+00:00" "time": "2018-07-24T23:27:56+00:00"
}, },
{ {
"name": "lcobucci/jwt", "name": "lcobucci/jwt",
"version": "3.2.1", "version": "3.2.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/lcobucci/jwt.git", "url": "https://github.com/lcobucci/jwt.git",
"reference": "ddce703826f9c5229781933b1a39069e38e6a0f3" "reference": "c9704b751315d21735dc98d78d4f37bd73596da7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/ddce703826f9c5229781933b1a39069e38e6a0f3", "url": "https://api.github.com/repos/lcobucci/jwt/zipball/c9704b751315d21735dc98d78d4f37bd73596da7",
"reference": "ddce703826f9c5229781933b1a39069e38e6a0f3", "reference": "c9704b751315d21735dc98d78d4f37bd73596da7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -412,7 +424,7 @@
"JWS", "JWS",
"jwt" "jwt"
], ],
"time": "2016-10-31T20:09:32+00:00" "time": "2018-08-03T11:23:50+00:00"
}, },
{ {
"name": "league/event", "name": "league/event",
@@ -466,33 +478,29 @@
}, },
{ {
"name": "paragonie/random_compat", "name": "paragonie/random_compat",
"version": "v2.0.10", "version": "v9.99.99",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/paragonie/random_compat.git", "url": "https://github.com/paragonie/random_compat.git",
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d" "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d", "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d", "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=5.2.0" "php": "^7"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "4.*|5.*" "phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
}, },
"suggest": { "suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
}, },
"type": "library", "type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
@@ -507,10 +515,129 @@
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [ "keywords": [
"csprng", "csprng",
"polyfill",
"pseudorandom", "pseudorandom",
"random" "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": [], "aliases": [],

View File

@@ -11,13 +11,9 @@ namespace OAuth2ServerExamples\Entities;
use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\Traits\EntityTrait; use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\ScopeTrait;
class ScopeEntity implements ScopeEntityInterface class ScopeEntity implements ScopeEntityInterface
{ {
use EntityTrait; use EntityTrait, ScopeTrait;
public function jsonSerialize()
{
return $this->getIdentifier();
}
} }

View File

@@ -18,7 +18,7 @@ class RefreshTokenRepository implements RefreshTokenRepositoryInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntityInterface) public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity)
{ {
// Some logic to persist the refresh token in a database // Some logic to persist the refresh token in a database
} }

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server; namespace League\OAuth2\Server;
use DateInterval;
use Defuse\Crypto\Key; use Defuse\Crypto\Key;
use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareInterface;
use League\Event\EmitterAwareTrait; use League\Event\EmitterAwareTrait;
@@ -34,7 +35,7 @@ class AuthorizationServer implements EmitterAwareInterface
protected $enabledGrantTypes = []; protected $enabledGrantTypes = [];
/** /**
* @var \DateInterval[] * @var DateInterval[]
*/ */
protected $grantTypeAccessTokenTTL = []; protected $grantTypeAccessTokenTTL = [];
@@ -49,7 +50,7 @@ class AuthorizationServer implements EmitterAwareInterface
protected $publicKey; protected $publicKey;
/** /**
* @var null|ResponseTypeInterface * @var ResponseTypeInterface
*/ */
protected $responseType; protected $responseType;
@@ -103,8 +104,16 @@ class AuthorizationServer implements EmitterAwareInterface
if ($privateKey instanceof CryptKey === false) { if ($privateKey instanceof CryptKey === false) {
$privateKey = new CryptKey($privateKey); $privateKey = new CryptKey($privateKey);
} }
$this->privateKey = $privateKey; $this->privateKey = $privateKey;
$this->encryptionKey = $encryptionKey; $this->encryptionKey = $encryptionKey;
if ($responseType === null) {
$responseType = new BearerTokenResponse();
} else {
$responseType = clone $responseType;
}
$this->responseType = $responseType; $this->responseType = $responseType;
} }
@@ -112,12 +121,12 @@ class AuthorizationServer implements EmitterAwareInterface
* Enable a grant type on the server. * Enable a grant type on the server.
* *
* @param GrantTypeInterface $grantType * @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) { if ($accessTokenTTL instanceof DateInterval === false) {
$accessTokenTTL = new \DateInterval('PT1H'); $accessTokenTTL = new DateInterval('PT1H');
} }
$grantType->setAccessTokenRepository($this->accessTokenRepository); $grantType->setAccessTokenRepository($this->accessTokenRepository);
@@ -204,16 +213,15 @@ class AuthorizationServer implements EmitterAwareInterface
*/ */
protected function getResponseType() protected function getResponseType()
{ {
if ($this->responseType instanceof ResponseTypeInterface === false) { $responseType = clone $this->responseType;
$this->responseType = new BearerTokenResponse();
if ($responseType instanceof AbstractResponseType) {
$responseType->setPrivateKey($this->privateKey);
} }
if ($this->responseType instanceof AbstractResponseType === true) { $responseType->setEncryptionKey($this->encryptionKey);
$this->responseType->setPrivateKey($this->privateKey);
}
$this->responseType->setEncryptionKey($this->encryptionKey);
return $this->responseType; return $responseType;
} }
/** /**

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server\AuthorizationValidators; namespace League\OAuth2\Server\AuthorizationValidators;
use BadMethodCallException;
use InvalidArgumentException;
use Lcobucci\JWT\Parser; use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\ValidationData; use Lcobucci\JWT\ValidationData;
@@ -17,6 +19,7 @@ use League\OAuth2\Server\CryptTrait;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
class BearerTokenValidator implements AuthorizationValidatorInterface class BearerTokenValidator implements AuthorizationValidatorInterface
{ {
@@ -28,7 +31,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
private $accessTokenRepository; private $accessTokenRepository;
/** /**
* @var \League\OAuth2\Server\CryptKey * @var CryptKey
*/ */
protected $publicKey; protected $publicKey;
@@ -43,7 +46,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
/** /**
* Set the public key * Set the public key
* *
* @param \League\OAuth2\Server\CryptKey $key * @param CryptKey $key
*/ */
public function setPublicKey(CryptKey $key) public function setPublicKey(CryptKey $key)
{ {
@@ -69,8 +72,8 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) { if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
throw OAuthServerException::accessDenied('Access token could not be verified'); throw OAuthServerException::accessDenied('Access token could not be verified');
} }
} catch (\BadMethodCallException $exception) { } 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 // Ensure access token hasn't expired
@@ -92,12 +95,12 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
->withAttribute('oauth_client_id', $token->getClaim('aud')) ->withAttribute('oauth_client_id', $token->getClaim('aud'))
->withAttribute('oauth_user_id', $token->getClaim('sub')) ->withAttribute('oauth_user_id', $token->getClaim('sub'))
->withAttribute('oauth_scopes', $token->getClaim('scopes')); ->withAttribute('oauth_scopes', $token->getClaim('scopes'));
} catch (\InvalidArgumentException $exception) { } catch (InvalidArgumentException $exception) {
// JWT couldn't be parsed so return the request as is // 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) { } catch (RuntimeException $exception) {
//JWR couldn't be parsed so return the request as is //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);
} }
} }
} }

View File

@@ -11,6 +11,9 @@
namespace League\OAuth2\Server; namespace League\OAuth2\Server;
use LogicException;
use RuntimeException;
class CryptKey class CryptKey
{ {
const RSA_KEY_PATTERN = const RSA_KEY_PATTERN =
@@ -42,7 +45,7 @@ class CryptKey
} }
if (!file_exists($keyPath) || !is_readable($keyPath)) { 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) { if ($keyPermissionsCheck === true) {
@@ -64,7 +67,7 @@ class CryptKey
/** /**
* @param string $key * @param string $key
* *
* @throws \RuntimeException * @throws RuntimeException
* *
* @return string * @return string
*/ */
@@ -79,19 +82,19 @@ class CryptKey
if (!touch($keyPath)) { if (!touch($keyPath)) {
// @codeCoverageIgnoreStart // @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 // @codeCoverageIgnoreEnd
} }
if (file_put_contents($keyPath, $key) === false) { if (file_put_contents($keyPath, $key) === false) {
// @codeCoverageIgnoreStart // @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 // @codeCoverageIgnoreEnd
} }
if (chmod($keyPath, 0600) === false) { if (chmod($keyPath, 0600) === false) {
// @codeCoverageIgnoreStart // @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 // @codeCoverageIgnoreEnd
} }

View File

@@ -13,6 +13,8 @@ namespace League\OAuth2\Server;
use Defuse\Crypto\Crypto; use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key; use Defuse\Crypto\Key;
use Exception;
use LogicException;
trait CryptTrait trait CryptTrait
{ {
@@ -26,7 +28,7 @@ trait CryptTrait
* *
* @param string $unencryptedData * @param string $unencryptedData
* *
* @throws \LogicException * @throws LogicException
* *
* @return string * @return string
*/ */
@@ -38,8 +40,8 @@ trait CryptTrait
} }
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
} catch (\Exception $e) { } catch (Exception $e) {
throw new \LogicException($e->getMessage()); throw new LogicException($e->getMessage(), null, $e);
} }
} }
@@ -48,7 +50,7 @@ trait CryptTrait
* *
* @param string $encryptedData * @param string $encryptedData
* *
* @throws \LogicException * @throws LogicException
* *
* @return string * @return string
*/ */
@@ -60,8 +62,8 @@ trait CryptTrait
} }
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
} catch (\Exception $e) { } catch (Exception $e) {
throw new \LogicException($e->getMessage()); throw new LogicException($e->getMessage(), null, $e);
} }
} }

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server\Entities; namespace League\OAuth2\Server\Entities;
use DateTime;
interface RefreshTokenEntityInterface interface RefreshTokenEntityInterface
{ {
/** /**
@@ -28,16 +30,16 @@ interface RefreshTokenEntityInterface
/** /**
* Get the token's expiry date time. * Get the token's expiry date time.
* *
* @return \DateTime * @return DateTime
*/ */
public function getExpiryDateTime(); public function getExpiryDateTime();
/** /**
* Set the date time when the token expires. * 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. * Set the access token that the refresh token was associated with.

View File

@@ -9,7 +9,9 @@
namespace League\OAuth2\Server\Entities; namespace League\OAuth2\Server\Entities;
interface ScopeEntityInterface extends \JsonSerializable use JsonSerializable;
interface ScopeEntityInterface extends JsonSerializable
{ {
/** /**
* Get the scope's identifier. * Get the scope's identifier.

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server\Entities; namespace League\OAuth2\Server\Entities;
use DateTime;
interface TokenInterface interface TokenInterface
{ {
/** /**
@@ -28,16 +30,16 @@ interface TokenInterface
/** /**
* Get the token's expiry date time. * Get the token's expiry date time.
* *
* @return \DateTime * @return DateTime
*/ */
public function getExpiryDateTime(); public function getExpiryDateTime();
/** /**
* Set the date time when the token expires. * 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. * Set the identifier of the user associated with the token.

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Entities\Traits; namespace League\OAuth2\Server\Entities\Traits;
use DateTime;
use Lcobucci\JWT\Builder; use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256; use Lcobucci\JWT\Signer\Rsa\Sha256;
@@ -46,7 +47,7 @@ trait AccessTokenTrait
abstract public function getClient(); abstract public function getClient();
/** /**
* @return \DateTime * @return DateTime
*/ */
abstract public function getExpiryDateTime(); abstract public function getExpiryDateTime();

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Entities\Traits; namespace League\OAuth2\Server\Entities\Traits;
use DateTime;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
trait RefreshTokenTrait trait RefreshTokenTrait
@@ -19,7 +20,7 @@ trait RefreshTokenTrait
protected $accessToken; protected $accessToken;
/** /**
* @var \DateTime * @var DateTime
*/ */
protected $expiryDateTime; protected $expiryDateTime;
@@ -42,7 +43,7 @@ trait RefreshTokenTrait
/** /**
* Get the token's expiry date time. * Get the token's expiry date time.
* *
* @return \DateTime * @return DateTime
*/ */
public function getExpiryDateTime() public function getExpiryDateTime()
{ {
@@ -52,9 +53,9 @@ trait RefreshTokenTrait
/** /**
* Set the date time when the token expires. * 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; $this->expiryDateTime = $dateTime;
} }

View File

@@ -0,0 +1,28 @@
<?php
/**
* @author Andrew Millington <andrew@noexceptions.io>
* @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();
}

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Entities\Traits; namespace League\OAuth2\Server\Entities\Traits;
use DateTime;
use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface;
@@ -20,7 +21,7 @@ trait TokenEntityTrait
protected $scopes = []; protected $scopes = [];
/** /**
* @var \DateTime * @var DateTime
*/ */
protected $expiryDateTime; protected $expiryDateTime;
@@ -57,7 +58,7 @@ trait TokenEntityTrait
/** /**
* Get the token's expiry date time. * Get the token's expiry date time.
* *
* @return \DateTime * @return DateTime
*/ */
public function getExpiryDateTime() public function getExpiryDateTime()
{ {
@@ -67,9 +68,9 @@ trait TokenEntityTrait
/** /**
* Set the date time when the token expires. * 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; $this->expiryDateTime = $dateTime;
} }

View File

@@ -9,9 +9,11 @@
namespace League\OAuth2\Server\Exception; namespace League\OAuth2\Server\Exception;
use Exception;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Throwable;
class OAuthServerException extends \Exception class OAuthServerException extends Exception
{ {
/** /**
* @var int * @var int
@@ -47,17 +49,18 @@ class OAuthServerException extends \Exception
* @param int $httpStatusCode HTTP status code to send (default = 400) * @param int $httpStatusCode HTTP status code to send (default = 400)
* @param null|string $hint A helper hint * @param null|string $hint A helper hint
* @param null|string $redirectUri A HTTP URI to redirect the user back to * @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->httpStatusCode = $httpStatusCode;
$this->errorType = $errorType; $this->errorType = $errorType;
$this->hint = $hint; $this->hint = $hint;
$this->redirectUri = $redirectUri; $this->redirectUri = $redirectUri;
$this->payload = [ $this->payload = [
'error' => $errorType, 'error' => $errorType,
'message' => $message, 'error_description' => $message,
]; ];
if ($hint !== null) { if ($hint !== null) {
$this->payload['hint'] = $hint; $this->payload['hint'] = $hint;
@@ -71,7 +74,15 @@ class OAuthServerException extends \Exception
*/ */
public function getPayload() 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;
} }
/** /**
@@ -102,16 +113,17 @@ class OAuthServerException extends \Exception
* *
* @param string $parameter The invalid parameter * @param string $parameter The invalid parameter
* @param null|string $hint * @param null|string $hint
* @param Throwable $previous Previous exception
* *
* @return static * @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, ' . $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
'includes a parameter more than once, or is otherwise malformed.'; 'includes a parameter more than once, or is otherwise malformed.';
$hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint; $hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
return new static($errorMessage, 3, 'invalid_request', 400, $hint); return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
} }
/** /**
@@ -163,20 +175,24 @@ class OAuthServerException extends \Exception
/** /**
* Server error. * Server error.
* *
* @param string $hint * @param string $hint
* @param Throwable $previous
* *
* @return static * @return static
* *
* @codeCoverageIgnore * @codeCoverageIgnore
*/ */
public static function serverError($hint) public static function serverError($hint, Throwable $previous = null)
{ {
return new static( return new static(
'The authorization server encountered an unexpected condition which prevented it from fulfilling' 'The authorization server encountered an unexpected condition which prevented it from fulfilling'
. ' the request: ' . $hint, . ' the request: ' . $hint,
7, 7,
'server_error', 'server_error',
500 500,
null,
null,
$previous
); );
} }
@@ -184,12 +200,13 @@ class OAuthServerException extends \Exception
* Invalid refresh token. * Invalid refresh token.
* *
* @param null|string $hint * @param null|string $hint
* @param Throwable $previous
* *
* @return static * @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 +214,11 @@ class OAuthServerException extends \Exception
* *
* @param null|string $hint * @param null|string $hint
* @param null|string $redirectUri * @param null|string $redirectUri
* @param Throwable $previous
* *
* @return static * @return static
*/ */
public static function accessDenied($hint = null, $redirectUri = null) public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null)
{ {
return new static( return new static(
'The resource owner or authorization server denied the request.', 'The resource owner or authorization server denied the request.',
@@ -208,7 +226,8 @@ class OAuthServerException extends \Exception
'access_denied', 'access_denied',
401, 401,
$hint, $hint,
$redirectUri $redirectUri,
$previous
); );
} }

View File

@@ -11,6 +11,9 @@ namespace League\OAuth2\Server\Exception;
class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException
{ {
/**
* @return UniqueTokenIdentifierConstraintViolationException
*/
public static function create() public static function create()
{ {
$errorMessage = 'Could not create unique access token identifier'; $errorMessage = 'Could not create unique access token identifier';

View File

@@ -10,6 +10,10 @@
*/ */
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use Error;
use Exception;
use League\Event\EmitterAwareTrait; use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\CryptTrait; 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\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use LogicException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use TypeError;
/** /**
* Abstract grant class. * Abstract grant class.
@@ -72,12 +78,12 @@ abstract class AbstractGrant implements GrantTypeInterface
protected $userRepository; protected $userRepository;
/** /**
* @var \DateInterval * @var DateInterval
*/ */
protected $refreshTokenTTL; protected $refreshTokenTTL;
/** /**
* @var \League\OAuth2\Server\CryptKey * @var CryptKey
*/ */
protected $privateKey; protected $privateKey;
@@ -137,7 +143,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL) public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
{ {
$this->refreshTokenTTL = $refreshTokenTTL; $this->refreshTokenTTL = $refreshTokenTTL;
} }
@@ -145,7 +151,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/** /**
* Set the private key * Set the private key
* *
* @param \League\OAuth2\Server\CryptKey $key * @param CryptKey $key
*/ */
public function setPrivateKey(CryptKey $key) public function setPrivateKey(CryptKey $key)
{ {
@@ -174,7 +180,7 @@ abstract class AbstractGrant implements GrantTypeInterface
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request); list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser); $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
if (is_null($clientId)) { if ($clientId === null) {
throw OAuthServerException::invalidRequest('client_id'); throw OAuthServerException::invalidRequest('client_id');
} }
@@ -217,13 +223,13 @@ abstract class AbstractGrant implements GrantTypeInterface
ClientEntityInterface $client, ClientEntityInterface $client,
ServerRequestInterface $request ServerRequestInterface $request
) { ) {
if (is_string($client->getRedirectUri()) if (\is_string($client->getRedirectUri())
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0) && (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) { ) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient(); throw OAuthServerException::invalidClient();
} elseif (is_array($client->getRedirectUri()) } elseif (\is_array($client->getRedirectUri())
&& in_array($redirectUri, $client->getRedirectUri(), true) === false && \in_array($redirectUri, $client->getRedirectUri(), true) === false
) { ) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient(); throw OAuthServerException::invalidClient();
@@ -233,8 +239,8 @@ abstract class AbstractGrant implements GrantTypeInterface
/** /**
* Validate scopes in the request. * Validate scopes in the request.
* *
* @param string $scopes * @param string|array $scopes
* @param string $redirectUri * @param string $redirectUri
* *
* @throws OAuthServerException * @throws OAuthServerException
* *
@@ -242,13 +248,13 @@ abstract class AbstractGrant implements GrantTypeInterface
*/ */
public function validateScopes($scopes, $redirectUri = null) public function validateScopes($scopes, $redirectUri = null)
{ {
$scopesList = array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) { if (!\is_array($scopes)) {
return !empty($scope); $scopes = $this->convertScopesQueryStringToArray($scopes);
}); }
$validScopes = []; $validScopes = [];
foreach ($scopesList as $scopeItem) { foreach ($scopes as $scopeItem) {
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem); $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
if ($scope instanceof ScopeEntityInterface === false) { if ($scope instanceof ScopeEntityInterface === false) {
@@ -261,6 +267,20 @@ abstract class AbstractGrant implements GrantTypeInterface
return $validScopes; 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. * Retrieve request parameter.
* *
@@ -274,7 +294,7 @@ abstract class AbstractGrant implements GrantTypeInterface
{ {
$requestParameters = (array) $request->getParsedBody(); $requestParameters = (array) $request->getParsedBody();
return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default; return $requestParameters[$parameter] ?? $default;
} }
/** /**
@@ -355,7 +375,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/** /**
* Issue an access token. * Issue an access token.
* *
* @param \DateInterval $accessTokenTTL * @param DateInterval $accessTokenTTL
* @param ClientEntityInterface $client * @param ClientEntityInterface $client
* @param string|null $userIdentifier * @param string|null $userIdentifier
* @param ScopeEntityInterface[] $scopes * @param ScopeEntityInterface[] $scopes
@@ -366,7 +386,7 @@ abstract class AbstractGrant implements GrantTypeInterface
* @return AccessTokenEntityInterface * @return AccessTokenEntityInterface
*/ */
protected function issueAccessToken( protected function issueAccessToken(
\DateInterval $accessTokenTTL, DateInterval $accessTokenTTL,
ClientEntityInterface $client, ClientEntityInterface $client,
$userIdentifier, $userIdentifier,
array $scopes = [] array $scopes = []
@@ -376,7 +396,7 @@ abstract class AbstractGrant implements GrantTypeInterface
$accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier); $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
$accessToken->setClient($client); $accessToken->setClient($client);
$accessToken->setUserIdentifier($userIdentifier); $accessToken->setUserIdentifier($userIdentifier);
$accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL)); $accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL));
foreach ($scopes as $scope) { foreach ($scopes as $scope) {
$accessToken->addScope($scope); $accessToken->addScope($scope);
@@ -399,7 +419,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/** /**
* Issue an auth code. * Issue an auth code.
* *
* @param \DateInterval $authCodeTTL * @param DateInterval $authCodeTTL
* @param ClientEntityInterface $client * @param ClientEntityInterface $client
* @param string $userIdentifier * @param string $userIdentifier
* @param string|null $redirectUri * @param string|null $redirectUri
@@ -411,7 +431,7 @@ abstract class AbstractGrant implements GrantTypeInterface
* @return AuthCodeEntityInterface * @return AuthCodeEntityInterface
*/ */
protected function issueAuthCode( protected function issueAuthCode(
\DateInterval $authCodeTTL, DateInterval $authCodeTTL,
ClientEntityInterface $client, ClientEntityInterface $client,
$userIdentifier, $userIdentifier,
$redirectUri, $redirectUri,
@@ -420,7 +440,7 @@ abstract class AbstractGrant implements GrantTypeInterface
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS; $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$authCode = $this->authCodeRepository->getNewAuthCode(); $authCode = $this->authCodeRepository->getNewAuthCode();
$authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL)); $authCode->setExpiryDateTime((new DateTime())->add($authCodeTTL));
$authCode->setClient($client); $authCode->setClient($client);
$authCode->setUserIdentifier($userIdentifier); $authCode->setUserIdentifier($userIdentifier);
@@ -452,16 +472,21 @@ abstract class AbstractGrant implements GrantTypeInterface
* @throws OAuthServerException * @throws OAuthServerException
* @throws UniqueTokenIdentifierConstraintViolationException * @throws UniqueTokenIdentifierConstraintViolationException
* *
* @return RefreshTokenEntityInterface * @return RefreshTokenEntityInterface|null
*/ */
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken) protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
{ {
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$refreshToken = $this->refreshTokenRepository->getNewRefreshToken(); $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
$refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL));
if ($refreshToken === null) {
return null;
}
$refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL));
$refreshToken->setAccessToken($accessToken); $refreshToken->setAccessToken($accessToken);
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
while ($maxGenerationAttempts-- > 0) { while ($maxGenerationAttempts-- > 0) {
$refreshToken->setIdentifier($this->generateUniqueIdentifier()); $refreshToken->setIdentifier($this->generateUniqueIdentifier());
try { try {
@@ -490,13 +515,13 @@ abstract class AbstractGrant implements GrantTypeInterface
try { try {
return bin2hex(random_bytes($length)); return bin2hex(random_bytes($length));
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (\TypeError $e) { } catch (TypeError $e) {
throw OAuthServerException::serverError('An unexpected error has occurred'); throw OAuthServerException::serverError('An unexpected error has occurred', $e);
} catch (\Error $e) { } catch (Error $e) {
throw OAuthServerException::serverError('An unexpected error has occurred'); throw OAuthServerException::serverError('An unexpected error has occurred', $e);
} catch (\Exception $e) { } catch (Exception $e) {
// If you get this message, the CSPRNG failed hard. // 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 // @codeCoverageIgnoreEnd
} }
@@ -527,7 +552,7 @@ abstract class AbstractGrant implements GrantTypeInterface
*/ */
public function validateAuthorizationRequest(ServerRequestInterface $request) 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');
} }
/** /**
@@ -535,6 +560,6 @@ abstract class AbstractGrant implements GrantTypeInterface
*/ */
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) 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');
} }
} }

View File

@@ -9,8 +9,10 @@
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use Exception;
use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
@@ -19,12 +21,14 @@ use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use stdClass;
class AuthCodeGrant extends AbstractAuthorizeGrant class AuthCodeGrant extends AbstractAuthorizeGrant
{ {
/** /**
* @var \DateInterval * @var DateInterval
*/ */
private $authCodeTTL; private $authCodeTTL;
@@ -36,17 +40,19 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
/** /**
* @param AuthCodeRepositoryInterface $authCodeRepository * @param AuthCodeRepositoryInterface $authCodeRepository
* @param RefreshTokenRepositoryInterface $refreshTokenRepository * @param RefreshTokenRepositoryInterface $refreshTokenRepository
* @param \DateInterval $authCodeTTL * @param DateInterval $authCodeTTL
*
* @throws Exception
*/ */
public function __construct( public function __construct(
AuthCodeRepositoryInterface $authCodeRepository, AuthCodeRepositoryInterface $authCodeRepository,
RefreshTokenRepositoryInterface $refreshTokenRepository, RefreshTokenRepositoryInterface $refreshTokenRepository,
\DateInterval $authCodeTTL DateInterval $authCodeTTL
) { ) {
$this->setAuthCodeRepository($authCodeRepository); $this->setAuthCodeRepository($authCodeRepository);
$this->setRefreshTokenRepository($refreshTokenRepository); $this->setRefreshTokenRepository($refreshTokenRepository);
$this->authCodeTTL = $authCodeTTL; $this->authCodeTTL = $authCodeTTL;
$this->refreshTokenTTL = new \DateInterval('P1M'); $this->refreshTokenTTL = new DateInterval('P1M');
} }
public function enableCodeExchangeProof() public function enableCodeExchangeProof()
@@ -59,7 +65,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
* *
* @param ServerRequestInterface $request * @param ServerRequestInterface $request
* @param ResponseTypeInterface $responseType * @param ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL * @param DateInterval $accessTokenTTL
* *
* @throws OAuthServerException * @throws OAuthServerException
* *
@@ -68,7 +74,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
public function respondToAccessTokenRequest( public function respondToAccessTokenRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseTypeInterface $responseType, ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL DateInterval $accessTokenTTL
) { ) {
// Validate request // Validate request
$client = $this->validateClient($request); $client = $this->validateClient($request);
@@ -78,58 +84,25 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
throw OAuthServerException::invalidRequest('code'); throw OAuthServerException::invalidRequest('code');
} }
// Validate the authorization code
try { try {
$authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); $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) { $this->validateAuthorizationCode($authCodePayload, $client, $request);
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');
}
$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->scopeRepository->finalizeScopes(
$scopes, $this->validateScopes($authCodePayload->scopes),
$this->getIdentifier(), $this->getIdentifier(),
$client, $client,
$authCodePayload->user_id $authCodePayload->user_id
); );
} catch (\LogicException $e) { } 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 // Validate code challenge
if ($this->enableCodeExchangeProof === true) { if ($this->enableCodeExchangeProof === true) {
$codeVerifier = $this->getRequestParameter('code_verifier', $request, null); $codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
if ($codeVerifier === null) { if ($codeVerifier === null) {
throw OAuthServerException::invalidRequest('code_verifier'); throw OAuthServerException::invalidRequest('code_verifier');
} }
@@ -172,17 +145,18 @@ 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); $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);
// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken); $refreshToken = $this->issueRefreshToken($accessToken);
// Send events to emitter if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); $responseType->setRefreshToken($refreshToken);
}
// Inject tokens into response type
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
// Revoke used auth code // Revoke used auth code
$this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
@@ -190,6 +164,41 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
return $responseType; return $responseType;
} }
/**
* Validate the authorization code.
*
* @param stdClass $authCodePayload
* @param ClientEntityInterface $client
* @param ServerRequestInterface $request
*/
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');
}
}
/** /**
* Return the grant identifier that can be used in matching up requests. * Return the grant identifier that can be used in matching up requests.
* *
@@ -223,7 +232,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$this->getServerParameter('PHP_AUTH_USER', $request) $this->getServerParameter('PHP_AUTH_USER', $request)
); );
if (is_null($clientId)) { if ($clientId === null) {
throw OAuthServerException::invalidRequest('client_id'); throw OAuthServerException::invalidRequest('client_id');
} }
@@ -243,12 +252,12 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
if ($redirectUri !== null) { if ($redirectUri !== null) {
$this->validateRedirectUri($redirectUri, $client, $request); $this->validateRedirectUri($redirectUri, $client, $request);
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1 } elseif (empty($client->getRedirectUri()) ||
|| empty($client->getRedirectUri())) { (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient(); throw OAuthServerException::invalidClient();
} else { } else {
$redirectUri = is_array($client->getRedirectUri()) $redirectUri = \is_array($client->getRedirectUri())
? $client->getRedirectUri()[0] ? $client->getRedirectUri()[0]
: $client->getRedirectUri(); : $client->getRedirectUri();
} }
@@ -279,7 +288,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain'); $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( throw OAuthServerException::invalidRequest(
'code_challenge_method', 'code_challenge_method',
'Code challenge method must be `plain` or `S256`' 'Code challenge method must be `plain` or `S256`'
@@ -308,14 +317,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{ {
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { 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) $finalRedirectUri = $authorizationRequest->getRedirectUri()
? is_array($authorizationRequest->getClient()->getRedirectUri()) ?? $this->getClientRedirectUri($authorizationRequest);
? $authorizationRequest->getClient()->getRedirectUri()[0]
: $authorizationRequest->getClient()->getRedirectUri()
: $authorizationRequest->getRedirectUri();
// The user approved the client, redirect them back with an auth code // The user approved the client, redirect them back with an auth code
if ($authorizationRequest->isAuthorizationApproved() === true) { if ($authorizationRequest->isAuthorizationApproved() === true) {
@@ -333,7 +339,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
'auth_code_id' => $authCode->getIdentifier(), 'auth_code_id' => $authCode->getIdentifier(),
'scopes' => $authCode->getScopes(), 'scopes' => $authCode->getScopes(),
'user_id' => $authCode->getUserIdentifier(), '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' => $authorizationRequest->getCodeChallenge(),
'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(), 'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
]; ];
@@ -367,4 +373,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();
}
} }

View File

@@ -11,6 +11,7 @@
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
@@ -26,7 +27,7 @@ class ClientCredentialsGrant extends AbstractGrant
public function respondToAccessTokenRequest( public function respondToAccessTokenRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseTypeInterface $responseType, ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL DateInterval $accessTokenTTL
) { ) {
// Validate request // Validate request
$client = $this->validateClient($request); $client = $this->validateClient($request);

View File

@@ -11,6 +11,7 @@
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use Defuse\Crypto\Key; use Defuse\Crypto\Key;
use League\Event\EmitterAwareInterface; use League\Event\EmitterAwareInterface;
use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptKey;
@@ -29,9 +30,9 @@ interface GrantTypeInterface extends EmitterAwareInterface
/** /**
* Set refresh token TTL. * 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. * Return the grant identifier that can be used in matching up requests.
@@ -45,14 +46,14 @@ interface GrantTypeInterface extends EmitterAwareInterface
* *
* @param ServerRequestInterface $request * @param ServerRequestInterface $request
* @param ResponseTypeInterface $responseType * @param ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL * @param DateInterval $accessTokenTTL
* *
* @return ResponseTypeInterface * @return ResponseTypeInterface
*/ */
public function respondToAccessTokenRequest( public function respondToAccessTokenRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseTypeInterface $responseType, ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL DateInterval $accessTokenTTL
); );
/** /**

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException; 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\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\RedirectResponse; use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
class ImplicitGrant extends AbstractAuthorizeGrant class ImplicitGrant extends AbstractAuthorizeGrant
{ {
/** /**
* @var \DateInterval * @var DateInterval
*/ */
private $accessTokenTTL; private $accessTokenTTL;
@@ -32,33 +35,33 @@ class ImplicitGrant extends AbstractAuthorizeGrant
private $queryDelimiter; private $queryDelimiter;
/** /**
* @param \DateInterval $accessTokenTTL * @param DateInterval $accessTokenTTL
* @param string $queryDelimiter * @param string $queryDelimiter
*/ */
public function __construct(\DateInterval $accessTokenTTL, $queryDelimiter = '#') public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#')
{ {
$this->accessTokenTTL = $accessTokenTTL; $this->accessTokenTTL = $accessTokenTTL;
$this->queryDelimiter = $queryDelimiter; $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 * @param RefreshTokenRepositoryInterface $refreshTokenRepository
* *
* @throw \LogicException * @throw LogicException
*/ */
public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository) 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 ServerRequestInterface $request
* @param ResponseTypeInterface $responseType * @param ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL * @param DateInterval $accessTokenTTL
* *
* @return ResponseTypeInterface * @return ResponseTypeInterface
*/ */
public function respondToAccessTokenRequest( public function respondToAccessTokenRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseTypeInterface $responseType, 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');
} }
/** /**
@@ -154,13 +157,6 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$redirectUri $redirectUri
); );
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$scopes,
$this->getIdentifier(),
$client
);
$stateParameter = $this->getQueryStringParameter('state', $request); $stateParameter = $this->getQueryStringParameter('state', $request);
$authorizationRequest = new AuthorizationRequest(); $authorizationRequest = new AuthorizationRequest();
@@ -172,7 +168,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$authorizationRequest->setState($stateParameter); $authorizationRequest->setState($stateParameter);
} }
$authorizationRequest->setScopes($finalizedScopes); $authorizationRequest->setScopes($scopes);
return $authorizationRequest; return $authorizationRequest;
} }
@@ -183,7 +179,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{ {
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { 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) $finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
@@ -194,11 +190,19 @@ class ImplicitGrant extends AbstractAuthorizeGrant
// The user approved the client, redirect them back with an access token // The user approved the client, redirect them back with an access token
if ($authorizationRequest->isAuthorizationApproved() === true) { if ($authorizationRequest->isAuthorizationApproved() === true) {
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$authorizationRequest->getScopes(),
$this->getIdentifier(),
$authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier()
);
$accessToken = $this->issueAccessToken( $accessToken = $this->issueAccessToken(
$this->accessTokenTTL, $this->accessTokenTTL,
$authorizationRequest->getClient(), $authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier(), $authorizationRequest->getUser()->getIdentifier(),
$authorizationRequest->getScopes() $finalizedScopes
); );
$response = new RedirectResponse(); $response = new RedirectResponse();
@@ -208,7 +212,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
[ [
'access_token' => (string) $accessToken->convertToJWT($this->privateKey), 'access_token' => (string) $accessToken->convertToJWT($this->privateKey),
'token_type' => 'Bearer', 'token_type' => 'Bearer',
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(), 'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new DateTime())->getTimestamp(),
'state' => $authorizationRequest->getState(), 'state' => $authorizationRequest->getState(),
], ],
$this->queryDelimiter $this->queryDelimiter

View File

@@ -11,6 +11,7 @@
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
@@ -36,7 +37,7 @@ class PasswordGrant extends AbstractGrant
$this->setUserRepository($userRepository); $this->setUserRepository($userRepository);
$this->setRefreshTokenRepository($refreshTokenRepository); $this->setRefreshTokenRepository($refreshTokenRepository);
$this->refreshTokenTTL = new \DateInterval('P1M'); $this->refreshTokenTTL = new DateInterval('P1M');
} }
/** /**
@@ -45,7 +46,7 @@ class PasswordGrant extends AbstractGrant
public function respondToAccessTokenRequest( public function respondToAccessTokenRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseTypeInterface $responseType, ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL DateInterval $accessTokenTTL
) { ) {
// Validate request // Validate request
$client = $this->validateClient($request); $client = $this->validateClient($request);
@@ -55,17 +56,18 @@ class PasswordGrant extends AbstractGrant
// Finalize the requested scopes // Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier()); $finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
// Issue and persist new tokens // Issue and persist new access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes); $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);
// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken); $refreshToken = $this->issueRefreshToken($accessToken);
// Send events to emitter if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); $responseType->setRefreshToken($refreshToken);
}
// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
return $responseType; return $responseType;
} }

View File

@@ -11,6 +11,8 @@
namespace League\OAuth2\Server\Grant; namespace League\OAuth2\Server\Grant;
use DateInterval;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\RequestEvent; use League\OAuth2\Server\RequestEvent;
@@ -29,7 +31,7 @@ class RefreshTokenGrant extends AbstractGrant
{ {
$this->setRefreshTokenRepository($refreshTokenRepository); $this->setRefreshTokenRepository($refreshTokenRepository);
$this->refreshTokenTTL = new \DateInterval('P1M'); $this->refreshTokenTTL = new DateInterval('P1M');
} }
/** /**
@@ -38,7 +40,7 @@ class RefreshTokenGrant extends AbstractGrant
public function respondToAccessTokenRequest( public function respondToAccessTokenRequest(
ServerRequestInterface $request, ServerRequestInterface $request,
ResponseTypeInterface $responseType, ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL DateInterval $accessTokenTTL
) { ) {
// Validate request // Validate request
$client = $this->validateClient($request); $client = $this->validateClient($request);
@@ -61,17 +63,18 @@ class RefreshTokenGrant extends AbstractGrant
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']); $this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_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); $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
$responseType->setAccessToken($accessToken);
// Issue and persist new refresh token if given
$refreshToken = $this->issueRefreshToken($accessToken); $refreshToken = $this->issueRefreshToken($accessToken);
// Send events to emitter if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); $responseType->setRefreshToken($refreshToken);
}
// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
return $responseType; return $responseType;
} }
@@ -94,8 +97,8 @@ class RefreshTokenGrant extends AbstractGrant
// Validate refresh token // Validate refresh token
try { try {
$refreshToken = $this->decrypt($encryptedRefreshToken); $refreshToken = $this->decrypt($encryptedRefreshToken);
} catch (\Exception $e) { } 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); $refreshTokenData = json_decode($refreshToken, true);

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Middleware; namespace League\OAuth2\Server\Middleware;
use Exception;
use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@@ -43,7 +44,7 @@ class AuthorizationServerMiddleware
} catch (OAuthServerException $exception) { } catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response); return $exception->generateHttpResponse($response);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (\Exception $exception) { } catch (Exception $exception) {
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
->generateHttpResponse($response); ->generateHttpResponse($response);
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd

View File

@@ -9,6 +9,7 @@
namespace League\OAuth2\Server\Middleware; namespace League\OAuth2\Server\Middleware;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\ResourceServer; use League\OAuth2\Server\ResourceServer;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@@ -34,7 +35,7 @@ class ResourceServerMiddleware
* @param ResponseInterface $response * @param ResponseInterface $response
* @param callable $next * @param callable $next
* *
* @return \Psr\Http\Message\ResponseInterface * @return ResponseInterface
*/ */
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
{ {
@@ -43,7 +44,7 @@ class ResourceServerMiddleware
} catch (OAuthServerException $exception) { } catch (OAuthServerException $exception) {
return $exception->generateHttpResponse($response); return $exception->generateHttpResponse($response);
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
} catch (\Exception $exception) { } catch (Exception $exception) {
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500)) return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
->generateHttpResponse($response); ->generateHttpResponse($response);
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd

View File

@@ -20,7 +20,7 @@ interface RefreshTokenRepositoryInterface extends RepositoryInterface
/** /**
* Creates a new refresh token * Creates a new refresh token
* *
* @return RefreshTokenEntityInterface * @return RefreshTokenEntityInterface|null
*/ */
public function getNewRefreshToken(); public function getNewRefreshToken();

View File

@@ -54,7 +54,7 @@ abstract class AbstractResponseType implements ResponseTypeInterface
/** /**
* Set the private key * Set the private key
* *
* @param \League\OAuth2\Server\CryptKey $key * @param CryptKey $key
*/ */
public function setPrivateKey(CryptKey $key) public function setPrivateKey(CryptKey $key)
{ {

View File

@@ -11,6 +11,7 @@
namespace League\OAuth2\Server\ResponseTypes; namespace League\OAuth2\Server\ResponseTypes;
use DateTime;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
@@ -28,7 +29,7 @@ class BearerTokenResponse extends AbstractResponseType
$responseParams = [ $responseParams = [
'token_type' => 'Bearer', 'token_type' => 'Bearer',
'expires_in' => $expireDateTime - (new \DateTime())->getTimestamp(), 'expires_in' => $expireDateTime - (new DateTime())->getTimestamp(),
'access_token' => (string) $jwtAccessToken, 'access_token' => (string) $jwtAccessToken,
]; ];

View File

@@ -3,6 +3,7 @@
namespace LeagueTests; namespace LeagueTests;
use League\OAuth2\Server\AuthorizationServer; use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AuthCodeGrant; use League\OAuth2\Server\Grant\AuthCodeGrant;
use League\OAuth2\Server\Grant\ClientCredentialsGrant; use League\OAuth2\Server\Grant\ClientCredentialsGrant;
@@ -109,6 +110,95 @@ class AuthorizationServerTest extends TestCase
$this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server)); $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';
$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('getResponseType');
$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() public function testCompleteAuthorizationRequest()
{ {
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock(); $clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();

View File

@@ -2,6 +2,7 @@
namespace LeagueTests\Exception; namespace LeagueTests\Exception;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@@ -20,4 +21,19 @@ class OAuthServerExceptionTest extends TestCase
$this->assertFalse($exceptionWithoutRedirect->hasRedirect()); $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());
}
} }

View File

@@ -346,6 +346,27 @@ class AbstractGrantTest extends TestCase
$this->assertEquals($accessToken, $refreshToken->getAccessToken()); $this->assertEquals($accessToken, $refreshToken->getAccessToken());
} }
public function testIssueNullRefreshToken()
{
$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() public function testIssueAccessToken()
{ {
$accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();

View File

@@ -644,6 +644,73 @@ class AuthCodeGrantTest extends TestCase
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken()); $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();
$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() public function testRespondToAccessTokenRequestCodeChallengePlain()
{ {
$client = new ClientEntity(); $client = new ClientEntity();

View File

@@ -94,7 +94,6 @@ class ImplicitGrantTest extends TestCase
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeEntity = new ScopeEntity(); $scopeEntity = new ScopeEntity();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock); $grant->setClientRepository($clientRepositoryMock);
@@ -129,7 +128,6 @@ class ImplicitGrantTest extends TestCase
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(); $scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeEntity = new ScopeEntity(); $scopeEntity = new ScopeEntity();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity); $scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock); $grant->setClientRepository($clientRepositoryMock);
@@ -286,9 +284,13 @@ class ImplicitGrantTest extends TestCase
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
} }
@@ -309,9 +311,13 @@ class ImplicitGrantTest extends TestCase
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf(); $accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->completeAuthorizationRequest($authRequest); $grant->completeAuthorizationRequest($authRequest);
} }
@@ -330,9 +336,13 @@ class ImplicitGrantTest extends TestCase
$accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); $accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
$accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf(); $accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf();
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest)); $this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
} }
@@ -354,9 +364,13 @@ class ImplicitGrantTest extends TestCase
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened')); $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened'));
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->completeAuthorizationRequest($authRequest); $grant->completeAuthorizationRequest($authRequest);
} }
@@ -378,9 +392,13 @@ class ImplicitGrantTest extends TestCase
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity()); $accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create()); $accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$grant = new ImplicitGrant(new \DateInterval('PT10M')); $grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->completeAuthorizationRequest($authRequest); $grant->completeAuthorizationRequest($authRequest);
} }

View File

@@ -78,6 +78,51 @@ class PasswordGrantTest extends TestCase
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
} }
public function testRespondToRequestNullRefreshToken()
{
$client = new ClientEntity();
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
$userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock();
$userEntity = new UserEntity();
$userRepositoryMock->method('getUserEntityByUserCredentials')->willReturn($userEntity);
$scope = new ScopeEntity();
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope);
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$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 * @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/ */

View File

@@ -94,6 +94,63 @@ class RefreshTokenGrantTest extends TestCase
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
} }
public function testRespondToRequestNullRefreshToken()
{
$client = new ClientEntity();
$client->setIdentifier('foo');
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
$scopeEntity = new ScopeEntity();
$scopeEntity->setIdentifier('foo');
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->expects($this->once())->method('persistNewAccessToken')->willReturnSelf();
$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() public function testRespondToReducedScopes()
{ {
$client = new ClientEntity(); $client = new ClientEntity();

View File

@@ -104,7 +104,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
$response = $exception->generateHttpResponse(new Response()); $response = $exception->generateHttpResponse(new Response());
$this->assertEquals(302, $response->getStatusCode()); $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]); $response->getHeader('location')[0]);
} }
@@ -114,7 +114,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
$response = $exception->generateHttpResponse(new Response(), true); $response = $exception->generateHttpResponse(new Response(), true);
$this->assertEquals(302, $response->getStatusCode()); $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]); $response->getHeader('location')[0]);
} }
} }