mirror of
https://github.com/elyby/oauth2-server.git
synced 2025-05-31 14:12:07 +05:30
Compare commits
370 Commits
6.1.0
...
adaptation
Author | SHA1 | Date | |
---|---|---|---|
|
08e470e81a | ||
|
677c10a61d | ||
|
3684a76ade | ||
|
a777ca2adc | ||
|
7db4cdb875 | ||
|
4673e7de89 | ||
|
0b0b43d433 | ||
|
cd8742f630 | ||
|
2097edd6eb | ||
|
705120c974 | ||
|
8a78e00a2e | ||
|
3413c20590 | ||
|
e1dc4d708c | ||
|
18dabd36e3 | ||
|
1a3107b4fc | ||
|
1d9ca35fec | ||
|
c7f998ee02 | ||
|
4b1c9ed503 | ||
|
dc3c74601a | ||
|
f5e910e6ec | ||
|
2b7923c593 | ||
|
3f95c0d11e | ||
|
4be97e6fd0 | ||
|
aba5353257 | ||
|
7f0879b8b4 | ||
|
cb9aa25c89 | ||
|
a6a499f8fb | ||
|
ccf36588ee | ||
|
6b2a3db185 | ||
|
1a6ebdf81c | ||
|
46c86ed5b1 | ||
|
a92a274d15 | ||
|
c4c354e2df | ||
|
7bc1ec643e | ||
|
51b97f87c1 | ||
|
e3b23fa826 | ||
|
bac79a26a8 | ||
|
012808f094 | ||
|
0db54cf1e5 | ||
|
c7d047f7f5 | ||
|
e1324b88b2 | ||
|
c60e8e3581 | ||
|
e0ee244506 | ||
|
8b5841870f | ||
|
048e45d8cd | ||
|
bf75596989 | ||
|
c5cfc0a371 | ||
|
5ab4323856 | ||
|
28709f300f | ||
|
bd483d701b | ||
|
3dc324af6e | ||
|
17923634bf | ||
|
a1cf22a3a9 | ||
|
86d1581cd9 | ||
|
521ed9a8cb | ||
|
1bbcb57d63 | ||
|
93d4b947d8 | ||
|
27d5c5ed8d | ||
|
4ecd3131c1 | ||
|
3fdfbe11f6 | ||
|
42df2d9c47 | ||
|
2eb1cf79e5 | ||
|
382b6f5fbf | ||
|
86869eafbb | ||
|
9236e842d9 | ||
|
9bc7f6c8c5 | ||
|
1e9a468e66 | ||
|
c7f4998497 | ||
|
0a78236f17 | ||
|
a68f8001a4 | ||
|
b88198a9a4 | ||
|
8cf39fd9cd | ||
|
6f6820f629 | ||
|
0742d5150c | ||
|
64f0d89fad | ||
|
ebf78132d7 | ||
|
aa5bbe5f06 | ||
|
66d4ce6de8 | ||
|
2ea76ca4fd | ||
|
b2840474fd | ||
|
0227f14b7b | ||
|
fad42a88fd | ||
|
d7defafd83 | ||
|
16f37560d4 | ||
|
5ed8e59ef3 | ||
|
c2cd12e0b8 | ||
|
8e9368cf44 | ||
|
894724c45b | ||
|
fd65bf9e54 | ||
|
2a16dbeb7f | ||
|
faa350792a | ||
|
b6955a6c65 | ||
|
ec8a663a81 | ||
|
dc3181bbb0 | ||
|
1e3a7adb19 | ||
|
b71f382cd7 | ||
|
9783388523 | ||
|
46493c461e | ||
|
8b421818f2 | ||
|
b09154af33 | ||
|
f1454cde36 | ||
|
f2cd3646ff | ||
|
7839a61170 | ||
|
443d7c485a | ||
|
a61c6a318a | ||
|
eea9c30e70 | ||
|
a936962716 | ||
|
685dc6edea | ||
|
2b4974b697 | ||
|
94e75ba6f3 | ||
|
efa8ef6fce | ||
|
7982275757 | ||
|
f6c1070ccc | ||
|
d64fb3f526 | ||
|
95a9f4649d | ||
|
4bb5b747c1 | ||
|
5868996961 | ||
|
9542af627e | ||
|
3b983ad0b4 | ||
|
34ec35019b | ||
|
16f9de86f2 | ||
|
ac818bd921 | ||
|
73698e28d9 | ||
|
c87be9477c | ||
|
d288a2ad8a | ||
|
a34f5dd7db | ||
|
c0efdf0dd0 | ||
|
f96fca3b48 | ||
|
20b355b025 | ||
|
793f65d3a3 | ||
|
322b55eddf | ||
|
50ab9dd8ac | ||
|
b624124d5a | ||
|
dbf2b55bc5 | ||
|
b11d628e8b | ||
|
0515129c9c | ||
|
50566cdc87 | ||
|
b4d88995de | ||
|
398029be56 | ||
|
30ed221481 | ||
|
939c0619d0 | ||
|
4042a31159 | ||
|
0bdd02cdb4 | ||
|
7bf7700645 | ||
|
d76025d613 | ||
|
d6792c1662 | ||
|
9882f6716c | ||
|
71c605117a | ||
|
6bc6ac09d2 | ||
|
fe421878e6 | ||
|
0c2356a508 | ||
|
9645119ccb | ||
|
da2742bea7 | ||
|
efb5ce5e2a | ||
|
1ddc27e792 | ||
|
b7b7dda28c | ||
|
ef864b5cba | ||
|
fcd6eb8a3c | ||
|
133d9cc97a | ||
|
592dd2f433 | ||
|
4a464dd336 | ||
|
970df8f34b | ||
|
6a1645aebc | ||
|
e3e7abf41e | ||
|
d831868d58 | ||
|
36bf4ff8f2 | ||
|
07ebe43b91 | ||
|
5d3d9d95be | ||
|
e85a8e31e8 | ||
|
de899fbe0a | ||
|
3eabbafe5b | ||
|
cfa9b8d3b4 | ||
|
060a090479 | ||
|
46c2f99b06 | ||
|
27b956c149 | ||
|
6949a007e5 | ||
|
74495cac49 | ||
|
fb43801458 | ||
|
8ab27ede39 | ||
|
0105a20126 | ||
|
491852b521 | ||
|
7f2fd7b22c | ||
|
abef682031 | ||
|
04807a1e2a | ||
|
d07b5a4a03 | ||
|
838f206832 | ||
|
972808561d | ||
|
5ad00b0e33 | ||
|
f49cc65c13 | ||
|
acf16e924a | ||
|
a479b5762e | ||
|
dc2a048b95 | ||
|
0c542637fe | ||
|
0cdd535f7d | ||
|
2fcee76d13 | ||
|
574299d862 | ||
|
dad3b1e1c9 | ||
|
7df0dfff9d | ||
|
ca5fe10934 | ||
|
369c7005a3 | ||
|
8184f771d4 | ||
|
51b3b415b4 | ||
|
e3ad09d4a2 | ||
|
aeb1fe48d3 | ||
|
f54980da25 | ||
|
105834af96 | ||
|
ffffc4bfeb | ||
|
a77732e97c | ||
|
614bba2c11 | ||
|
224763cda6 | ||
|
a31bc7d4cc | ||
|
0d20c755d4 | ||
|
e36ff17ad9 | ||
|
a339d99135 | ||
|
a7a1065e38 | ||
|
09bf988922 | ||
|
a571e2262b | ||
|
519543e925 | ||
|
3614f8bd7c | ||
|
e4a7fea834 | ||
|
68c9fbd83c | ||
|
466e1a639d | ||
|
d7ab153500 | ||
|
72ead2e3ce | ||
|
ae4ab26aaf | ||
|
ca6be0577c | ||
|
ef75d13255 | ||
|
bd741e9203 | ||
|
aac64e49cf | ||
|
61156ef8c7 | ||
|
2a7f671a95 | ||
|
02609c37cc | ||
|
9941a96feb | ||
|
4b0383b16c | ||
|
4aeb92aa98 | ||
|
b182389395 | ||
|
92f598d1dc | ||
|
a40092ff54 | ||
|
2e47fa7fca | ||
|
fc55621f20 | ||
|
beec37d95f | ||
|
98812e6fab | ||
|
354aed7671 | ||
|
5a499bf03c | ||
|
2e3ee60a2a | ||
|
0242d0c996 | ||
|
3ea0cdc936 | ||
|
19d782d223 | ||
|
8a25e0a01b | ||
|
a3d4f583ed | ||
|
28276cb688 | ||
|
b1b33207ab | ||
|
793000f149 | ||
|
f8c2e721a0 | ||
|
cbce5f45ba | ||
|
c2dcdee266 | ||
|
33ce849617 | ||
|
ff5e9f57a5 | ||
|
577065c270 | ||
|
a1da9beb92 | ||
|
48ce5f36cf | ||
|
fd72d79ad3 | ||
|
35c6f28aef | ||
|
bd47b58f81 | ||
|
e38ea4ab34 | ||
|
8b40858509 | ||
|
aa5cc5eac7 | ||
|
38f26d5229 | ||
|
a5a51fad95 | ||
|
52d7952ba5 | ||
|
2375b8c7f3 | ||
|
f9a91a79c2 | ||
|
242dd4dcfe | ||
|
c94ec122aa | ||
|
05f5d90034 | ||
|
491c23c1e9 | ||
|
27323b5c9a | ||
|
80bc291c51 | ||
|
8a619e5c1e | ||
|
7e07033b10 | ||
|
6991777ff3 | ||
|
9febc32e14 | ||
|
c8b44ff5c7 | ||
|
9fc288ce53 | ||
|
8f1bf88792 | ||
|
cc19da50b4 | ||
|
bec0de16bb | ||
|
a56acc8dd0 | ||
|
c9b07f386c | ||
|
00a7972f74 | ||
|
e3266cb50a | ||
|
2fdd6ce494 | ||
|
6fd3024c48 | ||
|
62e06b7d3a | ||
|
009c109716 | ||
|
6723aadfe8 | ||
|
e24964af07 | ||
|
99e42f6f25 | ||
|
6700b113a8 | ||
|
28e1418f64 | ||
|
143afc9561 | ||
|
456c6cfdd2 | ||
|
4f68d2a5f2 | ||
|
49f66866f7 | ||
|
028d91f670 | ||
|
9287f587fc | ||
|
70396bec67 | ||
|
6679418436 | ||
|
e04b8f4e6d | ||
|
e0cc5ee1b0 | ||
|
b78c012796 | ||
|
bcd2fc38c0 | ||
|
25c3c216a0 | ||
|
8bbb20a012 | ||
|
cd5233392e | ||
|
7a6c35bc29 | ||
|
8614aea887 | ||
|
7a5c511807 | ||
|
e0b65a2831 | ||
|
ee7d52ecaa | ||
|
7d1d88cdf1 | ||
|
80a949601f | ||
|
fea577f25b | ||
|
1f87c7a7be | ||
|
3098f6d7fa | ||
|
06a23a1dd0 | ||
|
97089ad49e | ||
|
eca385ab08 | ||
|
ef06c29ee8 | ||
|
5fb9fc929a | ||
|
4c548dbd78 | ||
|
b3cd73cac7 | ||
|
3999c41fef | ||
|
ce2662ece7 | ||
|
d2641b560d | ||
|
8bbd218856 | ||
|
eb9cde5ab7 | ||
|
1b692e2298 | ||
|
d22f222e65 | ||
|
cf9acb32b8 | ||
|
92d8052a5b | ||
|
a3289c6ecb | ||
|
292272d128 | ||
|
ef8a741527 | ||
|
91d9c11fb4 | ||
|
2ec8d148b0 | ||
|
01d21b2533 | ||
|
ff29721ca9 | ||
|
5b79b40df9 | ||
|
b6d9835281 | ||
|
57ca83a8ba | ||
|
41bba7f58c | ||
|
dcae4af6ce | ||
|
a0cabb573c | ||
|
f79d3f27cf | ||
|
90fec63104 | ||
|
4563685375 | ||
|
4270f5bac1 | ||
|
88ccb6ff13 | ||
|
e2f9b73df3 | ||
|
fbb3586cae | ||
|
4710743b87 | ||
|
11ad87b5f5 | ||
|
880e3b4590 | ||
|
2167edf1d9 | ||
|
2482630221 | ||
|
d73b15ae32 | ||
|
945624eb51 | ||
|
d8ece093d5 | ||
|
655a4b2715 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -11,3 +11,4 @@
|
|||||||
/CHANGELOG.md export-ignore
|
/CHANGELOG.md export-ignore
|
||||||
/CONTRIBUTING.md export-ignore
|
/CONTRIBUTING.md export-ignore
|
||||||
/README.md export-ignore
|
/README.md export-ignore
|
||||||
|
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ phpunit.xml
|
|||||||
examples/public.key
|
examples/public.key
|
||||||
examples/private.key
|
examples/private.key
|
||||||
build
|
build
|
||||||
|
*.orig
|
||||||
|
@@ -4,6 +4,7 @@ enabled:
|
|||||||
- binary_operator_spaces
|
- binary_operator_spaces
|
||||||
- blank_line_before_return
|
- blank_line_before_return
|
||||||
- concat_with_spaces
|
- concat_with_spaces
|
||||||
|
- fully_qualified_strict_types
|
||||||
- function_typehint_space
|
- function_typehint_space
|
||||||
- hash_to_slash_comment
|
- hash_to_slash_comment
|
||||||
- include
|
- include
|
||||||
@@ -29,7 +30,6 @@ enabled:
|
|||||||
- phpdoc_inline_tag
|
- phpdoc_inline_tag
|
||||||
- phpdoc_no_access
|
- phpdoc_no_access
|
||||||
- phpdoc_no_simplified_null_return
|
- phpdoc_no_simplified_null_return
|
||||||
- phpdoc_order
|
|
||||||
- phpdoc_property
|
- phpdoc_property
|
||||||
- phpdoc_scalar
|
- phpdoc_scalar
|
||||||
- phpdoc_separation
|
- phpdoc_separation
|
||||||
@@ -41,7 +41,6 @@ enabled:
|
|||||||
- print_to_echo
|
- print_to_echo
|
||||||
- short_array_syntax
|
- short_array_syntax
|
||||||
- short_scalar_cast
|
- short_scalar_cast
|
||||||
- simplified_null_return
|
|
||||||
- single_quote
|
- single_quote
|
||||||
- spaces_cast
|
- spaces_cast
|
||||||
- standardize_not_equal
|
- standardize_not_equal
|
||||||
|
19
.travis.yml
19
.travis.yml
@@ -1,22 +1,31 @@
|
|||||||
language: php
|
language: php
|
||||||
|
|
||||||
|
dist: trusty
|
||||||
sudo: false
|
sudo: false
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
directories:
|
directories:
|
||||||
- vendor
|
- vendor
|
||||||
|
|
||||||
|
env:
|
||||||
|
- DEPENDENCIES=""
|
||||||
|
- DEPENDENCIES="--prefer-lowest --prefer-stable"
|
||||||
|
|
||||||
php:
|
php:
|
||||||
- 5.6
|
|
||||||
- 7.0
|
|
||||||
- 7.1
|
- 7.1
|
||||||
- 7.2
|
- 7.2
|
||||||
|
- 7.3
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- travis_retry composer install --no-interaction --prefer-source
|
- composer update --no-interaction --prefer-dist $DEPENDENCIES
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- vendor/bin/phpunit
|
- vendor/bin/phpunit --coverage-clover=coverage.clover
|
||||||
|
- vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- wget https://scrutinizer-ci.com/ocular.phar
|
||||||
|
- php ocular.phar code-coverage:upload --format=php-clover coverage.clover
|
||||||
|
|
||||||
branches:
|
branches:
|
||||||
only:
|
only:
|
||||||
|
681
CHANGELOG.md
681
CHANGELOG.md
@@ -1,352 +1,535 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## 6.0.2 (released 2017-08-03)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
* An invalid refresh token that can't be decrypted now returns a HTTP 401 error instead of HTTP 400 (Issue #759)
|
## [Unreleased]
|
||||||
* Removed chmod from CryptKey and add toggle to disable checking (Issue #776)
|
|
||||||
* Fixes invalid code challenge method payload key name (Issue #777)
|
|
||||||
|
|
||||||
## 6.0.1 (released 2017-07-19)
|
### Fixed
|
||||||
|
- Clients are now explicitly prevented from using the Client Credentials grant unless they are confidential to conform
|
||||||
|
with the OAuth2 spec (PR #1035)
|
||||||
|
|
||||||
|
## [8.0.0] - released 2019-07-13
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Flag, `requireCodeChallengeForPublicClients`, used to reject public clients that do not provide a code challenge for the Auth Code Grant; use AuthCodeGrant::disableRequireCodeCallengeForPublicClients() to turn off this requirement (PR #938)
|
||||||
|
- Public clients can now use the Auth Code Grant (PR #938)
|
||||||
|
- `isConfidential` getter added to `ClientEntity` to identify type of client (PR #938)
|
||||||
|
- Function `validateClient()` added to validate clients which was previously performed by the `getClientEntity()` function (PR #938)
|
||||||
|
- Add a new function to the AbstractGrant class called `getClientEntityOrFail()`. This is a wrapper around the `getClientEntity()` function that ensures we emit and throw an exception if the repo doesn't return a client entity. (PR #1010)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Replace `convertToJWT()` interface with a more generic `__toString()` to improve extensibility; AccessTokenEntityInterface now requires `setPrivateKey(CryptKey $privateKey)` so `__toString()` has everything it needs to work (PR #874)
|
||||||
|
- The `invalidClient()` function accepts a PSR-7 compliant `$serverRequest` argument to avoid accessing the `$_SERVER` global variable and improve testing (PR #899)
|
||||||
|
- `issueAccessToken()` in the Abstract Grant no longer sets access token client, user ID or scopes. These values should already have been set when calling `getNewToken()` (PR #919)
|
||||||
|
- No longer need to enable PKCE with `enableCodeExchangeProof` flag. Any client sending a code challenge will initiate PKCE checks. (PR #938)
|
||||||
|
- Function `getClientEntity()` no longer performs client validation (PR #938)
|
||||||
|
- Password Grant now returns an invalid_grant error instead of invalid_credentials if a user cannot be validated (PR #967)
|
||||||
|
- Use `DateTimeImmutable()` instead of `DateTime()`, `time()` instead of `(new DateTime())->getTimeStamp()`, and `DateTime::getTimeStamp()` instead of `DateTime::format('U')` (PR #963)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- `enableCodeExchangeProof` flag (PR #938)
|
||||||
|
- Support for PHP 7.0 (PR #1014)
|
||||||
|
- Remove JTI claim from JWT header (PR #1031)
|
||||||
|
|
||||||
|
## [7.4.0] - released 2019-05-05
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- 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)
|
||||||
|
- Enforce stricter static analysis checks and upgrade library dependencies (PR #852)
|
||||||
|
- Provide PHPStan coverage for tests and update PHPUnit (PR #849)
|
||||||
|
- Get and set methods for OAuth Server Exception payloads. Allow implementer to specify the JSON encode options (PR #719)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- ClientRepository interface will now accept null for the Grant type to improve extensibility options (PR #607)
|
||||||
|
- Do not issue an error if key file permissions are 400 or 440 (PR #839)
|
||||||
|
- Skip key file creation if the file already exists (PR #845)
|
||||||
|
- Change changelog format and update readme
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Support for PHP 5.6
|
||||||
|
- Support for version 5.x and 6.x of the library
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- PKCE implementation (PR #744)
|
||||||
|
- Set correct redirect URI when validating scopes (PR #840)
|
||||||
|
- S256 code challenege method (PR #842)
|
||||||
|
- Accept RSA key with CRLF line endings (PR #805)
|
||||||
|
|
||||||
|
## [6.1.1] - 2017-12-23
|
||||||
|
|
||||||
|
- Removed check on empty scopes
|
||||||
|
|
||||||
|
## [6.1.0] - 2017-12-23
|
||||||
|
|
||||||
|
- Changed the token type issued by the Implicit Grant to be Bearer instead of bearer. (PR #724)
|
||||||
|
- Replaced call to array_key_exists() with the faster isset() on the Implicit Grant. (PR #749)
|
||||||
|
- Allow specification of query delimiter character in the Password Grant (PR #801)
|
||||||
|
- Add Zend Diactoros library dependency to examples (PR #678)
|
||||||
|
- Can set default scope for the authorization endpoint. If no scope is passed during an authorization request, the default scope will be used if set. If not, the server will issue an invalid scope exception (PR #811)
|
||||||
|
- Added validation for redirect URIs on the authorization end point to ensure exactly one redirection URI has been passed (PR #573)
|
||||||
|
|
||||||
|
## [6.0.2] - 2017-08-03
|
||||||
|
|
||||||
|
- An invalid refresh token that can't be decrypted now returns a HTTP 401 error instead of HTTP 400 (Issue #759)
|
||||||
|
- Removed chmod from CryptKey and add toggle to disable checking (Issue #776)
|
||||||
|
- Fixes invalid code challenge method payload key name (Issue #777)
|
||||||
|
|
||||||
|
## [6.0.1] - 2017-07-19
|
||||||
|
|
||||||
To address feedback from the security release the following change has been made:
|
To address feedback from the security release the following change has been made:
|
||||||
|
|
||||||
* If an RSA key cannot be chmod'ed to 600 then it will now throw a E_USER_NOTICE instead of an exception.
|
- If an RSA key cannot be chmod'ed to 600 then it will now throw a E_USER_NOTICE instead of an exception.
|
||||||
|
|
||||||
## 6.0.0 (released 2017-07-01)
|
## [6.0.0] - 2017-07-01
|
||||||
|
|
||||||
* Breaking change: The `AuthorizationServer` constructor now expects an encryption key string instead of a public key
|
- Breaking change: The `AuthorizationServer` constructor now expects an encryption key string instead of a public key
|
||||||
* Remove support for HHVM
|
- Remove support for HHVM
|
||||||
* Remove support for PHP 5.5
|
- Remove support for PHP 5.5
|
||||||
|
|
||||||
## 5.1.4 (released 2017-07-01)
|
## [5.1.4] - 2017-07-01
|
||||||
|
|
||||||
* Fixed multiple security vulnerabilities as a result of a security audit paid for by the [Mozilla Secure Open Source Fund](https://wiki.mozilla.org/MOSS/Secure_Open_Source). All users of this library are encouraged to update as soon as possible to this version or version 6.0 or greater.
|
- Fixed multiple security vulnerabilities as a result of a security audit paid for by the [Mozilla Secure Open Source Fund](https://wiki.mozilla.org/MOSS/Secure_Open_Source). All users of this library are encouraged to update as soon as possible to this version or version 6.0 or greater.
|
||||||
* It is recommended on each `AuthorizationServer` instance you set the `setEncryptionKey()`. This will result in stronger encryption being used. If this method is not set messages will be sent to the defined error handling routines (using `error_log`). Please see the examples and documentation for examples.
|
- It is recommended on each `AuthorizationServer` instance you set the `setEncryptionKey()`. This will result in stronger encryption being used. If this method is not set messages will be sent to the defined error handling routines (using `error_log`). Please see the examples and documentation for examples.
|
||||||
* TravisCI now tests PHP 7.1 (Issue #671)
|
- TravisCI now tests PHP 7.1 (Issue #671)
|
||||||
* Fix middleware example fatal error (Issue #682)
|
- Fix middleware example fatal error (Issue #682)
|
||||||
* Fix typo in the first README sentence (Issue #690)
|
- Fix typo in the first README sentence (Issue #690)
|
||||||
* Corrected DateInterval from 1 min to 1 month (Issue #709)
|
- Corrected DateInterval from 1 min to 1 month (Issue #709)
|
||||||
|
|
||||||
## 5.1.3 (released 2016-10-12)
|
## [5.1.3] - 2016-10-12
|
||||||
|
|
||||||
* Fixed WWW-Authenticate header (Issue #669)
|
- Fixed WWW-Authenticate header (Issue #669)
|
||||||
* Increase the recommended RSA key length from 1024 to 2048 bits (Issue #668)
|
- Increase the recommended RSA key length from 1024 to 2048 bits (Issue #668)
|
||||||
|
|
||||||
## 5.1.2 (released 2016-09-19)
|
## [5.1.2] - 2016-09-19
|
||||||
|
|
||||||
* Fixed `finalizeScopes` call (Issue #650)
|
- Fixed `finalizeScopes` call (Issue #650)
|
||||||
|
|
||||||
## 5.1.1 (released 2016-07-26)
|
## [4.1.6] - 2016-09-13
|
||||||
|
|
||||||
* Improved test suite (Issue #614)
|
- Less restrictive on Authorization header check (Issue #652)
|
||||||
* Updated docblocks (Issue #616)
|
|
||||||
* Replace `array_shift` with `foreach` loop (Issue #621)
|
|
||||||
* Allow easy addition of custom fields to Bearer token response (Issue #624)
|
|
||||||
* Key file auto-generation from string (Issue #625)
|
|
||||||
|
|
||||||
## 5.1.0 (released 2016-06-28)
|
## [5.1.1] - 2016-07-26
|
||||||
|
|
||||||
* Implemented RFC7636 (Issue #574)
|
- Improved test suite (Issue #614)
|
||||||
* Unify middleware exception responses (Issue #578)
|
- Updated docblocks (Issue #616)
|
||||||
* Updated examples (Issue #589)
|
- Replace `array_shift` with `foreach` loop (Issue #621)
|
||||||
* Ensure state is in access denied redirect (Issue #597)
|
- Allow easy addition of custom fields to Bearer token response (Issue #624)
|
||||||
* Remove redundant `isExpired()` method from entity interfaces and traits (Issue #600)
|
- Key file auto-generation from string (Issue #625)
|
||||||
* Added a check for unique access token constraint violation (Issue #601)
|
|
||||||
* Look at Authorization header directly for HTTP Basic auth checks (Issue #604)
|
|
||||||
* Added catch Runtime exception when parsing JWT string (Issue #605)
|
|
||||||
* Allow `paragonie/random_compat` 2.x (Issue #606)
|
|
||||||
* Added `indigophp/hash-compat` to Composer suggestions and `require-dev` for PHP 5.5 support
|
|
||||||
|
|
||||||
## 5.0.3 (released 2016-05-04)
|
## [5.1.0] - 2016-06-28
|
||||||
|
|
||||||
* Fix hints in PasswordGrant (Issue #560)
|
- Implemented RFC7636 (Issue #574)
|
||||||
* Add meaning of `Resource owner` to terminology.md (Issue #561)
|
- Unify middleware exception responses (Issue #578)
|
||||||
* Use constant for event name instead of explicit string (Issue #563)
|
- Updated examples (Issue #589)
|
||||||
* Remove unused request property (Issue #564)
|
- Ensure state is in access denied redirect (Issue #597)
|
||||||
* Correct wrong phpdoc (Issue #569)
|
- Remove redundant `isExpired()` method from entity interfaces and traits (Issue #600)
|
||||||
* Fixed typo in exception string (Issue #570)
|
- Added a check for unique access token constraint violation (Issue #601)
|
||||||
|
- Look at Authorization header directly for HTTP Basic auth checks (Issue #604)
|
||||||
|
- Added catch Runtime exception when parsing JWT string (Issue #605)
|
||||||
|
- Allow `paragonie/random_compat` 2.x (Issue #606)
|
||||||
|
- Added `indigophp/hash-compat` to Composer suggestions and `require-dev` for PHP 5.5 support
|
||||||
|
|
||||||
## 5.0.2 (released 2016-04-18)
|
## [5.0.3] - 2016-05-04
|
||||||
|
|
||||||
* `state` parameter is now correctly returned after implicit grant authorization
|
- Fix hints in PasswordGrant (Issue #560)
|
||||||
* Small code and docblock improvements
|
- Add meaning of `Resource owner` to terminology.md (Issue #561)
|
||||||
|
- Use constant for event name instead of explicit string (Issue #563)
|
||||||
|
- Remove unused request property (Issue #564)
|
||||||
|
- Correct wrong phpdoc (Issue #569)
|
||||||
|
- Fixed typo in exception string (Issue #570)
|
||||||
|
|
||||||
## 5.0.1 (released 2016-04-18)
|
## [5.0.2] - 2016-04-18
|
||||||
|
|
||||||
* Fixes an issue (#550) whereby it was unclear whether or not to validate a client's secret during a request.
|
- `state` parameter is now correctly returned after implicit grant authorization
|
||||||
|
- Small code and docblock improvements
|
||||||
|
|
||||||
## 5.0.0 (released 2016-04-17)
|
## [5.0.1] - 2016-04-18
|
||||||
|
|
||||||
|
- Fixes an issue (#550) whereby it was unclear whether or not to validate a client's secret during a request.
|
||||||
|
|
||||||
|
## [5.0.0] - 2016-04-17
|
||||||
|
|
||||||
Version 5 is a complete code rewrite.
|
Version 5 is a complete code rewrite.
|
||||||
|
|
||||||
* JWT support
|
- Renamed Server class to AuthorizationServer
|
||||||
* PSR-7 support
|
- Added ResourceServer class
|
||||||
* Improved exception errors
|
- Run unit tests again PHP 5.5.9 as it's the minimum supported version
|
||||||
* Replace all occurrences of the term "Storage" with "Repository"
|
- Enable PHPUnit 5.0 support
|
||||||
* Simplify repositories
|
- Improved examples and documentation
|
||||||
* Entities conform to interfaces and use traits
|
- Make it clearer that the implicit grant doesn't support refresh tokens
|
||||||
* Auth code grant updated
|
- Improved refresh token validation errors
|
||||||
* Allow support for public clients
|
- Fixed refresh token expiry date
|
||||||
* Add support for #439
|
|
||||||
* Client credentials grant updated
|
|
||||||
* Password grant updated
|
|
||||||
* Allow support for public clients
|
|
||||||
* Refresh token grant updated
|
|
||||||
* Implement Implicit grant
|
|
||||||
* Bearer token output type
|
|
||||||
* Remove MAC token output type
|
|
||||||
* Authorization server rewrite
|
|
||||||
* Resource server class moved to PSR-7 middleware
|
|
||||||
* Tests
|
|
||||||
* Much much better documentation
|
|
||||||
|
|
||||||
Changes since RC2:
|
## [5.0.0-RC2] - 2016-04-10
|
||||||
|
|
||||||
* Renamed Server class to AuthorizationServer
|
- Allow multiple client redirect URIs (Issue #511)
|
||||||
* Added ResourceServer class
|
- Remove unused mac token interface (Issue #503)
|
||||||
* Run unit tests again PHP 5.5.9 as it's the minimum supported version
|
- Handle RSA key passphrase (Issue #502)
|
||||||
* Enable PHPUnit 5.0 support
|
- Remove access token repository from response types (Issue #501)
|
||||||
* Improved examples and documentation
|
- Remove unnecessary methods from entity interfaces (Issue #490)
|
||||||
* Make it clearer that the implicit grant doesn't support refresh tokens
|
- Ensure incoming JWT hasn't expired (Issue #509)
|
||||||
* Improved refresh token validation errors
|
- Fix client identifier passed where user identifier is expected (Issue #498)
|
||||||
* Fixed refresh token expiry date
|
- Removed built-in entities; added traits to for quick re-use (Issue #504)
|
||||||
|
- Redirect uri is required only if the "redirect_uri" parameter was included in the authorization request (Issue #514)
|
||||||
|
- Removed templating for auth code and implicit grants (Issue #499)
|
||||||
|
|
||||||
## 5.0.0-RC2 (released 2016-04-10)
|
## [5.0.0-RC1] - 2016-03-24
|
||||||
|
|
||||||
Changes since RC1:
|
|
||||||
|
|
||||||
* Allow multiple client redirect URIs (Issue #511)
|
|
||||||
* Remove unused mac token interface (Issue #503)
|
|
||||||
* Handle RSA key passphrase (Issue #502)
|
|
||||||
* Remove access token repository from response types (Issue #501)
|
|
||||||
* Remove unnecessary methods from entity interfaces (Issue #490)
|
|
||||||
* Ensure incoming JWT hasn't expired (Issue #509)
|
|
||||||
* Fix client identifier passed where user identifier is expected (Issue #498)
|
|
||||||
* Removed built-in entities; added traits to for quick re-use (Issue #504)
|
|
||||||
* Redirect uri is required only if the "redirect_uri" parameter was included in the authorization request (Issue #514)
|
|
||||||
* Removed templating for auth code and implicit grants (Issue #499)
|
|
||||||
|
|
||||||
## 5.0.0-RC1 (release 2016-03-24)
|
|
||||||
|
|
||||||
Version 5 is a complete code rewrite.
|
Version 5 is a complete code rewrite.
|
||||||
|
|
||||||
* JWT support
|
- JWT support
|
||||||
* PSR-7 support
|
- PSR-7 support
|
||||||
* Improved exception errors
|
- Improved exception errors
|
||||||
* Replace all occurrences of the term "Storage" with "Repository"
|
- Replace all occurrences of the term "Storage" with "Repository"
|
||||||
* Simplify repositories
|
- Simplify repositories
|
||||||
* Entities conform to interfaces and use traits
|
- Entities conform to interfaces and use traits
|
||||||
* Auth code grant updated
|
- Auth code grant updated
|
||||||
* Allow support for public clients
|
- Allow support for public clients
|
||||||
* Add support for #439
|
- Add support for #439
|
||||||
* Client credentials grant updated
|
- Client credentials grant updated
|
||||||
* Password grant updated
|
- Password grant updated
|
||||||
* Allow support for public clients
|
- Allow support for public clients
|
||||||
* Refresh token grant updated
|
- Refresh token grant updated
|
||||||
* Implement Implicit grant
|
- Implement Implicit grant
|
||||||
* Bearer token output type
|
- Bearer token output type
|
||||||
* Remove MAC token output type
|
- Remove MAC token output type
|
||||||
* Authorization server rewrite
|
- Authorization server rewrite
|
||||||
* Resource server class moved to PSR-7 middleware
|
- Resource server class moved to PSR-7 middleware
|
||||||
* Tests
|
- Tests
|
||||||
* Much much better documentation
|
- Much much better documentation
|
||||||
|
|
||||||
## 4.1.5 (released 2016-01-04)
|
## [4.1.5] - 2016-01-04
|
||||||
|
|
||||||
* Enable Symfony 3.0 support (#412)
|
- Enable Symfony 3.0 support (#412)
|
||||||
|
|
||||||
## 4.1.4 (released 2015-11-13)
|
## [4.1.4] - 2015-11-13
|
||||||
|
|
||||||
* Fix for determining access token in header (Issue #328)
|
- Fix for determining access token in header (Issue #328)
|
||||||
* Refresh tokens are now returned for MAC responses (Issue #356)
|
- Refresh tokens are now returned for MAC responses (Issue #356)
|
||||||
* Added integration list to readme (Issue #341)
|
- Added integration list to readme (Issue #341)
|
||||||
* Expose parameter passed to exceptions (Issue #345)
|
- Expose parameter passed to exceptions (Issue #345)
|
||||||
* Removed duplicate routing setup code (Issue #346)
|
- Removed duplicate routing setup code (Issue #346)
|
||||||
* Docs fix (Issues #347, #360, #380)
|
- Docs fix (Issues #347, #360, #380)
|
||||||
* Examples fix (Issues #348, #358)
|
- Examples fix (Issues #348, #358)
|
||||||
* Fix typo in docblock (Issue #352)
|
- Fix typo in docblock (Issue #352)
|
||||||
* Improved timeouts for MAC tokens (Issue #364)
|
- Improved timeouts for MAC tokens (Issue #364)
|
||||||
* `hash_hmac()` should output raw binary data, not hexits (Issue #370)
|
- `hash_hmac()` should output raw binary data, not hexits (Issue #370)
|
||||||
* Improved regex for matching all Base64 characters (Issue #371)
|
- Improved regex for matching all Base64 characters (Issue #371)
|
||||||
* Fix incorrect signature parameter (Issue #372)
|
- Fix incorrect signature parameter (Issue #372)
|
||||||
* AuthCodeGrant and RefreshTokenGrant don't require client_secret (Issue #377)
|
- AuthCodeGrant and RefreshTokenGrant don't require client_secret (Issue #377)
|
||||||
* Added priority argument to event listener (Issue #388)
|
- Added priority argument to event listener (Issue #388)
|
||||||
|
|
||||||
## 4.1.3 (released 2015-03-22)
|
## [4.1.3] - 2015-03-22
|
||||||
|
|
||||||
* Docblock, namespace and inconsistency fixes (Issue #303)
|
- Docblock, namespace and inconsistency fixes (Issue #303)
|
||||||
* Docblock type fix (Issue #310)
|
- Docblock type fix (Issue #310)
|
||||||
* Example bug fix (Issue #300)
|
- Example bug fix (Issue #300)
|
||||||
* Updated league/event to ~2.1 (Issue #311)
|
- Updated league/event to ~2.1 (Issue #311)
|
||||||
* Fixed missing session scope (Issue #319)
|
- Fixed missing session scope (Issue #319)
|
||||||
* Updated interface docs (Issue #323)
|
- Updated interface docs (Issue #323)
|
||||||
* `.travis.yml` updates
|
- `.travis.yml` updates
|
||||||
|
|
||||||
## 4.1.2 (released 2015-01-01)
|
## [4.1.2] - 2015-01-01
|
||||||
|
|
||||||
* Remove side-effects in hash_equals() implementation (Issue #290)
|
- Remove side-effects in hash_equals() implementation (Issue #290)
|
||||||
|
|
||||||
## 4.1.1 (released 2014-12-31)
|
## [4.1.1] - 2014-12-31
|
||||||
|
|
||||||
* Changed `symfony/http-foundation` dependency version to `~2.4` so package can be installed in Laravel `4.1.*`
|
- Changed `symfony/http-foundation` dependency version to `~2.4` so package can be installed in Laravel `4.1.*`
|
||||||
|
|
||||||
## 4.1.0 (released 2014-12-27)
|
## [4.1.0] - 2014-12-27
|
||||||
|
|
||||||
* Added MAC token support (Issue #158)
|
- Added MAC token support (Issue #158)
|
||||||
* Fixed example init code (Issue #280)
|
- Fixed example init code (Issue #280)
|
||||||
* Toggle refresh token rotation (Issue #286)
|
- Toggle refresh token rotation (Issue #286)
|
||||||
* Docblock fixes
|
- Docblock fixes
|
||||||
|
|
||||||
## 4.0.5 (released 2014-12-15)
|
## [4.0.5] - 2014-12-15
|
||||||
|
|
||||||
* Prevent duplicate session in auth code grant (Issue #282)
|
- Prevent duplicate session in auth code grant (Issue #282)
|
||||||
|
|
||||||
## 4.0.4 (released 2014-12-03)
|
## [4.0.4] - 2014-12-03
|
||||||
|
|
||||||
* Ensure refresh token hasn't expired (Issue #270)
|
- Ensure refresh token hasn't expired (Issue #270)
|
||||||
|
|
||||||
## 4.0.3 (released 2014-12-02)
|
## [4.0.3] - 2014-12-02
|
||||||
|
|
||||||
* Fix bad type hintings (Issue #267)
|
- Fix bad type hintings (Issue #267)
|
||||||
* Do not forget to set the expire time (Issue #268)
|
- Do not forget to set the expire time (Issue #268)
|
||||||
|
|
||||||
## 4.0.2 (released 2014-11-21)
|
## [4.0.2] - 2014-11-21
|
||||||
|
|
||||||
* Improved interfaces (Issue #255)
|
- Improved interfaces (Issue #255)
|
||||||
* Learnt how to spell delimiter and so `getScopeDelimiter()` and `setScopeDelimiter()` methods have been renamed
|
- Learnt how to spell delimiter and so `getScopeDelimiter()` and `setScopeDelimiter()` methods have been renamed
|
||||||
* Docblock improvements (Issue #254)
|
- Docblock improvements (Issue #254)
|
||||||
|
|
||||||
## 4.0.1 (released 2014-11-09)
|
## [4.0.1] - 2014-11-09
|
||||||
|
|
||||||
* Alias the master branch in composer.json (Issue #243)
|
- Alias the master branch in composer.json (Issue #243)
|
||||||
* Numerous PHP CodeSniffer fixes (Issue #244)
|
- Numerous PHP CodeSniffer fixes (Issue #244)
|
||||||
* .travis.yml update (Issue #245)
|
- .travis.yml update (Issue #245)
|
||||||
* The getAccessToken method should return an AccessTokenEntity object instead of a string in ResourceServer.php (#246)
|
- The getAccessToken method should return an AccessTokenEntity object instead of a string in ResourceServer.php (#246)
|
||||||
|
|
||||||
## 4.0.0 (released 2014-11-08)
|
## [4.0.0] - 2014-11-08
|
||||||
|
|
||||||
* Complete rewrite
|
- Complete rewrite
|
||||||
* Check out the documentation - [http://oauth2.thephpleague.com](http://oauth2.thephpleague.com)
|
- Check out the documentation - [http://oauth2.thephpleague.com](http://oauth2.thephpleague.com)
|
||||||
|
|
||||||
## 3.2.0 (released 2014-04-16)
|
## [3.2.0] - 2014-04-16
|
||||||
|
|
||||||
* Added the ability to change the algorithm that is used to generate the token strings (Issue #151)
|
- Added the ability to change the algorithm that is used to generate the token strings (Issue #151)
|
||||||
|
|
||||||
## 3.1.2 (released 2014-02-26)
|
## [3.1.2] - 2014-02-26
|
||||||
|
|
||||||
* Support Authorization being an environment variable. [See more](http://fortrabbit.com/docs/essentials/quirks-and-constraints#authorization-header)
|
- Support Authorization being an environment variable. [See more](http://fortrabbit.com/docs/essentials/quirks-and-constraints#authorization-header)
|
||||||
|
|
||||||
## 3.1.1 (released 2013-12-05)
|
## [3.1.1] - 2013-12-05
|
||||||
|
|
||||||
* Normalize headers when `getallheaders()` is available (Issues #108 and #114)
|
- Normalize headers when `getallheaders()` is available (Issues #108 and #114)
|
||||||
|
|
||||||
## 3.1.0 (released 2013-12-05)
|
## [3.1.0] - 2013-12-05
|
||||||
|
|
||||||
* No longer necessary to inject the authorisation server into a grant, the server will inject itself
|
- No longer necessary to inject the authorisation server into a grant, the server will inject itself
|
||||||
* Added test for 1419ba8cdcf18dd034c8db9f7de86a2594b68605
|
- Added test for 1419ba8cdcf18dd034c8db9f7de86a2594b68605
|
||||||
|
|
||||||
## 3.0.1 (released 2013-12-02)
|
## [3.0.1] - 2013-12-02
|
||||||
|
|
||||||
* Forgot to tell TravisCI from testing PHP 5.3
|
- Forgot to tell TravisCI from testing PHP 5.3
|
||||||
|
|
||||||
## 3.0.0 (released 2013-12-02)
|
## [3.0.0] - 2013-12-02
|
||||||
|
|
||||||
* Fixed spelling of Implicit grant class (Issue #84)
|
- Fixed spelling of Implicit grant class (Issue #84)
|
||||||
* Travis CI now tests for PHP 5.5
|
- Travis CI now tests for PHP 5.5
|
||||||
* Fixes for checking headers for resource server (Issues #79 and #)
|
- Fixes for checking headers for resource server (Issues #79 and #)
|
||||||
* The word "bearer" now has a capital "B" in JSON output to match OAuth 2.0 spec
|
- The word "bearer" now has a capital "B" in JSON output to match OAuth 2.0 spec
|
||||||
* All grants no longer remove old sessions by default
|
- All grants no longer remove old sessions by default
|
||||||
* All grants now support custom access token TTL (Issue #92)
|
- All grants now support custom access token TTL (Issue #92)
|
||||||
* All methods which didn't before return a value now return `$this` to support method chaining
|
- All methods which didn't before return a value now return `$this` to support method chaining
|
||||||
* Removed the build in DB providers - these will be put in their own repos to remove baggage in the main repository
|
- Removed the build in DB providers - these will be put in their own repos to remove baggage in the main repository
|
||||||
* Removed support for PHP 5.3 because this library now uses traits and will use other modern PHP features going forward
|
- Removed support for PHP 5.3 because this library now uses traits and will use other modern PHP features going forward
|
||||||
* Moved some grant related functions into a trait to reduce duplicate code
|
- Moved some grant related functions into a trait to reduce duplicate code
|
||||||
|
|
||||||
## 2.1.1 (released 2013-06-02)
|
## [2.1.1] - 2013-06-02
|
||||||
|
|
||||||
* Added conditional `isValid()` flag to check for Authorization header only (thanks @alexmcroberts)
|
- Added conditional `isValid()` flag to check for Authorization header only (thanks @alexmcroberts)
|
||||||
* Fixed semantic meaning of `requireScopeParam()` and `requireStateParam()` by changing their default value to true
|
- Fixed semantic meaning of `requireScopeParam()` and `requireStateParam()` by changing their default value to true
|
||||||
* Updated some duff docblocks
|
- Updated some duff docblocks
|
||||||
* Corrected array key call in Resource.php (Issue #63)
|
- Corrected array key call in Resource.php (Issue #63)
|
||||||
|
|
||||||
## 2.1 (released 2013-05-10)
|
## [2.1.0] - 2013-05-10
|
||||||
|
|
||||||
* Moved zetacomponents/database to "suggest" in composer.json. If you rely on this feature you now need to include " zetacomponents/database" into "require" key in your own composer.json. (Issue #51)
|
- Moved zetacomponents/database to "suggest" in composer.json. If you rely on this feature you now need to include " zetacomponents/database" into "require" key in your own composer.json. (Issue #51)
|
||||||
* New method in Refresh grant called `rotateRefreshTokens()`. Pass in `true` to issue a new refresh token each time an access token is refreshed. This parameter needs to be set to true in order to request reduced scopes with the new access token. (Issue #47)
|
- New method in Refresh grant called `rotateRefreshTokens()`. Pass in `true` to issue a new refresh token each time an access token is refreshed. This parameter needs to be set to true in order to request reduced scopes with the new access token. (Issue #47)
|
||||||
* Rename `key` column in oauth_scopes table to `scope` as `key` is a reserved SQL word. (Issue #45)
|
- Rename `key` column in oauth_scopes table to `scope` as `key` is a reserved SQL word. (Issue #45)
|
||||||
* The `scope` parameter is no longer required by default as per the RFC. (Issue #43)
|
- The `scope` parameter is no longer required by default as per the RFC. (Issue #43)
|
||||||
* You can now set multiple default scopes by passing an array into `setDefaultScope()`. (Issue #42)
|
- You can now set multiple default scopes by passing an array into `setDefaultScope()`. (Issue #42)
|
||||||
* The password and client credentials grants now allow for multiple sessions per user. (Issue #32)
|
- The password and client credentials grants now allow for multiple sessions per user. (Issue #32)
|
||||||
* Scopes associated to authorization codes are not held in their own table (Issue #44)
|
- Scopes associated to authorization codes are not held in their own table (Issue #44)
|
||||||
* Database schema updates.
|
- Database schema updates.
|
||||||
|
|
||||||
## 2.0.5 (released 2013-05-09)
|
## [2.0.5] - 2013-05-09
|
||||||
|
|
||||||
* Fixed `oauth_session_token_scopes` table primary key
|
- Fixed `oauth_session_token_scopes` table primary key
|
||||||
* Removed `DEFAULT ''` that has slipped into some tables
|
- Removed `DEFAULT ''` that has slipped into some tables
|
||||||
* Fixed docblock for `SessionInterface::associateRefreshToken()`
|
- Fixed docblock for `SessionInterface::associateRefreshToken()`
|
||||||
|
|
||||||
## 2.0.4 (released 2013-05-09)
|
## [2.0.4] - 2013-05-09
|
||||||
|
|
||||||
* Renamed primary key in oauth_client_endpoints table
|
- Renamed primary key in oauth_client_endpoints table
|
||||||
* Adding missing column to oauth_session_authcodes
|
- Adding missing column to oauth_session_authcodes
|
||||||
* SECURITY FIX: A refresh token should be bound to a client ID
|
|
||||||
|
|
||||||
## 2.0.3 (released 2013-05-08)
|
### Security
|
||||||
|
- A refresh token should be bound to a client ID
|
||||||
|
|
||||||
* Fixed a link to code in composer.json
|
## [2.0.3] - 2013-05-08
|
||||||
|
|
||||||
## 2.0.2 (released 2013-05-08)
|
- Fixed a link to code in composer.json
|
||||||
|
|
||||||
* Updated README with wiki guides
|
## [2.0.2] - 2013-05-08
|
||||||
* Removed `null` as default parameters in some methods in the storage interfaces
|
|
||||||
* Fixed license copyright
|
|
||||||
|
|
||||||
## 2.0.0 (released 2013-05-08)
|
- Updated README with wiki guides
|
||||||
|
- Removed `null` as default parameters in some methods in the storage interfaces
|
||||||
|
- Fixed license copyright
|
||||||
|
|
||||||
|
## [2.0.0] - 2013-05-08
|
||||||
|
|
||||||
**If you're upgrading from v1.0.8 there are lots of breaking changes**
|
**If you're upgrading from v1.0.8 there are lots of breaking changes**
|
||||||
|
|
||||||
* Rewrote the session storage interface from scratch so methods are more obvious
|
- Rewrote the session storage interface from scratch so methods are more obvious
|
||||||
* Included a PDO driver which implements the storage interfaces so the library is more "get up and go"
|
- Included a PDO driver which implements the storage interfaces so the library is more "get up and go"
|
||||||
* Further normalised the database structure so all sessions no longer contain infomation related to authorization grant (which may or may not be enabled)
|
- Further normalised the database structure so all sessions no longer contain infomation related to authorization grant (which may or may not be enabled)
|
||||||
* A session can have multiple associated access tokens
|
- A session can have multiple associated access tokens
|
||||||
* Individual grants can have custom expire times for access tokens
|
- Individual grants can have custom expire times for access tokens
|
||||||
* Authorization codes now have a TTL of 10 minutes by default (can be manually set)
|
- Authorization codes now have a TTL of 10 minutes by default (can be manually set)
|
||||||
* Refresh tokens now have a TTL of one week by default (can be manually set)
|
- Refresh tokens now have a TTL of one week by default (can be manually set)
|
||||||
* The client credentials grant will no longer gives out refresh tokens as per the specification
|
- The client credentials grant will no longer gives out refresh tokens as per the specification
|
||||||
|
|
||||||
## 1.0.8 (released 2013-03-18)
|
## [1.0.8] - 2013-03-18
|
||||||
|
|
||||||
* Fixed check for required state parameter
|
- Fixed check for required state parameter
|
||||||
* Fixed check that user's credentials are correct in Password grant
|
- Fixed check that user's credentials are correct in Password grant
|
||||||
|
|
||||||
## 1.0.7 (released 2013-03-04)
|
## [1.0.7] - 2013-03-04
|
||||||
|
|
||||||
* Added method `requireStateParam()`
|
- Added method `requireStateParam()`
|
||||||
* Added method `requireScopeParam()`
|
- Added method `requireScopeParam()`
|
||||||
|
|
||||||
## 1.0.6 (released 2013-02-22)
|
## [1.0.6] - 2013-02-22
|
||||||
|
|
||||||
* Added links to tutorials in the README
|
- Added links to tutorials in the README
|
||||||
* Added missing `state` parameter request to the `checkAuthoriseParams()` method.
|
- Added missing `state` parameter request to the `checkAuthoriseParams()` method.
|
||||||
|
|
||||||
## 1.0.5 (released 2013-02-21)
|
## [1.0.5] - 2013-02-21
|
||||||
|
|
||||||
* Fixed the SQL example for SessionInterface::getScopes()
|
- Fixed the SQL example for SessionInterface::getScopes()
|
||||||
|
|
||||||
## 1.0.3 (released 2013-02-20)
|
## [1.0.3] - 2013-02-20
|
||||||
|
|
||||||
* Changed all instances of the "authentication server" to "authorization server"
|
- Changed all instances of the "authentication server" to "authorization server"
|
||||||
|
|
||||||
## 1.0.2 (released 2013-02-20)
|
## [1.0.2] - 2013-02-20
|
||||||
|
|
||||||
* Fixed MySQL create table order
|
- Fixed MySQL create table order
|
||||||
* Fixed version number in composer.json
|
- Fixed version number in composer.json
|
||||||
|
|
||||||
## 1.0.1 (released 2013-02-19)
|
## [1.0.1] - 2013-02-19
|
||||||
|
|
||||||
* Updated AuthServer.php to use `self::getParam()`
|
- Updated AuthServer.php to use `self::getParam()`
|
||||||
|
|
||||||
## 1.0.0 (released 2013-02-15)
|
## 1.0.0 - 2013-02-15
|
||||||
|
|
||||||
* First major release
|
- First major release
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/8.0.0...HEAD
|
||||||
|
[8.0.0]: https://github.com/thephpleague/oauth2-server/compare/7.4.0...8.0.0
|
||||||
|
[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
|
||||||
|
[6.0.2]: https://github.com/thephpleague/oauth2-server/compare/6.0.1...6.0.2
|
||||||
|
[6.0.1]: https://github.com/thephpleague/oauth2-server/compare/6.0.0...6.0.1
|
||||||
|
[6.0.0]: https://github.com/thephpleague/oauth2-server/compare/5.1.4...6.0.0
|
||||||
|
[5.1.4]: https://github.com/thephpleague/oauth2-server/compare/5.1.3...5.1.4
|
||||||
|
[5.1.3]: https://github.com/thephpleague/oauth2-server/compare/5.1.2...5.1.3
|
||||||
|
[5.1.2]: https://github.com/thephpleague/oauth2-server/compare/5.1.1...5.1.2
|
||||||
|
[5.1.1]: https://github.com/thephpleague/oauth2-server/compare/5.1.0...5.1.1
|
||||||
|
[5.1.0]: https://github.com/thephpleague/oauth2-server/compare/5.0.2...5.1.0
|
||||||
|
[5.0.3]: https://github.com/thephpleague/oauth2-server/compare/5.0.3...5.0.2
|
||||||
|
[5.0.2]: https://github.com/thephpleague/oauth2-server/compare/5.0.1...5.0.2
|
||||||
|
[5.0.1]: https://github.com/thephpleague/oauth2-server/compare/5.0.0...5.0.1
|
||||||
|
[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
|
||||||
|
[4.1.2]: https://github.com/thephpleague/oauth2-server/compare/4.1.1...4.1.2
|
||||||
|
[4.1.1]: https://github.com/thephpleague/oauth2-server/compare/4.0.0...4.1.1
|
||||||
|
[4.1.0]: https://github.com/thephpleague/oauth2-server/compare/4.0.5...4.1.0
|
||||||
|
[4.0.5]: https://github.com/thephpleague/oauth2-server/compare/4.0.4...4.0.5
|
||||||
|
[4.0.4]: https://github.com/thephpleague/oauth2-server/compare/4.0.3...4.0.4
|
||||||
|
[4.0.3]: https://github.com/thephpleague/oauth2-server/compare/4.0.2...4.0.3
|
||||||
|
[4.0.2]: https://github.com/thephpleague/oauth2-server/compare/4.0.1...4.0.2
|
||||||
|
[4.0.1]: https://github.com/thephpleague/oauth2-server/compare/4.0.0...4.0.1
|
||||||
|
[4.0.0]: https://github.com/thephpleague/oauth2-server/compare/3.2.0...4.0.0
|
||||||
|
[3.2.0]: https://github.com/thephpleague/oauth2-server/compare/3.1.2...3.2.0
|
||||||
|
[3.1.2]: https://github.com/thephpleague/oauth2-server/compare/3.1.1...3.1.2
|
||||||
|
[3.1.1]: https://github.com/thephpleague/oauth2-server/compare/3.1.0...3.1.1
|
||||||
|
[3.1.0]: https://github.com/thephpleague/oauth2-server/compare/3.0.1...3.1.0
|
||||||
|
[3.0.1]: https://github.com/thephpleague/oauth2-server/compare/3.0.0...3.0.1
|
||||||
|
[3.0.0]: https://github.com/thephpleague/oauth2-server/compare/2.1.1...3.0.0
|
||||||
|
[2.1.1]: https://github.com/thephpleague/oauth2-server/compare/2.1.0...2.1.1
|
||||||
|
[2.1.0]: https://github.com/thephpleague/oauth2-server/compare/2.0.5...2.1.0
|
||||||
|
[2.0.5]: https://github.com/thephpleague/oauth2-server/compare/2.0.4...2.0.5
|
||||||
|
[2.0.4]: https://github.com/thephpleague/oauth2-server/compare/2.0.3...2.0.4
|
||||||
|
[2.0.3]: https://github.com/thephpleague/oauth2-server/compare/2.0.2...2.0.3
|
||||||
|
[2.0.2]: https://github.com/thephpleague/oauth2-server/compare/2.0.0...2.0.2
|
||||||
|
[2.0.0]: https://github.com/thephpleague/oauth2-server/compare/1.0.8...2.0.0
|
||||||
|
[1.0.8]: https://github.com/thephpleague/oauth2-server/compare/1.0.7...1.0.8
|
||||||
|
[1.0.7]: https://github.com/thephpleague/oauth2-server/compare/1.0.6...1.0.7
|
||||||
|
[1.0.6]: https://github.com/thephpleague/oauth2-server/compare/1.0.5...1.0.6
|
||||||
|
[1.0.5]: https://github.com/thephpleague/oauth2-server/compare/1.0.3...1.0.5
|
||||||
|
[1.0.3]: https://github.com/thephpleague/oauth2-server/compare/1.0.2...1.0.3
|
||||||
|
[1.0.2]: https://github.com/thephpleague/oauth2-server/compare/1.0.1...1.0.2
|
||||||
|
[1.0.1]: https://github.com/thephpleague/oauth2-server/compare/1.0.0...1.0.1
|
||||||
|
73
CODE_OF_CONDUCT.md
Normal file
73
CODE_OF_CONDUCT.md
Normal 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
|
22
CONDUCT.md
22
CONDUCT.md
@@ -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/)
|
|
62
README.md
62
README.md
@@ -1,21 +1,16 @@
|
|||||||
# PHP OAuth 2.0 Server
|
# PHP OAuth 2.0 Server
|
||||||
|
|
||||||
### :warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning:
|
|
||||||
### Security Notice
|
|
||||||
|
|
||||||
### Please upgrade to version `>=5.1.6` (backwards compatible) or `6.x` (one tiny breaking change) to fix some potential security vulnerabilities - [visit this page for more information](https://oauth2.thephpleague.com/v5-security-improvements/)
|
|
||||||
### :warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning::warning:
|
|
||||||
|
|
||||||
[](https://github.com/thephpleague/oauth2-server/releases)
|
[](https://github.com/thephpleague/oauth2-server/releases)
|
||||||
[](LICENSE.md)
|
[](LICENSE.md)
|
||||||
[](https://travis-ci.org/thephpleague/oauth2-server)
|
[](https://travis-ci.org/thephpleague/oauth2-server)
|
||||||
[](https://scrutinizer-ci.com/g/thephpleague/oauth2-server/code-structure)
|
[](https://scrutinizer-ci.com/g/thephpleague/oauth2-server/code-structure)
|
||||||
[](https://scrutinizer-ci.com/g/thephpleague/oauth2-server)
|
[](https://scrutinizer-ci.com/g/thephpleague/oauth2-server)
|
||||||
[](https://packagist.org/packages/league/oauth2-server)
|
[](https://packagist.org/packages/league/oauth2-server)
|
||||||
|
[](https://github.com/phpstan/phpstan)
|
||||||
|
|
||||||
`league/oauth2-server` is a standards compliant implementation of an [OAuth 2.0](https://tools.ietf.org/html/rfc6749) authorization server written in PHP which makes working with OAuth 2.0 trivial. You can easily configure an OAuth 2.0 server to protect your API with access tokens, or allow clients to request new access tokens and refresh them.
|
`league/oauth2-server` is a standards compliant implementation of an [OAuth 2.0](https://tools.ietf.org/html/rfc6749) authorization server written in PHP which makes working with OAuth 2.0 trivial. You can easily configure an OAuth 2.0 server to protect your API with access tokens, or allow clients to request new access tokens and refresh them.
|
||||||
|
|
||||||
It supports out of the box the following grants:
|
Out of the box it supports the following grants:
|
||||||
|
|
||||||
* Authorization code grant
|
* Authorization code grant
|
||||||
* Implicit grant
|
* Implicit grant
|
||||||
@@ -36,25 +31,53 @@ This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](ht
|
|||||||
|
|
||||||
The following versions of PHP are supported:
|
The following versions of PHP are supported:
|
||||||
|
|
||||||
* PHP 5.6
|
|
||||||
* PHP 7.0
|
|
||||||
* PHP 7.1
|
* PHP 7.1
|
||||||
* PHP 7.2
|
* PHP 7.2
|
||||||
|
* PHP 7.3
|
||||||
|
|
||||||
The `openssl` extension is also required.
|
The `openssl` and `json` extensions are also required.
|
||||||
|
|
||||||
|
All HTTP messages passed to the server should be [PSR-7 compliant](https://www.php-fig.org/psr/psr-7/). This ensures interoperability with other packages and frameworks.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
composer require league/oauth2-server
|
||||||
|
```
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The library documentation can be found at [https://oauth2.thephpleague.com](https://oauth2.thephpleague.com).
|
The library documentation can be found at [https://oauth2.thephpleague.com](https://oauth2.thephpleague.com).
|
||||||
You can contribute to the documentation in the [gh-pages branch](https://github.com/thephpleague/oauth2-server/tree/gh-pages/).
|
You can contribute to the documentation in the [gh-pages branch](https://github.com/thephpleague/oauth2-server/tree/gh-pages/).
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The library uses [PHPUnit](https://phpunit.de/) for unit tests and [PHPStan](https://github.com/phpstan/phpstan) for static analysis of the code.
|
||||||
|
|
||||||
|
```
|
||||||
|
vendor/bin/phpunit
|
||||||
|
vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## Continous Integration
|
||||||
|
|
||||||
|
We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci.com/), and [StyleCI](https://styleci.io/) for continuous integration. Check out [our](https://github.com/thephpleague/oauth2-server/blob/master/.travis.yml) [configuration](https://github.com/thephpleague/oauth2-server/blob/master/.scrutinizer.yml) [files](https://github.com/thephpleague/oauth2-server/blob/master/.styleci.yml) if you'd like to know more.
|
||||||
|
|
||||||
|
## 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
|
## Changelog
|
||||||
|
|
||||||
[See the project releases page](https://github.com/thephpleague/oauth2-server/releases)
|
See the [project changelog](https://github.com/thephpleague/oauth2-server/blob/master/CHANGELOG.md)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
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
|
## Support
|
||||||
|
|
||||||
@@ -62,17 +85,9 @@ Bugs and feature request are tracked on [GitHub](https://github.com/thephpleague
|
|||||||
|
|
||||||
If you have any questions about OAuth _please_ open a ticket here; please **don't** email the address below.
|
If you have any questions about OAuth _please_ open a ticket here; please **don't** email the address below.
|
||||||
|
|
||||||
<a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/N2YMJcLBppt2Eg9E1jGu4gef/thephpleague/oauth2-server'>
|
|
||||||
<img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/N2YMJcLBppt2Eg9E1jGu4gef/thephpleague/oauth2-server.svg' />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
## Commercial Support
|
|
||||||
|
|
||||||
If you would like help implementing this library into your existing platform, or would be interested in OAuth advice or training for you and your team please get in touch with [Glynde Labs](https://glyndelabs.com).
|
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
If you discover any security related issues, please email `hello@alexbilbie.com` instead of using the issue tracker.
|
If you discover any security related issues, please email `andrew@noexceptions.io` instead of using the issue tracker.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
@@ -80,11 +95,12 @@ This package is released under the MIT License. See the bundled [LICENSE](https:
|
|||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster), [Brian
|
This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster).
|
||||||
Retterer](https://twitter.com/bretterer), and [Simon Hamp](https://twitter.com/simonhamp).
|
|
||||||
|
|
||||||
Between 2012 and 2017 this library was developed and maintained by [Alex Bilbie](https://alexbilbie.com/).
|
Between 2012 and 2017 this library was developed and maintained by [Alex Bilbie](https://alexbilbie.com/).
|
||||||
|
|
||||||
|
PHP OAuth 2.0 Server is one of many packages provided by The PHP League. To find out more, please visit [our website](https://thephpleague.com).
|
||||||
|
|
||||||
Special thanks to [all of these awesome contributors](https://github.com/thephpleague/oauth2-server/contributors).
|
Special thanks to [all of these awesome contributors](https://github.com/thephpleague/oauth2-server/contributors).
|
||||||
|
|
||||||
Additional thanks go to the [Mozilla Secure Open Source Fund](https://wiki.mozilla.org/MOSS/Secure_Open_Source) for funding a security audit of this library.
|
Additional thanks go to the [Mozilla Secure Open Source Fund](https://wiki.mozilla.org/MOSS/Secure_Open_Source) for funding a security audit of this library.
|
||||||
|
@@ -4,17 +4,20 @@
|
|||||||
"homepage": "https://oauth2.thephpleague.com/",
|
"homepage": "https://oauth2.thephpleague.com/",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.6.0",
|
"php": ">=7.1.0",
|
||||||
"ext-openssl": "*",
|
"ext-openssl": "*",
|
||||||
"league/event": "^2.1",
|
"league/event": "^2.2",
|
||||||
"lcobucci/jwt": "^3.1",
|
"lcobucci/jwt": "^3.3.1",
|
||||||
"paragonie/random_compat": "^2.0",
|
"psr/http-message": "^1.0.1",
|
||||||
"psr/http-message": "^1.0",
|
"defuse/php-encryption": "^2.2.1",
|
||||||
"defuse/php-encryption": "^2.1"
|
"ext-json": "*"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8.38 || ^5.7.21",
|
"phpunit/phpunit": "^7.5.13 || ^8.2.3",
|
||||||
"zendframework/zend-diactoros": "^1.0"
|
"zendframework/zend-diactoros": "^2.1.2",
|
||||||
|
"phpstan/phpstan": "^0.11.8",
|
||||||
|
"phpstan/phpstan-phpunit": "^0.11.2",
|
||||||
|
"roave/security-advisories": "dev-master"
|
||||||
},
|
},
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
@@ -44,6 +47,12 @@
|
|||||||
"email": "hello@alexbilbie.com",
|
"email": "hello@alexbilbie.com",
|
||||||
"homepage": "http://www.alexbilbie.com",
|
"homepage": "http://www.alexbilbie.com",
|
||||||
"role": "Developer"
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Andy Millington",
|
||||||
|
"email": "andrew@noexceptions.io",
|
||||||
|
"homepage": "https://www.noexceptions.io",
|
||||||
|
"role": "Developer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"replace": {
|
"replace": {
|
||||||
|
@@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
"slim/slim": "3.0.*"
|
"slim/slim": "^3.0.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"league/event": "^2.1",
|
"league/event": "^2.2",
|
||||||
"lcobucci/jwt": "^3.1",
|
"lcobucci/jwt": "^3.3",
|
||||||
"paragonie/random_compat": "^2.0",
|
|
||||||
"psr/http-message": "^1.0",
|
"psr/http-message": "^1.0",
|
||||||
"defuse/php-encryption": "^2.1",
|
"defuse/php-encryption": "^2.2",
|
||||||
"zendframework/zend-diactoros": "^1.0"
|
"zendframework/zend-diactoros": "^2.1.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
242
examples/composer.lock
generated
242
examples/composer.lock
generated
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"_readme": [
|
"_readme": [
|
||||||
"This file locks the dependencies of your project to a known state",
|
"This file locks the dependencies of your project to a known state",
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "9813ed7c3b6dcf107f44df9392935b8f",
|
"content-hash": "a7f5c3fdcadb17399bbd97f15e1b11f1",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "container-interop/container-interop",
|
"name": "container-interop/container-interop",
|
||||||
@@ -39,21 +39,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/fast-route",
|
"name": "nikic/fast-route",
|
||||||
"version": "v0.6.0",
|
"version": "v1.3.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/FastRoute.git",
|
"url": "https://github.com/nikic/FastRoute.git",
|
||||||
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22"
|
"reference": "181d480e08d9476e61381e04a71b34dc0432e812"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/31fa86924556b80735f98b294a7ffdfb26789f22",
|
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
|
||||||
"reference": "31fa86924556b80735f98b294a7ffdfb26789f22",
|
"reference": "181d480e08d9476e61381e04a71b34dc0432e812",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.4.0"
|
"php": ">=5.4.0"
|
||||||
},
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.35|~5.7"
|
||||||
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
@@ -78,29 +81,33 @@
|
|||||||
"router",
|
"router",
|
||||||
"routing"
|
"routing"
|
||||||
],
|
],
|
||||||
"time": "2015-06-18T19:15:47+00:00"
|
"time": "2018-02-13T20:26:39+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pimple/pimple",
|
"name": "pimple/pimple",
|
||||||
"version": "v3.0.2",
|
"version": "v3.2.3",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/silexphp/Pimple.git",
|
"url": "https://github.com/silexphp/Pimple.git",
|
||||||
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a"
|
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a",
|
"url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32",
|
||||||
"reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a",
|
"reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.3.0"
|
"php": ">=5.3.0",
|
||||||
|
"psr/container": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/phpunit-bridge": "^3.2"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "3.0.x-dev"
|
"dev-master": "3.2.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -124,7 +131,7 @@
|
|||||||
"container",
|
"container",
|
||||||
"dependency injection"
|
"dependency injection"
|
||||||
],
|
],
|
||||||
"time": "2015-09-11T15:10:35+00:00"
|
"time": "2018-01-21T07:42:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/container",
|
"name": "psr/container",
|
||||||
@@ -227,27 +234,32 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "slim/slim",
|
"name": "slim/slim",
|
||||||
"version": "3.0.0",
|
"version": "3.12.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/slimphp/Slim.git",
|
"url": "https://github.com/slimphp/Slim.git",
|
||||||
"reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e"
|
"reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/slimphp/Slim/zipball/3b06f0f2d84dabbe81b6cea46ace46a3e883253e",
|
"url": "https://api.github.com/repos/slimphp/Slim/zipball/eaee12ef8d0750db62b8c548016d82fb33addb6b",
|
||||||
"reference": "3b06f0f2d84dabbe81b6cea46ace46a3e883253e",
|
"reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"container-interop/container-interop": "^1.1",
|
"container-interop/container-interop": "^1.2",
|
||||||
"nikic/fast-route": "^0.6",
|
"nikic/fast-route": "^1.0",
|
||||||
"php": ">=5.5.0",
|
"php": ">=5.5.0",
|
||||||
"pimple/pimple": "^3.0",
|
"pimple/pimple": "^3.0",
|
||||||
|
"psr/container": "^1.0",
|
||||||
"psr/http-message": "^1.0"
|
"psr/http-message": "^1.0"
|
||||||
},
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-message-implementation": "1.0"
|
||||||
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.0"
|
"phpunit/phpunit": "^4.0",
|
||||||
|
"squizlabs/php_codesniffer": "^2.5"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -282,38 +294,38 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
|
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
|
||||||
"homepage": "http://slimframework.com",
|
"homepage": "https://slimframework.com",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"api",
|
"api",
|
||||||
"framework",
|
"framework",
|
||||||
"micro",
|
"micro",
|
||||||
"router"
|
"router"
|
||||||
],
|
],
|
||||||
"time": "2015-12-07T14:11:09+00:00"
|
"time": "2019-04-16T16:47:29+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
"name": "defuse/php-encryption",
|
"name": "defuse/php-encryption",
|
||||||
"version": "v2.1.0",
|
"version": "v2.2.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/defuse/php-encryption.git",
|
"url": "https://github.com/defuse/php-encryption.git",
|
||||||
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689"
|
"reference": "0f407c43b953d571421e0020ba92082ed5fb7620"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689",
|
"url": "https://api.github.com/repos/defuse/php-encryption/zipball/0f407c43b953d571421e0020ba92082ed5fb7620",
|
||||||
"reference": "5176f5abb38d3ea8a6e3ac6cd3bbb54d8185a689",
|
"reference": "0f407c43b953d571421e0020ba92082ed5fb7620",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-openssl": "*",
|
"ext-openssl": "*",
|
||||||
"paragonie/random_compat": "~2.0",
|
"paragonie/random_compat": ">= 2",
|
||||||
"php": ">=5.4.0"
|
"php": ">=5.4.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"nikic/php-parser": "^2.0|^3.0",
|
"nikic/php-parser": "^2.0|^3.0|^4.0",
|
||||||
"phpunit/phpunit": "^4|^5"
|
"phpunit/phpunit": "^4|^5"
|
||||||
},
|
},
|
||||||
"bin": [
|
"bin": [
|
||||||
@@ -354,37 +366,34 @@
|
|||||||
"security",
|
"security",
|
||||||
"symmetric key cryptography"
|
"symmetric key cryptography"
|
||||||
],
|
],
|
||||||
"time": "2017-05-18T21:28:48+00:00"
|
"time": "2018-07-24T23:27:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "lcobucci/jwt",
|
"name": "lcobucci/jwt",
|
||||||
"version": "3.2.1",
|
"version": "3.3.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/lcobucci/jwt.git",
|
"url": "https://github.com/lcobucci/jwt.git",
|
||||||
"reference": "ddce703826f9c5229781933b1a39069e38e6a0f3"
|
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/ddce703826f9c5229781933b1a39069e38e6a0f3",
|
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
|
||||||
"reference": "ddce703826f9c5229781933b1a39069e38e6a0f3",
|
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
"ext-openssl": "*",
|
"ext-openssl": "*",
|
||||||
"php": ">=5.5"
|
"php": "^5.6 || ^7.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"mdanter/ecc": "~0.3.1",
|
|
||||||
"mikey179/vfsstream": "~1.5",
|
"mikey179/vfsstream": "~1.5",
|
||||||
"phpmd/phpmd": "~2.2",
|
"phpmd/phpmd": "~2.2",
|
||||||
"phpunit/php-invoker": "~1.1",
|
"phpunit/php-invoker": "~1.1",
|
||||||
"phpunit/phpunit": "~4.5",
|
"phpunit/phpunit": "^5.7 || ^7.3",
|
||||||
"squizlabs/php_codesniffer": "~2.3"
|
"squizlabs/php_codesniffer": "~2.3"
|
||||||
},
|
},
|
||||||
"suggest": {
|
|
||||||
"mdanter/ecc": "Required to use Elliptic Curves based algorithms."
|
|
||||||
},
|
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
@@ -412,20 +421,20 @@
|
|||||||
"JWS",
|
"JWS",
|
||||||
"jwt"
|
"jwt"
|
||||||
],
|
],
|
||||||
"time": "2016-10-31T20:09:32+00:00"
|
"time": "2019-05-24T18:30:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/event",
|
"name": "league/event",
|
||||||
"version": "2.1.2",
|
"version": "2.2.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/event.git",
|
"url": "https://github.com/thephpleague/event.git",
|
||||||
"reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd"
|
"reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/event/zipball/e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd",
|
"url": "https://api.github.com/repos/thephpleague/event/zipball/d2cc124cf9a3fab2bb4ff963307f60361ce4d119",
|
||||||
"reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd",
|
"reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -433,7 +442,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"henrikbjorn/phpspec-code-coverage": "~1.0.1",
|
"henrikbjorn/phpspec-code-coverage": "~1.0.1",
|
||||||
"phpspec/phpspec": "~2.0.0"
|
"phpspec/phpspec": "^2.2"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
@@ -462,37 +471,33 @@
|
|||||||
"event",
|
"event",
|
||||||
"listener"
|
"listener"
|
||||||
],
|
],
|
||||||
"time": "2015-05-21T12:24:47+00:00"
|
"time": "2018-11-26T11:52:41+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "paragonie/random_compat",
|
"name": "paragonie/random_compat",
|
||||||
"version": "v2.0.10",
|
"version": "v9.99.99",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/paragonie/random_compat.git",
|
"url": "https://github.com/paragonie/random_compat.git",
|
||||||
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d"
|
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/634bae8e911eefa89c1abfbf1b66da679ac8f54d",
|
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
|
||||||
"reference": "634bae8e911eefa89c1abfbf1b66da679ac8f54d",
|
"reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.2.0"
|
"php": "^7"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "4.*|5.*"
|
"phpunit/phpunit": "4.*|5.*",
|
||||||
|
"vimeo/psalm": "^1"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
|
||||||
"files": [
|
|
||||||
"lib/random.php"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
"license": [
|
"license": [
|
||||||
"MIT"
|
"MIT"
|
||||||
@@ -507,10 +512,129 @@
|
|||||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"csprng",
|
"csprng",
|
||||||
|
"polyfill",
|
||||||
"pseudorandom",
|
"pseudorandom",
|
||||||
"random"
|
"random"
|
||||||
],
|
],
|
||||||
"time": "2017-03-13T16:27:32+00:00"
|
"time": "2018-07-02T15:55:56+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-factory",
|
||||||
|
"version": "1.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-factory.git",
|
||||||
|
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||||
|
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
|
||||||
|
"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": "2019-04-30T12:38:16+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "zendframework/zend-diactoros",
|
||||||
|
"version": "2.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/zendframework/zend-diactoros.git",
|
||||||
|
"reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1",
|
||||||
|
"reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1",
|
||||||
|
"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.1.x-dev",
|
||||||
|
"dev-develop": "2.2.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": "2019-07-10T16:13:25+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
@@ -49,16 +49,18 @@ $app->get(
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$totalUsers = count($users);
|
||||||
|
|
||||||
// If the access token doesn't have the `basic` scope hide users' names
|
// If the access token doesn't have the `basic` scope hide users' names
|
||||||
if (in_array('basic', $request->getAttribute('oauth_scopes')) === false) {
|
if (in_array('basic', $request->getAttribute('oauth_scopes')) === false) {
|
||||||
for ($i = 0; $i < count($users); $i++) {
|
for ($i = 0; $i < $totalUsers; $i++) {
|
||||||
unset($users[$i]['name']);
|
unset($users[$i]['name']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the access token doesn't have the `email` scope hide users' email addresses
|
// If the access token doesn't have the `email` scope hide users' email addresses
|
||||||
if (in_array('email', $request->getAttribute('oauth_scopes')) === false) {
|
if (in_array('email', $request->getAttribute('oauth_scopes')) === false) {
|
||||||
for ($i = 0; $i < count($users); $i++) {
|
for ($i = 0; $i < $totalUsers; $i++) {
|
||||||
unset($users[$i]['email']);
|
unset($users[$i]['email']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -41,7 +41,6 @@ $app = new App([
|
|||||||
$privateKeyPath,
|
$privateKeyPath,
|
||||||
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
|
'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
|
||||||
);
|
);
|
||||||
$server->setEncryptionKey('lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen');
|
|
||||||
|
|
||||||
// Enable the implicit grant on the server with a token TTL of 1 hour
|
// Enable the implicit grant on the server with a token TTL of 1 hour
|
||||||
$server->enableGrantType(new ImplicitGrant(new \DateInterval('PT1H')));
|
$server->enableGrantType(new ImplicitGrant(new \DateInterval('PT1H')));
|
||||||
|
@@ -11,13 +11,9 @@ namespace OAuth2ServerExamples\Entities;
|
|||||||
|
|
||||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
use League\OAuth2\Server\Entities\Traits\EntityTrait;
|
||||||
|
use League\OAuth2\Server\Entities\Traits\ScopeTrait;
|
||||||
|
|
||||||
class ScopeEntity implements ScopeEntityInterface
|
class ScopeEntity implements ScopeEntityInterface
|
||||||
{
|
{
|
||||||
use EntityTrait;
|
use EntityTrait, ScopeTrait;
|
||||||
|
|
||||||
public function jsonSerialize()
|
|
||||||
{
|
|
||||||
return $this->getIdentifier();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -14,16 +14,33 @@ use OAuth2ServerExamples\Entities\ClientEntity;
|
|||||||
|
|
||||||
class ClientRepository implements ClientRepositoryInterface
|
class ClientRepository implements ClientRepositoryInterface
|
||||||
{
|
{
|
||||||
|
const CLIENT_NAME = 'My Awesome App';
|
||||||
|
const REDIRECT_URI = 'http://foo/bar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true)
|
public function getClientEntity($clientIdentifier)
|
||||||
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
|
||||||
|
$client->setIdentifier($clientIdentifier);
|
||||||
|
$client->setName(self::CLIENT_NAME);
|
||||||
|
$client->setRedirectUri(self::REDIRECT_URI);
|
||||||
|
|
||||||
|
return $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validateClient($clientIdentifier, $clientSecret, $grantType)
|
||||||
{
|
{
|
||||||
$clients = [
|
$clients = [
|
||||||
'myawesomeapp' => [
|
'myawesomeapp' => [
|
||||||
'secret' => password_hash('abc123', PASSWORD_BCRYPT),
|
'secret' => password_hash('abc123', PASSWORD_BCRYPT),
|
||||||
'name' => 'My Awesome App',
|
'name' => self::CLIENT_NAME,
|
||||||
'redirect_uri' => 'http://foo/bar',
|
'redirect_uri' => self::REDIRECT_URI,
|
||||||
'is_confidential' => true,
|
'is_confidential' => true,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
@@ -34,18 +51,10 @@ class ClientRepository implements ClientRepositoryInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
$mustValidateSecret === true
|
$clients[$clientIdentifier]['is_confidential'] === true
|
||||||
&& $clients[$clientIdentifier]['is_confidential'] === true
|
|
||||||
&& password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false
|
&& password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$client = new ClientEntity();
|
|
||||||
$client->setIdentifier($clientIdentifier);
|
|
||||||
$client->setName($clients[$clientIdentifier]['name']);
|
|
||||||
$client->setRedirectUri($clients[$clientIdentifier]['redirect_uri']);
|
|
||||||
|
|
||||||
return $client;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,7 +18,7 @@ class RefreshTokenRepository implements RefreshTokenRepositoryInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntityInterface)
|
public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity)
|
||||||
{
|
{
|
||||||
// Some logic to persist the refresh token in a database
|
// Some logic to persist the refresh token in a database
|
||||||
}
|
}
|
||||||
|
8
phpstan.neon
Normal file
8
phpstan.neon
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
includes:
|
||||||
|
- vendor/phpstan/phpstan-phpunit/extension.neon
|
||||||
|
- vendor/phpstan/phpstan-phpunit/rules.neon
|
||||||
|
services:
|
||||||
|
-
|
||||||
|
class: LeagueTests\PHPStan\AbstractGrantExtension
|
||||||
|
tags:
|
||||||
|
- phpstan.broker.dynamicMethodReturnTypeExtension
|
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server;
|
namespace League\OAuth2\Server;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use Defuse\Crypto\Key;
|
||||||
use League\Event\EmitterAwareInterface;
|
use League\Event\EmitterAwareInterface;
|
||||||
use League\Event\EmitterAwareTrait;
|
use League\Event\EmitterAwareTrait;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
@@ -17,6 +19,7 @@ use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
|||||||
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
||||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||||
|
use League\OAuth2\Server\ResponseTypes\AbstractResponseType;
|
||||||
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
|
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
|
||||||
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
@@ -32,22 +35,22 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
protected $enabledGrantTypes = [];
|
protected $enabledGrantTypes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \DateInterval[]
|
* @var DateInterval[]
|
||||||
*/
|
*/
|
||||||
protected $grantTypeAccessTokenTTL = [];
|
protected $grantTypeAccessTokenTTL = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var CryptKey
|
* @var CryptKeyInterface
|
||||||
*/
|
*/
|
||||||
protected $privateKey;
|
protected $privateKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var CryptKey
|
* @var CryptKeyInterface
|
||||||
*/
|
*/
|
||||||
protected $publicKey;
|
protected $publicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var null|ResponseTypeInterface
|
* @var ResponseTypeInterface
|
||||||
*/
|
*/
|
||||||
protected $responseType;
|
protected $responseType;
|
||||||
|
|
||||||
@@ -67,7 +70,7 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
private $scopeRepository;
|
private $scopeRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string|Key
|
||||||
*/
|
*/
|
||||||
private $encryptionKey;
|
private $encryptionKey;
|
||||||
|
|
||||||
@@ -82,8 +85,8 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
* @param ClientRepositoryInterface $clientRepository
|
* @param ClientRepositoryInterface $clientRepository
|
||||||
* @param AccessTokenRepositoryInterface $accessTokenRepository
|
* @param AccessTokenRepositoryInterface $accessTokenRepository
|
||||||
* @param ScopeRepositoryInterface $scopeRepository
|
* @param ScopeRepositoryInterface $scopeRepository
|
||||||
* @param CryptKey|string $privateKey
|
* @param CryptKeyInterface|string $privateKey
|
||||||
* @param string $encryptionKey
|
* @param string|Key $encryptionKey
|
||||||
* @param null|ResponseTypeInterface $responseType
|
* @param null|ResponseTypeInterface $responseType
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -98,11 +101,19 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
$this->accessTokenRepository = $accessTokenRepository;
|
$this->accessTokenRepository = $accessTokenRepository;
|
||||||
$this->scopeRepository = $scopeRepository;
|
$this->scopeRepository = $scopeRepository;
|
||||||
|
|
||||||
if ($privateKey instanceof CryptKey === false) {
|
if ($privateKey instanceof CryptKeyInterface === false) {
|
||||||
$privateKey = new CryptKey($privateKey);
|
$privateKey = new CryptKey($privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->privateKey = $privateKey;
|
$this->privateKey = $privateKey;
|
||||||
$this->encryptionKey = $encryptionKey;
|
$this->encryptionKey = $encryptionKey;
|
||||||
|
|
||||||
|
if ($responseType === null) {
|
||||||
|
$responseType = new BearerTokenResponse();
|
||||||
|
} else {
|
||||||
|
$responseType = clone $responseType;
|
||||||
|
}
|
||||||
|
|
||||||
$this->responseType = $responseType;
|
$this->responseType = $responseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +121,12 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
* Enable a grant type on the server.
|
* Enable a grant type on the server.
|
||||||
*
|
*
|
||||||
* @param GrantTypeInterface $grantType
|
* @param GrantTypeInterface $grantType
|
||||||
* @param null|\DateInterval $accessTokenTTL
|
* @param null|DateInterval $accessTokenTTL
|
||||||
*/
|
*/
|
||||||
public function enableGrantType(GrantTypeInterface $grantType, \DateInterval $accessTokenTTL = null)
|
public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null)
|
||||||
{
|
{
|
||||||
if ($accessTokenTTL instanceof \DateInterval === false) {
|
if ($accessTokenTTL === null) {
|
||||||
$accessTokenTTL = new \DateInterval('PT1H');
|
$accessTokenTTL = new DateInterval('PT1H');
|
||||||
}
|
}
|
||||||
|
|
||||||
$grantType->setAccessTokenRepository($this->accessTokenRepository);
|
$grantType->setAccessTokenRepository($this->accessTokenRepository);
|
||||||
@@ -190,7 +201,6 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
if ($tokenResponse instanceof ResponseTypeInterface) {
|
if ($tokenResponse instanceof ResponseTypeInterface) {
|
||||||
return $tokenResponse->generateHttpResponse($response);
|
return $tokenResponse->generateHttpResponse($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw OAuthServerException::unsupportedGrantType();
|
throw OAuthServerException::unsupportedGrantType();
|
||||||
@@ -203,14 +213,15 @@ class AuthorizationServer implements EmitterAwareInterface
|
|||||||
*/
|
*/
|
||||||
protected function getResponseType()
|
protected function getResponseType()
|
||||||
{
|
{
|
||||||
if ($this->responseType instanceof ResponseTypeInterface === false) {
|
$responseType = clone $this->responseType;
|
||||||
$this->responseType = new BearerTokenResponse();
|
|
||||||
|
if ($responseType instanceof AbstractResponseType) {
|
||||||
|
$responseType->setPrivateKey($this->privateKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->responseType->setPrivateKey($this->privateKey);
|
$responseType->setEncryptionKey($this->encryptionKey);
|
||||||
$this->responseType->setEncryptionKey($this->encryptionKey);
|
|
||||||
|
|
||||||
return $this->responseType;
|
return $responseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,14 +9,17 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\AuthorizationValidators;
|
namespace League\OAuth2\Server\AuthorizationValidators;
|
||||||
|
|
||||||
|
use BadMethodCallException;
|
||||||
|
use InvalidArgumentException;
|
||||||
use Lcobucci\JWT\Parser;
|
use Lcobucci\JWT\Parser;
|
||||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||||
use Lcobucci\JWT\ValidationData;
|
use Lcobucci\JWT\ValidationData;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKeyInterface;
|
||||||
use League\OAuth2\Server\CryptTrait;
|
use League\OAuth2\Server\CryptTrait;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
class BearerTokenValidator implements AuthorizationValidatorInterface
|
class BearerTokenValidator implements AuthorizationValidatorInterface
|
||||||
{
|
{
|
||||||
@@ -28,7 +31,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
|
|||||||
private $accessTokenRepository;
|
private $accessTokenRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \League\OAuth2\Server\CryptKey
|
* @var CryptKeyInterface
|
||||||
*/
|
*/
|
||||||
protected $publicKey;
|
protected $publicKey;
|
||||||
|
|
||||||
@@ -43,9 +46,9 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
|
|||||||
/**
|
/**
|
||||||
* Set the public key
|
* Set the public key
|
||||||
*
|
*
|
||||||
* @param \League\OAuth2\Server\CryptKey $key
|
* @param CryptKeyInterface $key
|
||||||
*/
|
*/
|
||||||
public function setPublicKey(CryptKey $key)
|
public function setPublicKey(CryptKeyInterface $key)
|
||||||
{
|
{
|
||||||
$this->publicKey = $key;
|
$this->publicKey = $key;
|
||||||
}
|
}
|
||||||
@@ -60,13 +63,17 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
$header = $request->getHeader('authorization');
|
$header = $request->getHeader('authorization');
|
||||||
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0]));
|
$jwt = trim((string) preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0]));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Attempt to parse and validate the JWT
|
// Attempt to parse and validate the JWT
|
||||||
$token = (new Parser())->parse($jwt);
|
$token = (new Parser())->parse($jwt);
|
||||||
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
|
try {
|
||||||
throw OAuthServerException::accessDenied('Access token could not be verified');
|
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
|
// Ensure access token hasn't expired
|
||||||
@@ -88,12 +95,12 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
|
|||||||
->withAttribute('oauth_client_id', $token->getClaim('aud'))
|
->withAttribute('oauth_client_id', $token->getClaim('aud'))
|
||||||
->withAttribute('oauth_user_id', $token->getClaim('sub'))
|
->withAttribute('oauth_user_id', $token->getClaim('sub'))
|
||||||
->withAttribute('oauth_scopes', $token->getClaim('scopes'));
|
->withAttribute('oauth_scopes', $token->getClaim('scopes'));
|
||||||
} catch (\InvalidArgumentException $exception) {
|
} catch (InvalidArgumentException $exception) {
|
||||||
// JWT couldn't be parsed so return the request as is
|
// JWT couldn't be parsed so return the request as is
|
||||||
throw OAuthServerException::accessDenied($exception->getMessage());
|
throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception);
|
||||||
} catch (\RuntimeException $exception) {
|
} catch (RuntimeException $exception) {
|
||||||
//JWR couldn't be parsed so return the request as is
|
//JWR couldn't be parsed so return the request as is
|
||||||
throw OAuthServerException::accessDenied('Error while decoding to JSON');
|
throw OAuthServerException::accessDenied('Error while decoding to JSON', null, $exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Lukáš Unger <lookymsc@gmail.com>
|
||||||
|
* @copyright Copyright (c) Lukáš Unger
|
||||||
|
* @license http://mit-license.org/
|
||||||
|
*
|
||||||
|
* @link https://github.com/thephpleague/oauth2-server
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace League\OAuth2\Server\CodeChallengeVerifiers;
|
||||||
|
|
||||||
|
interface CodeChallengeVerifierInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return code challenge method.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMethod();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the code challenge.
|
||||||
|
*
|
||||||
|
* @param string $codeVerifier
|
||||||
|
* @param string $codeChallenge
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function verifyCodeChallenge($codeVerifier, $codeChallenge);
|
||||||
|
}
|
36
src/CodeChallengeVerifiers/PlainVerifier.php
Normal file
36
src/CodeChallengeVerifiers/PlainVerifier.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Lukáš Unger <lookymsc@gmail.com>
|
||||||
|
* @copyright Copyright (c) Lukáš Unger
|
||||||
|
* @license http://mit-license.org/
|
||||||
|
*
|
||||||
|
* @link https://github.com/thephpleague/oauth2-server
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace League\OAuth2\Server\CodeChallengeVerifiers;
|
||||||
|
|
||||||
|
class PlainVerifier implements CodeChallengeVerifierInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return code challenge method.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMethod()
|
||||||
|
{
|
||||||
|
return 'plain';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the code challenge.
|
||||||
|
*
|
||||||
|
* @param string $codeVerifier
|
||||||
|
* @param string $codeChallenge
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function verifyCodeChallenge($codeVerifier, $codeChallenge)
|
||||||
|
{
|
||||||
|
return hash_equals($codeVerifier, $codeChallenge);
|
||||||
|
}
|
||||||
|
}
|
39
src/CodeChallengeVerifiers/S256Verifier.php
Normal file
39
src/CodeChallengeVerifiers/S256Verifier.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @author Lukáš Unger <lookymsc@gmail.com>
|
||||||
|
* @copyright Copyright (c) Lukáš Unger
|
||||||
|
* @license http://mit-license.org/
|
||||||
|
*
|
||||||
|
* @link https://github.com/thephpleague/oauth2-server
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace League\OAuth2\Server\CodeChallengeVerifiers;
|
||||||
|
|
||||||
|
class S256Verifier implements CodeChallengeVerifierInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return code challenge method.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getMethod()
|
||||||
|
{
|
||||||
|
return 'S256';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the code challenge.
|
||||||
|
*
|
||||||
|
* @param string $codeVerifier
|
||||||
|
* @param string $codeChallenge
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function verifyCodeChallenge($codeVerifier, $codeChallenge)
|
||||||
|
{
|
||||||
|
return hash_equals(
|
||||||
|
strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'),
|
||||||
|
$codeChallenge
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -11,10 +11,13 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server;
|
namespace League\OAuth2\Server;
|
||||||
|
|
||||||
class CryptKey
|
use LogicException;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class CryptKey implements CryptKeyInterface
|
||||||
{
|
{
|
||||||
const RSA_KEY_PATTERN =
|
const RSA_KEY_PATTERN =
|
||||||
'/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----\n)(.|\n)+(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)$/';
|
'/^(-----BEGIN (RSA )?(PUBLIC|PRIVATE) KEY-----)\R.*(-----END (RSA )?(PUBLIC|PRIVATE) KEY-----)\R?$/s';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
@@ -42,15 +45,15 @@ class CryptKey
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!file_exists($keyPath) || !is_readable($keyPath)) {
|
if (!file_exists($keyPath) || !is_readable($keyPath)) {
|
||||||
throw new \LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
|
throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($keyPermissionsCheck === true) {
|
if ($keyPermissionsCheck === true) {
|
||||||
// Verify the permissions of the key
|
// Verify the permissions of the key
|
||||||
$keyPathPerms = decoct(fileperms($keyPath) & 0777);
|
$keyPathPerms = decoct(fileperms($keyPath) & 0777);
|
||||||
if (in_array($keyPathPerms, ['600', '660'], true) === false) {
|
if (in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
|
||||||
trigger_error(sprintf(
|
trigger_error(sprintf(
|
||||||
'Key file "%s" permissions are not correct, should be 600 or 660 instead of %s',
|
'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
|
||||||
$keyPath,
|
$keyPath,
|
||||||
$keyPathPerms
|
$keyPathPerms
|
||||||
), E_USER_NOTICE);
|
), E_USER_NOTICE);
|
||||||
@@ -64,7 +67,7 @@ class CryptKey
|
|||||||
/**
|
/**
|
||||||
* @param string $key
|
* @param string $key
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException
|
* @throws RuntimeException
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -73,43 +76,37 @@ class CryptKey
|
|||||||
$tmpDir = sys_get_temp_dir();
|
$tmpDir = sys_get_temp_dir();
|
||||||
$keyPath = $tmpDir . '/' . sha1($key) . '.key';
|
$keyPath = $tmpDir . '/' . sha1($key) . '.key';
|
||||||
|
|
||||||
if (!file_exists($keyPath) && !touch($keyPath)) {
|
if (file_exists($keyPath)) {
|
||||||
|
return 'file://' . $keyPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!touch($keyPath)) {
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
throw new \RuntimeException(sprintf('"%s" key file could not be created', $keyPath));
|
throw new RuntimeException(sprintf('"%s" key file could not be created', $keyPath));
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_put_contents($keyPath, $key) === false) {
|
if (file_put_contents($keyPath, $key) === false) {
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
throw new \RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
|
throw new RuntimeException(sprintf('Unable to write key file to temporary directory "%s"', $tmpDir));
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chmod($keyPath, 0600) === false) {
|
if (chmod($keyPath, 0600) === false) {
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
throw new \RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
|
throw new RuntimeException(sprintf('The key file "%s" file mode could not be changed with chmod to 600', $keyPath));
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'file://' . $keyPath;
|
return 'file://' . $keyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getKeyPath(): string
|
||||||
* Retrieve key path.
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function getKeyPath()
|
|
||||||
{
|
{
|
||||||
return $this->keyPath;
|
return $this->keyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getPassPhrase(): ?string
|
||||||
* Retrieve key pass phrase.
|
|
||||||
*
|
|
||||||
* @return null|string
|
|
||||||
*/
|
|
||||||
public function getPassPhrase()
|
|
||||||
{
|
{
|
||||||
return $this->passPhrase;
|
return $this->passPhrase;
|
||||||
}
|
}
|
||||||
|
21
src/CryptKeyInterface.php
Normal file
21
src/CryptKeyInterface.php
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace League\OAuth2\Server;
|
||||||
|
|
||||||
|
interface CryptKeyInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Retrieve key path.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getKeyPath(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve key pass phrase.
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getPassPhrase(): ?string;
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Public/private key encryption.
|
* Encrypt/decrypt with encryptionKey.
|
||||||
*
|
*
|
||||||
* @author Alex Bilbie <hello@alexbilbie.com>
|
* @author Alex Bilbie <hello@alexbilbie.com>
|
||||||
* @copyright Copyright (c) Alex Bilbie
|
* @copyright Copyright (c) Alex Bilbie
|
||||||
@@ -12,54 +12,73 @@
|
|||||||
namespace League\OAuth2\Server;
|
namespace League\OAuth2\Server;
|
||||||
|
|
||||||
use Defuse\Crypto\Crypto;
|
use Defuse\Crypto\Crypto;
|
||||||
|
use Defuse\Crypto\Key;
|
||||||
|
use Exception;
|
||||||
|
use LogicException;
|
||||||
|
|
||||||
trait CryptTrait
|
trait CryptTrait
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var string
|
* @var string|Key|null
|
||||||
*/
|
*/
|
||||||
protected $encryptionKey;
|
protected $encryptionKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt data with a private key.
|
* Encrypt data with encryptionKey.
|
||||||
*
|
*
|
||||||
* @param string $unencryptedData
|
* @param string $unencryptedData
|
||||||
*
|
*
|
||||||
* @throws \LogicException
|
* @throws LogicException
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function encrypt($unencryptedData)
|
protected function encrypt($unencryptedData)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
|
if ($this->encryptionKey instanceof Key) {
|
||||||
} catch (\Exception $e) {
|
return Crypto::encrypt($unencryptedData, $this->encryptionKey);
|
||||||
throw new \LogicException($e->getMessage());
|
}
|
||||||
|
|
||||||
|
if (is_string($this->encryptionKey)) {
|
||||||
|
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new LogicException('Encryption key not set when attempting to encrypt');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new LogicException($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt data with a public key.
|
* Decrypt data with encryptionKey.
|
||||||
*
|
*
|
||||||
* @param string $encryptedData
|
* @param string $encryptedData
|
||||||
*
|
*
|
||||||
* @throws \LogicException
|
* @throws LogicException
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
protected function decrypt($encryptedData)
|
protected function decrypt($encryptedData)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
|
if ($this->encryptionKey instanceof Key) {
|
||||||
} catch (\Exception $e) {
|
return Crypto::decrypt($encryptedData, $this->encryptionKey);
|
||||||
throw new \LogicException($e->getMessage());
|
}
|
||||||
|
|
||||||
|
if (is_string($this->encryptionKey)) {
|
||||||
|
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new LogicException('Encryption key not set when attempting to decrypt');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new LogicException($e->getMessage(), 0, $e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the encryption key
|
* Set the encryption key
|
||||||
*
|
*
|
||||||
* @param string $key
|
* @param string|Key $key
|
||||||
*/
|
*/
|
||||||
public function setEncryptionKey($key = null)
|
public function setEncryptionKey($key = null)
|
||||||
{
|
{
|
||||||
|
@@ -9,16 +9,17 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities;
|
namespace League\OAuth2\Server\Entities;
|
||||||
|
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKeyInterface;
|
||||||
|
|
||||||
interface AccessTokenEntityInterface extends TokenInterface
|
interface AccessTokenEntityInterface extends TokenInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Generate a JWT from the access token
|
* Set a private key used to encrypt the access token.
|
||||||
*
|
|
||||||
* @param CryptKey $privateKey
|
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function convertToJWT(CryptKey $privateKey);
|
public function setPrivateKey(CryptKeyInterface $privateKey);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a string representation of the access token.
|
||||||
|
*/
|
||||||
|
public function __toString();
|
||||||
}
|
}
|
||||||
|
@@ -12,7 +12,7 @@ namespace League\OAuth2\Server\Entities;
|
|||||||
interface AuthCodeEntityInterface extends TokenInterface
|
interface AuthCodeEntityInterface extends TokenInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getRedirectUri();
|
public function getRedirectUri();
|
||||||
|
|
||||||
|
@@ -33,4 +33,11 @@ interface ClientEntityInterface
|
|||||||
* @return string|string[]
|
* @return string|string[]
|
||||||
*/
|
*/
|
||||||
public function getRedirectUri();
|
public function getRedirectUri();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the client is confidential.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConfidential();
|
||||||
}
|
}
|
||||||
|
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities;
|
namespace League\OAuth2\Server\Entities;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
interface RefreshTokenEntityInterface
|
interface RefreshTokenEntityInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -21,23 +23,23 @@ interface RefreshTokenEntityInterface
|
|||||||
/**
|
/**
|
||||||
* Set the token's identifier.
|
* Set the token's identifier.
|
||||||
*
|
*
|
||||||
* @param $identifier
|
* @param mixed $identifier
|
||||||
*/
|
*/
|
||||||
public function setIdentifier($identifier);
|
public function setIdentifier($identifier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the token's expiry date time.
|
* Get the token's expiry date time.
|
||||||
*
|
*
|
||||||
* @return \DateTime
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
public function getExpiryDateTime();
|
public function getExpiryDateTime();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the date time when the token expires.
|
* Set the date time when the token expires.
|
||||||
*
|
*
|
||||||
* @param \DateTime $dateTime
|
* @param DateTimeImmutable $dateTime
|
||||||
*/
|
*/
|
||||||
public function setExpiryDateTime(\DateTime $dateTime);
|
public function setExpiryDateTime(DateTimeImmutable $dateTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the access token that the refresh token was associated with.
|
* Set the access token that the refresh token was associated with.
|
||||||
|
@@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities;
|
namespace League\OAuth2\Server\Entities;
|
||||||
|
|
||||||
interface ScopeEntityInterface extends \JsonSerializable
|
use JsonSerializable;
|
||||||
|
|
||||||
|
interface ScopeEntityInterface extends JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get the scope's identifier.
|
* Get the scope's identifier.
|
||||||
|
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities;
|
namespace League\OAuth2\Server\Entities;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
|
||||||
interface TokenInterface
|
interface TokenInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -21,35 +23,35 @@ interface TokenInterface
|
|||||||
/**
|
/**
|
||||||
* Set the token's identifier.
|
* Set the token's identifier.
|
||||||
*
|
*
|
||||||
* @param $identifier
|
* @param mixed $identifier
|
||||||
*/
|
*/
|
||||||
public function setIdentifier($identifier);
|
public function setIdentifier($identifier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the token's expiry date time.
|
* Get the token's expiry date time.
|
||||||
*
|
*
|
||||||
* @return \DateTime
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
public function getExpiryDateTime();
|
public function getExpiryDateTime();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the date time when the token expires.
|
* Set the date time when the token expires.
|
||||||
*
|
*
|
||||||
* @param \DateTime $dateTime
|
* @param DateTimeImmutable $dateTime
|
||||||
*/
|
*/
|
||||||
public function setExpiryDateTime(\DateTime $dateTime);
|
public function setExpiryDateTime(DateTimeImmutable $dateTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the identifier of the user associated with the token.
|
* Set the identifier of the user associated with the token.
|
||||||
*
|
*
|
||||||
* @param string|int $identifier The identifier of the user
|
* @param string|int|null $identifier The identifier of the user
|
||||||
*/
|
*/
|
||||||
public function setUserIdentifier($identifier);
|
public function setUserIdentifier($identifier);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the token user's identifier.
|
* Get the token user's identifier.
|
||||||
*
|
*
|
||||||
* @return string|int
|
* @return string|int|null
|
||||||
*/
|
*/
|
||||||
public function getUserIdentifier();
|
public function getUserIdentifier();
|
||||||
|
|
||||||
|
@@ -9,43 +9,66 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities\Traits;
|
namespace League\OAuth2\Server\Entities\Traits;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
use Lcobucci\JWT\Builder;
|
use Lcobucci\JWT\Builder;
|
||||||
use Lcobucci\JWT\Signer\Key;
|
use Lcobucci\JWT\Signer\Key;
|
||||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use Lcobucci\JWT\Token;
|
||||||
|
use League\OAuth2\Server\CryptKeyInterface;
|
||||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||||
|
|
||||||
trait AccessTokenTrait
|
trait AccessTokenTrait
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var CryptKeyInterface
|
||||||
|
*/
|
||||||
|
private $privateKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the private key used to encrypt this access token.
|
||||||
|
*/
|
||||||
|
public function setPrivateKey(CryptKeyInterface $privateKey)
|
||||||
|
{
|
||||||
|
$this->privateKey = $privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a JWT from the access token
|
* Generate a JWT from the access token
|
||||||
*
|
*
|
||||||
* @param CryptKey $privateKey
|
* @param CryptKeyInterface $privateKey
|
||||||
*
|
*
|
||||||
* @return string
|
* @return Token
|
||||||
*/
|
*/
|
||||||
public function convertToJWT(CryptKey $privateKey)
|
private function convertToJWT(CryptKeyInterface $privateKey)
|
||||||
{
|
{
|
||||||
return (new Builder())
|
return (new Builder())
|
||||||
->setAudience($this->getClient()->getIdentifier())
|
->setAudience($this->getClient()->getIdentifier())
|
||||||
->setId($this->getIdentifier(), true)
|
->setId($this->getIdentifier())
|
||||||
->setIssuedAt(time())
|
->setIssuedAt(time())
|
||||||
->setNotBefore(time())
|
->setNotBefore(time())
|
||||||
->setExpiration($this->getExpiryDateTime()->getTimestamp())
|
->setExpiration($this->getExpiryDateTime()->getTimestamp())
|
||||||
->setSubject($this->getUserIdentifier())
|
->setSubject((string) $this->getUserIdentifier())
|
||||||
->set('scopes', $this->getScopes())
|
->set('scopes', $this->getScopes())
|
||||||
->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase()))
|
->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase()))
|
||||||
->getToken();
|
->getToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a string representation from the access token
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return (string) $this->convertToJWT($this->privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return ClientEntityInterface
|
* @return ClientEntityInterface
|
||||||
*/
|
*/
|
||||||
abstract public function getClient();
|
abstract public function getClient();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return \DateTime
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
abstract public function getExpiryDateTime();
|
abstract public function getExpiryDateTime();
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ trait AuthCodeTrait
|
|||||||
protected $redirectUri;
|
protected $redirectUri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getRedirectUri()
|
public function getRedirectUri()
|
||||||
{
|
{
|
||||||
|
@@ -21,6 +21,11 @@ trait ClientTrait
|
|||||||
*/
|
*/
|
||||||
protected $redirectUri;
|
protected $redirectUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $isConfidential = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the client's name.
|
* Get the client's name.
|
||||||
*
|
*
|
||||||
@@ -43,4 +48,14 @@ trait ClientTrait
|
|||||||
{
|
{
|
||||||
return $this->redirectUri;
|
return $this->redirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the client is confidential.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isConfidential()
|
||||||
|
{
|
||||||
|
return $this->isConfidential;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ namespace League\OAuth2\Server\Entities\Traits;
|
|||||||
|
|
||||||
trait EntityTrait
|
trait EntityTrait
|
||||||
{
|
{
|
||||||
/*
|
/**
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $identifier;
|
protected $identifier;
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities\Traits;
|
namespace League\OAuth2\Server\Entities\Traits;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
|
|
||||||
trait RefreshTokenTrait
|
trait RefreshTokenTrait
|
||||||
@@ -19,7 +20,7 @@ trait RefreshTokenTrait
|
|||||||
protected $accessToken;
|
protected $accessToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \DateTime
|
* @var DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
protected $expiryDateTime;
|
protected $expiryDateTime;
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ trait RefreshTokenTrait
|
|||||||
/**
|
/**
|
||||||
* Get the token's expiry date time.
|
* Get the token's expiry date time.
|
||||||
*
|
*
|
||||||
* @return \DateTime
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
public function getExpiryDateTime()
|
public function getExpiryDateTime()
|
||||||
{
|
{
|
||||||
@@ -52,9 +53,9 @@ trait RefreshTokenTrait
|
|||||||
/**
|
/**
|
||||||
* Set the date time when the token expires.
|
* Set the date time when the token expires.
|
||||||
*
|
*
|
||||||
* @param \DateTime $dateTime
|
* @param DateTimeImmutable $dateTime
|
||||||
*/
|
*/
|
||||||
public function setExpiryDateTime(\DateTime $dateTime)
|
public function setExpiryDateTime(DateTimeImmutable $dateTime)
|
||||||
{
|
{
|
||||||
$this->expiryDateTime = $dateTime;
|
$this->expiryDateTime = $dateTime;
|
||||||
}
|
}
|
||||||
|
28
src/Entities/Traits/ScopeTrait.php
Normal file
28
src/Entities/Traits/ScopeTrait.php
Normal 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();
|
||||||
|
}
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Entities\Traits;
|
namespace League\OAuth2\Server\Entities\Traits;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
||||||
|
|
||||||
@@ -20,12 +21,12 @@ trait TokenEntityTrait
|
|||||||
protected $scopes = [];
|
protected $scopes = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \DateTime
|
* @var DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
protected $expiryDateTime;
|
protected $expiryDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string|int
|
* @var string|int|null
|
||||||
*/
|
*/
|
||||||
protected $userIdentifier;
|
protected $userIdentifier;
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ trait TokenEntityTrait
|
|||||||
/**
|
/**
|
||||||
* Get the token's expiry date time.
|
* Get the token's expiry date time.
|
||||||
*
|
*
|
||||||
* @return \DateTime
|
* @return DateTimeImmutable
|
||||||
*/
|
*/
|
||||||
public function getExpiryDateTime()
|
public function getExpiryDateTime()
|
||||||
{
|
{
|
||||||
@@ -67,9 +68,9 @@ trait TokenEntityTrait
|
|||||||
/**
|
/**
|
||||||
* Set the date time when the token expires.
|
* Set the date time when the token expires.
|
||||||
*
|
*
|
||||||
* @param \DateTime $dateTime
|
* @param DateTimeImmutable $dateTime
|
||||||
*/
|
*/
|
||||||
public function setExpiryDateTime(\DateTime $dateTime)
|
public function setExpiryDateTime(DateTimeImmutable $dateTime)
|
||||||
{
|
{
|
||||||
$this->expiryDateTime = $dateTime;
|
$this->expiryDateTime = $dateTime;
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,7 @@ trait TokenEntityTrait
|
|||||||
/**
|
/**
|
||||||
* Set the identifier of the user associated with the token.
|
* Set the identifier of the user associated with the token.
|
||||||
*
|
*
|
||||||
* @param string|int $identifier The identifier of the user
|
* @param string|int|null $identifier The identifier of the user
|
||||||
*/
|
*/
|
||||||
public function setUserIdentifier($identifier)
|
public function setUserIdentifier($identifier)
|
||||||
{
|
{
|
||||||
@@ -87,7 +88,7 @@ trait TokenEntityTrait
|
|||||||
/**
|
/**
|
||||||
* Get the token user's identifier.
|
* Get the token user's identifier.
|
||||||
*
|
*
|
||||||
* @return string|int
|
* @return string|int|null
|
||||||
*/
|
*/
|
||||||
public function getUserIdentifier()
|
public function getUserIdentifier()
|
||||||
{
|
{
|
||||||
|
@@ -9,9 +9,12 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Exception;
|
namespace League\OAuth2\Server\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class OAuthServerException extends \Exception
|
class OAuthServerException extends Exception
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var int
|
* @var int
|
||||||
@@ -33,6 +36,16 @@ class OAuthServerException extends \Exception
|
|||||||
*/
|
*/
|
||||||
private $redirectUri;
|
private $redirectUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ServerRequestInterface
|
||||||
|
*/
|
||||||
|
private $serverRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throw a new exception.
|
* Throw a new exception.
|
||||||
*
|
*
|
||||||
@@ -42,14 +55,60 @@ class OAuthServerException extends \Exception
|
|||||||
* @param int $httpStatusCode HTTP status code to send (default = 400)
|
* @param int $httpStatusCode HTTP status code to send (default = 400)
|
||||||
* @param null|string $hint A helper hint
|
* @param null|string $hint A helper hint
|
||||||
* @param null|string $redirectUri A HTTP URI to redirect the user back to
|
* @param null|string $redirectUri A HTTP URI to redirect the user back to
|
||||||
|
* @param Throwable $previous Previous exception
|
||||||
*/
|
*/
|
||||||
public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null)
|
public function __construct($message, $code, $errorType, $httpStatusCode = 400, $hint = null, $redirectUri = null, Throwable $previous = null)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code);
|
parent::__construct($message, $code, $previous);
|
||||||
$this->httpStatusCode = $httpStatusCode;
|
$this->httpStatusCode = $httpStatusCode;
|
||||||
$this->errorType = $errorType;
|
$this->errorType = $errorType;
|
||||||
$this->hint = $hint;
|
$this->hint = $hint;
|
||||||
$this->redirectUri = $redirectUri;
|
$this->redirectUri = $redirectUri;
|
||||||
|
$this->payload = [
|
||||||
|
'error' => $errorType,
|
||||||
|
'error_description' => $message,
|
||||||
|
];
|
||||||
|
if ($hint !== null) {
|
||||||
|
$this->payload['hint'] = $hint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current payload.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPayload()
|
||||||
|
{
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current payload.
|
||||||
|
*
|
||||||
|
* @param array $payload
|
||||||
|
*/
|
||||||
|
public function setPayload(array $payload)
|
||||||
|
{
|
||||||
|
$this->payload = $payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the server request that is responsible for generating the exception
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $serverRequest
|
||||||
|
*/
|
||||||
|
public function setServerRequest(ServerRequestInterface $serverRequest)
|
||||||
|
{
|
||||||
|
$this->serverRequest = $serverRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +119,7 @@ class OAuthServerException extends \Exception
|
|||||||
public static function unsupportedGrantType()
|
public static function unsupportedGrantType()
|
||||||
{
|
{
|
||||||
$errorMessage = 'The authorization grant type is not supported by the authorization server.';
|
$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);
|
return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
|
||||||
}
|
}
|
||||||
@@ -70,28 +129,33 @@ class OAuthServerException extends \Exception
|
|||||||
*
|
*
|
||||||
* @param string $parameter The invalid parameter
|
* @param string $parameter The invalid parameter
|
||||||
* @param null|string $hint
|
* @param null|string $hint
|
||||||
|
* @param Throwable $previous Previous exception
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function invalidRequest($parameter, $hint = null)
|
public static function invalidRequest($parameter, $hint = null, Throwable $previous = null)
|
||||||
{
|
{
|
||||||
$errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
|
$errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
|
||||||
'includes a parameter more than once, or is otherwise malformed.';
|
'includes a parameter more than once, or is otherwise malformed.';
|
||||||
$hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
|
$hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;
|
||||||
|
|
||||||
return new static($errorMessage, 3, 'invalid_request', 400, $hint);
|
return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid client error.
|
* Invalid client error.
|
||||||
*
|
*
|
||||||
|
* @param ServerRequestInterface $serverRequest
|
||||||
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function invalidClient()
|
public static function invalidClient(ServerRequestInterface $serverRequest)
|
||||||
{
|
{
|
||||||
$errorMessage = 'Client authentication failed';
|
$exception = new static('Client authentication failed', 4, 'invalid_client', 401);
|
||||||
|
|
||||||
return new static($errorMessage, 4, 'invalid_client', 401);
|
$exception->setServerRequest($serverRequest);
|
||||||
|
|
||||||
|
return $exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,20 +195,24 @@ class OAuthServerException extends \Exception
|
|||||||
/**
|
/**
|
||||||
* Server error.
|
* Server error.
|
||||||
*
|
*
|
||||||
* @param $hint
|
* @param string $hint
|
||||||
|
* @param Throwable $previous
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
* @codeCoverageIgnore
|
||||||
*/
|
*/
|
||||||
public static function serverError($hint)
|
public static function serverError($hint, Throwable $previous = null)
|
||||||
{
|
{
|
||||||
return new static(
|
return new static(
|
||||||
'The authorization server encountered an unexpected condition which prevented it from fulfilling'
|
'The authorization server encountered an unexpected condition which prevented it from fulfilling'
|
||||||
. ' the request: ' . $hint,
|
. ' the request: ' . $hint,
|
||||||
7,
|
7,
|
||||||
'server_error',
|
'server_error',
|
||||||
500
|
500,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
$previous
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,12 +220,13 @@ class OAuthServerException extends \Exception
|
|||||||
* Invalid refresh token.
|
* Invalid refresh token.
|
||||||
*
|
*
|
||||||
* @param null|string $hint
|
* @param null|string $hint
|
||||||
|
* @param Throwable $previous
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function invalidRefreshToken($hint = null)
|
public static function invalidRefreshToken($hint = null, Throwable $previous = null)
|
||||||
{
|
{
|
||||||
return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint);
|
return new static('The refresh token is invalid.', 8, 'invalid_request', 401, $hint, null, $previous);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -165,10 +234,11 @@ class OAuthServerException extends \Exception
|
|||||||
*
|
*
|
||||||
* @param null|string $hint
|
* @param null|string $hint
|
||||||
* @param null|string $redirectUri
|
* @param null|string $redirectUri
|
||||||
|
* @param Throwable $previous
|
||||||
*
|
*
|
||||||
* @return static
|
* @return static
|
||||||
*/
|
*/
|
||||||
public static function accessDenied($hint = null, $redirectUri = null)
|
public static function accessDenied($hint = null, $redirectUri = null, Throwable $previous = null)
|
||||||
{
|
{
|
||||||
return new static(
|
return new static(
|
||||||
'The resource owner or authorization server denied the request.',
|
'The resource owner or authorization server denied the request.',
|
||||||
@@ -176,7 +246,8 @@ class OAuthServerException extends \Exception
|
|||||||
'access_denied',
|
'access_denied',
|
||||||
401,
|
401,
|
||||||
$hint,
|
$hint,
|
||||||
$redirectUri
|
$redirectUri,
|
||||||
|
$previous
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,37 +284,28 @@ class OAuthServerException extends \Exception
|
|||||||
*
|
*
|
||||||
* @param ResponseInterface $response
|
* @param ResponseInterface $response
|
||||||
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
|
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
|
||||||
|
* @param int $jsonOptions options passed to json_encode
|
||||||
*
|
*
|
||||||
* @return ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function generateHttpResponse(ResponseInterface $response, $useFragment = false)
|
public function generateHttpResponse(ResponseInterface $response, $useFragment = false, $jsonOptions = 0)
|
||||||
{
|
{
|
||||||
$headers = $this->getHttpHeaders();
|
$headers = $this->getHttpHeaders();
|
||||||
|
|
||||||
$payload = [
|
$payload = $this->getPayload();
|
||||||
'error' => $this->getErrorType(),
|
|
||||||
'message' => $this->getMessage(),
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($this->hint !== null) {
|
$redirectUri = $this->getRedirectUri($useFragment);
|
||||||
$payload['hint'] = $this->hint;
|
if ($redirectUri !== null) {
|
||||||
}
|
return $response->withStatus(302)->withHeader('Location', $redirectUri);
|
||||||
|
|
||||||
if ($this->redirectUri !== null) {
|
|
||||||
if ($useFragment === true) {
|
|
||||||
$this->redirectUri .= (strstr($this->redirectUri, '#') === false) ? '#' : '&';
|
|
||||||
} else {
|
|
||||||
$this->redirectUri .= (strstr($this->redirectUri, '?') === false) ? '?' : '&';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($headers as $header => $content) {
|
foreach ($headers as $header => $content) {
|
||||||
$response = $response->withHeader($header, $content);
|
$response = $response->withHeader($header, $content);
|
||||||
}
|
}
|
||||||
|
|
||||||
$response->getBody()->write(json_encode($payload));
|
$responseBody = json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed';
|
||||||
|
|
||||||
|
$response->getBody()->write($responseBody);
|
||||||
|
|
||||||
return $response->withStatus($this->getHttpStatusCode());
|
return $response->withStatus($this->getHttpStatusCode());
|
||||||
}
|
}
|
||||||
@@ -268,19 +330,55 @@ class OAuthServerException extends \Exception
|
|||||||
// include the "WWW-Authenticate" response header field
|
// include the "WWW-Authenticate" response header field
|
||||||
// matching the authentication scheme used by the client.
|
// matching the authentication scheme used by the client.
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
if ($this->errorType === 'invalid_client') {
|
if ($this->errorType === 'invalid_client' && $this->serverRequest->hasHeader('Authorization') === true) {
|
||||||
$authScheme = 'Basic';
|
$authScheme = strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic';
|
||||||
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false
|
|
||||||
&& strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0
|
|
||||||
) {
|
|
||||||
$authScheme = 'Bearer';
|
|
||||||
}
|
|
||||||
$headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
|
$headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
|
||||||
}
|
}
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
return $headers;
|
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 redirectUri with all necessary args.
|
||||||
|
*
|
||||||
|
* Null will be returned if the exception doesn't contain the redirectUri.
|
||||||
|
*
|
||||||
|
* @param bool $useFragment True if errors should be in the URI fragment instead of query string
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getRedirectUri(bool $useFragment = false): ?string
|
||||||
|
{
|
||||||
|
if ($this->redirectUri === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$redirectUri = $this->redirectUri;
|
||||||
|
if ($useFragment) {
|
||||||
|
$redirectUri .= strpos($this->redirectUri, '#') === false ? '#' : '&';
|
||||||
|
} else {
|
||||||
|
$redirectUri .= strpos($this->redirectUri, '?') === false ? '?' : '&';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $redirectUri . http_build_query($this->getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the HTTP status code to send when the exceptions is output.
|
* Returns the HTTP status code to send when the exceptions is output.
|
||||||
*
|
*
|
||||||
|
@@ -11,6 +11,9 @@ namespace League\OAuth2\Server\Exception;
|
|||||||
|
|
||||||
class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException
|
class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @return UniqueTokenIdentifierConstraintViolationException
|
||||||
|
*/
|
||||||
public static function create()
|
public static function create()
|
||||||
{
|
{
|
||||||
$errorMessage = 'Could not create unique access token identifier';
|
$errorMessage = 'Could not create unique access token identifier';
|
||||||
|
@@ -10,8 +10,12 @@
|
|||||||
*/
|
*/
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Error;
|
||||||
|
use Exception;
|
||||||
use League\Event\EmitterAwareTrait;
|
use League\Event\EmitterAwareTrait;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKeyInterface;
|
||||||
use League\OAuth2\Server\CryptTrait;
|
use League\OAuth2\Server\CryptTrait;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||||
@@ -28,7 +32,9 @@ use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
|||||||
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
|
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
|
||||||
use League\OAuth2\Server\RequestEvent;
|
use League\OAuth2\Server\RequestEvent;
|
||||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||||
|
use LogicException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use TypeError;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract grant class.
|
* Abstract grant class.
|
||||||
@@ -72,12 +78,12 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
protected $userRepository;
|
protected $userRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \DateInterval
|
* @var DateInterval
|
||||||
*/
|
*/
|
||||||
protected $refreshTokenTTL;
|
protected $refreshTokenTTL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var \League\OAuth2\Server\CryptKey
|
* @var CryptKeyInterface
|
||||||
*/
|
*/
|
||||||
protected $privateKey;
|
protected $privateKey;
|
||||||
|
|
||||||
@@ -137,7 +143,7 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL)
|
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
|
||||||
{
|
{
|
||||||
$this->refreshTokenTTL = $refreshTokenTTL;
|
$this->refreshTokenTTL = $refreshTokenTTL;
|
||||||
}
|
}
|
||||||
@@ -145,9 +151,9 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
/**
|
/**
|
||||||
* Set the private key
|
* Set the private key
|
||||||
*
|
*
|
||||||
* @param \League\OAuth2\Server\CryptKey $key
|
* @param CryptKeyInterface $key
|
||||||
*/
|
*/
|
||||||
public function setPrivateKey(CryptKey $key)
|
public function setPrivateKey(CryptKeyInterface $key)
|
||||||
{
|
{
|
||||||
$this->privateKey = $key;
|
$this->privateKey = $key;
|
||||||
}
|
}
|
||||||
@@ -171,54 +177,109 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
*/
|
*/
|
||||||
protected function validateClient(ServerRequestInterface $request)
|
protected function validateClient(ServerRequestInterface $request)
|
||||||
{
|
{
|
||||||
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
|
list($clientId, $clientSecret) = $this->getClientCredentials($request);
|
||||||
|
|
||||||
$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
|
if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
|
||||||
if (is_null($clientId)) {
|
|
||||||
throw OAuthServerException::invalidRequest('client_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the client is confidential require the client secret
|
|
||||||
$clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
|
|
||||||
|
|
||||||
$client = $this->clientRepository->getClientEntity(
|
|
||||||
$clientId,
|
|
||||||
$this->getIdentifier(),
|
|
||||||
$clientSecret,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($client instanceof ClientEntityInterface === false) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||||
throw OAuthServerException::invalidClient();
|
|
||||||
|
throw OAuthServerException::invalidClient($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$client = $this->getClientEntityOrFail($clientId, $request);
|
||||||
|
|
||||||
// If a redirect URI is provided ensure it matches what is pre-registered
|
// If a redirect URI is provided ensure it matches what is pre-registered
|
||||||
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
|
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
|
||||||
|
|
||||||
if ($redirectUri !== null) {
|
if ($redirectUri !== null) {
|
||||||
if (
|
$this->validateRedirectUri($redirectUri, $client, $request);
|
||||||
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()) === false
|
|
||||||
) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
|
||||||
throw OAuthServerException::invalidClient();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $client;
|
return $client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around ClientRepository::getClientEntity() that ensures we emit
|
||||||
|
* an event and throw an exception if the repo doesn't return a client
|
||||||
|
* entity.
|
||||||
|
*
|
||||||
|
* This is a bit of defensive coding because the interface contract
|
||||||
|
* doesn't actually enforce non-null returns/exception-on-no-client so
|
||||||
|
* getClientEntity might return null. By contrast, this method will
|
||||||
|
* always either return a ClientEntityInterface or throw.
|
||||||
|
*
|
||||||
|
* @param string $clientId
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
*
|
||||||
|
* @return ClientEntityInterface
|
||||||
|
*/
|
||||||
|
protected function getClientEntityOrFail($clientId, ServerRequestInterface $request)
|
||||||
|
{
|
||||||
|
$client = $this->clientRepository->getClientEntity($clientId);
|
||||||
|
|
||||||
|
if ($client instanceof ClientEntityInterface === false) {
|
||||||
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||||
|
throw OAuthServerException::invalidClient($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the client credentials from the request from the request body or
|
||||||
|
* the Http Basic Authorization header
|
||||||
|
*
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getClientCredentials(ServerRequestInterface $request)
|
||||||
|
{
|
||||||
|
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
|
||||||
|
|
||||||
|
$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
|
||||||
|
|
||||||
|
if (is_null($clientId)) {
|
||||||
|
throw OAuthServerException::invalidRequest('client_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
$clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
|
||||||
|
|
||||||
|
return [$clientId, $clientSecret];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate redirectUri from the request.
|
||||||
|
* If a redirect URI is provided ensure it matches what is pre-registered
|
||||||
|
*
|
||||||
|
* @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($request);
|
||||||
|
} elseif (\is_array($client->getRedirectUri())
|
||||||
|
&& \in_array($redirectUri, $client->getRedirectUri(), true) === false
|
||||||
|
) {
|
||||||
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||||
|
throw OAuthServerException::invalidClient($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate scopes in the request.
|
* Validate scopes in the request.
|
||||||
*
|
*
|
||||||
* @param string $scopes
|
* @param string|array $scopes
|
||||||
* @param string $redirectUri
|
* @param string $redirectUri
|
||||||
*
|
*
|
||||||
* @throws OAuthServerException
|
* @throws OAuthServerException
|
||||||
*
|
*
|
||||||
@@ -226,13 +287,13 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
*/
|
*/
|
||||||
public function validateScopes($scopes, $redirectUri = null)
|
public function validateScopes($scopes, $redirectUri = null)
|
||||||
{
|
{
|
||||||
$scopesList = array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
|
if (!\is_array($scopes)) {
|
||||||
return !empty($scope);
|
$scopes = $this->convertScopesQueryStringToArray($scopes);
|
||||||
});
|
}
|
||||||
|
|
||||||
$validScopes = [];
|
$validScopes = [];
|
||||||
|
|
||||||
foreach ($scopesList as $scopeItem) {
|
foreach ($scopes as $scopeItem) {
|
||||||
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
|
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);
|
||||||
|
|
||||||
if ($scope instanceof ScopeEntityInterface === false) {
|
if ($scope instanceof ScopeEntityInterface === false) {
|
||||||
@@ -242,13 +303,23 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
$validScopes[] = $scope;
|
$validScopes[] = $scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (empty($validScopes)) {
|
|
||||||
throw OAuthServerException::invalidScope('', $redirectUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $validScopes;
|
return $validScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a scopes query string to an array to easily iterate for validation.
|
||||||
|
*
|
||||||
|
* @param string $scopes
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function convertScopesQueryStringToArray($scopes)
|
||||||
|
{
|
||||||
|
return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), function ($scope) {
|
||||||
|
return !empty($scope);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve request parameter.
|
* Retrieve request parameter.
|
||||||
*
|
*
|
||||||
@@ -262,7 +333,7 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
{
|
{
|
||||||
$requestParameters = (array) $request->getParsedBody();
|
$requestParameters = (array) $request->getParsedBody();
|
||||||
|
|
||||||
return isset($requestParameters[$parameter]) ? $requestParameters[$parameter] : $default;
|
return $requestParameters[$parameter] ?? $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -343,9 +414,9 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
/**
|
/**
|
||||||
* Issue an access token.
|
* Issue an access token.
|
||||||
*
|
*
|
||||||
* @param \DateInterval $accessTokenTTL
|
* @param DateInterval $accessTokenTTL
|
||||||
* @param ClientEntityInterface $client
|
* @param ClientEntityInterface $client
|
||||||
* @param string $userIdentifier
|
* @param string|null $userIdentifier
|
||||||
* @param ScopeEntityInterface[] $scopes
|
* @param ScopeEntityInterface[] $scopes
|
||||||
*
|
*
|
||||||
* @throws OAuthServerException
|
* @throws OAuthServerException
|
||||||
@@ -354,7 +425,7 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
* @return AccessTokenEntityInterface
|
* @return AccessTokenEntityInterface
|
||||||
*/
|
*/
|
||||||
protected function issueAccessToken(
|
protected function issueAccessToken(
|
||||||
\DateInterval $accessTokenTTL,
|
DateInterval $accessTokenTTL,
|
||||||
ClientEntityInterface $client,
|
ClientEntityInterface $client,
|
||||||
$userIdentifier,
|
$userIdentifier,
|
||||||
array $scopes = []
|
array $scopes = []
|
||||||
@@ -362,13 +433,8 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
|
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
|
||||||
|
|
||||||
$accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
|
$accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
|
||||||
$accessToken->setClient($client);
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL));
|
||||||
$accessToken->setUserIdentifier($userIdentifier);
|
$accessToken->setPrivateKey($this->privateKey);
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add($accessTokenTTL));
|
|
||||||
|
|
||||||
foreach ($scopes as $scope) {
|
|
||||||
$accessToken->addScope($scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
while ($maxGenerationAttempts-- > 0) {
|
while ($maxGenerationAttempts-- > 0) {
|
||||||
$accessToken->setIdentifier($this->generateUniqueIdentifier());
|
$accessToken->setIdentifier($this->generateUniqueIdentifier());
|
||||||
@@ -387,10 +453,10 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
/**
|
/**
|
||||||
* Issue an auth code.
|
* Issue an auth code.
|
||||||
*
|
*
|
||||||
* @param \DateInterval $authCodeTTL
|
* @param DateInterval $authCodeTTL
|
||||||
* @param ClientEntityInterface $client
|
* @param ClientEntityInterface $client
|
||||||
* @param string $userIdentifier
|
* @param string $userIdentifier
|
||||||
* @param string $redirectUri
|
* @param string|null $redirectUri
|
||||||
* @param ScopeEntityInterface[] $scopes
|
* @param ScopeEntityInterface[] $scopes
|
||||||
*
|
*
|
||||||
* @throws OAuthServerException
|
* @throws OAuthServerException
|
||||||
@@ -399,7 +465,7 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
* @return AuthCodeEntityInterface
|
* @return AuthCodeEntityInterface
|
||||||
*/
|
*/
|
||||||
protected function issueAuthCode(
|
protected function issueAuthCode(
|
||||||
\DateInterval $authCodeTTL,
|
DateInterval $authCodeTTL,
|
||||||
ClientEntityInterface $client,
|
ClientEntityInterface $client,
|
||||||
$userIdentifier,
|
$userIdentifier,
|
||||||
$redirectUri,
|
$redirectUri,
|
||||||
@@ -408,10 +474,13 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
|
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
|
||||||
|
|
||||||
$authCode = $this->authCodeRepository->getNewAuthCode();
|
$authCode = $this->authCodeRepository->getNewAuthCode();
|
||||||
$authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL));
|
$authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL));
|
||||||
$authCode->setClient($client);
|
$authCode->setClient($client);
|
||||||
$authCode->setUserIdentifier($userIdentifier);
|
$authCode->setUserIdentifier($userIdentifier);
|
||||||
$authCode->setRedirectUri($redirectUri);
|
|
||||||
|
if ($redirectUri !== null) {
|
||||||
|
$authCode->setRedirectUri($redirectUri);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($scopes as $scope) {
|
foreach ($scopes as $scope) {
|
||||||
$authCode->addScope($scope);
|
$authCode->addScope($scope);
|
||||||
@@ -437,16 +506,21 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
* @throws OAuthServerException
|
* @throws OAuthServerException
|
||||||
* @throws UniqueTokenIdentifierConstraintViolationException
|
* @throws UniqueTokenIdentifierConstraintViolationException
|
||||||
*
|
*
|
||||||
* @return RefreshTokenEntityInterface
|
* @return RefreshTokenEntityInterface|null
|
||||||
*/
|
*/
|
||||||
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
|
protected function issueRefreshToken(AccessTokenEntityInterface $accessToken)
|
||||||
{
|
{
|
||||||
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
|
|
||||||
|
|
||||||
$refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
|
$refreshToken = $this->refreshTokenRepository->getNewRefreshToken();
|
||||||
$refreshToken->setExpiryDateTime((new \DateTime())->add($this->refreshTokenTTL));
|
|
||||||
|
if ($refreshToken === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL));
|
||||||
$refreshToken->setAccessToken($accessToken);
|
$refreshToken->setAccessToken($accessToken);
|
||||||
|
|
||||||
|
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
|
||||||
|
|
||||||
while ($maxGenerationAttempts-- > 0) {
|
while ($maxGenerationAttempts-- > 0) {
|
||||||
$refreshToken->setIdentifier($this->generateUniqueIdentifier());
|
$refreshToken->setIdentifier($this->generateUniqueIdentifier());
|
||||||
try {
|
try {
|
||||||
@@ -475,13 +549,13 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
try {
|
try {
|
||||||
return bin2hex(random_bytes($length));
|
return bin2hex(random_bytes($length));
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
} catch (\TypeError $e) {
|
} catch (TypeError $e) {
|
||||||
throw OAuthServerException::serverError('An unexpected error has occurred');
|
throw OAuthServerException::serverError('An unexpected error has occurred', $e);
|
||||||
} catch (\Error $e) {
|
} catch (Error $e) {
|
||||||
throw OAuthServerException::serverError('An unexpected error has occurred');
|
throw OAuthServerException::serverError('An unexpected error has occurred', $e);
|
||||||
} catch (\Exception $e) {
|
} catch (Exception $e) {
|
||||||
// If you get this message, the CSPRNG failed hard.
|
// If you get this message, the CSPRNG failed hard.
|
||||||
throw OAuthServerException::serverError('Could not generate a random string');
|
throw OAuthServerException::serverError('Could not generate a random string', $e);
|
||||||
}
|
}
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
@@ -512,7 +586,7 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
*/
|
*/
|
||||||
public function validateAuthorizationRequest(ServerRequestInterface $request)
|
public function validateAuthorizationRequest(ServerRequestInterface $request)
|
||||||
{
|
{
|
||||||
throw new \LogicException('This grant cannot validate an authorization request');
|
throw new LogicException('This grant cannot validate an authorization request');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -520,6 +594,6 @@ abstract class AbstractGrant implements GrantTypeInterface
|
|||||||
*/
|
*/
|
||||||
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
|
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
|
||||||
{
|
{
|
||||||
throw new \LogicException('This grant cannot complete an authorization request');
|
throw new LogicException('This grant cannot complete an authorization request');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,8 +9,13 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Exception;
|
||||||
|
use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface;
|
||||||
|
use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier;
|
||||||
|
use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier;
|
||||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
|
||||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
|
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
|
||||||
@@ -19,39 +24,59 @@ use League\OAuth2\Server\RequestEvent;
|
|||||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||||
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
|
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
|
||||||
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
||||||
|
use LogicException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class AuthCodeGrant extends AbstractAuthorizeGrant
|
class AuthCodeGrant extends AbstractAuthorizeGrant
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \DateInterval
|
* @var DateInterval
|
||||||
*/
|
*/
|
||||||
private $authCodeTTL;
|
private $authCodeTTL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
private $enableCodeExchangeProof = false;
|
private $requireCodeChallengeForPublicClients = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CodeChallengeVerifierInterface[]
|
||||||
|
*/
|
||||||
|
private $codeChallengeVerifiers = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param AuthCodeRepositoryInterface $authCodeRepository
|
* @param AuthCodeRepositoryInterface $authCodeRepository
|
||||||
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
|
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
|
||||||
* @param \DateInterval $authCodeTTL
|
* @param DateInterval $authCodeTTL
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
AuthCodeRepositoryInterface $authCodeRepository,
|
AuthCodeRepositoryInterface $authCodeRepository,
|
||||||
RefreshTokenRepositoryInterface $refreshTokenRepository,
|
RefreshTokenRepositoryInterface $refreshTokenRepository,
|
||||||
\DateInterval $authCodeTTL
|
DateInterval $authCodeTTL
|
||||||
) {
|
) {
|
||||||
$this->setAuthCodeRepository($authCodeRepository);
|
$this->setAuthCodeRepository($authCodeRepository);
|
||||||
$this->setRefreshTokenRepository($refreshTokenRepository);
|
$this->setRefreshTokenRepository($refreshTokenRepository);
|
||||||
$this->authCodeTTL = $authCodeTTL;
|
$this->authCodeTTL = $authCodeTTL;
|
||||||
$this->refreshTokenTTL = new \DateInterval('P1M');
|
$this->refreshTokenTTL = new DateInterval('P1M');
|
||||||
|
|
||||||
|
if (in_array('sha256', hash_algos(), true)) {
|
||||||
|
$s256Verifier = new S256Verifier();
|
||||||
|
$this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
$plainVerifier = new PlainVerifier();
|
||||||
|
$this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function enableCodeExchangeProof()
|
/**
|
||||||
|
* Disable the requirement for a code challenge for public clients.
|
||||||
|
*/
|
||||||
|
public function disableRequireCodeChallengeForPublicClients()
|
||||||
{
|
{
|
||||||
$this->enableCodeExchangeProof = true;
|
$this->requireCodeChallengeForPublicClients = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +84,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
*
|
*
|
||||||
* @param ServerRequestInterface $request
|
* @param ServerRequestInterface $request
|
||||||
* @param ResponseTypeInterface $responseType
|
* @param ResponseTypeInterface $responseType
|
||||||
* @param \DateInterval $accessTokenTTL
|
* @param DateInterval $accessTokenTTL
|
||||||
*
|
*
|
||||||
* @throws OAuthServerException
|
* @throws OAuthServerException
|
||||||
*
|
*
|
||||||
@@ -68,108 +93,85 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
public function respondToAccessTokenRequest(
|
public function respondToAccessTokenRequest(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
ResponseTypeInterface $responseType,
|
ResponseTypeInterface $responseType,
|
||||||
\DateInterval $accessTokenTTL
|
DateInterval $accessTokenTTL
|
||||||
) {
|
) {
|
||||||
// Validate request
|
list($clientId) = $this->getClientCredentials($request);
|
||||||
$client = $this->validateClient($request);
|
|
||||||
|
$client = $this->getClientEntityOrFail($clientId, $request);
|
||||||
|
|
||||||
|
// Only validate the client if it is confidential
|
||||||
|
if ($client->isConfidential()) {
|
||||||
|
$this->validateClient($request);
|
||||||
|
}
|
||||||
|
|
||||||
$encryptedAuthCode = $this->getRequestParameter('code', $request, null);
|
$encryptedAuthCode = $this->getRequestParameter('code', $request, null);
|
||||||
|
|
||||||
if ($encryptedAuthCode === null) {
|
if ($encryptedAuthCode === null) {
|
||||||
throw OAuthServerException::invalidRequest('code');
|
throw OAuthServerException::invalidRequest('code');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the authorization code
|
|
||||||
try {
|
try {
|
||||||
$authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
|
$authCodePayload = json_decode($this->decrypt($encryptedAuthCode));
|
||||||
if (time() > $authCodePayload->expire_time) {
|
|
||||||
throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
|
$this->validateAuthorizationCode($authCodePayload, $client, $request);
|
||||||
throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($authCodePayload->client_id !== $client->getIdentifier()) {
|
|
||||||
throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
|
|
||||||
}
|
|
||||||
|
|
||||||
// The redirect URI is required in this request
|
|
||||||
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
|
|
||||||
if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
|
|
||||||
throw OAuthServerException::invalidRequest('redirect_uri');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($authCodePayload->redirect_uri !== $redirectUri) {
|
|
||||||
throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
|
|
||||||
}
|
|
||||||
|
|
||||||
$scopes = [];
|
|
||||||
foreach ($authCodePayload->scopes as $scopeId) {
|
|
||||||
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId);
|
|
||||||
|
|
||||||
if ($scope instanceof ScopeEntityInterface === false) {
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
throw OAuthServerException::invalidScope($scopeId);
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
$scopes[] = $scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finalize the requested scopes
|
|
||||||
$scopes = $this->scopeRepository->finalizeScopes(
|
$scopes = $this->scopeRepository->finalizeScopes(
|
||||||
$scopes,
|
$this->validateScopes($authCodePayload->scopes),
|
||||||
$this->getIdentifier(),
|
$this->getIdentifier(),
|
||||||
$client,
|
$client,
|
||||||
$authCodePayload->user_id
|
$authCodePayload->user_id
|
||||||
);
|
);
|
||||||
} catch (\LogicException $e) {
|
} catch (LogicException $e) {
|
||||||
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code');
|
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate code challenge
|
// Validate code challenge
|
||||||
if ($this->enableCodeExchangeProof === true) {
|
if (!empty($authCodePayload->code_challenge)) {
|
||||||
$codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
|
$codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
|
||||||
|
|
||||||
if ($codeVerifier === null) {
|
if ($codeVerifier === null) {
|
||||||
throw OAuthServerException::invalidRequest('code_verifier');
|
throw OAuthServerException::invalidRequest('code_verifier');
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($authCodePayload->code_challenge_method) {
|
// Validate code_verifier according to RFC-7636
|
||||||
case 'plain':
|
// @see: https://tools.ietf.org/html/rfc7636#section-4.1
|
||||||
if (hash_equals($codeVerifier, $authCodePayload->code_challenge) === false) {
|
if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) {
|
||||||
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
|
throw OAuthServerException::invalidRequest(
|
||||||
}
|
'code_verifier',
|
||||||
|
'Code Verifier must follow the specifications of RFC-7636.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
if (property_exists($authCodePayload, 'code_challenge_method')) {
|
||||||
case 'S256':
|
if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) {
|
||||||
if (
|
$codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method];
|
||||||
hash_equals(
|
|
||||||
hash('sha256', strtr(rtrim(base64_encode($codeVerifier), '='), '+/', '-_')),
|
if ($codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) {
|
||||||
$authCodePayload->code_challenge
|
|
||||||
) === false
|
|
||||||
) {
|
|
||||||
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
|
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
|
||||||
}
|
}
|
||||||
// @codeCoverageIgnoreStart
|
} else {
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw OAuthServerException::serverError(
|
throw OAuthServerException::serverError(
|
||||||
sprintf(
|
sprintf(
|
||||||
'Unsupported code challenge method `%s`',
|
'Unsupported code challenge method `%s`',
|
||||||
$authCodePayload->code_challenge_method
|
$authCodePayload->code_challenge_method
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
// @codeCoverageIgnoreEnd
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue and persist access + refresh tokens
|
// Issue and persist new access token
|
||||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
|
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
|
||||||
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||||
|
$responseType->setAccessToken($accessToken);
|
||||||
|
|
||||||
|
// Issue and persist new refresh token if given
|
||||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||||
|
|
||||||
// Inject tokens into response type
|
if ($refreshToken !== null) {
|
||||||
$responseType->setAccessToken($accessToken);
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
// Revoke used auth code
|
// Revoke used auth code
|
||||||
$this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
|
$this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);
|
||||||
@@ -177,6 +179,41 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
return $responseType;
|
return $responseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the authorization code.
|
||||||
|
*
|
||||||
|
* @param stdClass $authCodePayload
|
||||||
|
* @param ClientEntityInterface $client
|
||||||
|
* @param ServerRequestInterface $request
|
||||||
|
*/
|
||||||
|
private function validateAuthorizationCode(
|
||||||
|
$authCodePayload,
|
||||||
|
ClientEntityInterface $client,
|
||||||
|
ServerRequestInterface $request
|
||||||
|
) {
|
||||||
|
if (time() > $authCodePayload->expire_time) {
|
||||||
|
throw OAuthServerException::invalidRequest('code', 'Authorization code has expired');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
|
||||||
|
throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authCodePayload->client_id !== $client->getIdentifier()) {
|
||||||
|
throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
|
||||||
|
}
|
||||||
|
|
||||||
|
// The redirect URI is required in this request
|
||||||
|
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
|
||||||
|
if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) {
|
||||||
|
throw OAuthServerException::invalidRequest('redirect_uri');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($authCodePayload->redirect_uri !== $redirectUri) {
|
||||||
|
throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the grant identifier that can be used in matching up requests.
|
* Return the grant identifier that can be used in matching up requests.
|
||||||
*
|
*
|
||||||
@@ -209,49 +246,30 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
$request,
|
$request,
|
||||||
$this->getServerParameter('PHP_AUTH_USER', $request)
|
$this->getServerParameter('PHP_AUTH_USER', $request)
|
||||||
);
|
);
|
||||||
if (is_null($clientId)) {
|
|
||||||
|
if ($clientId === null) {
|
||||||
throw OAuthServerException::invalidRequest('client_id');
|
throw OAuthServerException::invalidRequest('client_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
$client = $this->clientRepository->getClientEntity(
|
$client = $this->getClientEntityOrFail($clientId, $request);
|
||||||
$clientId,
|
|
||||||
$this->getIdentifier(),
|
|
||||||
null,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($client instanceof ClientEntityInterface === false) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
|
||||||
throw OAuthServerException::invalidClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
|
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
|
||||||
|
|
||||||
if ($redirectUri !== null) {
|
if ($redirectUri !== null) {
|
||||||
if (
|
$this->validateRedirectUri($redirectUri, $client, $request);
|
||||||
is_string($client->getRedirectUri())
|
} elseif (empty($client->getRedirectUri()) ||
|
||||||
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
|
(\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) {
|
||||||
) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
|
||||||
throw OAuthServerException::invalidClient();
|
|
||||||
} elseif (
|
|
||||||
is_array($client->getRedirectUri())
|
|
||||||
&& in_array($redirectUri, $client->getRedirectUri()) === 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->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||||
throw OAuthServerException::invalidClient();
|
throw OAuthServerException::invalidClient($request);
|
||||||
|
} else {
|
||||||
|
$redirectUri = \is_array($client->getRedirectUri())
|
||||||
|
? $client->getRedirectUri()[0]
|
||||||
|
: $client->getRedirectUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scopes = $this->validateScopes(
|
$scopes = $this->validateScopes(
|
||||||
$this->getQueryStringParameter('scope', $request, $this->defaultScope),
|
$this->getQueryStringParameter('scope', $request, $this->defaultScope),
|
||||||
is_array($client->getRedirectUri())
|
$redirectUri
|
||||||
? $client->getRedirectUri()[0]
|
|
||||||
: $client->getRedirectUri()
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$stateParameter = $this->getQueryStringParameter('state', $request);
|
$stateParameter = $this->getQueryStringParameter('state', $request);
|
||||||
@@ -260,32 +278,43 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
$authorizationRequest->setGrantTypeId($this->getIdentifier());
|
$authorizationRequest->setGrantTypeId($this->getIdentifier());
|
||||||
$authorizationRequest->setClient($client);
|
$authorizationRequest->setClient($client);
|
||||||
$authorizationRequest->setRedirectUri($redirectUri);
|
$authorizationRequest->setRedirectUri($redirectUri);
|
||||||
$authorizationRequest->setState($stateParameter);
|
|
||||||
|
if ($stateParameter !== null) {
|
||||||
|
$authorizationRequest->setState($stateParameter);
|
||||||
|
}
|
||||||
|
|
||||||
$authorizationRequest->setScopes($scopes);
|
$authorizationRequest->setScopes($scopes);
|
||||||
|
|
||||||
if ($this->enableCodeExchangeProof === true) {
|
$codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
|
||||||
$codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
|
|
||||||
if ($codeChallenge === null) {
|
|
||||||
throw OAuthServerException::invalidRequest('code_challenge');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) {
|
if ($codeChallenge !== null) {
|
||||||
|
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
|
||||||
|
|
||||||
|
if (array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) {
|
||||||
throw OAuthServerException::invalidRequest(
|
throw OAuthServerException::invalidRequest(
|
||||||
'code_challenge',
|
'code_challenge_method',
|
||||||
'The code_challenge must be between 43 and 128 characters'
|
'Code challenge method must be one of ' . implode(', ', array_map(
|
||||||
|
function ($method) {
|
||||||
|
return '`' . $method . '`';
|
||||||
|
},
|
||||||
|
array_keys($this->codeChallengeVerifiers)
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
|
// Validate code_challenge according to RFC-7636
|
||||||
if (in_array($codeChallengeMethod, ['plain', 'S256']) === false) {
|
// @see: https://tools.ietf.org/html/rfc7636#section-4.2
|
||||||
|
if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) {
|
||||||
throw OAuthServerException::invalidRequest(
|
throw OAuthServerException::invalidRequest(
|
||||||
'code_challenge_method',
|
'code_challenged',
|
||||||
'Code challenge method must be `plain` or `S256`'
|
'Code challenge must follow the specifications of RFC-7636.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$authorizationRequest->setCodeChallenge($codeChallenge);
|
$authorizationRequest->setCodeChallenge($codeChallenge);
|
||||||
$authorizationRequest->setCodeChallengeMethod($codeChallengeMethod);
|
$authorizationRequest->setCodeChallengeMethod($codeChallengeMethod);
|
||||||
|
} elseif ($this->requireCodeChallengeForPublicClients && !$client->isConfidential()) {
|
||||||
|
throw OAuthServerException::invalidRequest('code_challenge', 'Code challenge must be provided for public clients');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $authorizationRequest;
|
return $authorizationRequest;
|
||||||
@@ -297,14 +326,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
|
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
|
||||||
{
|
{
|
||||||
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
|
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
|
||||||
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
|
throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
|
||||||
}
|
}
|
||||||
|
|
||||||
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
|
$finalRedirectUri = $authorizationRequest->getRedirectUri()
|
||||||
? is_array($authorizationRequest->getClient()->getRedirectUri())
|
?? $this->getClientRedirectUri($authorizationRequest);
|
||||||
? $authorizationRequest->getClient()->getRedirectUri()[0]
|
|
||||||
: $authorizationRequest->getClient()->getRedirectUri()
|
|
||||||
: $authorizationRequest->getRedirectUri();
|
|
||||||
|
|
||||||
// The user approved the client, redirect them back with an auth code
|
// The user approved the client, redirect them back with an auth code
|
||||||
if ($authorizationRequest->isAuthorizationApproved() === true) {
|
if ($authorizationRequest->isAuthorizationApproved() === true) {
|
||||||
@@ -322,21 +348,23 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
|||||||
'auth_code_id' => $authCode->getIdentifier(),
|
'auth_code_id' => $authCode->getIdentifier(),
|
||||||
'scopes' => $authCode->getScopes(),
|
'scopes' => $authCode->getScopes(),
|
||||||
'user_id' => $authCode->getUserIdentifier(),
|
'user_id' => $authCode->getUserIdentifier(),
|
||||||
'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'),
|
'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(),
|
||||||
'code_challenge' => $authorizationRequest->getCodeChallenge(),
|
'code_challenge' => $authorizationRequest->getCodeChallenge(),
|
||||||
'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
|
'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$jsonPayload = json_encode($payload);
|
||||||
|
|
||||||
|
if ($jsonPayload === false) {
|
||||||
|
throw new LogicException('An error was encountered when JSON encoding the authorization request response');
|
||||||
|
}
|
||||||
|
|
||||||
$response = new RedirectResponse();
|
$response = new RedirectResponse();
|
||||||
$response->setRedirectUri(
|
$response->setRedirectUri(
|
||||||
$this->makeRedirectUri(
|
$this->makeRedirectUri(
|
||||||
$finalRedirectUri,
|
$finalRedirectUri,
|
||||||
[
|
[
|
||||||
'code' => $this->encrypt(
|
'code' => $this->encrypt($jsonPayload),
|
||||||
json_encode(
|
|
||||||
$payload
|
|
||||||
)
|
|
||||||
),
|
|
||||||
'state' => $authorizationRequest->getState(),
|
'state' => $authorizationRequest->getState(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@@ -356,4 +384,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,9 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
|
use League\OAuth2\Server\RequestEvent;
|
||||||
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
@@ -25,10 +28,21 @@ class ClientCredentialsGrant extends AbstractGrant
|
|||||||
public function respondToAccessTokenRequest(
|
public function respondToAccessTokenRequest(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
ResponseTypeInterface $responseType,
|
ResponseTypeInterface $responseType,
|
||||||
\DateInterval $accessTokenTTL
|
DateInterval $accessTokenTTL
|
||||||
) {
|
) {
|
||||||
|
list($clientId) = $this->getClientCredentials($request);
|
||||||
|
|
||||||
|
$client = $this->getClientEntityOrFail($clientId, $request);
|
||||||
|
|
||||||
|
if (!$client->isConfidential()) {
|
||||||
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||||
|
|
||||||
|
throw OAuthServerException::invalidClient($request);
|
||||||
|
}
|
||||||
|
|
||||||
// Validate request
|
// Validate request
|
||||||
$client = $this->validateClient($request);
|
$this->validateClient($request);
|
||||||
|
|
||||||
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
|
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request, $this->defaultScope));
|
||||||
|
|
||||||
// Finalize the requested scopes
|
// Finalize the requested scopes
|
||||||
@@ -37,6 +51,9 @@ class ClientCredentialsGrant extends AbstractGrant
|
|||||||
// Issue and persist access token
|
// Issue and persist access token
|
||||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes);
|
$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
|
// Inject access token into response type
|
||||||
$responseType->setAccessToken($accessToken);
|
$responseType->setAccessToken($accessToken);
|
||||||
|
|
||||||
|
@@ -11,8 +11,10 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use Defuse\Crypto\Key;
|
||||||
use League\Event\EmitterAwareInterface;
|
use League\Event\EmitterAwareInterface;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKeyInterface;
|
||||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||||
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
||||||
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
|
||||||
@@ -28,9 +30,9 @@ interface GrantTypeInterface extends EmitterAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Set refresh token TTL.
|
* Set refresh token TTL.
|
||||||
*
|
*
|
||||||
* @param \DateInterval $refreshTokenTTL
|
* @param DateInterval $refreshTokenTTL
|
||||||
*/
|
*/
|
||||||
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL);
|
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the grant identifier that can be used in matching up requests.
|
* Return the grant identifier that can be used in matching up requests.
|
||||||
@@ -44,14 +46,14 @@ interface GrantTypeInterface extends EmitterAwareInterface
|
|||||||
*
|
*
|
||||||
* @param ServerRequestInterface $request
|
* @param ServerRequestInterface $request
|
||||||
* @param ResponseTypeInterface $responseType
|
* @param ResponseTypeInterface $responseType
|
||||||
* @param \DateInterval $accessTokenTTL
|
* @param DateInterval $accessTokenTTL
|
||||||
*
|
*
|
||||||
* @return ResponseTypeInterface
|
* @return ResponseTypeInterface
|
||||||
*/
|
*/
|
||||||
public function respondToAccessTokenRequest(
|
public function respondToAccessTokenRequest(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
ResponseTypeInterface $responseType,
|
ResponseTypeInterface $responseType,
|
||||||
\DateInterval $accessTokenTTL
|
DateInterval $accessTokenTTL
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,14 +131,14 @@ interface GrantTypeInterface extends EmitterAwareInterface
|
|||||||
/**
|
/**
|
||||||
* Set the path to the private key.
|
* Set the path to the private key.
|
||||||
*
|
*
|
||||||
* @param CryptKey $privateKey
|
* @param CryptKeyInterface $privateKey
|
||||||
*/
|
*/
|
||||||
public function setPrivateKey(CryptKey $privateKey);
|
public function setPrivateKey(CryptKeyInterface $privateKey);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the encryption key
|
* Set the encryption key
|
||||||
*
|
*
|
||||||
* @param string|null $key
|
* @param string|Key|null $key
|
||||||
*/
|
*/
|
||||||
public function setEncryptionKey($key = null);
|
public function setEncryptionKey($key = null);
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
use DateInterval;
|
||||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
||||||
@@ -17,12 +17,13 @@ use League\OAuth2\Server\RequestEvent;
|
|||||||
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
|
||||||
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
|
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
|
||||||
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
||||||
|
use LogicException;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
class ImplicitGrant extends AbstractAuthorizeGrant
|
class ImplicitGrant extends AbstractAuthorizeGrant
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var \DateInterval
|
* @var DateInterval
|
||||||
*/
|
*/
|
||||||
private $accessTokenTTL;
|
private $accessTokenTTL;
|
||||||
|
|
||||||
@@ -32,33 +33,33 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
private $queryDelimiter;
|
private $queryDelimiter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \DateInterval $accessTokenTTL
|
* @param DateInterval $accessTokenTTL
|
||||||
* @param string $queryDelimiter
|
* @param string $queryDelimiter
|
||||||
*/
|
*/
|
||||||
public function __construct(\DateInterval $accessTokenTTL, $queryDelimiter = '#')
|
public function __construct(DateInterval $accessTokenTTL, $queryDelimiter = '#')
|
||||||
{
|
{
|
||||||
$this->accessTokenTTL = $accessTokenTTL;
|
$this->accessTokenTTL = $accessTokenTTL;
|
||||||
$this->queryDelimiter = $queryDelimiter;
|
$this->queryDelimiter = $queryDelimiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param \DateInterval $refreshTokenTTL
|
* @param DateInterval $refreshTokenTTL
|
||||||
*
|
*
|
||||||
* @throw \LogicException
|
* @throw LogicException
|
||||||
*/
|
*/
|
||||||
public function setRefreshTokenTTL(\DateInterval $refreshTokenTTL)
|
public function setRefreshTokenTTL(DateInterval $refreshTokenTTL)
|
||||||
{
|
{
|
||||||
throw new \LogicException('The Implicit Grant does not return refresh tokens');
|
throw new LogicException('The Implicit Grant does not return refresh tokens');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
|
* @param RefreshTokenRepositoryInterface $refreshTokenRepository
|
||||||
*
|
*
|
||||||
* @throw \LogicException
|
* @throw LogicException
|
||||||
*/
|
*/
|
||||||
public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
|
public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository)
|
||||||
{
|
{
|
||||||
throw new \LogicException('The Implicit Grant does not return refresh tokens');
|
throw new LogicException('The Implicit Grant does not return refresh tokens');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,16 +85,16 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
*
|
*
|
||||||
* @param ServerRequestInterface $request
|
* @param ServerRequestInterface $request
|
||||||
* @param ResponseTypeInterface $responseType
|
* @param ResponseTypeInterface $responseType
|
||||||
* @param \DateInterval $accessTokenTTL
|
* @param DateInterval $accessTokenTTL
|
||||||
*
|
*
|
||||||
* @return ResponseTypeInterface
|
* @return ResponseTypeInterface
|
||||||
*/
|
*/
|
||||||
public function respondToAccessTokenRequest(
|
public function respondToAccessTokenRequest(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
ResponseTypeInterface $responseType,
|
ResponseTypeInterface $responseType,
|
||||||
\DateInterval $accessTokenTTL
|
DateInterval $accessTokenTTL
|
||||||
) {
|
) {
|
||||||
throw new \LogicException('This grant does not used this method');
|
throw new LogicException('This grant does not used this method');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -118,56 +119,30 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
$request,
|
$request,
|
||||||
$this->getServerParameter('PHP_AUTH_USER', $request)
|
$this->getServerParameter('PHP_AUTH_USER', $request)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (is_null($clientId)) {
|
if (is_null($clientId)) {
|
||||||
throw OAuthServerException::invalidRequest('client_id');
|
throw OAuthServerException::invalidRequest('client_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
$client = $this->clientRepository->getClientEntity(
|
$client = $this->getClientEntityOrFail($clientId, $request);
|
||||||
$clientId,
|
|
||||||
$this->getIdentifier(),
|
|
||||||
null,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($client instanceof ClientEntityInterface === false) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
|
||||||
throw OAuthServerException::invalidClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
|
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
|
||||||
|
|
||||||
if ($redirectUri !== null) {
|
if ($redirectUri !== null) {
|
||||||
if (
|
$this->validateRedirectUri($redirectUri, $client, $request);
|
||||||
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()) === false
|
|
||||||
) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
|
||||||
throw OAuthServerException::invalidClient();
|
|
||||||
}
|
|
||||||
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|
||||||
|| empty($client->getRedirectUri())
|
|| empty($client->getRedirectUri())) {
|
||||||
) {
|
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||||
throw OAuthServerException::invalidClient();
|
throw OAuthServerException::invalidClient($request);
|
||||||
|
} else {
|
||||||
|
$redirectUri = is_array($client->getRedirectUri())
|
||||||
|
? $client->getRedirectUri()[0]
|
||||||
|
: $client->getRedirectUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
$scopes = $this->validateScopes(
|
$scopes = $this->validateScopes(
|
||||||
$this->getQueryStringParameter('scope', $request, $this->defaultScope),
|
$this->getQueryStringParameter('scope', $request, $this->defaultScope),
|
||||||
is_array($client->getRedirectUri())
|
$redirectUri
|
||||||
? $client->getRedirectUri()[0]
|
|
||||||
: $client->getRedirectUri()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Finalize the requested scopes
|
|
||||||
$finalizedScopes = $this->scopeRepository->finalizeScopes(
|
|
||||||
$scopes,
|
|
||||||
$this->getIdentifier(),
|
|
||||||
$client
|
|
||||||
);
|
);
|
||||||
|
|
||||||
$stateParameter = $this->getQueryStringParameter('state', $request);
|
$stateParameter = $this->getQueryStringParameter('state', $request);
|
||||||
@@ -176,8 +151,12 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
$authorizationRequest->setGrantTypeId($this->getIdentifier());
|
$authorizationRequest->setGrantTypeId($this->getIdentifier());
|
||||||
$authorizationRequest->setClient($client);
|
$authorizationRequest->setClient($client);
|
||||||
$authorizationRequest->setRedirectUri($redirectUri);
|
$authorizationRequest->setRedirectUri($redirectUri);
|
||||||
$authorizationRequest->setState($stateParameter);
|
|
||||||
$authorizationRequest->setScopes($finalizedScopes);
|
if ($stateParameter !== null) {
|
||||||
|
$authorizationRequest->setState($stateParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$authorizationRequest->setScopes($scopes);
|
||||||
|
|
||||||
return $authorizationRequest;
|
return $authorizationRequest;
|
||||||
}
|
}
|
||||||
@@ -188,7 +167,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
|
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest)
|
||||||
{
|
{
|
||||||
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
|
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) {
|
||||||
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
|
throw new LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest');
|
||||||
}
|
}
|
||||||
|
|
||||||
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
|
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null)
|
||||||
@@ -199,11 +178,19 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
|
|
||||||
// The user approved the client, redirect them back with an access token
|
// The user approved the client, redirect them back with an access token
|
||||||
if ($authorizationRequest->isAuthorizationApproved() === true) {
|
if ($authorizationRequest->isAuthorizationApproved() === true) {
|
||||||
|
// Finalize the requested scopes
|
||||||
|
$finalizedScopes = $this->scopeRepository->finalizeScopes(
|
||||||
|
$authorizationRequest->getScopes(),
|
||||||
|
$this->getIdentifier(),
|
||||||
|
$authorizationRequest->getClient(),
|
||||||
|
$authorizationRequest->getUser()->getIdentifier()
|
||||||
|
);
|
||||||
|
|
||||||
$accessToken = $this->issueAccessToken(
|
$accessToken = $this->issueAccessToken(
|
||||||
$this->accessTokenTTL,
|
$this->accessTokenTTL,
|
||||||
$authorizationRequest->getClient(),
|
$authorizationRequest->getClient(),
|
||||||
$authorizationRequest->getUser()->getIdentifier(),
|
$authorizationRequest->getUser()->getIdentifier(),
|
||||||
$authorizationRequest->getScopes()
|
$finalizedScopes
|
||||||
);
|
);
|
||||||
|
|
||||||
$response = new RedirectResponse();
|
$response = new RedirectResponse();
|
||||||
@@ -211,9 +198,9 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
|||||||
$this->makeRedirectUri(
|
$this->makeRedirectUri(
|
||||||
$finalRedirectUri,
|
$finalRedirectUri,
|
||||||
[
|
[
|
||||||
'access_token' => (string) $accessToken->convertToJWT($this->privateKey),
|
'access_token' => (string) $accessToken,
|
||||||
'token_type' => 'Bearer',
|
'token_type' => 'Bearer',
|
||||||
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new \DateTime())->getTimestamp(),
|
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - \time(),
|
||||||
'state' => $authorizationRequest->getState(),
|
'state' => $authorizationRequest->getState(),
|
||||||
],
|
],
|
||||||
$this->queryDelimiter
|
$this->queryDelimiter
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
use League\OAuth2\Server\Entities\ClientEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\UserEntityInterface;
|
use League\OAuth2\Server\Entities\UserEntityInterface;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
@@ -36,7 +37,7 @@ class PasswordGrant extends AbstractGrant
|
|||||||
$this->setUserRepository($userRepository);
|
$this->setUserRepository($userRepository);
|
||||||
$this->setRefreshTokenRepository($refreshTokenRepository);
|
$this->setRefreshTokenRepository($refreshTokenRepository);
|
||||||
|
|
||||||
$this->refreshTokenTTL = new \DateInterval('P1M');
|
$this->refreshTokenTTL = new DateInterval('P1M');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,7 +46,7 @@ class PasswordGrant extends AbstractGrant
|
|||||||
public function respondToAccessTokenRequest(
|
public function respondToAccessTokenRequest(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
ResponseTypeInterface $responseType,
|
ResponseTypeInterface $responseType,
|
||||||
\DateInterval $accessTokenTTL
|
DateInterval $accessTokenTTL
|
||||||
) {
|
) {
|
||||||
// Validate request
|
// Validate request
|
||||||
$client = $this->validateClient($request);
|
$client = $this->validateClient($request);
|
||||||
@@ -55,13 +56,18 @@ class PasswordGrant extends AbstractGrant
|
|||||||
// Finalize the requested scopes
|
// Finalize the requested scopes
|
||||||
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
|
$finalizedScopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
|
||||||
|
|
||||||
// Issue and persist new tokens
|
// Issue and persist new access token
|
||||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
|
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
|
||||||
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||||
|
$responseType->setAccessToken($accessToken);
|
||||||
|
|
||||||
|
// Issue and persist new refresh token if given
|
||||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||||
|
|
||||||
// Inject tokens into response
|
if ($refreshToken !== null) {
|
||||||
$responseType->setAccessToken($accessToken);
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
return $responseType;
|
return $responseType;
|
||||||
}
|
}
|
||||||
@@ -77,11 +83,13 @@ class PasswordGrant extends AbstractGrant
|
|||||||
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
|
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
|
||||||
{
|
{
|
||||||
$username = $this->getRequestParameter('username', $request);
|
$username = $this->getRequestParameter('username', $request);
|
||||||
|
|
||||||
if (is_null($username)) {
|
if (is_null($username)) {
|
||||||
throw OAuthServerException::invalidRequest('username');
|
throw OAuthServerException::invalidRequest('username');
|
||||||
}
|
}
|
||||||
|
|
||||||
$password = $this->getRequestParameter('password', $request);
|
$password = $this->getRequestParameter('password', $request);
|
||||||
|
|
||||||
if (is_null($password)) {
|
if (is_null($password)) {
|
||||||
throw OAuthServerException::invalidRequest('password');
|
throw OAuthServerException::invalidRequest('password');
|
||||||
}
|
}
|
||||||
@@ -92,10 +100,11 @@ class PasswordGrant extends AbstractGrant
|
|||||||
$this->getIdentifier(),
|
$this->getIdentifier(),
|
||||||
$client
|
$client
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($user instanceof UserEntityInterface === false) {
|
if ($user instanceof UserEntityInterface === false) {
|
||||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
|
||||||
|
|
||||||
throw OAuthServerException::invalidCredentials();
|
throw OAuthServerException::invalidGrant();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
|
@@ -11,7 +11,8 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Grant;
|
namespace League\OAuth2\Server\Grant;
|
||||||
|
|
||||||
use League\OAuth2\Server\Entities\ScopeEntityInterface;
|
use DateInterval;
|
||||||
|
use Exception;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
|
||||||
use League\OAuth2\Server\RequestEvent;
|
use League\OAuth2\Server\RequestEvent;
|
||||||
@@ -30,7 +31,7 @@ class RefreshTokenGrant extends AbstractGrant
|
|||||||
{
|
{
|
||||||
$this->setRefreshTokenRepository($refreshTokenRepository);
|
$this->setRefreshTokenRepository($refreshTokenRepository);
|
||||||
|
|
||||||
$this->refreshTokenTTL = new \DateInterval('P1M');
|
$this->refreshTokenTTL = new DateInterval('P1M');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,7 +40,7 @@ class RefreshTokenGrant extends AbstractGrant
|
|||||||
public function respondToAccessTokenRequest(
|
public function respondToAccessTokenRequest(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
ResponseTypeInterface $responseType,
|
ResponseTypeInterface $responseType,
|
||||||
\DateInterval $accessTokenTTL
|
DateInterval $accessTokenTTL
|
||||||
) {
|
) {
|
||||||
// Validate request
|
// Validate request
|
||||||
$client = $this->validateClient($request);
|
$client = $this->validateClient($request);
|
||||||
@@ -53,7 +54,7 @@ class RefreshTokenGrant extends AbstractGrant
|
|||||||
// The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure
|
// The OAuth spec says that a refreshed access token can have the original scopes or fewer so ensure
|
||||||
// the request doesn't include any new scopes
|
// the request doesn't include any new scopes
|
||||||
foreach ($scopes as $scope) {
|
foreach ($scopes as $scope) {
|
||||||
if (in_array($scope->getIdentifier(), $oldRefreshToken['scopes']) === false) {
|
if (in_array($scope->getIdentifier(), $oldRefreshToken['scopes'], true) === false) {
|
||||||
throw OAuthServerException::invalidScope($scope->getIdentifier());
|
throw OAuthServerException::invalidScope($scope->getIdentifier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,13 +63,18 @@ class RefreshTokenGrant extends AbstractGrant
|
|||||||
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
|
$this->accessTokenRepository->revokeAccessToken($oldRefreshToken['access_token_id']);
|
||||||
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
|
$this->refreshTokenRepository->revokeRefreshToken($oldRefreshToken['refresh_token_id']);
|
||||||
|
|
||||||
// Issue and persist new tokens
|
// Issue and persist new access token
|
||||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
|
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
|
||||||
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||||
|
$responseType->setAccessToken($accessToken);
|
||||||
|
|
||||||
|
// Issue and persist new refresh token if given
|
||||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||||
|
|
||||||
// Inject tokens into response
|
if ($refreshToken !== null) {
|
||||||
$responseType->setAccessToken($accessToken);
|
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
return $responseType;
|
return $responseType;
|
||||||
}
|
}
|
||||||
@@ -91,8 +97,8 @@ class RefreshTokenGrant extends AbstractGrant
|
|||||||
// Validate refresh token
|
// Validate refresh token
|
||||||
try {
|
try {
|
||||||
$refreshToken = $this->decrypt($encryptedRefreshToken);
|
$refreshToken = $this->decrypt($encryptedRefreshToken);
|
||||||
} catch (\Exception $e) {
|
} catch (Exception $e) {
|
||||||
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token');
|
throw OAuthServerException::invalidRefreshToken('Cannot decrypt the refresh token', $e);
|
||||||
}
|
}
|
||||||
|
|
||||||
$refreshTokenData = json_decode($refreshToken, true);
|
$refreshTokenData = json_decode($refreshToken, true);
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Middleware;
|
namespace League\OAuth2\Server\Middleware;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use League\OAuth2\Server\AuthorizationServer;
|
use League\OAuth2\Server\AuthorizationServer;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
@@ -43,7 +44,7 @@ class AuthorizationServerMiddleware
|
|||||||
} catch (OAuthServerException $exception) {
|
} catch (OAuthServerException $exception) {
|
||||||
return $exception->generateHttpResponse($response);
|
return $exception->generateHttpResponse($response);
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
} catch (\Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
|
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
|
||||||
->generateHttpResponse($response);
|
->generateHttpResponse($response);
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\Middleware;
|
namespace League\OAuth2\Server\Middleware;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\ResourceServer;
|
use League\OAuth2\Server\ResourceServer;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
@@ -34,7 +35,7 @@ class ResourceServerMiddleware
|
|||||||
* @param ResponseInterface $response
|
* @param ResponseInterface $response
|
||||||
* @param callable $next
|
* @param callable $next
|
||||||
*
|
*
|
||||||
* @return \Psr\Http\Message\ResponseInterface
|
* @return ResponseInterface
|
||||||
*/
|
*/
|
||||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
|
||||||
{
|
{
|
||||||
@@ -43,7 +44,7 @@ class ResourceServerMiddleware
|
|||||||
} catch (OAuthServerException $exception) {
|
} catch (OAuthServerException $exception) {
|
||||||
return $exception->generateHttpResponse($response);
|
return $exception->generateHttpResponse($response);
|
||||||
// @codeCoverageIgnoreStart
|
// @codeCoverageIgnoreStart
|
||||||
} catch (\Exception $exception) {
|
} catch (Exception $exception) {
|
||||||
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
|
return (new OAuthServerException($exception->getMessage(), 0, 'unknown_error', 500))
|
||||||
->generateHttpResponse($response);
|
->generateHttpResponse($response);
|
||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
|
@@ -19,13 +19,20 @@ interface ClientRepositoryInterface extends RepositoryInterface
|
|||||||
/**
|
/**
|
||||||
* Get a client.
|
* Get a client.
|
||||||
*
|
*
|
||||||
* @param string $clientIdentifier The client's identifier
|
* @param string $clientIdentifier The client's identifier
|
||||||
* @param string $grantType The grant type used
|
|
||||||
* @param null|string $clientSecret The client's secret (if sent)
|
|
||||||
* @param bool $mustValidateSecret If true the client must attempt to validate the secret if the client
|
|
||||||
* is confidential
|
|
||||||
*
|
*
|
||||||
* @return ClientEntityInterface
|
* @return ClientEntityInterface|null
|
||||||
*/
|
*/
|
||||||
public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true);
|
public function getClientEntity($clientIdentifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a client's secret.
|
||||||
|
*
|
||||||
|
* @param string $clientIdentifier The client's identifier
|
||||||
|
* @param null|string $clientSecret The client's secret (if sent)
|
||||||
|
* @param null|string $grantType The type of grant the client is using (if sent)
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateClient($clientIdentifier, $clientSecret, $grantType);
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,7 @@ interface RefreshTokenRepositoryInterface extends RepositoryInterface
|
|||||||
/**
|
/**
|
||||||
* Creates a new refresh token
|
* Creates a new refresh token
|
||||||
*
|
*
|
||||||
* @return RefreshTokenEntityInterface
|
* @return RefreshTokenEntityInterface|null
|
||||||
*/
|
*/
|
||||||
public function getNewRefreshToken();
|
public function getNewRefreshToken();
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ interface ScopeRepositoryInterface extends RepositoryInterface
|
|||||||
*
|
*
|
||||||
* @param string $identifier The scope identifier
|
* @param string $identifier The scope identifier
|
||||||
*
|
*
|
||||||
* @return ScopeEntityInterface
|
* @return ScopeEntityInterface|null
|
||||||
*/
|
*/
|
||||||
public function getScopeEntityByIdentifier($identifier);
|
public function getScopeEntityByIdentifier($identifier);
|
||||||
|
|
||||||
|
@@ -22,7 +22,7 @@ interface UserRepositoryInterface extends RepositoryInterface
|
|||||||
* @param string $grantType The grant type used
|
* @param string $grantType The grant type used
|
||||||
* @param ClientEntityInterface $clientEntity
|
* @param ClientEntityInterface $clientEntity
|
||||||
*
|
*
|
||||||
* @return UserEntityInterface
|
* @return UserEntityInterface|null
|
||||||
*/
|
*/
|
||||||
public function getUserEntityByUserCredentials(
|
public function getUserEntityByUserCredentials(
|
||||||
$username,
|
$username,
|
||||||
|
@@ -18,6 +18,9 @@ class RequestEvent extends Event
|
|||||||
const USER_AUTHENTICATION_FAILED = 'user.authentication.failed';
|
const USER_AUTHENTICATION_FAILED = 'user.authentication.failed';
|
||||||
const REFRESH_TOKEN_CLIENT_FAILED = 'refresh_token.client.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
|
* @var ServerRequestInterface
|
||||||
*/
|
*/
|
||||||
|
@@ -53,14 +53,14 @@ class AuthorizationRequest
|
|||||||
/**
|
/**
|
||||||
* The redirect URI used in the request
|
* The redirect URI used in the request
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
protected $redirectUri;
|
protected $redirectUri;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The state parameter on the authorization request
|
* The state parameter on the authorization request
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string|null
|
||||||
*/
|
*/
|
||||||
protected $state;
|
protected $state;
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ class AuthorizationRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return UserEntityInterface
|
* @return UserEntityInterface|null
|
||||||
*/
|
*/
|
||||||
public function getUser()
|
public function getUser()
|
||||||
{
|
{
|
||||||
@@ -159,7 +159,7 @@ class AuthorizationRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getRedirectUri()
|
public function getRedirectUri()
|
||||||
{
|
{
|
||||||
@@ -167,7 +167,7 @@ class AuthorizationRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $redirectUri
|
* @param string|null $redirectUri
|
||||||
*/
|
*/
|
||||||
public function setRedirectUri($redirectUri)
|
public function setRedirectUri($redirectUri)
|
||||||
{
|
{
|
||||||
@@ -175,7 +175,7 @@ class AuthorizationRequest
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function getState()
|
public function getState()
|
||||||
{
|
{
|
||||||
|
@@ -23,7 +23,7 @@ class ResourceServer
|
|||||||
private $accessTokenRepository;
|
private $accessTokenRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var CryptKey
|
* @var CryptKeyInterface
|
||||||
*/
|
*/
|
||||||
private $publicKey;
|
private $publicKey;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class ResourceServer
|
|||||||
* New server instance.
|
* New server instance.
|
||||||
*
|
*
|
||||||
* @param AccessTokenRepositoryInterface $accessTokenRepository
|
* @param AccessTokenRepositoryInterface $accessTokenRepository
|
||||||
* @param CryptKey|string $publicKey
|
* @param CryptKeyInterface|string $publicKey
|
||||||
* @param null|AuthorizationValidatorInterface $authorizationValidator
|
* @param null|AuthorizationValidatorInterface $authorizationValidator
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -46,7 +46,7 @@ class ResourceServer
|
|||||||
) {
|
) {
|
||||||
$this->accessTokenRepository = $accessTokenRepository;
|
$this->accessTokenRepository = $accessTokenRepository;
|
||||||
|
|
||||||
if ($publicKey instanceof CryptKey === false) {
|
if ($publicKey instanceof CryptKeyInterface === false) {
|
||||||
$publicKey = new CryptKey($publicKey);
|
$publicKey = new CryptKey($publicKey);
|
||||||
}
|
}
|
||||||
$this->publicKey = $publicKey;
|
$this->publicKey = $publicKey;
|
||||||
@@ -63,7 +63,9 @@ class ResourceServer
|
|||||||
$this->authorizationValidator = new BearerTokenValidator($this->accessTokenRepository);
|
$this->authorizationValidator = new BearerTokenValidator($this->accessTokenRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->authorizationValidator->setPublicKey($this->publicKey);
|
if ($this->authorizationValidator instanceof BearerTokenValidator === true) {
|
||||||
|
$this->authorizationValidator->setPublicKey($this->publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->authorizationValidator;
|
return $this->authorizationValidator;
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\ResponseTypes;
|
namespace League\OAuth2\Server\ResponseTypes;
|
||||||
|
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKeyInterface;
|
||||||
use League\OAuth2\Server\CryptTrait;
|
use League\OAuth2\Server\CryptTrait;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||||
@@ -31,7 +31,7 @@ abstract class AbstractResponseType implements ResponseTypeInterface
|
|||||||
protected $refreshToken;
|
protected $refreshToken;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var CryptKey
|
* @var CryptKeyInterface
|
||||||
*/
|
*/
|
||||||
protected $privateKey;
|
protected $privateKey;
|
||||||
|
|
||||||
@@ -54,9 +54,9 @@ abstract class AbstractResponseType implements ResponseTypeInterface
|
|||||||
/**
|
/**
|
||||||
* Set the private key
|
* Set the private key
|
||||||
*
|
*
|
||||||
* @param \League\OAuth2\Server\CryptKey $key
|
* @param CryptKeyInterface $key
|
||||||
*/
|
*/
|
||||||
public function setPrivateKey(CryptKey $key)
|
public function setPrivateKey(CryptKeyInterface $key)
|
||||||
{
|
{
|
||||||
$this->privateKey = $key;
|
$this->privateKey = $key;
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@ namespace League\OAuth2\Server\ResponseTypes;
|
|||||||
|
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||||
|
use LogicException;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
class BearerTokenResponse extends AbstractResponseType
|
class BearerTokenResponse extends AbstractResponseType
|
||||||
@@ -24,32 +25,34 @@ class BearerTokenResponse extends AbstractResponseType
|
|||||||
{
|
{
|
||||||
$expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp();
|
$expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp();
|
||||||
|
|
||||||
$jwtAccessToken = $this->accessToken->convertToJWT($this->privateKey);
|
|
||||||
|
|
||||||
$responseParams = [
|
$responseParams = [
|
||||||
'token_type' => 'Bearer',
|
'token_type' => 'Bearer',
|
||||||
'expires_in' => $expireDateTime - (new \DateTime())->getTimestamp(),
|
'expires_in' => $expireDateTime - \time(),
|
||||||
'access_token' => (string) $jwtAccessToken,
|
'access_token' => (string) $this->accessToken,
|
||||||
];
|
];
|
||||||
|
|
||||||
if ($this->refreshToken instanceof RefreshTokenEntityInterface) {
|
if ($this->refreshToken instanceof RefreshTokenEntityInterface) {
|
||||||
$refreshToken = $this->encrypt(
|
$refreshTokenPayload = json_encode([
|
||||||
json_encode(
|
'client_id' => $this->accessToken->getClient()->getIdentifier(),
|
||||||
[
|
'refresh_token_id' => $this->refreshToken->getIdentifier(),
|
||||||
'client_id' => $this->accessToken->getClient()->getIdentifier(),
|
'access_token_id' => $this->accessToken->getIdentifier(),
|
||||||
'refresh_token_id' => $this->refreshToken->getIdentifier(),
|
'scopes' => $this->accessToken->getScopes(),
|
||||||
'access_token_id' => $this->accessToken->getIdentifier(),
|
'user_id' => $this->accessToken->getUserIdentifier(),
|
||||||
'scopes' => $this->accessToken->getScopes(),
|
'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(),
|
||||||
'user_id' => $this->accessToken->getUserIdentifier(),
|
]);
|
||||||
'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseParams['refresh_token'] = $refreshToken;
|
if ($refreshTokenPayload === false) {
|
||||||
|
throw new LogicException('Error encountered JSON encoding the refresh token payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
$responseParams = array_merge($this->getExtraParams($this->accessToken), $responseParams);
|
$responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams));
|
||||||
|
|
||||||
|
if ($responseParams === false) {
|
||||||
|
throw new LogicException('Error encountered JSON encoding response parameters');
|
||||||
|
}
|
||||||
|
|
||||||
$response = $response
|
$response = $response
|
||||||
->withStatus(200)
|
->withStatus(200)
|
||||||
@@ -57,7 +60,7 @@ class BearerTokenResponse extends AbstractResponseType
|
|||||||
->withHeader('cache-control', 'no-store')
|
->withHeader('cache-control', 'no-store')
|
||||||
->withHeader('content-type', 'application/json; charset=UTF-8');
|
->withHeader('content-type', 'application/json; charset=UTF-8');
|
||||||
|
|
||||||
$response->getBody()->write(json_encode($responseParams));
|
$response->getBody()->write($responseParams);
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
namespace League\OAuth2\Server\ResponseTypes;
|
namespace League\OAuth2\Server\ResponseTypes;
|
||||||
|
|
||||||
|
use Defuse\Crypto\Key;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
@@ -37,7 +38,7 @@ interface ResponseTypeInterface
|
|||||||
/**
|
/**
|
||||||
* Set the encryption key
|
* Set the encryption key
|
||||||
*
|
*
|
||||||
* @param string|null $key
|
* @param string|Key|null $key
|
||||||
*/
|
*/
|
||||||
public function setEncryptionKey($key = null);
|
public function setEncryptionKey($key = null);
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace LeagueTests;
|
namespace LeagueTests;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
use League\OAuth2\Server\AuthorizationServer;
|
use League\OAuth2\Server\AuthorizationServer;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Grant\AuthCodeGrant;
|
use League\OAuth2\Server\Grant\AuthCodeGrant;
|
||||||
@@ -19,22 +20,22 @@ use LeagueTests\Stubs\ClientEntity;
|
|||||||
use LeagueTests\Stubs\ScopeEntity;
|
use LeagueTests\Stubs\ScopeEntity;
|
||||||
use LeagueTests\Stubs\StubResponseType;
|
use LeagueTests\Stubs\StubResponseType;
|
||||||
use LeagueTests\Stubs\UserEntity;
|
use LeagueTests\Stubs\UserEntity;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Zend\Diactoros\Response;
|
use Zend\Diactoros\Response;
|
||||||
use Zend\Diactoros\ServerRequest;
|
use Zend\Diactoros\ServerRequest;
|
||||||
use Zend\Diactoros\ServerRequestFactory;
|
use Zend\Diactoros\ServerRequestFactory;
|
||||||
|
|
||||||
class AuthorizationServerTest extends TestCase
|
class AuthorizationServerTest extends TestCase
|
||||||
{
|
{
|
||||||
|
|
||||||
const DEFAULT_SCOPE = 'basic';
|
const DEFAULT_SCOPE = 'basic';
|
||||||
|
|
||||||
public function setUp()
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
// Make sure the keys have the correct permissions.
|
// Make sure the keys have the correct permissions.
|
||||||
chmod(__DIR__ . '/Stubs/private.key', 0600);
|
chmod(__DIR__ . '/Stubs/private.key', 0600);
|
||||||
chmod(__DIR__ . '/Stubs/public.key', 0600);
|
chmod(__DIR__ . '/Stubs/public.key', 0600);
|
||||||
|
chmod(__DIR__ . '/Stubs/private.key.crlf', 0600);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRespondToRequestInvalidGrantType()
|
public function testRespondToRequestInvalidGrantType()
|
||||||
@@ -48,7 +49,7 @@ class AuthorizationServerTest extends TestCase
|
|||||||
new StubResponseType()
|
new StubResponseType()
|
||||||
);
|
);
|
||||||
|
|
||||||
$server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M'));
|
$server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response);
|
$server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response);
|
||||||
@@ -60,8 +61,11 @@ class AuthorizationServerTest extends TestCase
|
|||||||
|
|
||||||
public function testRespondToRequest()
|
public function testRespondToRequest()
|
||||||
{
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setConfidential();
|
||||||
|
|
||||||
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepository->method('getClientEntity')->willReturn(new ClientEntity());
|
$clientRepository->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
$scope = new ScopeEntity();
|
$scope = new ScopeEntity();
|
||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
@@ -81,7 +85,7 @@ class AuthorizationServerTest extends TestCase
|
|||||||
);
|
);
|
||||||
|
|
||||||
$server->setDefaultScope(self::DEFAULT_SCOPE);
|
$server->setDefaultScope(self::DEFAULT_SCOPE);
|
||||||
$server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M'));
|
$server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M'));
|
||||||
|
|
||||||
$_POST['grant_type'] = 'client_credentials';
|
$_POST['grant_type'] = 'client_credentials';
|
||||||
$_POST['client_id'] = 'foo';
|
$_POST['client_id'] = 'foo';
|
||||||
@@ -109,6 +113,91 @@ class AuthorizationServerTest extends TestCase
|
|||||||
$this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server));
|
$this->assertInstanceOf(BearerTokenResponse::class, $method->invoke($server));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testGetResponseTypeExtended()
|
||||||
|
{
|
||||||
|
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$privateKey = 'file://' . __DIR__ . '/Stubs/private.key';
|
||||||
|
$encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key';
|
||||||
|
|
||||||
|
$server = new AuthorizationServer(
|
||||||
|
$clientRepository,
|
||||||
|
$this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(),
|
||||||
|
$this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(),
|
||||||
|
'file://' . __DIR__ . '/Stubs/private.key',
|
||||||
|
'file://' . __DIR__ . '/Stubs/public.key'
|
||||||
|
);
|
||||||
|
|
||||||
|
$abstractGrantReflection = new \ReflectionClass($server);
|
||||||
|
$method = $abstractGrantReflection->getMethod('getResponseType');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$responseType = $method->invoke($server);
|
||||||
|
|
||||||
|
$responseTypeReflection = new \ReflectionClass($responseType);
|
||||||
|
|
||||||
|
$privateKeyProperty = $responseTypeReflection->getProperty('privateKey');
|
||||||
|
$privateKeyProperty->setAccessible(true);
|
||||||
|
|
||||||
|
$encryptionKeyProperty = $responseTypeReflection->getProperty('encryptionKey');
|
||||||
|
$encryptionKeyProperty->setAccessible(true);
|
||||||
|
|
||||||
|
// generated instances should have keys setup
|
||||||
|
$this->assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath());
|
||||||
|
$this->assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleRequestsGetDifferentResponseTypeInstances()
|
||||||
|
{
|
||||||
|
$privateKey = 'file://' . __DIR__ . '/Stubs/private.key';
|
||||||
|
$encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key';
|
||||||
|
|
||||||
|
$responseTypePrototype = new class extends BearerTokenResponse {
|
||||||
|
/* @return null|\League\OAuth2\Server\CryptKeyInterface */
|
||||||
|
public function getPrivateKey()
|
||||||
|
{
|
||||||
|
return $this->privateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEncryptionKey()
|
||||||
|
{
|
||||||
|
return $this->encryptionKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
|
||||||
|
$server = new AuthorizationServer(
|
||||||
|
$clientRepository,
|
||||||
|
$this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(),
|
||||||
|
$this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(),
|
||||||
|
$privateKey,
|
||||||
|
$encryptionKey,
|
||||||
|
$responseTypePrototype
|
||||||
|
);
|
||||||
|
|
||||||
|
$abstractGrantReflection = new \ReflectionClass($server);
|
||||||
|
$method = $abstractGrantReflection->getMethod('getResponseType');
|
||||||
|
$method->setAccessible(true);
|
||||||
|
|
||||||
|
$responseTypeA = $method->invoke($server);
|
||||||
|
$responseTypeB = $method->invoke($server);
|
||||||
|
|
||||||
|
// prototype should not get changed
|
||||||
|
$this->assertNull($responseTypePrototype->getPrivateKey());
|
||||||
|
$this->assertNull($responseTypePrototype->getEncryptionKey());
|
||||||
|
|
||||||
|
// generated instances should have keys setup
|
||||||
|
$this->assertSame($privateKey, $responseTypeA->getPrivateKey()->getKeyPath());
|
||||||
|
$this->assertSame($encryptionKey, $responseTypeA->getEncryptionKey());
|
||||||
|
|
||||||
|
// all instances should be different but based on the same prototype
|
||||||
|
$this->assertSame(get_class($responseTypePrototype), get_class($responseTypeA));
|
||||||
|
$this->assertSame(get_class($responseTypePrototype), get_class($responseTypeB));
|
||||||
|
$this->assertNotSame($responseTypePrototype, $responseTypeA);
|
||||||
|
$this->assertNotSame($responseTypePrototype, $responseTypeB);
|
||||||
|
$this->assertNotSame($responseTypeA, $responseTypeB);
|
||||||
|
}
|
||||||
|
|
||||||
public function testCompleteAuthorizationRequest()
|
public function testCompleteAuthorizationRequest()
|
||||||
{
|
{
|
||||||
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
@@ -127,7 +216,7 @@ class AuthorizationServerTest extends TestCase
|
|||||||
$grant = new AuthCodeGrant(
|
$grant = new AuthCodeGrant(
|
||||||
$authCodeRepository,
|
$authCodeRepository,
|
||||||
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
|
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
|
||||||
new \DateInterval('PT10M')
|
new DateInterval('PT10M')
|
||||||
);
|
);
|
||||||
|
|
||||||
$server->enableGrantType($grant);
|
$server->enableGrantType($grant);
|
||||||
@@ -148,6 +237,7 @@ class AuthorizationServerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
$client->setRedirectUri('http://foo/bar');
|
$client->setRedirectUri('http://foo/bar');
|
||||||
|
$client->setConfidential();
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
@@ -158,7 +248,7 @@ class AuthorizationServerTest extends TestCase
|
|||||||
$grant = new AuthCodeGrant(
|
$grant = new AuthCodeGrant(
|
||||||
$this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(),
|
$this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(),
|
||||||
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
|
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
|
||||||
new \DateInterval('PT10M')
|
new DateInterval('PT10M')
|
||||||
);
|
);
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
@@ -197,16 +287,16 @@ class AuthorizationServerTest extends TestCase
|
|||||||
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
$grant = new AuthCodeGrant(
|
$grant = new AuthCodeGrant(
|
||||||
$this->getMock(AuthCodeRepositoryInterface::class),
|
$this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(),
|
||||||
$this->getMock(RefreshTokenRepositoryInterface::class),
|
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
|
||||||
new \DateInterval('PT10M')
|
new DateInterval('PT10M')
|
||||||
);
|
);
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
$server = new AuthorizationServer(
|
$server = new AuthorizationServer(
|
||||||
$clientRepositoryMock,
|
$clientRepositoryMock,
|
||||||
$this->getMock(AccessTokenRepositoryInterface::class),
|
$this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(),
|
||||||
$this->getMock(ScopeRepositoryInterface::class),
|
$this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(),
|
||||||
'file://' . __DIR__ . '/Stubs/private.key',
|
'file://' . __DIR__ . '/Stubs/private.key',
|
||||||
'file://' . __DIR__ . '/Stubs/public.key'
|
'file://' . __DIR__ . '/Stubs/public.key'
|
||||||
);
|
);
|
||||||
@@ -234,10 +324,6 @@ class AuthorizationServerTest extends TestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 2
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequestUnregistered()
|
public function testValidateAuthorizationRequestUnregistered()
|
||||||
{
|
{
|
||||||
$server = new AuthorizationServer(
|
$server = new AuthorizationServer(
|
||||||
@@ -248,19 +334,13 @@ class AuthorizationServerTest extends TestCase
|
|||||||
'file://' . __DIR__ . '/Stubs/public.key'
|
'file://' . __DIR__ . '/Stubs/public.key'
|
||||||
);
|
);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'code',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
]);
|
||||||
null,
|
|
||||||
'php://input',
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
$headers = [],
|
$this->expectExceptionCode(2);
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$server->validateAuthorizationRequest($request);
|
$server->validateAuthorizationRequest($request);
|
||||||
}
|
}
|
||||||
|
38
tests/AuthorizationValidators/BearerTokenValidatorTest.php
Normal file
38
tests/AuthorizationValidators/BearerTokenValidatorTest.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
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())->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(9);
|
||||||
|
|
||||||
|
$bearerTokenValidator->validateAuthorization($request);
|
||||||
|
}
|
||||||
|
}
|
24
tests/CodeChallengeVerifiers/PlainVerifierTest.php
Normal file
24
tests/CodeChallengeVerifiers/PlainVerifierTest.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace LeagueTests\CodeChallengeVerifiers;
|
||||||
|
|
||||||
|
use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class PlainVerifierTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetMethod()
|
||||||
|
{
|
||||||
|
$verifier = new PlainVerifier();
|
||||||
|
|
||||||
|
$this->assertEquals('plain', $verifier->getMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVerifyCodeChallenge()
|
||||||
|
{
|
||||||
|
$verifier = new PlainVerifier();
|
||||||
|
|
||||||
|
$this->assertTrue($verifier->verifyCodeChallenge('foo', 'foo'));
|
||||||
|
$this->assertFalse($verifier->verifyCodeChallenge('foo', 'bar'));
|
||||||
|
}
|
||||||
|
}
|
37
tests/CodeChallengeVerifiers/S256VerifierTest.php
Normal file
37
tests/CodeChallengeVerifiers/S256VerifierTest.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace LeagueTests\CodeChallengeVerifiers;
|
||||||
|
|
||||||
|
use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class S256VerifierTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testGetMethod()
|
||||||
|
{
|
||||||
|
$verifier = new S256Verifier();
|
||||||
|
|
||||||
|
$this->assertEquals('S256', $verifier->getMethod());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVerifyCodeChallengeSucceeds()
|
||||||
|
{
|
||||||
|
$codeChallenge = $this->createCodeChallenge('foo');
|
||||||
|
$verifier = new S256Verifier();
|
||||||
|
|
||||||
|
$this->assertTrue($verifier->verifyCodeChallenge('foo', $codeChallenge));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testVerifyCodeChallengeFails()
|
||||||
|
{
|
||||||
|
$codeChallenge = $this->createCodeChallenge('bar');
|
||||||
|
$verifier = new S256Verifier();
|
||||||
|
|
||||||
|
$this->assertFalse($verifier->verifyCodeChallenge('foo', $codeChallenge));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createCodeChallenge($codeVerifier)
|
||||||
|
{
|
||||||
|
return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
|
||||||
|
}
|
||||||
|
}
|
@@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace LeagueTests\Utils;
|
|
||||||
|
|
||||||
use LeagueTests\Stubs\CryptTraitStub;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
|
|
||||||
class CryptTraitTest extends TestCase
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @var \LeagueTests\Stubs\CryptTraitStub
|
|
||||||
*/
|
|
||||||
protected $cryptStub;
|
|
||||||
|
|
||||||
public function setUp()
|
|
||||||
{
|
|
||||||
$this->cryptStub = new CryptTraitStub;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testEncryptDecrypt()
|
|
||||||
{
|
|
||||||
$payload = 'alex loves whisky';
|
|
||||||
$encrypted = $this->cryptStub->doEncrypt($payload);
|
|
||||||
$plainText = $this->cryptStub->doDecrypt($encrypted);
|
|
||||||
|
|
||||||
$this->assertNotEquals($payload, $encrypted);
|
|
||||||
$this->assertEquals($payload, $plainText);
|
|
||||||
}
|
|
||||||
}
|
|
108
tests/Exception/OAuthServerExceptionTest.php
Normal file
108
tests/Exception/OAuthServerExceptionTest.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace LeagueTests\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
|
use League\OAuth2\Server\Grant\AbstractGrant;
|
||||||
|
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Zend\Diactoros\Response;
|
||||||
|
use Zend\Diactoros\ServerRequest;
|
||||||
|
|
||||||
|
class OAuthServerExceptionTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testInvalidClientExceptionSetsAuthenticateHeader()
|
||||||
|
{
|
||||||
|
$serverRequest = (new ServerRequest())
|
||||||
|
->withParsedBody([
|
||||||
|
'client_id' => 'foo',
|
||||||
|
])
|
||||||
|
->withAddedHeader('Authorization', 'Basic fakeauthdetails');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->issueInvalidClientException($serverRequest);
|
||||||
|
} catch (OAuthServerException $e) {
|
||||||
|
$response = $e->generateHttpResponse(new Response());
|
||||||
|
|
||||||
|
$this->assertTrue($response->hasHeader('WWW-Authenticate'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testInvalidClientExceptionOmitsAuthenticateHeader()
|
||||||
|
{
|
||||||
|
$serverRequest = (new ServerRequest())
|
||||||
|
->withParsedBody([
|
||||||
|
'client_id' => 'foo',
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->issueInvalidClientException($serverRequest);
|
||||||
|
} catch (OAuthServerException $e) {
|
||||||
|
$response = $e->generateHttpResponse(new Response());
|
||||||
|
|
||||||
|
$this->assertFalse($response->hasHeader('WWW-Authenticate'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue an invalid client exception
|
||||||
|
*
|
||||||
|
* @throws OAuthServerException
|
||||||
|
*/
|
||||||
|
private function issueInvalidClientException($serverRequest)
|
||||||
|
{
|
||||||
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
$clientRepositoryMock->method('validateClient')->willReturn(false);
|
||||||
|
|
||||||
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
|
$grantMock->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$validateClientMethod->invoke($grantMock, $serverRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasRedirect()
|
||||||
|
{
|
||||||
|
$exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error');
|
||||||
|
|
||||||
|
$this->assertTrue($exceptionWithRedirect->hasRedirect());
|
||||||
|
$this->assertSame(
|
||||||
|
'https://example.com/error?error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&hint=some+hint&message=The+resource+owner+or+authorization+server+denied+the+request.',
|
||||||
|
$exceptionWithRedirect->getRedirectUri()
|
||||||
|
);
|
||||||
|
$this->assertSame(
|
||||||
|
'https://example.com/error#error=access_denied&error_description=The+resource+owner+or+authorization+server+denied+the+request.&hint=some+hint&message=The+resource+owner+or+authorization+server+denied+the+request.',
|
||||||
|
$exceptionWithRedirect->getRedirectUri(true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoesNotHaveRedirect()
|
||||||
|
{
|
||||||
|
$exceptionWithoutRedirect = OAuthServerException::accessDenied('Some hint');
|
||||||
|
|
||||||
|
$this->assertFalse($exceptionWithoutRedirect->hasRedirect());
|
||||||
|
$this->assertNull($exceptionWithoutRedirect->getRedirectUri());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testHasPrevious()
|
||||||
|
{
|
||||||
|
$previous = new Exception('This is the previous');
|
||||||
|
$exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $previous);
|
||||||
|
|
||||||
|
$previousMessage = $exceptionWithPrevious->getPrevious() !== null ? $exceptionWithPrevious->getPrevious()->getMessage() : null;
|
||||||
|
|
||||||
|
$this->assertSame('This is the previous', $previousMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDoesNotHavePrevious()
|
||||||
|
{
|
||||||
|
$exceptionWithoutPrevious = OAuthServerException::accessDenied();
|
||||||
|
|
||||||
|
$this->assertNull($exceptionWithoutPrevious->getPrevious());
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Grant;
|
namespace LeagueTests\Grant;
|
||||||
|
|
||||||
use League\Event\Emitter;
|
use DateInterval;
|
||||||
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||||
@@ -23,21 +24,13 @@ use Zend\Diactoros\ServerRequest;
|
|||||||
|
|
||||||
class AbstractGrantTest extends TestCase
|
class AbstractGrantTest extends TestCase
|
||||||
{
|
{
|
||||||
public function testGetSet()
|
|
||||||
{
|
|
||||||
/** @var AbstractGrant $grantMock */
|
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
|
||||||
$grantMock->setEmitter(new Emitter());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testHttpBasicWithPassword()
|
public function testHttpBasicWithPassword()
|
||||||
{
|
{
|
||||||
/** @var AbstractGrant $grantMock */
|
/** @var AbstractGrant $grantMock */
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:Sesame'));
|
||||||
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('Open:Sesame'));
|
|
||||||
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
||||||
$basicAuthMethod->setAccessible(true);
|
$basicAuthMethod->setAccessible(true);
|
||||||
|
|
||||||
@@ -50,8 +43,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:'));
|
||||||
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('Open:'));
|
|
||||||
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
||||||
$basicAuthMethod->setAccessible(true);
|
$basicAuthMethod->setAccessible(true);
|
||||||
|
|
||||||
@@ -64,8 +56,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Foo ' . base64_encode('Open:Sesame'));
|
||||||
$serverRequest = $serverRequest->withHeader('Authorization', 'Foo ' . base64_encode('Open:Sesame'));
|
|
||||||
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
||||||
$basicAuthMethod->setAccessible(true);
|
$basicAuthMethod->setAccessible(true);
|
||||||
|
|
||||||
@@ -78,8 +69,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ||');
|
||||||
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ||');
|
|
||||||
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
||||||
$basicAuthMethod->setAccessible(true);
|
$basicAuthMethod->setAccessible(true);
|
||||||
|
|
||||||
@@ -92,8 +82,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame'));
|
||||||
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame'));
|
|
||||||
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
|
||||||
$basicAuthMethod->setAccessible(true);
|
$basicAuthMethod->setAccessible(true);
|
||||||
|
|
||||||
@@ -113,16 +102,14 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
]);
|
||||||
'client_id' => 'foo',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
$result = $validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
$result = $validateClientMethod->invoke($grantMock, $serverRequest);
|
||||||
$this->assertEquals($client, $result);
|
$this->assertEquals($client, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,14 +126,12 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'redirect_uri' => 'http://foo/bar',
|
||||||
'client_secret' => 'bar',
|
]);
|
||||||
'redirect_uri' => 'http://foo/bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
@@ -154,9 +139,6 @@ class AbstractGrantTest extends TestCase
|
|||||||
$this->assertEquals($client, $result);
|
$this->assertEquals($client, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateClientMissingClientId()
|
public function testValidateClientMissingClientId()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -173,16 +155,15 @@ class AbstractGrantTest extends TestCase
|
|||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateClientMissingClientSecret()
|
public function testValidateClientMissingClientSecret()
|
||||||
{
|
{
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
|
$clientRepositoryMock->method('validateClient')->willReturn(false);
|
||||||
|
|
||||||
/** @var AbstractGrant $grantMock */
|
/** @var AbstractGrant $grantMock */
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
@@ -190,24 +171,22 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody([
|
|
||||||
'client_id' => 'foo',
|
'client_id' => 'foo',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateClientInvalidClientSecret()
|
public function testValidateClientInvalidClientSecret()
|
||||||
{
|
{
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
|
$clientRepositoryMock->method('validateClient')->willReturn(false);
|
||||||
|
|
||||||
/** @var AbstractGrant $grantMock */
|
/** @var AbstractGrant $grantMock */
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
@@ -215,8 +194,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody([
|
|
||||||
'client_id' => 'foo',
|
'client_id' => 'foo',
|
||||||
'client_secret' => 'foo',
|
'client_secret' => 'foo',
|
||||||
]);
|
]);
|
||||||
@@ -224,12 +202,11 @@ class AbstractGrantTest extends TestCase
|
|||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateClientInvalidRedirectUri()
|
public function testValidateClientInvalidRedirectUri()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -243,8 +220,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody([
|
|
||||||
'client_id' => 'foo',
|
'client_id' => 'foo',
|
||||||
'redirect_uri' => 'http://bar/foo',
|
'redirect_uri' => 'http://bar/foo',
|
||||||
]);
|
]);
|
||||||
@@ -252,12 +228,11 @@ class AbstractGrantTest extends TestCase
|
|||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateClientInvalidRedirectUriArray()
|
public function testValidateClientInvalidRedirectUriArray()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -271,8 +246,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody([
|
|
||||||
'client_id' => 'foo',
|
'client_id' => 'foo',
|
||||||
'redirect_uri' => 'http://bar/foo',
|
'redirect_uri' => 'http://bar/foo',
|
||||||
]);
|
]);
|
||||||
@@ -280,16 +254,15 @@ class AbstractGrantTest extends TestCase
|
|||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateClientBadClient()
|
public function testValidateClientBadClient()
|
||||||
{
|
{
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
|
$clientRepositoryMock->method('validateClient')->willReturn(false);
|
||||||
|
|
||||||
/** @var AbstractGrant $grantMock */
|
/** @var AbstractGrant $grantMock */
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
@@ -297,8 +270,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody([
|
|
||||||
'client_id' => 'foo',
|
'client_id' => 'foo',
|
||||||
'client_secret' => 'bar',
|
'client_secret' => 'bar',
|
||||||
]);
|
]);
|
||||||
@@ -306,6 +278,8 @@ class AbstractGrantTest extends TestCase
|
|||||||
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
|
||||||
$validateClientMethod->setAccessible(true);
|
$validateClientMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$validateClientMethod->invoke($grantMock, $serverRequest, true);
|
$validateClientMethod->invoke($grantMock, $serverRequest, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,8 +288,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$grantMock->method('getIdentifier')->willReturn('foobar');
|
$grantMock->method('getIdentifier')->willReturn('foobar');
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody([
|
|
||||||
'grant_type' => 'foobar',
|
'grant_type' => 'foobar',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -332,7 +305,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
/** @var AbstractGrant $grantMock */
|
/** @var AbstractGrant $grantMock */
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$grantMock->setRefreshTokenTTL(new \DateInterval('PT1M'));
|
$grantMock->setRefreshTokenTTL(new DateInterval('PT1M'));
|
||||||
$grantMock->setRefreshTokenRepository($refreshTokenRepoMock);
|
$grantMock->setRefreshTokenRepository($refreshTokenRepoMock);
|
||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
@@ -346,6 +319,27 @@ class AbstractGrantTest extends TestCase
|
|||||||
$this->assertEquals($accessToken, $refreshToken->getAccessToken());
|
$this->assertEquals($accessToken, $refreshToken->getAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testIssueNullRefreshToken()
|
||||||
|
{
|
||||||
|
$refreshTokenRepoMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
$refreshTokenRepoMock
|
||||||
|
->expects($this->once())
|
||||||
|
->method('getNewRefreshToken')
|
||||||
|
->willReturn(null);
|
||||||
|
|
||||||
|
/** @var AbstractGrant $grantMock */
|
||||||
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
|
$grantMock->setRefreshTokenTTL(new \DateInterval('PT1M'));
|
||||||
|
$grantMock->setRefreshTokenRepository($refreshTokenRepoMock);
|
||||||
|
|
||||||
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
|
$issueRefreshTokenMethod = $abstractGrantReflection->getMethod('issueRefreshToken');
|
||||||
|
$issueRefreshTokenMethod->setAccessible(true);
|
||||||
|
|
||||||
|
$accessToken = new AccessTokenEntity();
|
||||||
|
$this->assertNull($issueRefreshTokenMethod->invoke($grantMock, $accessToken));
|
||||||
|
}
|
||||||
|
|
||||||
public function testIssueAccessToken()
|
public function testIssueAccessToken()
|
||||||
{
|
{
|
||||||
$accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$accessTokenRepoMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
@@ -353,6 +347,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
|
|
||||||
/** @var AbstractGrant $grantMock */
|
/** @var AbstractGrant $grantMock */
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
|
$grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$grantMock->setAccessTokenRepository($accessTokenRepoMock);
|
$grantMock->setAccessTokenRepository($accessTokenRepoMock);
|
||||||
|
|
||||||
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
$abstractGrantReflection = new \ReflectionClass($grantMock);
|
||||||
@@ -362,7 +357,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
/** @var AccessTokenEntityInterface $accessToken */
|
/** @var AccessTokenEntityInterface $accessToken */
|
||||||
$accessToken = $issueAccessTokenMethod->invoke(
|
$accessToken = $issueAccessTokenMethod->invoke(
|
||||||
$grantMock,
|
$grantMock,
|
||||||
new \DateInterval('PT1H'),
|
new DateInterval('PT1H'),
|
||||||
new ClientEntity(),
|
new ClientEntity(),
|
||||||
123,
|
123,
|
||||||
[new ScopeEntity()]
|
[new ScopeEntity()]
|
||||||
@@ -387,7 +382,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
AuthCodeEntityInterface::class,
|
AuthCodeEntityInterface::class,
|
||||||
$issueAuthCodeMethod->invoke(
|
$issueAuthCodeMethod->invoke(
|
||||||
$grantMock,
|
$grantMock,
|
||||||
new \DateInterval('PT1H'),
|
new DateInterval('PT1H'),
|
||||||
new ClientEntity(),
|
new ClientEntity(),
|
||||||
123,
|
123,
|
||||||
'http://foo/bar',
|
'http://foo/bar',
|
||||||
@@ -405,8 +400,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$method = $abstractGrantReflection->getMethod('getCookieParameter');
|
$method = $abstractGrantReflection->getMethod('getCookieParameter');
|
||||||
$method->setAccessible(true);
|
$method->setAccessible(true);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withCookieParams([
|
||||||
$serverRequest = $serverRequest->withCookieParams([
|
|
||||||
'foo' => 'bar',
|
'foo' => 'bar',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -423,8 +417,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$method = $abstractGrantReflection->getMethod('getQueryStringParameter');
|
$method = $abstractGrantReflection->getMethod('getQueryStringParameter');
|
||||||
$method->setAccessible(true);
|
$method->setAccessible(true);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withQueryParams([
|
||||||
$serverRequest = $serverRequest->withQueryParams([
|
|
||||||
'foo' => 'bar',
|
'foo' => 'bar',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -445,9 +438,6 @@ class AbstractGrantTest extends TestCase
|
|||||||
$this->assertEquals([$scope], $grantMock->validateScopes('basic '));
|
$this->assertEquals([$scope], $grantMock->validateScopes('basic '));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testValidateScopesBadScope()
|
public function testValidateScopesBadScope()
|
||||||
{
|
{
|
||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
@@ -457,6 +447,8 @@ class AbstractGrantTest extends TestCase
|
|||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
$grantMock->setScopeRepository($scopeRepositoryMock);
|
$grantMock->setScopeRepository($scopeRepositoryMock);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
$grantMock->validateScopes('basic ');
|
$grantMock->validateScopes('basic ');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,7 +460,7 @@ class AbstractGrantTest extends TestCase
|
|||||||
$method = $abstractGrantReflection->getMethod('generateUniqueIdentifier');
|
$method = $abstractGrantReflection->getMethod('generateUniqueIdentifier');
|
||||||
$method->setAccessible(true);
|
$method->setAccessible(true);
|
||||||
|
|
||||||
$this->assertInternalType('string', $method->invoke($grantMock));
|
$this->assertIsString($method->invoke($grantMock));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanRespondToAuthorizationRequest()
|
public function testCanRespondToAuthorizationRequest()
|
||||||
@@ -477,21 +469,21 @@ class AbstractGrantTest extends TestCase
|
|||||||
$this->assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest()));
|
$this->assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequest()
|
public function testValidateAuthorizationRequest()
|
||||||
{
|
{
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
|
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
$grantMock->validateAuthorizationRequest(new ServerRequest());
|
$grantMock->validateAuthorizationRequest(new ServerRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testCompleteAuthorizationRequest()
|
public function testCompleteAuthorizationRequest()
|
||||||
{
|
{
|
||||||
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
|
||||||
|
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
$grantMock->completeAuthorizationRequest(new AuthorizationRequest());
|
$grantMock->completeAuthorizationRequest(new AuthorizationRequest());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Grant;
|
namespace LeagueTests\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
|
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
|
||||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||||
@@ -27,6 +29,8 @@ class ClientCredentialsGrantTest extends TestCase
|
|||||||
public function testRespondToRequest()
|
public function testRespondToRequest()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
|
$client->setConfidential();
|
||||||
|
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
@@ -44,54 +48,16 @@ class ClientCredentialsGrantTest extends TestCase
|
|||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
]);
|
||||||
'client_secret' => 'bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
|
|
||||||
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 5
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestFailsWithoutScope()
|
|
||||||
{
|
|
||||||
$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();
|
|
||||||
|
|
||||||
$scope = new ScopeEntity();
|
|
||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
|
||||||
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope);
|
|
||||||
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
|
||||||
|
|
||||||
$grant = new ClientCredentialsGrant();
|
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
|
||||||
[
|
|
||||||
'client_id' => 'foo',
|
|
||||||
'client_secret' => 'bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Grant;
|
namespace LeagueTests\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
|
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
|
||||||
@@ -30,56 +31,47 @@ class ImplicitGrantTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
protected $cryptStub;
|
protected $cryptStub;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->cryptStub = new CryptTraitStub();
|
$this->cryptStub = new CryptTraitStub();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testGetIdentifier()
|
public function testGetIdentifier()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$this->assertEquals('implicit', $grant->getIdentifier());
|
$this->assertEquals('implicit', $grant->getIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanRespondToAccessTokenRequest()
|
public function testCanRespondToAccessTokenRequest()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
|
|
||||||
$this->assertFalse(
|
$this->assertFalse(
|
||||||
$grant->canRespondToAccessTokenRequest(new ServerRequest())
|
$grant->canRespondToAccessTokenRequest(new ServerRequest())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testRespondToAccessTokenRequest()
|
public function testRespondToAccessTokenRequest()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
|
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
$grant->respondToAccessTokenRequest(
|
$grant->respondToAccessTokenRequest(
|
||||||
new ServerRequest(),
|
new ServerRequest(),
|
||||||
new StubResponseType(),
|
new StubResponseType(),
|
||||||
new \DateInterval('PT10M')
|
new DateInterval('PT10M')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCanRespondToAuthorizationRequest()
|
public function testCanRespondToAuthorizationRequest()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'token',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
]);
|
||||||
null,
|
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'token',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertTrue($grant->canRespondToAuthorizationRequest($request));
|
$this->assertTrue($grant->canRespondToAuthorizationRequest($request));
|
||||||
}
|
}
|
||||||
@@ -94,27 +86,17 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
$scopeEntity = new ScopeEntity();
|
$scopeEntity = new ScopeEntity();
|
||||||
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'code',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
'redirect_uri' => 'http://foo/bar',
|
||||||
null,
|
]);
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
'redirect_uri' => 'http://foo/bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request));
|
$this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request));
|
||||||
}
|
}
|
||||||
@@ -129,91 +111,55 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
$scopeEntity = new ScopeEntity();
|
$scopeEntity = new ScopeEntity();
|
||||||
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
|
||||||
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'code',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
'redirect_uri' => 'http://foo/bar',
|
||||||
null,
|
]);
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
'redirect_uri' => 'http://foo/bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request));
|
$this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 3
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequestMissingClientId()
|
public function testValidateAuthorizationRequestMissingClientId()
|
||||||
{
|
{
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams(['response_type' => 'code']);
|
||||||
[],
|
|
||||||
[],
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
null,
|
$this->expectExceptionCode(3);
|
||||||
null,
|
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$grant->validateAuthorizationRequest($request);
|
$grant->validateAuthorizationRequest($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 4
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequestInvalidClientId()
|
public function testValidateAuthorizationRequestInvalidClientId()
|
||||||
{
|
{
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
|
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'code',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
]);
|
||||||
null,
|
|
||||||
'php://input',
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
$headers = [],
|
$this->expectExceptionCode(4);
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$grant->validateAuthorizationRequest($request);
|
$grant->validateAuthorizationRequest($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 4
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequestBadRedirectUriString()
|
public function testValidateAuthorizationRequestBadRedirectUriString()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -221,31 +167,21 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'code',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
'redirect_uri' => 'http://bar',
|
||||||
null,
|
]);
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
$cookies = [],
|
$this->expectExceptionCode(4);
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
'redirect_uri' => 'http://bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$grant->validateAuthorizationRequest($request);
|
$grant->validateAuthorizationRequest($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 4
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequestBadRedirectUriArray()
|
public function testValidateAuthorizationRequestBadRedirectUriArray()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -253,50 +189,50 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
|
|
||||||
$request = new ServerRequest(
|
$request = (new ServerRequest())->withQueryParams([
|
||||||
[],
|
'response_type' => 'code',
|
||||||
[],
|
'client_id' => 'foo',
|
||||||
null,
|
'redirect_uri' => 'http://bar',
|
||||||
null,
|
]);
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
$cookies = [],
|
$this->expectExceptionCode(4);
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
'redirect_uri' => 'http://bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$grant->validateAuthorizationRequest($request);
|
$grant->validateAuthorizationRequest($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testCompleteAuthorizationRequest()
|
public function testCompleteAuthorizationRequest()
|
||||||
{
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('identifier');
|
||||||
|
|
||||||
$authRequest = new AuthorizationRequest();
|
$authRequest = new AuthorizationRequest();
|
||||||
$authRequest->setAuthorizationApproved(true);
|
$authRequest->setAuthorizationApproved(true);
|
||||||
$authRequest->setClient(new ClientEntity());
|
$authRequest->setClient($client);
|
||||||
$authRequest->setGrantTypeId('authorization_code');
|
$authRequest->setGrantTypeId('authorization_code');
|
||||||
$authRequest->setUser(new UserEntity());
|
$authRequest->setUser(new UserEntity());
|
||||||
|
|
||||||
|
$accessToken = new AccessTokenEntity();
|
||||||
|
$accessToken->setClient($client);
|
||||||
|
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken);
|
||||||
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
||||||
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
|
||||||
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
|
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 9
|
|
||||||
*/
|
|
||||||
public function testCompleteAuthorizationRequestDenied()
|
public function testCompleteAuthorizationRequestDenied()
|
||||||
{
|
{
|
||||||
$authRequest = new AuthorizationRequest();
|
$authRequest = new AuthorizationRequest();
|
||||||
@@ -309,38 +245,51 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
||||||
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(9);
|
||||||
|
|
||||||
$grant->completeAuthorizationRequest($authRequest);
|
$grant->completeAuthorizationRequest($authRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAccessTokenRepositoryUniqueConstraintCheck()
|
public function testAccessTokenRepositoryUniqueConstraintCheck()
|
||||||
{
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setIdentifier('identifier');
|
||||||
|
|
||||||
$authRequest = new AuthorizationRequest();
|
$authRequest = new AuthorizationRequest();
|
||||||
$authRequest->setAuthorizationApproved(true);
|
$authRequest->setAuthorizationApproved(true);
|
||||||
$authRequest->setClient(new ClientEntity());
|
$authRequest->setClient($client);
|
||||||
$authRequest->setGrantTypeId('authorization_code');
|
$authRequest->setGrantTypeId('authorization_code');
|
||||||
$authRequest->setUser(new UserEntity());
|
$authRequest->setUser(new UserEntity());
|
||||||
|
|
||||||
/** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */
|
$accessToken = new AccessTokenEntity();
|
||||||
|
$accessToken->setClient($client);
|
||||||
|
|
||||||
|
/** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken);
|
||||||
$accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
|
$accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
|
||||||
$accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf();
|
$accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf();
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
||||||
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
|
||||||
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
|
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 7
|
|
||||||
*/
|
|
||||||
public function testAccessTokenRepositoryFailToPersist()
|
public function testAccessTokenRepositoryFailToPersist()
|
||||||
{
|
{
|
||||||
$authRequest = new AuthorizationRequest();
|
$authRequest = new AuthorizationRequest();
|
||||||
@@ -349,22 +298,25 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$authRequest->setGrantTypeId('authorization_code');
|
$authRequest->setGrantTypeId('authorization_code');
|
||||||
$authRequest->setUser(new UserEntity());
|
$authRequest->setUser(new UserEntity());
|
||||||
|
|
||||||
/** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */
|
/** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened'));
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened'));
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
||||||
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(7);
|
||||||
|
|
||||||
$grant->completeAuthorizationRequest($authRequest);
|
$grant->completeAuthorizationRequest($authRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException
|
|
||||||
* @expectedExceptionCode 100
|
|
||||||
*/
|
|
||||||
public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop()
|
public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop()
|
||||||
{
|
{
|
||||||
$authRequest = new AuthorizationRequest();
|
$authRequest = new AuthorizationRequest();
|
||||||
@@ -373,81 +325,51 @@ class ImplicitGrantTest extends TestCase
|
|||||||
$authRequest->setGrantTypeId('authorization_code');
|
$authRequest->setGrantTypeId('authorization_code');
|
||||||
$authRequest->setUser(new UserEntity());
|
$authRequest->setUser(new UserEntity());
|
||||||
|
|
||||||
/** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */
|
/** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
||||||
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
|
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
|
||||||
|
|
||||||
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
|
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
||||||
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class);
|
||||||
|
$this->expectExceptionCode(100);
|
||||||
|
|
||||||
$grant->completeAuthorizationRequest($authRequest);
|
$grant->completeAuthorizationRequest($authRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testSetRefreshTokenTTL()
|
public function testSetRefreshTokenTTL()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
$grant->setRefreshTokenTTL(new \DateInterval('PT10M'));
|
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
|
$grant->setRefreshTokenTTL(new DateInterval('PT10M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testSetRefreshTokenRepository()
|
public function testSetRefreshTokenRepository()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
|
|
||||||
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
||||||
|
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testCompleteAuthorizationRequestNoUser()
|
public function testCompleteAuthorizationRequestNoUser()
|
||||||
{
|
{
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
$grant = new ImplicitGrant(new DateInterval('PT10M'));
|
||||||
|
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
$grant->completeAuthorizationRequest(new AuthorizationRequest());
|
$grant->completeAuthorizationRequest(new AuthorizationRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 5
|
|
||||||
*/
|
|
||||||
public function testValidateAuthorizationRequestFailsWithoutScope()
|
|
||||||
{
|
|
||||||
$client = new ClientEntity();
|
|
||||||
$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);
|
|
||||||
|
|
||||||
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
|
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
|
||||||
|
|
||||||
$request = new ServerRequest(
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
'php://input',
|
|
||||||
$headers = [],
|
|
||||||
$cookies = [],
|
|
||||||
$queryParams = [
|
|
||||||
'response_type' => 'code',
|
|
||||||
'client_id' => 'foo',
|
|
||||||
'redirect_uri' => 'http://foo/bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$grant->validateAuthorizationRequest($request);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Grant;
|
namespace LeagueTests\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Grant\PasswordGrant;
|
use League\OAuth2\Server\Grant\PasswordGrant;
|
||||||
@@ -60,27 +62,65 @@ class PasswordGrantTest extends TestCase
|
|||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
$grant->setScopeRepository($scopeRepositoryMock);
|
||||||
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
$grant->setDefaultScope(self::DEFAULT_SCOPE);
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'username' => 'foo',
|
||||||
'client_secret' => 'bar',
|
'password' => 'bar',
|
||||||
'username' => 'foo',
|
]);
|
||||||
'password' => 'bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
|
|
||||||
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
||||||
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
|
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function testRespondToRequestNullRefreshToken()
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
{
|
||||||
*/
|
$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);
|
||||||
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
|
$serverRequest = (new 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());
|
||||||
|
}
|
||||||
|
|
||||||
public function testRespondToRequestMissingUsername()
|
public function testRespondToRequestMissingUsername()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -97,21 +137,18 @@ class PasswordGrantTest extends TestCase
|
|||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withQueryParams([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
]);
|
||||||
'client_secret' => 'bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestMissingPassword()
|
public function testRespondToRequestMissingPassword()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -128,22 +165,19 @@ class PasswordGrantTest extends TestCase
|
|||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'username' => 'alex',
|
||||||
'client_secret' => 'bar',
|
]);
|
||||||
'username' => 'alex',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestBadCredentials()
|
public function testRespondToRequestBadCredentials()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -161,63 +195,18 @@ class PasswordGrantTest extends TestCase
|
|||||||
$grant->setClientRepository($clientRepositoryMock);
|
$grant->setClientRepository($clientRepositoryMock);
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'username' => 'alex',
|
||||||
'client_secret' => 'bar',
|
'password' => 'whisky',
|
||||||
'username' => 'alex',
|
]);
|
||||||
'password' => 'whisky',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
$this->expectExceptionCode(10);
|
||||||
* @expectedExceptionCode 5
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestFailsWithoutScope()
|
|
||||||
{
|
|
||||||
$client = new ClientEntity();
|
|
||||||
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
|
||||||
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
|
|
||||||
|
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
|
|
||||||
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
|
|
||||||
|
|
||||||
$userRepositoryMock = $this->getMockBuilder(UserRepositoryInterface::class)->getMock();
|
|
||||||
$userEntity = new UserEntity();
|
|
||||||
$userRepositoryMock->method('getUserEntityByUserCredentials')->willReturn($userEntity);
|
|
||||||
|
|
||||||
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
|
|
||||||
$refreshTokenRepositoryMock->method('persistNewRefreshToken')->willReturnSelf();
|
|
||||||
$refreshTokenRepositoryMock->method('getNewRefreshToken')->willReturn(new RefreshTokenEntity());
|
|
||||||
|
|
||||||
$scope = new ScopeEntity();
|
|
||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
|
||||||
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scope);
|
|
||||||
$scopeRepositoryMock->method('finalizeScopes')->willReturnArgument(0);
|
|
||||||
|
|
||||||
$grant = new PasswordGrant($userRepositoryMock, $refreshTokenRepositoryMock);
|
|
||||||
$grant->setClientRepository($clientRepositoryMock);
|
|
||||||
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
|
|
||||||
$grant->setScopeRepository($scopeRepositoryMock);
|
|
||||||
|
|
||||||
$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'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Grant;
|
namespace LeagueTests\Grant;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||||
@@ -26,7 +27,7 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
protected $cryptStub;
|
protected $cryptStub;
|
||||||
|
|
||||||
public function setUp()
|
public function setUp(): void
|
||||||
{
|
{
|
||||||
$this->cryptStub = new CryptTraitStub();
|
$this->cryptStub = new CryptTraitStub();
|
||||||
}
|
}
|
||||||
@@ -79,8 +80,63 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$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->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())->withParsedBody([
|
||||||
'client_id' => 'foo',
|
'client_id' => 'foo',
|
||||||
'client_secret' => 'bar',
|
'client_secret' => 'bar',
|
||||||
'refresh_token' => $oldRefreshToken,
|
'refresh_token' => $oldRefreshToken,
|
||||||
@@ -91,7 +147,7 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
||||||
|
|
||||||
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
||||||
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
|
$this->assertNull($responseType->getRefreshToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRespondToReducedScopes()
|
public function testRespondToReducedScopes()
|
||||||
@@ -134,27 +190,20 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'refresh_token' => $oldRefreshToken,
|
||||||
'client_secret' => 'bar',
|
'scope' => 'foo',
|
||||||
'refresh_token' => $oldRefreshToken,
|
]);
|
||||||
'scope' => 'foo',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
|
|
||||||
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
|
||||||
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
|
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 5
|
|
||||||
*/
|
|
||||||
public function testRespondToUnexpectedScope()
|
public function testRespondToUnexpectedScope()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -193,24 +242,21 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'refresh_token' => $oldRefreshToken,
|
||||||
'client_secret' => 'bar',
|
'scope' => 'foobar',
|
||||||
'refresh_token' => $oldRefreshToken,
|
]);
|
||||||
'scope' => 'foobar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(5);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 3
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestMissingOldToken()
|
public function testRespondToRequestMissingOldToken()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -227,22 +273,19 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
$grant->setEncryptionKey($this->cryptStub->getKey());
|
$grant->setEncryptionKey($this->cryptStub->getKey());
|
||||||
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
]);
|
||||||
'client_secret' => 'bar',
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(3);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 8
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestInvalidOldToken()
|
public function testRespondToRequestInvalidOldToken()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -261,23 +304,20 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
|
|
||||||
$oldRefreshToken = 'foobar';
|
$oldRefreshToken = 'foobar';
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'refresh_token' => $oldRefreshToken,
|
||||||
'client_secret' => 'bar',
|
]);
|
||||||
'refresh_token' => $oldRefreshToken,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(8);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 8
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestClientMismatch()
|
public function testRespondToRequestClientMismatch()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -310,23 +350,20 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'refresh_token' => $oldRefreshToken,
|
||||||
'client_secret' => 'bar',
|
]);
|
||||||
'refresh_token' => $oldRefreshToken,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(8);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 8
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestExpiredToken()
|
public function testRespondToRequestExpiredToken()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -356,23 +393,20 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'refresh_token' => $oldRefreshToken,
|
||||||
'client_secret' => 'bar',
|
]);
|
||||||
'refresh_token' => $oldRefreshToken,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(8);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
|
|
||||||
* @expectedExceptionCode 8
|
|
||||||
*/
|
|
||||||
public function testRespondToRequestRevokedToken()
|
public function testRespondToRequestRevokedToken()
|
||||||
{
|
{
|
||||||
$client = new ClientEntity();
|
$client = new ClientEntity();
|
||||||
@@ -403,16 +437,17 @@ class RefreshTokenGrantTest extends TestCase
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
$serverRequest = new ServerRequest();
|
$serverRequest = (new ServerRequest())->withParsedBody([
|
||||||
$serverRequest = $serverRequest->withParsedBody(
|
'client_id' => 'foo',
|
||||||
[
|
'client_secret' => 'bar',
|
||||||
'client_id' => 'foo',
|
'refresh_token' => $oldRefreshToken,
|
||||||
'client_secret' => 'bar',
|
]);
|
||||||
'refresh_token' => $oldRefreshToken,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$responseType = new StubResponseType();
|
$responseType = new StubResponseType();
|
||||||
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
|
|
||||||
|
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
|
||||||
|
$this->expectExceptionCode(8);
|
||||||
|
|
||||||
|
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Middleware;
|
namespace LeagueTests\Middleware;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
use League\OAuth2\Server\AuthorizationServer;
|
use League\OAuth2\Server\AuthorizationServer;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
|
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
|
||||||
@@ -23,8 +24,11 @@ class AuthorizationServerMiddlewareTest extends TestCase
|
|||||||
|
|
||||||
public function testValidResponse()
|
public function testValidResponse()
|
||||||
{
|
{
|
||||||
|
$client = new ClientEntity();
|
||||||
|
$client->setConfidential();
|
||||||
|
|
||||||
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepository->method('getClientEntity')->willReturn(new ClientEntity());
|
$clientRepository->method('getClientEntity')->willReturn($client);
|
||||||
|
|
||||||
$scopeEntity = new ScopeEntity;
|
$scopeEntity = new ScopeEntity;
|
||||||
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
|
||||||
@@ -66,7 +70,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
|
|||||||
public function testOAuthErrorResponse()
|
public function testOAuthErrorResponse()
|
||||||
{
|
{
|
||||||
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
|
||||||
$clientRepository->method('getClientEntity')->willReturn(null);
|
$clientRepository->method('validateClient')->willReturn(false);
|
||||||
|
|
||||||
$server = new AuthorizationServer(
|
$server = new AuthorizationServer(
|
||||||
$clientRepository,
|
$clientRepository,
|
||||||
@@ -77,7 +81,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
|
|||||||
new StubResponseType()
|
new StubResponseType()
|
||||||
);
|
);
|
||||||
|
|
||||||
$server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M'));
|
$server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M'));
|
||||||
|
|
||||||
$_POST['grant_type'] = 'client_credentials';
|
$_POST['grant_type'] = 'client_credentials';
|
||||||
$_POST['client_id'] = 'foo';
|
$_POST['client_id'] = 'foo';
|
||||||
@@ -104,7 +108,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
|
|||||||
$response = $exception->generateHttpResponse(new Response());
|
$response = $exception->generateHttpResponse(new Response());
|
||||||
|
|
||||||
$this->assertEquals(302, $response->getStatusCode());
|
$this->assertEquals(302, $response->getStatusCode());
|
||||||
$this->assertEquals('http://foo/bar?error=invalid_scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope',
|
$this->assertEquals('http://foo/bar?error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed',
|
||||||
$response->getHeader('location')[0]);
|
$response->getHeader('location')[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +118,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
|
|||||||
$response = $exception->generateHttpResponse(new Response(), true);
|
$response = $exception->generateHttpResponse(new Response(), true);
|
||||||
|
|
||||||
$this->assertEquals(302, $response->getStatusCode());
|
$this->assertEquals(302, $response->getStatusCode());
|
||||||
$this->assertEquals('http://foo/bar#error=invalid_scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope',
|
$this->assertEquals('http://foo/bar#error=invalid_scope&error_description=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed&hint=Check+the+%60test%60+scope&message=The+requested+scope+is+invalid%2C+unknown%2C+or+malformed',
|
||||||
$response->getHeader('location')[0]);
|
$response->getHeader('location')[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace LeagueTests\Middleware;
|
namespace LeagueTests\Middleware;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTimeImmutable;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
|
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
|
||||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||||
@@ -27,13 +29,13 @@ class ResourceServerMiddlewareTest extends TestCase
|
|||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('test');
|
$accessToken->setIdentifier('test');
|
||||||
$accessToken->setUserIdentifier(123);
|
$accessToken->setUserIdentifier(123);
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$token = (string) $accessToken;
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token));
|
||||||
$request = $request->withHeader('authorization', sprintf('Bearer %s', $token));
|
|
||||||
|
|
||||||
$middleware = new ResourceServerMiddleware($server);
|
$middleware = new ResourceServerMiddleware($server);
|
||||||
$response = $middleware->__invoke(
|
$response = $middleware->__invoke(
|
||||||
@@ -62,13 +64,13 @@ class ResourceServerMiddlewareTest extends TestCase
|
|||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('test');
|
$accessToken->setIdentifier('test');
|
||||||
$accessToken->setUserIdentifier(123);
|
$accessToken->setUserIdentifier(123);
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$token = (string) $accessToken;
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token));
|
||||||
$request = $request->withHeader('authorization', sprintf('Bearer %s', $token));
|
|
||||||
|
|
||||||
$middleware = new ResourceServerMiddleware($server);
|
$middleware = new ResourceServerMiddleware($server);
|
||||||
$response = $middleware->__invoke(
|
$response = $middleware->__invoke(
|
||||||
@@ -91,8 +93,7 @@ class ResourceServerMiddlewareTest extends TestCase
|
|||||||
'file://' . __DIR__ . '/../Stubs/public.key'
|
'file://' . __DIR__ . '/../Stubs/public.key'
|
||||||
);
|
);
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', '');
|
||||||
$request = $request->withHeader('authorization', '');
|
|
||||||
|
|
||||||
$middleware = new ResourceServerMiddleware($server);
|
$middleware = new ResourceServerMiddleware($server);
|
||||||
$response = $middleware->__invoke(
|
$response = $middleware->__invoke(
|
||||||
|
39
tests/PHPStan/AbstractGrantExtension.php
Normal file
39
tests/PHPStan/AbstractGrantExtension.php
Normal 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(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace LeagueTests\ResponseTypes;
|
namespace LeagueTests\ResponseTypes;
|
||||||
|
|
||||||
|
use DateInterval;
|
||||||
|
use DateTimeImmutable;
|
||||||
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
|
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
|
||||||
use League\OAuth2\Server\CryptKey;
|
use League\OAuth2\Server\CryptKey;
|
||||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
@@ -20,9 +22,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
{
|
{
|
||||||
public function testGenerateHttpResponse()
|
public function testGenerateHttpResponse()
|
||||||
{
|
{
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$responseType = new BearerTokenResponse();
|
||||||
|
|
||||||
$responseType = new BearerTokenResponse($accessTokenRepositoryMock);
|
|
||||||
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||||
|
|
||||||
@@ -34,14 +34,15 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('abcdef');
|
$accessToken->setIdentifier('abcdef');
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
$accessToken->addScope($scope);
|
$accessToken->addScope($scope);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$refreshToken = new RefreshTokenEntity();
|
$refreshToken = new RefreshTokenEntity();
|
||||||
$refreshToken->setIdentifier('abcdef');
|
$refreshToken->setIdentifier('abcdef');
|
||||||
$refreshToken->setAccessToken($accessToken);
|
$refreshToken->setAccessToken($accessToken);
|
||||||
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
|
|
||||||
$responseType->setAccessToken($accessToken);
|
$responseType->setAccessToken($accessToken);
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
@@ -56,7 +57,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
$response->getBody()->rewind();
|
$response->getBody()->rewind();
|
||||||
$json = json_decode($response->getBody()->getContents());
|
$json = json_decode($response->getBody()->getContents());
|
||||||
$this->assertAttributeEquals('Bearer', 'token_type', $json);
|
$this->assertEquals('Bearer', $json->token_type);
|
||||||
$this->assertObjectHasAttribute('expires_in', $json);
|
$this->assertObjectHasAttribute('expires_in', $json);
|
||||||
$this->assertObjectHasAttribute('access_token', $json);
|
$this->assertObjectHasAttribute('access_token', $json);
|
||||||
$this->assertObjectHasAttribute('refresh_token', $json);
|
$this->assertObjectHasAttribute('refresh_token', $json);
|
||||||
@@ -64,9 +65,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
public function testGenerateHttpResponseWithExtraParams()
|
public function testGenerateHttpResponseWithExtraParams()
|
||||||
{
|
{
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$responseType = new BearerTokenResponseWithParams();
|
||||||
|
|
||||||
$responseType = new BearerTokenResponseWithParams($accessTokenRepositoryMock);
|
|
||||||
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||||
|
|
||||||
@@ -78,14 +77,15 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('abcdef');
|
$accessToken->setIdentifier('abcdef');
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
$accessToken->addScope($scope);
|
$accessToken->addScope($scope);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$refreshToken = new RefreshTokenEntity();
|
$refreshToken = new RefreshTokenEntity();
|
||||||
$refreshToken->setIdentifier('abcdef');
|
$refreshToken->setIdentifier('abcdef');
|
||||||
$refreshToken->setAccessToken($accessToken);
|
$refreshToken->setAccessToken($accessToken);
|
||||||
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
|
|
||||||
$responseType->setAccessToken($accessToken);
|
$responseType->setAccessToken($accessToken);
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
@@ -100,21 +100,18 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
$response->getBody()->rewind();
|
$response->getBody()->rewind();
|
||||||
$json = json_decode($response->getBody()->getContents());
|
$json = json_decode($response->getBody()->getContents());
|
||||||
$this->assertAttributeEquals('Bearer', 'token_type', $json);
|
$this->assertEquals('Bearer', $json->token_type);
|
||||||
$this->assertObjectHasAttribute('expires_in', $json);
|
$this->assertObjectHasAttribute('expires_in', $json);
|
||||||
$this->assertObjectHasAttribute('access_token', $json);
|
$this->assertObjectHasAttribute('access_token', $json);
|
||||||
$this->assertObjectHasAttribute('refresh_token', $json);
|
$this->assertObjectHasAttribute('refresh_token', $json);
|
||||||
|
|
||||||
$this->assertObjectHasAttribute('foo', $json);
|
$this->assertObjectHasAttribute('foo', $json);
|
||||||
$this->assertAttributeEquals('bar', 'foo', $json);
|
$this->assertEquals('bar', $json->foo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testDetermineAccessTokenInHeaderValidToken()
|
public function testDetermineAccessTokenInHeaderValidToken()
|
||||||
{
|
{
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$responseType = new BearerTokenResponse();
|
||||||
$accessTokenRepositoryMock->method('isAccessTokenRevoked')->willReturn(false);
|
|
||||||
|
|
||||||
$responseType = new BearerTokenResponse($accessTokenRepositoryMock);
|
|
||||||
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||||
|
|
||||||
@@ -124,13 +121,14 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('abcdef');
|
$accessToken->setIdentifier('abcdef');
|
||||||
$accessToken->setUserIdentifier(123);
|
$accessToken->setUserIdentifier(123);
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$refreshToken = new RefreshTokenEntity();
|
$refreshToken = new RefreshTokenEntity();
|
||||||
$refreshToken->setIdentifier('abcdef');
|
$refreshToken->setIdentifier('abcdef');
|
||||||
$refreshToken->setAccessToken($accessToken);
|
$refreshToken->setAccessToken($accessToken);
|
||||||
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
|
|
||||||
$responseType->setAccessToken($accessToken);
|
$responseType->setAccessToken($accessToken);
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
@@ -144,8 +142,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
||||||
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
|
||||||
$request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
|
|
||||||
|
|
||||||
$request = $authorizationValidator->validateAuthorization($request);
|
$request = $authorizationValidator->validateAuthorization($request);
|
||||||
|
|
||||||
@@ -158,9 +155,8 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
public function testDetermineAccessTokenInHeaderInvalidJWT()
|
public function testDetermineAccessTokenInHeaderInvalidJWT()
|
||||||
{
|
{
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||||
$accessTokenRepositoryMock->method('isAccessTokenRevoked')->willReturn(false);
|
|
||||||
|
|
||||||
$responseType = new BearerTokenResponse($accessTokenRepositoryMock);
|
$responseType = new BearerTokenResponse();
|
||||||
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||||
|
|
||||||
@@ -170,13 +166,14 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('abcdef');
|
$accessToken->setIdentifier('abcdef');
|
||||||
$accessToken->setUserIdentifier(123);
|
$accessToken->setUserIdentifier(123);
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$refreshToken = new RefreshTokenEntity();
|
$refreshToken = new RefreshTokenEntity();
|
||||||
$refreshToken->setIdentifier('abcdef');
|
$refreshToken->setIdentifier('abcdef');
|
||||||
$refreshToken->setAccessToken($accessToken);
|
$refreshToken->setAccessToken($accessToken);
|
||||||
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
|
|
||||||
$responseType->setAccessToken($accessToken);
|
$responseType->setAccessToken($accessToken);
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
@@ -187,8 +184,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
||||||
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token . 'foo'));
|
||||||
$request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token . 'foo'));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$authorizationValidator->validateAuthorization($request);
|
$authorizationValidator->validateAuthorization($request);
|
||||||
@@ -212,13 +208,14 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$accessToken = new AccessTokenEntity();
|
$accessToken = new AccessTokenEntity();
|
||||||
$accessToken->setIdentifier('abcdef');
|
$accessToken->setIdentifier('abcdef');
|
||||||
$accessToken->setUserIdentifier(123);
|
$accessToken->setUserIdentifier(123);
|
||||||
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
$accessToken->setClient($client);
|
$accessToken->setClient($client);
|
||||||
|
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
|
|
||||||
$refreshToken = new RefreshTokenEntity();
|
$refreshToken = new RefreshTokenEntity();
|
||||||
$refreshToken->setIdentifier('abcdef');
|
$refreshToken->setIdentifier('abcdef');
|
||||||
$refreshToken->setAccessToken($accessToken);
|
$refreshToken->setAccessToken($accessToken);
|
||||||
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
|
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
|
||||||
|
|
||||||
$responseType->setAccessToken($accessToken);
|
$responseType->setAccessToken($accessToken);
|
||||||
$responseType->setRefreshToken($refreshToken);
|
$responseType->setRefreshToken($refreshToken);
|
||||||
@@ -232,8 +229,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
||||||
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
|
||||||
$request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$authorizationValidator->validateAuthorization($request);
|
$authorizationValidator->validateAuthorization($request);
|
||||||
@@ -247,9 +243,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
public function testDetermineAccessTokenInHeaderInvalidToken()
|
public function testDetermineAccessTokenInHeaderInvalidToken()
|
||||||
{
|
{
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$responseType = new BearerTokenResponse();
|
||||||
|
|
||||||
$responseType = new BearerTokenResponse($accessTokenRepositoryMock);
|
|
||||||
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||||
|
|
||||||
@@ -258,8 +252,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
||||||
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', 'Bearer blah');
|
||||||
$request = $request->withHeader('authorization', 'Bearer blah');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$authorizationValidator->validateAuthorization($request);
|
$authorizationValidator->validateAuthorization($request);
|
||||||
@@ -273,9 +266,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
|
|
||||||
public function testDetermineMissingBearerInHeader()
|
public function testDetermineMissingBearerInHeader()
|
||||||
{
|
{
|
||||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
$responseType = new BearerTokenResponse();
|
||||||
|
|
||||||
$responseType = new BearerTokenResponse($accessTokenRepositoryMock);
|
|
||||||
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
$responseType->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
|
||||||
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
$responseType->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||||
|
|
||||||
@@ -284,8 +275,7 @@ class BearerResponseTypeTest extends TestCase
|
|||||||
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
||||||
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||||
|
|
||||||
$request = new ServerRequest();
|
$request = (new ServerRequest())->withHeader('authorization', 'Bearer blah.blah.blah');
|
||||||
$request = $request->withHeader('authorization', 'Bearer blah.blah.blah');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$authorizationValidator->validateAuthorization($request);
|
$authorizationValidator->validateAuthorization($request);
|
||||||
|
1
tests/Stubs/.gitattributes
vendored
Normal file
1
tests/Stubs/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
private.key.crlf text eol=crlf
|
@@ -15,8 +15,8 @@ class ClientEntity implements ClientEntityInterface
|
|||||||
$this->redirectUri = $uri;
|
$this->redirectUri = $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setName($name)
|
public function setConfidential()
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->isConfidential = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
27
tests/Stubs/private.key.crlf
Normal file
27
tests/Stubs/private.key.crlf
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAtHYxRBYATiiyDFs3pEhFg6Ei/UiQEmolTaQyQK810xHY23+X
|
||||||
|
4elLl6HP1J09mefmJ3ZdIgjIOS6rfK1BQnZIvI+IkoC7+qpD92y9f48iL0tCYKsn
|
||||||
|
i1LFFjP0bESTGDe7XANifQPkp9GvKgJbu7h1/ac8x4CBSU0ZjtEvinQRsdYil6OM
|
||||||
|
MXLWGozbBy13X8G+Ganv2i1aPZ2B25GyrH6lVIEwztGrSYxUrFVL+8dHhONf6PYX
|
||||||
|
19gjdzxkXCYQy2AGMc1FevZmnpIqDNQwX7CUUXQ4TDJmiP0aBEni094gUhnRFUr9
|
||||||
|
dmGpLQcCb2i0WMh2K+swFk3EutDAJ+73LKoZ3QIDAQABAoIBADo8Tge3xd9zGIoO
|
||||||
|
QbV9MRmaPW1ZJk0a/fDBRQpEwGzdvIqQ8VWQ8Lj9GdF18LQi9s3TT5i1FtAFNIfm
|
||||||
|
bUHiY/SdqSgF7SOmIIrPB5QLf6+dbM0/TmKSklFo8L6jnohZK9g0q2rGf9p8Ozem
|
||||||
|
TS4WB9WUS3PiD1a1T8Mb1Gisri0h7rvI4TIkrcx6lUUCgphCZd2TWUhmE3YmybOg
|
||||||
|
4h855W685g/ydzjwB+5Y6CS3V6a78Z5Gb4df3l0XfqCWh/xzuNs7nIpRv8CE0vRE
|
||||||
|
vq9j/cVyKkzMjiagteJaisTCBkDmtAi9dEVL8uaSDoTJq1g+VOGuJxHUm31Pavqr
|
||||||
|
3RwvXS0CgYEA74jUqmzxAwr/uBWquIkfMg+hsKjJe3gsSAJIAPzcA9OkzZd9w/1R
|
||||||
|
P8C92N2UaDbCW7ZEl7ZzS+IO6nA4OcR98j77/nBk6cYykyVRkSaj01epz3bRApxc
|
||||||
|
R18e49MBftSMnI5R7lIJO/UAIRfd0rntX4jkdVAdn9s/VOvG8w4KQXcCgYEAwN3W
|
||||||
|
b3azSNYlj4CW8+t6qS/3JQ/qpPgVuqkqP9dQXC9O6VlV03pJIwFk2Ldjd7/eXT+0
|
||||||
|
hFVB3O71iECfet/1UgustlgFp5I4ZrPmYF/J1nGpx1KIE8P4d0qC8lODtdnsGAcU
|
||||||
|
+/vBjXinX7pWgM8e6LAJzqNUq/xal/wNY325dEsCgYB7J0+n+/ECToJhhApNbHq0
|
||||||
|
g2LvcCh/Ka8iqsGYeGkqMoOWDKBlxvUiIRe6y1nFJvpQquqjUfP/fM+Ma3wM/2B9
|
||||||
|
zzJChEjuBK/2BYblaQdr3rN47i7R99BeBaLdIZywN9m/mFC5hkYnJHUXjqzG7j8E
|
||||||
|
El7bjgBdMx1hrQOR7ZMKSwKBgQC2SXXBiBlPwEdj6I/EH06h1hnrR63pGim/cN/j
|
||||||
|
0ye62WPmHW+HH888bLbaNgqnRgtvayS85rAHlzst+pZBVqfRUgN9nJhLl2IDgAlA
|
||||||
|
EYj9TBTBtXmz5MdUSHKXguO73yrMUvU8bOi1Q9I+IipcOGboWmoKikke/LbLa4lj
|
||||||
|
/ZJpHQKBgQCuDanU+AJKgUQkkC2gHwT8quxPoRcFFErHp3iaDAwd5XsZJG9FHQUP
|
||||||
|
RkPE+JkSaj65byFLhCPHUayfk4Y4udHEy4cXiv2SxZNK8q1HwuFEvb7uFprj0hNs
|
||||||
|
14qJunONVt/jzswdwO5kGVbpGlHl7U0JABnTJP71fW/rE5SH4zYxqg==
|
||||||
|
-----END RSA PRIVATE KEY-----
|
@@ -7,17 +7,16 @@ use PHPUnit\Framework\TestCase;
|
|||||||
|
|
||||||
class CryptKeyTest extends TestCase
|
class CryptKeyTest extends TestCase
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @expectedException \LogicException
|
|
||||||
*/
|
|
||||||
public function testNoFile()
|
public function testNoFile()
|
||||||
{
|
{
|
||||||
|
$this->expectException(\LogicException::class);
|
||||||
|
|
||||||
new CryptKey('undefined file');
|
new CryptKey('undefined file');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testKeyCreation()
|
public function testKeyCreation()
|
||||||
{
|
{
|
||||||
$keyFile = __DIR__ . '/Stubs/public.key';
|
$keyFile = __DIR__ . '/../Stubs/public.key';
|
||||||
$key = new CryptKey($keyFile, 'secret');
|
$key = new CryptKey($keyFile, 'secret');
|
||||||
|
|
||||||
$this->assertEquals('file://' . $keyFile, $key->getKeyPath());
|
$this->assertEquals('file://' . $keyFile, $key->getKeyPath());
|
||||||
@@ -26,7 +25,25 @@ class CryptKeyTest extends TestCase
|
|||||||
|
|
||||||
public function testKeyFileCreation()
|
public function testKeyFileCreation()
|
||||||
{
|
{
|
||||||
$keyContent = file_get_contents(__DIR__ . '/Stubs/public.key');
|
$keyContent = file_get_contents(__DIR__ . '/../Stubs/public.key');
|
||||||
|
|
||||||
|
if (!is_string($keyContent)) {
|
||||||
|
$this->fail('The public key stub is not a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = new CryptKey($keyContent);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
'file://' . sys_get_temp_dir() . '/' . sha1($keyContent) . '.key',
|
||||||
|
$key->getKeyPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
$keyContent = file_get_contents(__DIR__ . '/../Stubs/private.key.crlf');
|
||||||
|
|
||||||
|
if (!is_string($keyContent)) {
|
||||||
|
$this->fail('The private key (crlf) stub is not a string');
|
||||||
|
}
|
||||||
|
|
||||||
$key = new CryptKey($keyContent);
|
$key = new CryptKey($keyContent);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
41
tests/Utils/CryptTraitTest.php
Normal file
41
tests/Utils/CryptTraitTest.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace LeagueTests\Utils;
|
||||||
|
|
||||||
|
use Defuse\Crypto\Key;
|
||||||
|
use LeagueTests\Stubs\CryptTraitStub;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class CryptTraitTest extends TestCase
|
||||||
|
{
|
||||||
|
protected $cryptStub;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->cryptStub = new CryptTraitStub();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
$plainText = $this->cryptStub->doDecrypt($encrypted);
|
||||||
|
|
||||||
|
$this->assertNotEquals($payload, $encrypted);
|
||||||
|
$this->assertEquals($payload, $plainText);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user