Compare commits

...

160 Commits
7.0.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
Andrew Millington
8184f771d4 Update for version 7.2.0 2018-06-23 17:57:59 +01:00
Andrew Millington
51b3b415b4 Update changelog for version 4.1.7 2018-06-23 17:46:19 +01:00
Andrew Millington
e3ad09d4a2 Update unreleased link in changelog 2018-06-23 17:35:51 +01:00
Andrew Millington
aeb1fe48d3 Add missing 4.1.6 release to changelog 2018-06-23 17:35:14 +01:00
Andrew Millington
f54980da25 Update changelog to add PR 917 2018-06-21 23:24:13 +01:00
Andrew Millington
105834af96 Merge pull request #917 from Erikvv/master
Allow 640 as key file permisions
2018-06-21 23:16:13 +01:00
Erik van Velzen
ffffc4bfeb Allow 640 as key file permisions 2018-06-21 17:02:01 +02:00
Andrew Millington
a77732e97c Merge pull request #912 from fizzka/extract-validate-uri
Extract validate uri
2018-06-15 14:42:01 +01:00
Ilya Bulah
614bba2c11 update changelog 2018-06-15 15:57:01 +03:00
Ilya Bulah
224763cda6 Fix docblock 2018-06-15 00:06:33 +03:00
Ilya Bulah
a31bc7d4cc Extract validateRedirectUri() 2018-06-14 23:50:58 +03:00
Ilya Bulah
0d20c755d4 Formatting 2018-06-14 23:50:58 +03:00
Ilya Bulah
e36ff17ad9 Fix psr2 2018-06-14 23:15:01 +03:00
Andrew Millington
a339d99135 Change sentence 2018-06-08 11:19:27 +01:00
Andrew Millington
a7a1065e38 Merge pull request #908 from fizzka/update-encryptionKey-comments
update encryptionKey comments
2018-06-05 10:34:56 +01:00
Andrew Millington
09bf988922 Add capital letter to start of class doc summary 2018-06-05 10:34:12 +01:00
Ilya Bulakh
a571e2262b Update CryptTrait.php 2018-06-04 16:32:02 +03:00
Andrew Millington
519543e925 Merge pull request #703 from iansltx/exception-has-redirect
Add hasRedirect() method for OAuthServerException

Fixes #694
2018-05-25 10:09:15 +01:00
Andrew Millington
3614f8bd7c Update changelog 2018-05-25 10:03:58 +01:00
Andrew Millington
e4a7fea834 Move OAuthServerExceptionTest to appropriate folder 2018-05-25 10:00:21 +01:00
Andrew Millington
68c9fbd83c Add a summary for hasRedirect function 2018-05-25 09:53:59 +01:00
Andrew Millington
466e1a639d Merge remote-tracking branch 'upstream/master' into exception-has-redirect 2018-05-25 09:49:14 +01:00
Andrew Millington
d7ab153500 Merge pull request #905 from Sephster/add-test-for-unsigned-token-exception
Add Exception Detection Test for Unsigned JWT
2018-05-25 09:42:19 +01:00
Andrew Millington
72ead2e3ce Fix unused use statement 2018-05-24 12:23:26 +01:00
Andrew Millington
ae4ab26aaf Add test for unsigned access token 2018-05-24 12:19:55 +01:00
Andrew Millington
2a7f671a95 Merge pull request #904 from dzibma/master
Fix uncaught exception produced by unsigned token
2018-05-22 18:13:17 +01:00
Andrew Millington
02609c37cc Update changelog 2018-05-22 18:10:19 +01:00
Martin Dzibela
9941a96feb Fix uncaught exception produced by unsigned token 2018-05-22 14:22:12 +02:00
Andrew Millington
2e47fa7fca Add PR reference 2018-05-21 15:01:37 +01:00
Andrew Millington
fc55621f20 Add link to 7.1.1 release in changelog 2018-05-21 15:00:06 +01:00
Andrew Millington
beec37d95f Modify changelog for 7.1.1 release 2018-05-21 14:58:56 +01:00
Andrew Millington
5a499bf03c Merge pull request #902 from Sephster/fix-745-without-tests
Only Add Authenticate Header if Present in Original Request. Fix #745
2018-05-17 13:34:30 +01:00
Andrew Millington
2e3ee60a2a Remove additional whitespace 2018-05-17 13:27:30 +01:00
Andrew Millington
0242d0c996 Remove spaces at end of line 2018-05-17 13:21:39 +01:00
Andrew Millington
3ea0cdc936 Set authScheme 2018-05-17 13:19:32 +01:00
Andrew Millington
19d782d223 Fix alignment 2018-05-17 13:13:30 +01:00
Andrew Millington
8a25e0a01b Update changelog 2018-05-17 13:12:32 +01:00
Andrew Millington
a3d4f583ed Fix #745 2018-05-17 13:06:03 +01:00
Andrew Millington
28276cb688 Add PSR-7 to the requirements in the readme
This fixes issue #640
2018-05-16 13:36:29 +01:00
Andrew Millington
35c6f28aef Add drupal integration to the readme 2018-05-03 17:06:27 +01:00
Andrew Millington
bd47b58f81 Update changelog for version 7.1.0 2018-04-22 15:16:23 +01:00
Andrew Millington
e38ea4ab34 Update code of conduct link in the readme 2018-04-22 12:58:05 +01:00
Andrew Millington
8b40858509 Rename CONDUCT.md to CODE_OF_CONDUCT.md 2018-04-21 22:25:51 +01:00
Andrew Millington
aa5cc5eac7 Adding contact email address 2018-04-21 22:22:53 +01:00
Andrew Millington
38f26d5229 Update our code of conduct 2018-04-21 22:07:30 +01:00
Andrew Millington
a5a51fad95 Add PR 893 to the changelog 2018-04-21 22:05:08 +01:00
Andrew Millington
52d7952ba5 Merge pull request #893 from Sephster/fix-exception-hint
Change hint so it applies to both the auth and access token requests
2018-04-21 22:02:09 +01:00
Andrew Millington
2375b8c7f3 Merge pull request #856 from lookyman/phpstan-level-7
PHPStan level 7
2018-04-21 22:01:25 +01:00
Andrew Millington
f9a91a79c2 Put upgrade of PHPStan under changed category 2018-04-21 21:59:47 +01:00
Andrew Millington
242dd4dcfe Fix docblock 2018-04-21 21:51:25 +01:00
Andrew Millington
c94ec122aa Fix PR number for changelog 2018-04-21 21:41:48 +01:00
Andrew Millington
05f5d90034 Update changelog 2018-04-21 21:39:28 +01:00
Andrew Millington
491c23c1e9 Merge remote-tracking branch 'upstream/master' into phpstan-level-7 2018-04-21 21:37:24 +01:00
Andrew Millington
27323b5c9a Fix spacing issue 2018-04-21 21:31:48 +01:00
Andrew Millington
80bc291c51 Added null checks before calling set functions 2018-04-21 21:29:21 +01:00
Andrew Millington
8a619e5c1e Change hint so it applies to both the auth and access token requests 2018-04-21 18:07:38 +01:00
Andrew Millington
7e07033b10 Merge pull request #892 from Sephster/fix-issue-837
Revert fix for client ID exception
2018-04-21 17:49:42 +01:00
Andrew Millington
6991777ff3 Fix blank line spacing issue 2018-04-20 18:33:46 +01:00
Andrew Millington
9febc32e14 Add spacing around logical blocks 2018-04-20 18:27:47 +01:00
Andrew Millington
c8b44ff5c7 Revert fix for client ID exception 2018-04-20 18:22:07 +01:00
Andrew Millington
9fc288ce53 Merge pull request #876 from steverhoades/fix-example
Fix fatal error in examples caused by ClientRepositoryInterface change
2018-03-17 20:43:53 +00:00
Steve Rhoades
8f1bf88792 Fix fatal error caused by ClientRepositoryInterface change 2018-03-17 09:30:14 -07:00
Andrew Millington
cc19da50b4 Merge pull request #814 from SunMar/master
Allow CryptTrait to accept a \Defuse\Crypto\Key as encryption key #812
2018-02-28 21:12:39 +00:00
Andrew Millington
bec0de16bb Update Changelog 2018-02-28 21:00:30 +00:00
Andrew Millington
a56acc8dd0 Minor code tidy up 2018-02-28 20:33:19 +00:00
Andrew Millington
c9b07f386c Fix StyleCI issues and remove phpdoc order from StyleCI 2018-02-28 20:01:01 +00:00
Andrew Millington
00a7972f74 Merge remote-tracking branch 'upstream/master' 2018-02-28 19:45:41 +00:00
Andrew Millington
e3266cb50a Fix changelog categorisation 2018-02-26 20:08:02 +00:00
Andrew Millington
2fdd6ce494 Add change for access and refresh token emitters 2018-02-26 20:07:02 +00:00
Andrew Millington
6fd3024c48 Merge pull request #860 from Zaszczyk/new-events-to-emitter-#825
Add new event types: access_token_issued and refresh_token_issued.
2018-02-26 20:01:22 +00:00
Andrew Millington
62e06b7d3a Removing Yoda condition
Removed Yoda condition from code base
2018-02-26 19:51:03 +00:00
Simon Hamp
009c109716 TravisCI fix for PHPStan 2018-02-26 16:04:48 +00:00
Simon Hamp
6723aadfe8 Fix #837
Unifies how we fetch the client_id from the request and allows us to throw a more appropriate exception when the client_id parameter is missing.

Improves the test method for this validation by checking the culpable method in this particular case. The test was missing this by calling the wrong method.
2018-02-26 15:56:28 +00:00
Andrew Millington
e24964af07 Update changelog
Add removal of paragone/random_compat to changelog
2018-02-26 12:57:11 +00:00
Andrew Millington
99e42f6f25 Remove paragonie/random_compat
Removing paragonie/random_compat as no longer supporting PHP 5.x branches
2018-02-26 12:38:31 +00:00
Mateusz Błaszczyk
6700b113a8 Add new event types: access_token_issued and refresh_token_issued. 2018-02-23 17:48:51 +01:00
Andrew Millington
28e1418f64 Change to use correct release date for version 7 2018-02-18 20:29:37 +00:00
Lukáš Unger
143afc9561 PHPStan level 7 2018-02-18 21:20:48 +01:00
SunMar
292272d128 Allow CryptTrait to accept a \Defuse\Crypto\Key as encryption key #812 2018-01-04 15:14:03 +01:00
Ian Littman
d8ece093d5 Add hasRedirect() method for OAuthServerException
Resolves #694.
2017-02-04 14:50:46 -05:00
54 changed files with 1259 additions and 369 deletions

View File

@@ -4,6 +4,7 @@ enabled:
- binary_operator_spaces
- blank_line_before_return
- concat_with_spaces
- fully_qualified_strict_types
- function_typehint_space
- hash_to_slash_comment
- include
@@ -29,7 +30,6 @@ enabled:
- phpdoc_inline_tag
- phpdoc_no_access
- phpdoc_no_simplified_null_return
- phpdoc_order
- phpdoc_property
- phpdoc_scalar
- phpdoc_separation
@@ -41,7 +41,6 @@ enabled:
- print_to_echo
- short_array_syntax
- short_scalar_cast
- simplified_null_return
- single_quote
- spaces_cast
- standardize_not_equal

View File

@@ -15,13 +15,14 @@ php:
- 7.0
- 7.1
- 7.2
- 7.3
install:
- composer update --no-interaction --prefer-dist $DEPENDENCIES
script:
- vendor/bin/phpunit --coverage-clover=coverage.clover
- vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests
- vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests
after_script:
- wget https://scrutinizer-ci.com/ocular.phar

View File

@@ -6,7 +6,77 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [7.0.0] - released 2018-02-17
## [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
### Changed
- Added new`validateRedirectUri` method AbstractGrant to remove three instances of code duplication (PR #912)
- Allow 640 as a crypt key file permission (PR #917)
### Added
- Function `hasRedirect()` added to `OAuthServerException` (PR #703)
### Fixed
- Catch and handle `BadMethodCallException` from the `verify()` method of the JWT token in the `validateAuthorization` method (PR #904)
## [4.1.7] - released 2018-06-23
### Fixed
- Ensure `empty()` function call only contains variable to be compatible with PHP 5.4 (PR #918)
## [7.1.1] - released 2018-05-21
### Fixed
- No longer set a WWW-Authenticate header for invalid clients if the client did not send an Authorization header in the original request (PR #902)
## [7.1.0] - released 2018-04-22
### Changed
- Changed hint for unsupportedGrantType exception so it no longer references the grant type parameter which isn't always expected (PR #893)
- Upgrade PHPStan checks to level 7 (PR #856)
### Added
- Added event emitters for issued access and refresh tokens (PR #860)
- Can now use Defuse\Crypto\Key for encryption/decryption of keys which is faster than the Cryto class (PR #812)
### Removed
- Remove paragone/random_compat from dependencies
## [7.0.0] - released 2018-02-18
### Added
- Use PHPStan for static analysis of code (PR #848)
@@ -79,6 +149,10 @@ To address feedback from the security release the following change has been made
- Fixed `finalizeScopes` call (Issue #650)
## [4.1.6] - 2016-09-13
- Less restrictive on Authorization header check (Issue #652)
## [5.1.1] - 2016-07-26
- Improved test suite (Issue #614)
@@ -371,7 +445,15 @@ Version 5 is a complete code rewrite.
- First major release
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.0.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.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.0.0]: https://github.com/thephpleague/oauth2-server/compare/6.1.1...7.0.0
[6.1.1]: https://github.com/thephpleague/oauth2-server/compare/6.0.0...6.1.1
[6.1.0]: https://github.com/thephpleague/oauth2-server/compare/6.0.2...6.1.0
@@ -389,6 +471,8 @@ Version 5 is a complete code rewrite.
[5.0.0]: https://github.com/thephpleague/oauth2-server/compare/5.0.0-RC2...5.0.0
[5.0.0-RC2]: https://github.com/thephpleague/oauth2-server/compare/5.0.0-RC1...5.0.0-RC2
[5.0.0-RC1]: https://github.com/thephpleague/oauth2-server/compare/4.1.5...5.0.0-RC1
[4.1.7]: https://github.com/thephpleague/oauth2-server/compare/4.1.6...4.1.7
[4.1.6]: https://github.com/thephpleague/oauth2-server/compare/4.1.5...4.1.6
[4.1.5]: https://github.com/thephpleague/oauth2-server/compare/4.1.4...4.1.5
[4.1.4]: https://github.com/thephpleague/oauth2-server/compare/4.1.3...4.1.4
[4.1.3]: https://github.com/thephpleague/oauth2-server/compare/4.1.2...4.1.3

73
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,73 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at andrew@noexceptions.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org

View File

@@ -1,22 +0,0 @@
# Contributor Code of Conduct
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community in a direct capacity. Personal views, beliefs and values of individuals do not necessarily reflect those of the organisation or affiliated individuals and organisations.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)

View File

@@ -34,9 +34,12 @@ The following versions of PHP are supported:
* PHP 7.0
* PHP 7.1
* PHP 7.2
* PHP 7.3
The `openssl` extension is also required.
All HTTP messages passed to the server should be [PSR-7 compliant](https://www.php-fig.org/psr/psr-7/). This ensures interoperability with other packages and frameworks.
## Installation
```
@@ -54,7 +57,7 @@ The library uses [PHPUnit](https://phpunit.de/) for unit tests and [PHPStan](htt
```
vendor/bin/phpunit
vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests
vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests
```
## Continous Integration
@@ -63,8 +66,11 @@ We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci
## Community Integrations
* [Drupal](https://www.drupal.org/project/simple_oauth)
* [Laravel Passport](https://github.com/laravel/passport)
* [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server)
* [OAuth 2 Server for Expressive](https://github.com/zendframework/zend-expressive-authentication-oauth2)
* [Trikoder OAuth 2 Bundle (Symfony)](https://github.com/trikoder/oauth2-bundle)
## Changelog
@@ -72,7 +78,7 @@ See the [project changelog](https://github.com/thephpleague/oauth2-server/blob/m
## Contributing
Contributions are always welcome. Please see [CONTRIBUTING.md](https://github.com/thephpleague/oauth2-server/blob/master/CONTRIBUTING.md) and [CONDUCT.md](https://github.com/thephpleague/oauth2-server/blob/master/CONDUCT.md) for details.
Contributions are always welcome. Please see [CONTRIBUTING.md](https://github.com/thephpleague/oauth2-server/blob/master/CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](https://github.com/thephpleague/oauth2-server/blob/master/CODE_OF_CONDUCT.md) for details.
## Support

View File

@@ -8,7 +8,6 @@
"ext-openssl": "*",
"league/event": "^2.1",
"lcobucci/jwt": "^3.2.2",
"paragonie/random_compat": "^2.0",
"psr/http-message": "^1.0.1",
"defuse/php-encryption": "^2.1"
},
@@ -17,7 +16,8 @@
"zendframework/zend-diactoros": "^1.3.2",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-phpunit": "^0.9.4",
"phpstan/phpstan-strict-rules": "^0.9.0"
"phpstan/phpstan-strict-rules": "^0.9.0",
"roave/security-advisories": "dev-master"
},
"repositories": [
{
@@ -47,6 +47,12 @@
"email": "hello@alexbilbie.com",
"homepage": "http://www.alexbilbie.com",
"role": "Developer"
},
{
"name": "Andy Millington",
"email": "andrew@noexceptions.io",
"homepage": "https://www.noexceptions.io",
"role": "Developer"
}
],
"replace": {

View File

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

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",
"This file is @generated automatically"
],
"content-hash": "9813ed7c3b6dcf107f44df9392935b8f",
"content-hash": "97f2878428e37d1d8e5418cc85cbfa3d",
"packages": [
{
"name": "container-interop/container-interop",
@@ -39,21 +39,24 @@
},
{
"name": "nikic/fast-route",
"version": "v0.6.0",
"version": "v1.3.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/FastRoute.git",
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22"
"reference": "181d480e08d9476e61381e04a71b34dc0432e812"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22",
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22",
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
"reference": "181d480e08d9476e61381e04a71b34dc0432e812",
"shasum": ""
},
"require": {
"php": ">=5.4.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35|~5.7"
},
"type": "library",
"autoload": {
"psr-4": {
@@ -78,29 +81,33 @@
"router",
"routing"
],
"time": "2015-06-18T19:15:47+00:00"
"time": "2018-02-13T20:26:39+00:00"
},
{
"name": "pimple/pimple",
"version": "v3.0.2",
"version": "v3.2.3",
"source": {
"type": "git",
"url": "https://github.com/silexphp/Pimple.git",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a",
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
"php": ">=5.3.0",
"psr/container": "^1.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
"dev-master": "3.2.x-dev"
}
},
"autoload": {
@@ -124,7 +131,7 @@
"container",
"dependency injection"
],
"time": "2015-09-11T15:10:35+00:00"
"time": "2018-01-21T07:42:36+00:00"
},
{
"name": "psr/container",
@@ -227,27 +234,32 @@
},
{
"name": "slim/slim",
"version": "3.0.0",
"version": "3.11.0",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
"reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e"
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/3b06f0f2d84dabbe81b6cea46ace46a3e883253e",
"reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"shasum": ""
},
"require": {
"container-interop/container-interop": "^1.1",
"nikic/fast-route": "^0.6",
"container-interop/container-interop": "^1.2",
"nikic/fast-route": "^1.0",
"php": ">=5.5.0",
"pimple/pimple": "^3.0",
"psr/container": "^1.0",
"psr/http-message": "^1.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.0"
"phpunit/phpunit": "^4.0",
"squizlabs/php_codesniffer": "^2.5"
},
"type": "library",
"autoload": {
@@ -282,38 +294,38 @@
}
],
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
"homepage": "http://slimframework.com",
"homepage": "https://slimframework.com",
"keywords": [
"api",
"framework",
"micro",
"router"
],
"time": "2015-12-07T14:11:09+00:00"
"time": "2018-09-16T10:54:21+00:00"
}
],
"packages-dev": [
{
"name": "defuse/php-encryption",
"version": "v2.1.0",
"version": "v2.2.1",
"source": {
"type": "git",
"url": "https://github.com/defuse/php-encryption.git",
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689"
"reference": "0f407c43b953d571421e0020ba92082ed5fb7620"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689",
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689",
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/0f407c43b953d571421e0020ba92082ed5fb7620",
"reference": "0f407c43b953d571421e0020ba92082ed5fb7620",
"shasum": ""
},
"require": {
"ext-openssl": "*",
"paragonie/random_compat": "~2.0",
"paragonie/random_compat": ">= 2",
"php": ">=5.4.0"
},
"require-dev": {
"nikic/php-parser": "^2.0|^3.0",
"nikic/php-parser": "^2.0|^3.0|^4.0",
"phpunit/phpunit": "^4|^5"
},
"bin": [
@@ -354,20 +366,20 @@
"security",
"symmetric key cryptography"
],
"time": "2017-05-18T21:28:48+00:00"
"time": "2018-07-24T23:27:56+00:00"
},
{
"name": "lcobucci/jwt",
"version": "3.2.1",
"version": "3.2.4",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "ddce703826f9c5229781933b1a39069e38e6a0f3"
"reference": "c9704b751315d21735dc98d78d4f37bd73596da7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/ddce703826f9c5229781933b1a39069e38e6a0f3",
"reference": "ddce703826f9c5229781933b1a39069e38e6a0f3",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/c9704b751315d21735dc98d78d4f37bd73596da7",
"reference": "c9704b751315d21735dc98d78d4f37bd73596da7",
"shasum": ""
},
"require": {
@@ -412,7 +424,7 @@
"JWS",
"jwt"
],
"time": "2016-10-31T20:09:32+00:00"
"time": "2018-08-03T11:23:50+00:00"
},
{
"name": "league/event",
@@ -466,33 +478,29 @@
},
{
"name": "paragonie/random_compat",
"version": "v2.0.10",
"version": "v9.99.99",
"source": {
"type": "git",
"url": "https://github.com/paragonie/random_compat.git",
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d"
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d",
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d",
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
"shasum": ""
},
"require": {
"php": ">=5.2.0"
"php": "^7"
},
"require-dev": {
"phpunit/phpunit": "4.*|5.*"
"phpunit/phpunit": "4.*|5.*",
"vimeo/psalm": "^1"
},
"suggest": {
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
},
"type": "library",
"autoload": {
"files": [
"lib/random.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
@@ -507,10 +515,129 @@
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
"keywords": [
"csprng",
"polyfill",
"pseudorandom",
"random"
],
"time": "2017-03-13T16:27:32+00:00"
"time": "2018-07-02T15:55:56+00:00"
},
{
"name": "psr/http-factory",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
"reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
"shasum": ""
},
"require": {
"php": ">=7.0.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interfaces for PSR-7 HTTP message factories",
"keywords": [
"factory",
"http",
"message",
"psr",
"psr-17",
"psr-7",
"request",
"response"
],
"time": "2018-07-30T21:54:04+00:00"
},
{
"name": "zendframework/zend-diactoros",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-diactoros.git",
"reference": "0bae78192e634774b5584f0210c1232da82cb1ff"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/0bae78192e634774b5584f0210c1232da82cb1ff",
"reference": "0bae78192e634774b5584f0210c1232da82cb1ff",
"shasum": ""
},
"require": {
"php": "^7.1",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0"
},
"provide": {
"psr/http-factory-implementation": "1.0",
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"ext-dom": "*",
"ext-libxml": "*",
"http-interop/http-factory-tests": "^0.5.0",
"php-http/psr7-integration-tests": "dev-master",
"phpunit/phpunit": "^7.0.2",
"zendframework/zend-coding-standard": "~1.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev",
"dev-develop": "2.1.x-dev",
"dev-release-1.8": "1.8.x-dev"
}
},
"autoload": {
"files": [
"src/functions/create_uploaded_file.php",
"src/functions/marshal_headers_from_sapi.php",
"src/functions/marshal_method_from_sapi.php",
"src/functions/marshal_protocol_version_from_sapi.php",
"src/functions/marshal_uri_from_sapi.php",
"src/functions/normalize_server.php",
"src/functions/normalize_uploaded_files.php",
"src/functions/parse_cookie_header.php"
],
"psr-4": {
"Zend\\Diactoros\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"description": "PSR HTTP Message implementations",
"keywords": [
"http",
"psr",
"psr-7"
],
"time": "2018-09-27T19:49:04+00:00"
}
],
"aliases": [],

View File

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

View File

@@ -17,7 +17,7 @@ class ClientRepository implements ClientRepositoryInterface
/**
* {@inheritdoc}
*/
public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true)
public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true)
{
$clients = [
'myawesomeapp' => [

View File

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

View File

@@ -3,3 +3,8 @@ includes:
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-phpunit/strictRules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
services:
-
class: LeagueTests\PHPStan\AbstractGrantExtension
tags:
- phpstan.broker.dynamicMethodReturnTypeExtension

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server;
use DateInterval;
use Defuse\Crypto\Key;
use League\Event\EmitterAwareInterface;
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -33,7 +35,7 @@ class AuthorizationServer implements EmitterAwareInterface
protected $enabledGrantTypes = [];
/**
* @var \DateInterval[]
* @var DateInterval[]
*/
protected $grantTypeAccessTokenTTL = [];
@@ -48,7 +50,7 @@ class AuthorizationServer implements EmitterAwareInterface
protected $publicKey;
/**
* @var null|ResponseTypeInterface
* @var ResponseTypeInterface
*/
protected $responseType;
@@ -68,7 +70,7 @@ class AuthorizationServer implements EmitterAwareInterface
private $scopeRepository;
/**
* @var string
* @var string|Key
*/
private $encryptionKey;
@@ -84,7 +86,7 @@ class AuthorizationServer implements EmitterAwareInterface
* @param AccessTokenRepositoryInterface $accessTokenRepository
* @param ScopeRepositoryInterface $scopeRepository
* @param CryptKey|string $privateKey
* @param string $encryptionKey
* @param string|Key $encryptionKey
* @param null|ResponseTypeInterface $responseType
*/
public function __construct(
@@ -102,8 +104,16 @@ class AuthorizationServer implements EmitterAwareInterface
if ($privateKey instanceof CryptKey === false) {
$privateKey = new CryptKey($privateKey);
}
$this->privateKey = $privateKey;
$this->encryptionKey = $encryptionKey;
if ($responseType === null) {
$responseType = new BearerTokenResponse();
} else {
$responseType = clone $responseType;
}
$this->responseType = $responseType;
}
@@ -111,12 +121,12 @@ class AuthorizationServer implements EmitterAwareInterface
* Enable a grant type on the server.
*
* @param GrantTypeInterface $grantType
* @param null|\DateInterval $accessTokenTTL
* @param null|DateInterval $accessTokenTTL
*/
public function enableGrantType(GrantTypeInterface $grantType, \DateInterval $accessTokenTTL = null)
public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null)
{
if ($accessTokenTTL instanceof \DateInterval === false) {
$accessTokenTTL = new \DateInterval('PT1H');
if ($accessTokenTTL instanceof DateInterval === false) {
$accessTokenTTL = new DateInterval('PT1H');
}
$grantType->setAccessTokenRepository($this->accessTokenRepository);
@@ -203,16 +213,15 @@ class AuthorizationServer implements EmitterAwareInterface
*/
protected function getResponseType()
{
if ($this->responseType instanceof ResponseTypeInterface === false) {
$this->responseType = new BearerTokenResponse();
$responseType = clone $this->responseType;
if ($responseType instanceof AbstractResponseType) {
$responseType->setPrivateKey($this->privateKey);
}
if ($this->responseType instanceof AbstractResponseType === true) {
$this->responseType->setPrivateKey($this->privateKey);
}
$this->responseType->setEncryptionKey($this->encryptionKey);
$responseType->setEncryptionKey($this->encryptionKey);
return $this->responseType;
return $responseType;
}
/**

View File

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

View File

@@ -11,6 +11,9 @@
namespace League\OAuth2\Server;
use LogicException;
use RuntimeException;
class CryptKey
{
const RSA_KEY_PATTERN =
@@ -42,13 +45,13 @@ class CryptKey
}
if (!file_exists($keyPath) || !is_readable($keyPath)) {
throw new \LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
}
if ($keyPermissionsCheck === true) {
// Verify the permissions of the key
$keyPathPerms = decoct(fileperms($keyPath) & 0777);
if (in_array($keyPathPerms, ['400', '440', '600', '660'], true) === false) {
if (in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
trigger_error(sprintf(
'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
$keyPath,
@@ -64,7 +67,7 @@ class CryptKey
/**
* @param string $key
*
* @throws \RuntimeException
* @throws RuntimeException
*
* @return string
*/
@@ -79,19 +82,19 @@ class CryptKey
if (!touch($keyPath)) {
// @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('"%s" key file could not be created', $keyPath));
throw new RuntimeException(sprintf('"%s" key file could not be created', $keyPath));
// @codeCoverageIgnoreEnd
}
if (file_put_contents($keyPath, $key) === false) {
// @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
throw new RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
// @codeCoverageIgnoreEnd
}
if (chmod($keyPath, 0600) === false) {
// @codeCoverageIgnoreStart
throw new \RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
throw new RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
// @codeCoverageIgnoreEnd
}

View File

@@ -1,6 +1,6 @@
<?php
/**
* Public/private key encryption.
* Encrypt/decrypt with encryptionKey.
*
* @author Alex Bilbie <hello@alexbilbie.com>
* @copyright Copyright (c) Alex Bilbie
@@ -12,54 +12,65 @@
namespace League\OAuth2\Server;
use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use Exception;
use LogicException;
trait CryptTrait
{
/**
* @var string
* @var string|Key
*/
protected $encryptionKey;
/**
* Encrypt data with a private key.
* Encrypt data with encryptionKey.
*
* @param string $unencryptedData
*
* @throws \LogicException
* @throws LogicException
*
* @return string
*/
protected function encrypt($unencryptedData)
{
try {
if ($this->encryptionKey instanceof Key) {
return Crypto::encrypt($unencryptedData, $this->encryptionKey);
}
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
} catch (\Exception $e) {
throw new \LogicException($e->getMessage());
} catch (Exception $e) {
throw new LogicException($e->getMessage(), null, $e);
}
}
/**
* Decrypt data with a public key.
* Decrypt data with encryptionKey.
*
* @param string $encryptedData
*
* @throws \LogicException
* @throws LogicException
*
* @return string
*/
protected function decrypt($encryptedData)
{
try {
if ($this->encryptionKey instanceof Key) {
return Crypto::decrypt($encryptedData, $this->encryptionKey);
}
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
} catch (\Exception $e) {
throw new \LogicException($e->getMessage());
} catch (Exception $e) {
throw new LogicException($e->getMessage(), null, $e);
}
}
/**
* Set the encryption key
*
* @param string $key
* @param string|Key $key
*/
public function setEncryptionKey($key = null)
{

View File

@@ -12,7 +12,7 @@ namespace League\OAuth2\Server\Entities;
interface AuthCodeEntityInterface extends TokenInterface
{
/**
* @return string
* @return string|null
*/
public function getRedirectUri();

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,7 +17,7 @@ trait AuthCodeTrait
protected $redirectUri;
/**
* @return string
* @return string|null
*/
public function getRedirectUri()
{

View File

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

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

View File

@@ -9,9 +9,11 @@
namespace League\OAuth2\Server\Exception;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class OAuthServerException extends \Exception
class OAuthServerException extends Exception
{
/**
* @var int
@@ -47,17 +49,18 @@ class OAuthServerException extends \Exception
* @param int $httpStatusCode HTTP status code to send (default = 400)
* @param null|string $hint A helper hint
* @param null|string $redirectUri A HTTP URI to redirect the user back to
* @param Throwable $previous Previous exception
*/
public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null)
public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null)
{
parent::__construct($message, $code);
parent::__construct($message, $code, $previous);
$this->httpStatusCode = $httpStatusCode;
$this->errorType = $errorType;
$this->hint = $hint;
$this->redirectUri = $redirectUri;
$this->payload = [
'error' => $errorType,
'message' => $message,
'error' => $errorType,
'error_description' => $message,
];
if ($hint !== null) {
$this->payload['hint'] = $hint;
@@ -71,7 +74,15 @@ class OAuthServerException extends \Exception
*/
public function getPayload()
{
return $this->payload;
$payload = $this->payload;
// The "message" property is deprecated and replaced by "error_description"
// TODO: remove "message" property
if (isset($payload['error_description']) && !isset($payload['message'])) {
$payload['message'] = $payload['error_description'];
}
return $payload;
}
/**
@@ -92,7 +103,7 @@ class OAuthServerException extends \Exception
public static function unsupportedGrantType()
{
$errorMessage = 'The authorization grant type is not supported by the authorization server.';
$hint = 'Check the `grant_type` parameter';
$hint = 'Check that all required parameters have been provided';
return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
}
@@ -102,16 +113,17 @@ class OAuthServerException extends \Exception
*
* @param string $parameter The invalid parameter
* @param null|string $hint
* @param Throwable $previous Previous exception
*
* @return static
*/
public static function invalidRequest($parameter, $hint = null)
public static function invalidRequest($parameter, $hint = null, Throwable $previous = null)
{
$errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
'includes a parameter more than once, or is otherwise malformed.';
$hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
return new static($errorMessage, 3, 'invalid_request', 400, $hint);
return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
}
/**
@@ -163,20 +175,24 @@ class OAuthServerException extends \Exception
/**
* Server error.
*
* @param string $hint
* @param string $hint
* @param Throwable $previous
*
* @return static
*
* @codeCoverageIgnore
*/
public static function serverError($hint)
public static function serverError($hint, Throwable $previous = null)
{
return new static(
'The authorization server encountered an unexpected condition which prevented it from fulfilling'
. ' the request: ' . $hint,
7,
'server_error',
500
500,
null,
null,
$previous
);
}
@@ -184,12 +200,13 @@ class OAuthServerException extends \Exception
* Invalid refresh token.
*
* @param null|string $hint
* @param Throwable $previous
*
* @return static
*/
public static function invalidRefreshToken($hint = null)
public static function invalidRefreshToken($hint = null, Throwable $previous = null)
{
return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint);
return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous);
}
/**
@@ -197,10 +214,11 @@ class OAuthServerException extends \Exception
*
* @param null|string $hint
* @param null|string $redirectUri
* @param Throwable $previous
*
* @return static
*/
public static function accessDenied($hint = null, $redirectUri = null)
public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null)
{
return new static(
'The resource owner or authorization server denied the request.',
@@ -208,7 +226,8 @@ class OAuthServerException extends \Exception
'access_denied',
401,
$hint,
$redirectUri
$redirectUri,
$previous
);
}
@@ -294,19 +313,30 @@ class OAuthServerException extends \Exception
// include the "WWW-Authenticate" response header field
// matching the authentication scheme used by the client.
// @codeCoverageIgnoreStart
if ($this->errorType === 'invalid_client') {
$authScheme = 'Basic';
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false
&& strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0
) {
$authScheme = 'Bearer';
}
if ($this->errorType === 'invalid_client' && array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false) {
$authScheme = strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0 ? 'Bearer' : 'Basic';
$headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
}
// @codeCoverageIgnoreEnd
return $headers;
}
/**
* Check if the exception has an associated redirect URI.
*
* Returns whether the exception includes a redirect, since
* getHttpStatusCode() doesn't return a 302 when there's a
* redirect enabled. This helps when you want to override local
* error pages but want to let redirects through.
*
* @return bool
*/
public function hasRedirect()
{
return $this->redirectUri !== null;
}
/**
* Returns the HTTP status code to send when the exceptions is output.
*

View File

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

View File

@@ -10,6 +10,10 @@
*/
namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use Error;
use Exception;
use League\Event\EmitterAwareTrait;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\CryptTrait;
@@ -28,7 +32,9 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use TypeError;
/**
* Abstract grant class.
@@ -72,12 +78,12 @@ abstract class AbstractGrant implements GrantTypeInterface
protected $userRepository;
/**
* @var \DateInterval
* @var DateInterval
*/
protected $refreshTokenTTL;
/**
* @var \League\OAuth2\Server\CryptKey
* @var CryptKey
*/
protected $privateKey;
@@ -137,7 +143,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/**
* {@inheritdoc}
*/
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL)
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
{
$this->refreshTokenTTL = $refreshTokenTTL;
}
@@ -145,7 +151,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/**
* Set the private key
*
* @param \League\OAuth2\Server\CryptKey $key
* @param CryptKey $key
*/
public function setPrivateKey(CryptKey $key)
{
@@ -174,7 +180,7 @@ abstract class AbstractGrant implements GrantTypeInterface
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
if (is_null($clientId)) {
if ($clientId === null) {
throw OAuthServerException::invalidRequest('client_id');
}
@@ -193,32 +199,48 @@ abstract class AbstractGrant implements GrantTypeInterface
throw OAuthServerException::invalidClient();
}
// If a redirect URI is provided ensure it matches what is pre-registered
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
if ($redirectUri !== null) {
if (
is_string($client->getRedirectUri())
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
} elseif (
is_array($client->getRedirectUri())
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
$this->validateRedirectUri($redirectUri, $client, $request);
}
return $client;
}
/**
* Validate redirectUri from the request.
* If a redirect URI is provided ensure it matches what is pre-registered
*
* @param string $redirectUri
* @param ClientEntityInterface $client
* @param ServerRequestInterface $request
*
* @throws OAuthServerException
*/
protected function validateRedirectUri(
string $redirectUri,
ClientEntityInterface $client,
ServerRequestInterface $request
) {
if (\is_string($client->getRedirectUri())
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
} elseif (\is_array($client->getRedirectUri())
&& \in_array($redirectUri, $client->getRedirectUri(), true) === false
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
}
/**
* Validate scopes in the request.
*
* @param string $scopes
* @param string $redirectUri
* @param string|array $scopes
* @param string $redirectUri
*
* @throws OAuthServerException
*
@@ -226,13 +248,13 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function validateScopes($scopes, $redirectUri = null)
{
$scopesList = array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
return !empty($scope);
});
if (!\is_array($scopes)) {
$scopes = $this->convertScopesQueryStringToArray($scopes);
}
$validScopes = [];
foreach ($scopesList as $scopeItem) {
foreach ($scopes as $scopeItem) {
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
if ($scope instanceof ScopeEntityInterface === false) {
@@ -245,6 +267,20 @@ abstract class AbstractGrant implements GrantTypeInterface
return $validScopes;
}
/**
* Converts a scopes query string to an array to easily iterate for validation.
*
* @param string $scopes
*
* @return array
*/
private function convertScopesQueryStringToArray($scopes)
{
return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
return !empty($scope);
});
}
/**
* Retrieve request parameter.
*
@@ -258,7 +294,7 @@ abstract class AbstractGrant implements GrantTypeInterface
{
$requestParameters = (array) $request->getParsedBody();
return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
return $requestParameters[$parameter] ?? $default;
}
/**
@@ -339,7 +375,7 @@ abstract class AbstractGrant implements GrantTypeInterface
/**
* Issue an access token.
*
* @param \DateInterval $accessTokenTTL
* @param DateInterval $accessTokenTTL
* @param ClientEntityInterface $client
* @param string|null $userIdentifier
* @param ScopeEntityInterface[] $scopes
@@ -350,7 +386,7 @@ abstract class AbstractGrant implements GrantTypeInterface
* @return AccessTokenEntityInterface
*/
protected function issueAccessToken(
\DateInterval $accessTokenTTL,
DateInterval $accessTokenTTL,
ClientEntityInterface $client,
$userIdentifier,
array $scopes = []
@@ -360,7 +396,7 @@ abstract class AbstractGrant implements GrantTypeInterface
$accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
$accessToken->setClient($client);
$accessToken->setUserIdentifier($userIdentifier);
$accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL));
$accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL));
foreach ($scopes as $scope) {
$accessToken->addScope($scope);
@@ -383,10 +419,10 @@ abstract class AbstractGrant implements GrantTypeInterface
/**
* Issue an auth code.
*
* @param \DateInterval $authCodeTTL
* @param DateInterval $authCodeTTL
* @param ClientEntityInterface $client
* @param string $userIdentifier
* @param string $redirectUri
* @param string|null $redirectUri
* @param ScopeEntityInterface[] $scopes
*
* @throws OAuthServerException
@@ -395,7 +431,7 @@ abstract class AbstractGrant implements GrantTypeInterface
* @return AuthCodeEntityInterface
*/
protected function issueAuthCode(
\DateInterval $authCodeTTL,
DateInterval $authCodeTTL,
ClientEntityInterface $client,
$userIdentifier,
$redirectUri,
@@ -404,10 +440,13 @@ abstract class AbstractGrant implements GrantTypeInterface
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$authCode = $this->authCodeRepository->getNewAuthCode();
$authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL));
$authCode->setExpiryDateTime((new DateTime())->add($authCodeTTL));
$authCode->setClient($client);
$authCode->setUserIdentifier($userIdentifier);
$authCode->setRedirectUri($redirectUri);
if ($redirectUri !== null) {
$authCode->setRedirectUri($redirectUri);
}
foreach ($scopes as $scope) {
$authCode->addScope($scope);
@@ -433,16 +472,21 @@ abstract class AbstractGrant implements GrantTypeInterface
* @throws OAuthServerException
* @throws UniqueTokenIdentifierConstraintViolationException
*
* @return RefreshTokenEntityInterface
* @return RefreshTokenEntityInterface|null
*/
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
{
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
$refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL));
if ($refreshToken === null) {
return null;
}
$refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL));
$refreshToken->setAccessToken($accessToken);
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
while ($maxGenerationAttempts-- > 0) {
$refreshToken->setIdentifier($this->generateUniqueIdentifier());
try {
@@ -471,13 +515,13 @@ abstract class AbstractGrant implements GrantTypeInterface
try {
return bin2hex(random_bytes($length));
// @codeCoverageIgnoreStart
} catch (\TypeError $e) {
throw OAuthServerException::serverError('An unexpected error has occurred');
} catch (\Error $e) {
throw OAuthServerException::serverError('An unexpected error has occurred');
} catch (\Exception $e) {
} catch (TypeError $e) {
throw OAuthServerException::serverError('An unexpected error has occurred', $e);
} catch (Error $e) {
throw OAuthServerException::serverError('An unexpected error has occurred', $e);
} catch (Exception $e) {
// If you get this message, the CSPRNG failed hard.
throw OAuthServerException::serverError('Could not generate a random string');
throw OAuthServerException::serverError('Could not generate a random string', $e);
}
// @codeCoverageIgnoreEnd
}
@@ -508,7 +552,7 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function validateAuthorizationRequest(ServerRequestInterface $request)
{
throw new \LogicException('This grant cannot validate an authorization request');
throw new LogicException('This grant cannot validate an authorization request');
}
/**
@@ -516,6 +560,6 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
throw new \LogicException('This grant cannot complete an authorization request');
throw new LogicException('This grant cannot complete an authorization request');
}
}

View File

@@ -9,8 +9,10 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use Exception;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
@@ -19,12 +21,14 @@ use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use stdClass;
class AuthCodeGrant extends AbstractAuthorizeGrant
{
/**
* @var \DateInterval
* @var DateInterval
*/
private $authCodeTTL;
@@ -36,17 +40,19 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
/**
* @param AuthCodeRepositoryInterface $authCodeRepository
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
* @param \DateInterval $authCodeTTL
* @param DateInterval $authCodeTTL
*
* @throws Exception
*/
public function __construct(
AuthCodeRepositoryInterface $authCodeRepository,
RefreshTokenRepositoryInterface $refreshTokenRepository,
\DateInterval $authCodeTTL
DateInterval $authCodeTTL
) {
$this->setAuthCodeRepository($authCodeRepository);
$this->setRefreshTokenRepository($refreshTokenRepository);
$this->authCodeTTL = $authCodeTTL;
$this->refreshTokenTTL = new \DateInterval('P1M');
$this->refreshTokenTTL = new DateInterval('P1M');
}
public function enableCodeExchangeProof()
@@ -59,7 +65,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
*
* @param ServerRequestInterface $request
* @param ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL
* @param DateInterval $accessTokenTTL
*
* @throws OAuthServerException
*
@@ -68,7 +74,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
@@ -78,58 +84,25 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
throw OAuthServerException::invalidRequest('code');
}
// Validate the authorization code
try {
$authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
if (time() > $authCodePayload->expire_time) {
throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
}
if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
}
$this->validateAuthorizationCode($authCodePayload, $client, $request);
if ($authCodePayload->client_id !== $client->getIdentifier()) {
throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
}
// The redirect URI is required in this request
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
throw OAuthServerException::invalidRequest('redirect_uri');
}
if ($authCodePayload->redirect_uri !== $redirectUri) {
throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
}
$scopes = [];
foreach ($authCodePayload->scopes as $scopeId) {
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
if ($scope instanceof ScopeEntityInterface === false) {
// @codeCoverageIgnoreStart
throw OAuthServerException::invalidScope($scopeId);
// @codeCoverageIgnoreEnd
}
$scopes[] = $scope;
}
// Finalize the requested scopes
$scopes = $this->scopeRepository->finalizeScopes(
$scopes,
$this->validateScopes($authCodePayload->scopes),
$this->getIdentifier(),
$client,
$authCodePayload->user_id
);
} catch (\LogicException $e) {
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
} catch (LogicException $e) {
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e);
}
// Validate code challenge
if ($this->enableCodeExchangeProof === true) {
$codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
if ($codeVerifier === null) {
throw OAuthServerException::invalidRequest('code_verifier');
}
@@ -172,13 +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);
$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);
// Inject tokens into response type
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
if ($refreshToken !== null) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
$responseType->setRefreshToken($refreshToken);
}
// Revoke used auth code
$this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
@@ -186,6 +164,41 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
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.
*
@@ -218,7 +231,8 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$request,
$this->getServerParameter('PHP_AUTH_USER', $request)
);
if (is_null($clientId)) {
if ($clientId === null) {
throw OAuthServerException::invalidRequest('client_id');
}
@@ -235,26 +249,15 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
}
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
if ($redirectUri !== null) {
if (
is_string($client->getRedirectUri())
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
} elseif (
is_array($client->getRedirectUri())
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|| empty($client->getRedirectUri())) {
$this->validateRedirectUri($redirectUri, $client, $request);
} elseif (empty($client->getRedirectUri()) ||
(\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
} else {
$redirectUri = is_array($client->getRedirectUri())
$redirectUri = \is_array($client->getRedirectUri())
? $client->getRedirectUri()[0]
: $client->getRedirectUri();
}
@@ -270,7 +273,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$authorizationRequest->setGrantTypeId($this->getIdentifier());
$authorizationRequest->setClient($client);
$authorizationRequest->setRedirectUri($redirectUri);
$authorizationRequest->setState($stateParameter);
if ($stateParameter !== null) {
$authorizationRequest->setState($stateParameter);
}
$authorizationRequest->setScopes($scopes);
if ($this->enableCodeExchangeProof === true) {
@@ -280,7 +287,8 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
}
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
if (in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) {
if (\in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) {
throw OAuthServerException::invalidRequest(
'code_challenge_method',
'Code challenge method must be `plain` or `S256`'
@@ -309,14 +317,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
}
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
? is_array($authorizationRequest->getClient()->getRedirectUri())
? $authorizationRequest->getClient()->getRedirectUri()[0]
: $authorizationRequest->getClient()->getRedirectUri()
: $authorizationRequest->getRedirectUri();
$finalRedirectUri = $authorizationRequest->getRedirectUri()
?? $this->getClientRedirectUri($authorizationRequest);
// The user approved the client, redirect them back with an auth code
if ($authorizationRequest->isAuthorizationApproved() === true) {
@@ -334,7 +339,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
'auth_code_id' => $authCode->getIdentifier(),
'scopes' => $authCode->getScopes(),
'user_id' => $authCode->getUserIdentifier(),
'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'),
'expire_time' => (new DateTime())->add($this->authCodeTTL)->format('U'),
'code_challenge' => $authorizationRequest->getCodeChallenge(),
'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
];
@@ -368,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,8 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ServerRequestInterface;
@@ -25,7 +27,7 @@ class ClientCredentialsGrant extends AbstractGrant
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
@@ -37,6 +39,9 @@ class ClientCredentialsGrant extends AbstractGrant
// Issue and persist access token
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes);
// Send event to emitter
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
// Inject access token into response type
$responseType->setAccessToken($accessToken);

View File

@@ -11,6 +11,8 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use Defuse\Crypto\Key;
use League\Event\EmitterAwareInterface;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
@@ -28,9 +30,9 @@ interface GrantTypeInterface extends EmitterAwareInterface
/**
* Set refresh token TTL.
*
* @param \DateInterval $refreshTokenTTL
* @param DateInterval $refreshTokenTTL
*/
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL);
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL);
/**
* Return the grant identifier that can be used in matching up requests.
@@ -44,14 +46,14 @@ interface GrantTypeInterface extends EmitterAwareInterface
*
* @param ServerRequestInterface $request
* @param ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL
* @param DateInterval $accessTokenTTL
*
* @return ResponseTypeInterface
*/
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
DateInterval $accessTokenTTL
);
/**
@@ -136,7 +138,7 @@ interface GrantTypeInterface extends EmitterAwareInterface
/**
* Set the encryption key
*
* @param string|null $key
* @param string|Key|null $key
*/
public function setEncryptionKey($key = null);
}

View File

@@ -9,6 +9,8 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -17,12 +19,13 @@ use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
class ImplicitGrant extends AbstractAuthorizeGrant
{
/**
* @var \DateInterval
* @var DateInterval
*/
private $accessTokenTTL;
@@ -32,33 +35,33 @@ class ImplicitGrant extends AbstractAuthorizeGrant
private $queryDelimiter;
/**
* @param \DateInterval $accessTokenTTL
* @param string $queryDelimiter
* @param DateInterval $accessTokenTTL
* @param string $queryDelimiter
*/
public function __construct(\DateInterval $accessTokenTTL, $queryDelimiter = '#')
public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#')
{
$this->accessTokenTTL = $accessTokenTTL;
$this->queryDelimiter = $queryDelimiter;
}
/**
* @param \DateInterval $refreshTokenTTL
* @param DateInterval $refreshTokenTTL
*
* @throw \LogicException
* @throw LogicException
*/
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL)
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
{
throw new \LogicException('The Implicit Grant does not return refresh tokens');
throw new LogicException('The Implicit Grant does not return refresh tokens');
}
/**
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
*
* @throw \LogicException
* @throw LogicException
*/
public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
{
throw new \LogicException('The Implicit Grant does not return refresh tokens');
throw new LogicException('The Implicit Grant does not return refresh tokens');
}
/**
@@ -84,16 +87,16 @@ class ImplicitGrant extends AbstractAuthorizeGrant
*
* @param ServerRequestInterface $request
* @param ResponseTypeInterface $responseType
* @param \DateInterval $accessTokenTTL
* @param DateInterval $accessTokenTTL
*
* @return ResponseTypeInterface
*/
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
DateInterval $accessTokenTTL
) {
throw new \LogicException('This grant does not used this method');
throw new LogicException('This grant does not used this method');
}
/**
@@ -118,6 +121,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$request,
$this->getServerParameter('PHP_AUTH_USER', $request)
);
if (is_null($clientId)) {
throw OAuthServerException::invalidRequest('client_id');
}
@@ -135,20 +139,9 @@ class ImplicitGrant extends AbstractAuthorizeGrant
}
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
if ($redirectUri !== null) {
if (
is_string($client->getRedirectUri())
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
} elseif (
is_array($client->getRedirectUri())
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
$this->validateRedirectUri($redirectUri, $client, $request);
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|| empty($client->getRedirectUri())) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
@@ -164,21 +157,18 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$redirectUri
);
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$scopes,
$this->getIdentifier(),
$client
);
$stateParameter = $this->getQueryStringParameter('state', $request);
$authorizationRequest = new AuthorizationRequest();
$authorizationRequest->setGrantTypeId($this->getIdentifier());
$authorizationRequest->setClient($client);
$authorizationRequest->setRedirectUri($redirectUri);
$authorizationRequest->setState($stateParameter);
$authorizationRequest->setScopes($finalizedScopes);
if ($stateParameter !== null) {
$authorizationRequest->setState($stateParameter);
}
$authorizationRequest->setScopes($scopes);
return $authorizationRequest;
}
@@ -189,7 +179,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
{
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
}
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
@@ -200,11 +190,19 @@ class ImplicitGrant extends AbstractAuthorizeGrant
// The user approved the client, redirect them back with an access token
if ($authorizationRequest->isAuthorizationApproved() === true) {
// Finalize the requested scopes
$finalizedScopes = $this->scopeRepository->finalizeScopes(
$authorizationRequest->getScopes(),
$this->getIdentifier(),
$authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier()
);
$accessToken = $this->issueAccessToken(
$this->accessTokenTTL,
$authorizationRequest->getClient(),
$authorizationRequest->getUser()->getIdentifier(),
$authorizationRequest->getScopes()
$finalizedScopes
);
$response = new RedirectResponse();
@@ -214,7 +212,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
[
'access_token' => (string) $accessToken->convertToJWT($this->privateKey),
'token_type' => 'Bearer',
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(),
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new DateTime())->getTimestamp(),
'state' => $authorizationRequest->getState(),
],
$this->queryDelimiter

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,6 +18,9 @@ class RequestEvent extends Event
const USER_AUTHENTICATION_FAILED = 'user.authentication.failed';
const REFRESH_TOKEN_CLIENT_FAILED = 'refresh_token.client.failed';
const REFRESH_TOKEN_ISSUED = 'refresh_token.issued';
const ACCESS_TOKEN_ISSUED = 'access_token.issued';
/**
* @var ServerRequestInterface
*/

View File

@@ -60,7 +60,7 @@ class AuthorizationRequest
/**
* The state parameter on the authorization request
*
* @var string
* @var string|null
*/
protected $state;
@@ -175,7 +175,7 @@ class AuthorizationRequest
}
/**
* @return string
* @return string|null
*/
public function getState()
{

View File

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

View File

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

View File

@@ -11,6 +11,7 @@
namespace League\OAuth2\Server\ResponseTypes;
use Defuse\Crypto\Key;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use Psr\Http\Message\ResponseInterface;
@@ -37,7 +38,7 @@ interface ResponseTypeInterface
/**
* Set the encryption key
*
* @param string|null $key
* @param string|Key|null $key
*/
public function setEncryptionKey($key = null);
}

View File

@@ -3,6 +3,7 @@
namespace LeagueTests;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AuthCodeGrant;
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
@@ -109,6 +110,95 @@ class AuthorizationServerTest extends TestCase
$this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server));
}
public function testGetResponseTypeExtended()
{
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$privateKey = 'file://' . __DIR__ . '/Stubs/private.key';
$encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key';
$server = new class($clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), $privateKey, $encryptionKey) extends AuthorizationServer {
protected function getResponseType()
{
$this->responseType = new class extends BearerTokenResponse {
/* @return null|CryptKey */
public function getPrivateKey()
{
return $this->privateKey;
}
public function getEncryptionKey()
{
return $this->encryptionKey;
}
};
return parent::getResponseType();
}
};
$abstractGrantReflection = new \ReflectionClass($server);
$method = $abstractGrantReflection->getMethod('getResponseType');
$method->setAccessible(true);
$responseType = $method->invoke($server);
$this->assertInstanceOf(BearerTokenResponse::class, $responseType);
// generated instances should have keys setup
$this->assertSame($privateKey, $responseType->getPrivateKey()->getKeyPath());
$this->assertSame($encryptionKey, $responseType->getEncryptionKey());
}
public function testMultipleRequestsGetDifferentResponseTypeInstances()
{
$privateKey = 'file://' . __DIR__ . '/Stubs/private.key';
$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()
{
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();

View File

@@ -0,0 +1,40 @@
<?php
namespace LeagueTests\AuthorizationValidators;
use Lcobucci\JWT\Builder;
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\ServerRequest;
class BearerTokenValidatorTest extends TestCase
{
/**
* @expectedException League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 9
*/
public function testThrowExceptionWhenAccessTokenIsNotSigned()
{
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock);
$bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$unsignedJwt = (new Builder())
->setAudience('client-id')
->setId('token-id', true)
->setIssuedAt(time())
->setNotBefore(time())
->setExpiration(time())
->setSubject('user-id')
->set('scopes', 'scope1 scope2 scope3 scope4')
->getToken();
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
$bearerTokenValidator->validateAuthorization($request);
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace LeagueTests\Exception;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException;
use PHPUnit\Framework\TestCase;
class OAuthServerExceptionTest extends TestCase
{
public function testHasRedirect()
{
$exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error');
$this->assertTrue($exceptionWithRedirect->hasRedirect());
}
public function testDoesNotHaveRedirect()
{
$exceptionWithoutRedirect = OAuthServerException::accessDenied('Some hint');
$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());
}
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()
{
$accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();

View File

@@ -644,6 +644,73 @@ class AuthCodeGrantTest extends TestCase
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $response->getRefreshToken());
}
public function testRespondToAccessTokenRequestNullRefreshToken()
{
$client = new ClientEntity();
$client->setIdentifier('foo');
$client->setRedirectUri('http://foo/bar');
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
$scopeEntity = new ScopeEntity();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(null);
$grant = new AuthCodeGrant(
$this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(),
$refreshTokenRepositoryMock,
new \DateInterval('PT10M')
);
$grant->setClientRepository($clientRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setEncryptionKey($this->cryptStub->getKey());
$request = new ServerRequest(
[],
[],
null,
'POST',
'php://input',
[],
[],
[],
[
'grant_type' => 'authorization_code',
'client_id' => 'foo',
'redirect_uri' => 'http://foo/bar',
'code' => $this->cryptStub->doEncrypt(
json_encode(
[
'auth_code_id' => uniqid(),
'expire_time' => time() + 3600,
'client_id' => 'foo',
'user_id' => 123,
'scopes' => ['foo'],
'redirect_uri' => 'http://foo/bar',
]
)
),
]
);
/** @var StubResponseType $response */
$response = $grant->respondToAccessTokenRequest($request, new StubResponseType(), new \DateInterval('PT10M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $response->getAccessToken());
$this->assertNull($response->getRefreshToken());
}
public function testRespondToAccessTokenRequestCodeChallengePlain()
{
$client = new ClientEntity();

View File

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

View File

@@ -78,6 +78,51 @@ class PasswordGrantTest extends TestCase
$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
*/

View File

@@ -94,6 +94,63 @@ class RefreshTokenGrantTest extends TestCase
$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()
{
$client = new ClientEntity();

View File

@@ -104,7 +104,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
$response = $exception->generateHttpResponse(new Response());
$this->assertEquals(302, $response->getStatusCode());
$this->assertEquals('http://foo/bar?error=invalid_scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope',
$this->assertEquals('http://foo/bar?error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed',
$response->getHeader('location')[0]);
}
@@ -114,7 +114,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
$response = $exception->generateHttpResponse(new Response(), true);
$this->assertEquals(302, $response->getStatusCode());
$this->assertEquals('http://foo/bar#error=invalid_scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope',
$this->assertEquals('http://foo/bar#error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed',
$response->getHeader('location')[0]);
}
}

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types = 1);
namespace LeagueTests\PHPStan;
use League\OAuth2\Server\Grant\AbstractGrant;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\NullType;
use PHPStan\Type\StringType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
final class AbstractGrantExtension implements DynamicMethodReturnTypeExtension
{
public function getClass(): string
{
return AbstractGrant::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return in_array($methodReflection->getName(), [
'getRequestParameter',
'getQueryStringParameter',
'getCookieParameter',
], true);
}
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
return TypeCombinator::union(...[
new StringType(),
isset($methodCall->args[2]) ? $scope->getType($methodCall->args[2]->value) : new NullType(),
]);
}
}

View File

@@ -2,22 +2,34 @@
namespace LeagueTests\Utils;
use Defuse\Crypto\Key;
use LeagueTests\Stubs\CryptTraitStub;
use PHPUnit\Framework\TestCase;
class CryptTraitTest extends TestCase
{
/**
* @var \LeagueTests\Stubs\CryptTraitStub
*/
protected $cryptStub;
public function setUp()
protected function setUp()
{
$this->cryptStub = new CryptTraitStub;
$this->cryptStub = new CryptTraitStub();
}
public function testEncryptDecrypt()
public function testEncryptDecryptWithPassword()
{
$this->cryptStub->setEncryptionKey(base64_encode(random_bytes(36)));
$this->encryptDecrypt();
}
public function testEncryptDecryptWithKey()
{
$this->cryptStub->setEncryptionKey(Key::createNewRandomKey());
$this->encryptDecrypt();
}
private function encryptDecrypt()
{
$payload = 'alex loves whisky';
$encrypted = $this->cryptStub->doEncrypt($payload);