Compare commits

...

123 Commits

Author SHA1 Message Date
Andrew Millington
2b7923c593 Fix inheritdoc case 2019-07-13 17:49:26 +01:00
Andrew Millington
3f95c0d11e Update validateClient arguments list 2019-07-13 17:40:38 +01:00
Andrew Millington
4be97e6fd0 Update composer dependencies and remove mustValidateSecret 2019-07-13 17:37:45 +01:00
Andrew Millington
aba5353257 Add validateClient() function to ClientRepository 2019-07-13 17:31:09 +01:00
sephster
7f0879b8b4 Change header type 2019-07-02 22:52:13 +01:00
sephster
cb9aa25c89 Re-add removed changelog instances 2019-07-02 22:51:12 +01:00
sephster
a6a499f8fb Remove Simon Hamp from README 2019-07-02 22:20:37 +01:00
Andrew Millington
ccf36588ee Merge pull request #1024 from Sephster/update-dependencies
Update Dependencies
2019-07-02 22:15:29 +01:00
sephster
6b2a3db185 Removing php stan strict rules 2019-07-02 22:10:17 +01:00
sephster
1a6ebdf81c Fix order of imports 2019-07-02 19:24:19 +01:00
sephster
46c86ed5b1 Apply style fix 2019-07-02 19:21:13 +01:00
sephster
a92a274d15 Use reflection instead of extension in test 2019-07-02 19:09:47 +01:00
sephster
c4c354e2df Fix phpstan issues 2019-07-01 19:17:43 +01:00
sephster
7bc1ec643e Remove unused import 2019-06-27 13:24:58 +01:00
sephster
51b97f87c1 Fix issues setting attributes on requests 2019-06-27 13:15:37 +01:00
sephster
e3b23fa826 Update dependencies and fix PHPUnit tests 2019-06-27 12:54:22 +01:00
Andrew Millington
bac79a26a8 Merge pull request #1010 from iansltx/protect-client-entity-gets
Ensure unvalidated ClientEntity gets throw/emit if they return null
2019-06-23 13:54:14 -04:00
sephster
012808f094 Update changelog 2019-06-23 17:56:32 +01:00
sephster
0db54cf1e5 Reinstate use for ClientEntityInterface 2019-06-23 17:40:39 +01:00
sephster
c7d047f7f5 Remove extra line spaces 2019-06-23 17:35:24 +01:00
sephster
e1324b88b2 Merge remote-tracking branch 'upstream/8.0.0' into protect-client-entity-gets 2019-06-23 17:23:40 +01:00
Andrew Millington
c60e8e3581 Merge pull request #1011 from iansltx/readme-release-notes-73-cleanup
Update release notes, clean up readme, add PHP 7.3 test for v8
2019-06-18 17:41:52 -04:00
sephster
e0ee244506 Remove duplicate entries from changelog 2019-06-18 22:30:17 +01:00
Ian Littman
8b5841870f Add more detail/precision to 8.0.0 changelog around breaking changes 2019-05-19 21:01:46 -05:00
Ian Littman
048e45d8cd Add more recent 7.x releases to changelog 2019-05-19 21:01:46 -05:00
Ian Littman
bf75596989 Update security contact email to current maintainer 2019-05-19 21:01:41 -05:00
Ian Littman
c5cfc0a371 Remove dead Commercial Support link 2019-05-19 20:52:28 -05:00
Andrew Millington
5ab4323856 Merge pull request #1014 from Sephster/drop-php-70-support
Drop php 7.0 support
2019-05-14 21:42:12 +01:00
sephster
28709f300f Add pull request number to changelog 2019-05-14 21:30:30 +01:00
sephster
bd483d701b Remove support for PHP 7.0 2019-05-14 21:26:17 +01:00
Andrew Millington
3dc324af6e Merge pull request #1013 from Sephster/8.0.0
Add The Latest Changes from Master into 8.0.0 Branch
2019-05-14 21:09:46 +01:00
sephster
17923634bf Set private keys in tests 2019-05-14 20:56:54 +01:00
sephster
a1cf22a3a9 Remove duplicate setting of expirydatetime 2019-05-14 16:11:34 +01:00
sephster
86d1581cd9 Remove unused imports 2019-05-14 15:57:13 +01:00
sephster
521ed9a8cb Merge master into 8.0.0 branch 2019-05-14 15:46:01 +01:00
Andrew Millington
1bbcb57d63 Merge pull request #1009 from iansltx/skip-s256-if-not-installed
Skip SHA256 verifier if system doesn't support sha256
2019-05-14 14:55:39 +01:00
Andrew Millington
93d4b947d8 Merge pull request #1008 from iansltx/typehints-and-exts
Typehint ServerRequestInterface on OAuthServerException, explicitly require ext-json
2019-05-13 10:25:32 +01:00
Ian Littman
27d5c5ed8d Ensure unvalidated ClientEntity gets throw/emit if they return null
In many cases, we validate client info before pulling from client itself
from the repository, in which case it's safe to assume that you can grab
the client once validation passes. However on implicit/auth code grants
we don't have this guarantee due to non-confidential clients that just
reference the client ID. In those cases the client may supply a client
ID that doesn't exist, and we don't do a validation step before pulling
it from the repo.

The issue with that is that ClientRepository doesn't actually enforce
returning a ClientInterface via typehint, nor does it even suggest an
exception to throw if the client doesn't exist. So in most places we
do an instanceof check after the repository returns and throw/emit an
error event if the client doesn't exist.

This approach ends up being a bit error-prone; we missed one case where
we should've been doing this check: in the access token request on an
auth code grant. We don't do enough validation beforehand to assume that
the incoming request has an accurate client ID, so L96 could absolutely
be a method call on a non-object.

This commit centralizes the return-check-emit-throw logic so it's a
one-liner for wherever we need it, including the access token request
processor for auth code grants.
2019-05-11 14:35:59 -05:00
Ian Littman
4ecd3131c1 Skip SHA256 verifier if system doesn't support sha256 2019-05-11 14:23:56 -05:00
Ian Littman
3fdfbe11f6 Explicitly require ext-json
Makes phpstorm happier; take or leave
2019-05-11 13:37:22 -05:00
Ian Littman
42df2d9c47 Add typehints to OAuthServerException calls 2019-05-11 13:35:24 -05:00
Andrew Millington
d7defafd83 Merge pull request #963 from marc-mabe/date-time-handling
BC-Break: cleanup DateTime handline for 8.0.0
2018-12-19 13:10:20 +00:00
sephster
16f37560d4 Merge latest version of 8 branch 2018-12-19 13:03:10 +00:00
sephster
5ed8e59ef3 Update changelog 2018-12-19 12:58:11 +00:00
sephster
c2cd12e0b8 Remove return types 2018-12-19 12:54:26 +00:00
Andrew Millington
8e9368cf44 Merge pull request #978 from Devristo/fix-http-basic-respond-to-access-token-request
Fixed respondToAccessTokenRequest using Http Basic Auth
2018-12-10 23:07:58 +00:00
sephster
894724c45b Remove invalid commenting 2018-12-10 23:01:45 +00:00
sephster
fd65bf9e54 Streamline tests 2018-12-10 22:51:58 +00:00
Chris Tanaskoski
b6955a6c65 Fixed respondToAccessTokenRequest such that it accepts client_id through request body and Http Basic Auth 2018-11-30 10:19:06 +01:00
Chris Tanaskoski
ec8a663a81 Added test for respondToAccessTokenRequest using Http Basic Auth for client credentials 2018-11-29 09:28:36 +01:00
Andrew Millington
eea9c30e70 Merge pull request #967 from Sephster/password-grant-use-invalid-grant
Password Grant Should Issue an invalid_grant Error When Credentials are Incorrect
2018-11-13 18:28:09 +00:00
sephster
a936962716 Update changelog 2018-11-13 18:27:03 +00:00
sephster
685dc6edea Update test 2018-11-13 18:19:20 +00:00
sephster
2b4974b697 Change to use invalid_grant 2018-11-13 18:18:07 +00:00
Marc Bennewitz
16f9de86f2 cleanup DateTime handline
* DateTime -> DateTimeImmutable
* DateTime::format('U') -> DateTime::getTimestamp()
* (new DateTime())->getTimestamp() -> time()
2018-11-08 12:45:18 +01:00
Andrew Millington
fe421878e6 Merge pull request #938 from Sephster/force-pkce-for-public-clients
Force PKCE for public clients
2018-09-21 20:50:46 +01:00
sephster
0c2356a508 Fix file names for code challenge verifier tests 2018-09-21 20:43:04 +01:00
sephster
9645119ccb Fix missing comma 2018-09-21 20:35:04 +01:00
sephster
da2742bea7 Add details on client validation changes 2018-09-21 20:32:47 +01:00
sephster
efb5ce5e2a Update changelog 2018-09-21 20:29:27 +01:00
sephster
1ddc27e792 Add code challenge verifier tests 2018-09-21 20:12:05 +01:00
sephster
fcd6eb8a3c Fix variable name 2018-09-18 18:01:24 +01:00
sephster
133d9cc97a Fix missing 2018-09-18 17:51:11 +01:00
Andrew Millington
592dd2f433 Fix typo in function name 2018-09-17 20:10:26 +01:00
sephster
4a464dd336 Fix coding standard issue 2018-09-17 12:49:37 +01:00
sephster
970df8f34b Add code challenge verifiers 2018-09-17 12:48:32 +01:00
sephster
6a1645aebc Start to add code challenge verifier interfaces 2018-09-14 18:56:22 +01:00
sephster
e3e7abf41e Set default isConfidential to false for client entity 2018-09-03 13:09:52 +01:00
sephster
d831868d58 Fix getClientEntity parameters 2018-09-02 16:27:31 +01:00
sephster
36bf4ff8f2 Fix accidental paste of code 2018-09-02 16:19:47 +01:00
sephster
07ebe43b91 Change else if to elseif 2018-09-02 16:17:34 +01:00
sephster
5d3d9d95be Remove extra line 2018-09-02 15:46:59 +01:00
sephster
e85a8e31e8 Remove assignment as not needed 2018-09-02 14:58:02 +01:00
sephster
de899fbe0a Fix incorrect usage of isConfidential 2018-09-01 15:05:12 +01:00
sephster
3eabbafe5b Client says if it is confidential instead of repository 2018-09-01 14:53:27 +01:00
sephster
cfa9b8d3b4 Move grant check for client back to validate method 2018-09-01 14:38:31 +01:00
sephster
060a090479 Change tests to use validClient instead of getClientEntity 2018-09-01 14:26:22 +01:00
sephster
46c2f99b06 Change function name to be more explicit 2018-09-01 13:17:36 +01:00
sephster
74495cac49 Set proper confidential settings in existing tests 2018-08-16 12:59:10 +01:00
Andrew Millington
fb43801458 Change function name to setConfidential() 2018-08-15 21:40:41 +01:00
sephster
8ab27ede39 Add test to ensure public clients are asked to provide a code challenge 2018-08-13 22:54:12 +01:00
sephster
0105a20126 Reverted tests to remove isConfidential check 2018-08-13 22:00:34 +01:00
sephster
491852b521 Move code challenge check to auth code request 2018-08-13 21:47:53 +01:00
sephster
7f2fd7b22c Add set confidential to clients for tests 2018-08-13 21:21:59 +01:00
Andrew Millington
abef682031 Add setIsConfidential to client stub for tests 2018-08-12 20:34:58 +01:00
Andrew Millington
04807a1e2a Fix incorrect variable reference 2018-08-12 20:29:39 +01:00
Andrew Millington
d07b5a4a03 Add isConfidential function to client entity trait 2018-08-12 20:26:46 +01:00
Andrew Millington
838f206832 Tidy up comments 2018-08-12 20:09:55 +01:00
Andrew Millington
972808561d Add optional code challenge check for public clients 2018-08-12 20:06:34 +01:00
Andrew Millington
5ad00b0e33 Remove enableCodeExchangeProof function 2018-07-29 22:34:37 +01:00
Andrew Millington
f49cc65c13 Change to store code challenge and method whenever sent for PKCE 2018-07-29 19:56:30 +01:00
Andrew Millington
dc2a048b95 Merge pull request #919 from Sephster/fix-909-v2
Fix 909
2018-06-24 13:55:33 +01:00
Andrew Millington
0c542637fe Merge branch '8.0.0' into fix-909-v2 2018-06-24 13:51:04 +01:00
Andrew Millington
0cdd535f7d Add changes to changelog 2018-06-24 13:48:52 +01:00
Andrew Millington
2fcee76d13 Remove unused stub function 2018-06-24 13:39:40 +01:00
Andrew Millington
574299d862 Fix tests 2018-06-24 13:38:55 +01:00
Andrew Millington
dad3b1e1c9 Remove unused test 2018-06-24 13:32:49 +01:00
Andrew Millington
7df0dfff9d Remove double function calls 2018-06-24 13:31:38 +01:00
Andrew Millington
ca5fe10934 Fix merge issues 2018-06-24 01:30:15 +01:00
Andrew Millington
369c7005a3 Merge master into version 8 branch 2018-06-24 01:10:02 +01:00
Andrew Millington
ca6be0577c Merge pull request #874 from lookyman/access-token-jwt
Generalized access token format
2018-05-24 11:00:29 +01:00
Andrew Millington
ef75d13255 Update changelog 2018-05-23 16:44:00 +01:00
Andrew Millington
bd741e9203 Update travis to check 8.0.0 branch 2018-05-23 16:39:55 +01:00
Andrew Millington
aac64e49cf Fix style issue 2018-05-23 16:36:43 +01:00
Andrew Millington
61156ef8c7 Use __toString() for access token 2018-05-23 16:34:39 +01:00
Andrew Millington
4b0383b16c Updated changelog 2018-05-21 16:20:48 +01:00
Andrew Millington
4aeb92aa98 Merge remote-tracking branch 'upstream/8.0.0' into access-token-jwt 2018-05-21 16:18:24 +01:00
Andrew Millington
b182389395 Remove native type hints 2018-05-21 15:45:09 +01:00
Andrew Millington
92f598d1dc Merge pull request #899 from Sephster/fix-745
Only Add Authenticate Header if Present in Original Request - Tests
2018-05-21 15:44:13 +01:00
Andrew Millington
a40092ff54 Update changelog to fix merge conflict 2018-05-21 15:28:42 +01:00
Andrew Millington
98812e6fab Update changelog 2018-05-21 11:21:44 +01:00
Andrew Millington
354aed7671 Merge in master 2018-05-21 10:53:24 +01:00
Andrew Millington
b1b33207ab Fix namespacing for Exception test 2018-05-13 18:02:23 +01:00
Andrew Millington
793000f149 Fix ServerRequestInterface docblock type 2018-05-13 17:52:45 +01:00
Andrew Millington
f8c2e721a0 Remove return voids and fix docblock and use orders 2018-05-13 17:41:21 +01:00
Andrew Millington
cbce5f45ba Fix case for serverRequest variable and remove unused variable 2018-05-13 17:38:07 +01:00
Andrew Millington
c2dcdee266 Change order of use statements 2018-05-13 17:34:06 +01:00
Andrew Millington
33ce849617 Add tests for invalid client exception 2018-05-13 17:29:07 +01:00
Andrew Millington
ff5e9f57a5 Only add authenticate header if present in original request thephpleague/oauth2-server#745 2018-05-10 22:07:03 +01:00
Lukáš Unger
577065c270 Use native typehints 2018-05-08 11:35:06 +02:00
Lukáš Unger
a1da9beb92 Removed convertToJWT() method from AccessTokenEntityInterface 2018-05-07 20:37:20 +02:00
Andrew Millington
48ce5f36cf Change function name to be less technically specific 2018-05-07 20:37:20 +02:00
Lukáš Unger
fd72d79ad3 Generalized access token format 2018-05-07 20:37:20 +02:00
50 changed files with 1326 additions and 1031 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ phpunit.xml
examples/public.key
examples/private.key
build
*.orig

View File

@@ -12,7 +12,6 @@ env:
- DEPENDENCIES="--prefer-lowest --prefer-stable"
php:
- 7.0
- 7.1
- 7.2
- 7.3
@@ -31,3 +30,4 @@ after_script:
branches:
only:
- master
- 8.0.0

View File

@@ -6,6 +6,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
### Added
- Flag, `requireCodeChallengeForPublicClients`, used to reject public clients that do not provide a code challenge for the Auth Code Grant; use AuthCodeGrant::disableRequireCodeCallengeForPublicClients() to turn off this requirement (PR #938)
- Public clients can now use the Auth Code Grant (PR #938)
- `isConfidential` getter added to `ClientEntity` to identify type of client (PR #938)
- Function `validateClient()` added to validate clients which was previously performed by the `getClientEntity()` function (PR #938)
- Add a new function to the AbstractGrant class called `getClientEntityOrFail()`. This is a wrapper around the `getClientEntity()` function that ensures we emit and throw an exception if the repo doesn't return a client entity. (PR #1010)
### Changed
- Replace `convertToJWT()` interface with a more generic `__toString()` to improve extensibility; AccessTokenEntityInterface now requires `setPrivateKey(CryptKey $privateKey)` so `__toString()` has everything it needs to work (PR #874)
- The `invalidClient()` function accepts a PSR-7 compliant `$serverRequest` argument to avoid accessing the `$_SERVER` global variable and improve testing (PR #899)
- `issueAccessToken()` in the Abstract Grant no longer sets access token client, user ID or scopes. These values should already have been set when calling `getNewToken()` (PR #919)
- No longer need to enable PKCE with `enableCodeExchangeProof` flag. Any client sending a code challenge will initiate PKCE checks. (PR #938)
- Function `getClientEntity()` no longer performs client validation (PR #938)
- Password Grant now returns an invalid_grant error instead of invalid_credentials if a user cannot be validated (PR #967)
- Use `DateTimeImmutable()` instead of `DateTime()`, `time()` instead of `(new DateTime())->getTimeStamp()`, and `DateTime::getTimeStamp()` instead of `DateTime::format('U')` (PR #963)
### Removed
- `enableCodeExchangeProof` flag (PR #938)
- Support for PHP 7.0 (PR #1014)
## [7.4.0] - released 2019-05-05
### Changed

View File

@@ -31,12 +31,11 @@ This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](ht
The following versions of PHP are supported:
* PHP 7.0
* PHP 7.1
* PHP 7.2
* PHP 7.3
The `openssl` extension is also required.
The `openssl` and `json` extensions are also required.
All HTTP messages passed to the server should be [PSR-7 compliant](https://www.php-fig.org/psr/psr-7/). This ensures interoperability with other packages and frameworks.
@@ -86,13 +85,9 @@ Bugs and feature request are tracked on [GitHub](https://github.com/thephpleague
If you have any questions about OAuth _please_ open a ticket here; please **don't** email the address below.
## Commercial Support
If you would like help implementing this library into your existing platform, or would be interested in OAuth advice or training for you and your team please get in touch with [Glynde Labs](https://glyndelabs.com).
## Security
If you discover any security related issues, please email `hello@alexbilbie.com` instead of using the issue tracker.
If you discover any security related issues, please email `andrew@noexceptions.io` instead of using the issue tracker.
## License
@@ -100,7 +95,7 @@ This package is released under the MIT License. See the bundled [LICENSE](https:
## Credits
This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster) and [Simon Hamp](https://twitter.com/simonhamp).
This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster).
Between 2012 and 2017 this library was developed and maintained by [Alex Bilbie](https://alexbilbie.com/).

View File

@@ -4,19 +4,19 @@
"homepage": "https://oauth2.thephpleague.com/",
"license": "MIT",
"require": {
"php": ">=7.0.0",
"php": ">=7.1.0",
"ext-openssl": "*",
"league/event": "^2.1",
"lcobucci/jwt": "^3.2.2",
"league/event": "^2.2",
"lcobucci/jwt": "^3.3.1",
"psr/http-message": "^1.0.1",
"defuse/php-encryption": "^2.1"
"defuse/php-encryption": "^2.2.1",
"ext-json": "*"
},
"require-dev": {
"phpunit/phpunit": "^6.3 || ^7.0",
"zendframework/zend-diactoros": "^1.3.2",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-phpunit": "^0.9.4",
"phpstan/phpstan-strict-rules": "^0.9.0",
"phpunit/phpunit": "^7.5.13 || ^8.2.3",
"zendframework/zend-diactoros": "^2.1.2",
"phpstan/phpstan": "^0.11.8",
"phpstan/phpstan-phpunit": "^0.11.2",
"roave/security-advisories": "dev-master"
},
"repositories": [

View File

@@ -3,11 +3,11 @@
"slim/slim": "^3.0.0"
},
"require-dev": {
"league/event": "^2.1",
"lcobucci/jwt": "^3.2",
"league/event": "^2.2",
"lcobucci/jwt": "^3.3",
"psr/http-message": "^1.0",
"defuse/php-encryption": "^2.2",
"zendframework/zend-diactoros": "^2.0.0"
"zendframework/zend-diactoros": "^2.1.0"
},
"autoload": {
"psr-4": {

69
examples/composer.lock generated
View File

@@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "97f2878428e37d1d8e5418cc85cbfa3d",
"content-hash": "a7f5c3fdcadb17399bbd97f15e1b11f1",
"packages": [
{
"name": "container-interop/container-interop",
@@ -234,16 +234,16 @@
},
{
"name": "slim/slim",
"version": "3.11.0",
"version": "3.12.1",
"source": {
"type": "git",
"url": "https://github.com/slimphp/Slim.git",
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
"reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
"url": "https://api.github.com/repos/slimphp/Slim/zipball/eaee12ef8d0750db62b8c548016d82fb33addb6b",
"reference": "eaee12ef8d0750db62b8c548016d82fb33addb6b",
"shasum": ""
},
"require": {
@@ -301,7 +301,7 @@
"micro",
"router"
],
"time": "2018-09-16T10:54:21+00:00"
"time": "2019-04-16T16:47:29+00:00"
}
],
"packages-dev": [
@@ -370,33 +370,30 @@
},
{
"name": "lcobucci/jwt",
"version": "3.2.4",
"version": "3.3.1",
"source": {
"type": "git",
"url": "https://github.com/lcobucci/jwt.git",
"reference": "c9704b751315d21735dc98d78d4f37bd73596da7"
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/c9704b751315d21735dc98d78d4f37bd73596da7",
"reference": "c9704b751315d21735dc98d78d4f37bd73596da7",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
"reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-openssl": "*",
"php": ">=5.5"
"php": "^5.6 || ^7.0"
},
"require-dev": {
"mdanter/ecc": "~0.3.1",
"mikey179/vfsstream": "~1.5",
"phpmd/phpmd": "~2.2",
"phpunit/php-invoker": "~1.1",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit": "^5.7 || ^7.3",
"squizlabs/php_codesniffer": "~2.3"
},
"suggest": {
"mdanter/ecc": "Required to use Elliptic Curves based algorithms."
},
"type": "library",
"extra": {
"branch-alias": {
@@ -424,20 +421,20 @@
"JWS",
"jwt"
],
"time": "2018-08-03T11:23:50+00:00"
"time": "2019-05-24T18:30:49+00:00"
},
{
"name": "league/event",
"version": "2.1.2",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/event.git",
"reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd"
"reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/event/zipball/e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd",
"reference": "e4bfc88dbcb60c8d8a2939a71f9813e141bbe4cd",
"url": "https://api.github.com/repos/thephpleague/event/zipball/d2cc124cf9a3fab2bb4ff963307f60361ce4d119",
"reference": "d2cc124cf9a3fab2bb4ff963307f60361ce4d119",
"shasum": ""
},
"require": {
@@ -445,7 +442,7 @@
},
"require-dev": {
"henrikbjorn/phpspec-code-coverage": "~1.0.1",
"phpspec/phpspec": "~2.0.0"
"phpspec/phpspec": "^2.2"
},
"type": "library",
"extra": {
@@ -474,7 +471,7 @@
"event",
"listener"
],
"time": "2015-05-21T12:24:47+00:00"
"time": "2018-11-26T11:52:41+00:00"
},
{
"name": "paragonie/random_compat",
@@ -523,16 +520,16 @@
},
{
"name": "psr/http-factory",
"version": "1.0.0",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c"
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
"reference": "378bfe27931ecc54ff824a20d6f6bfc303bbd04c",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
"shasum": ""
},
"require": {
@@ -571,20 +568,20 @@
"request",
"response"
],
"time": "2018-07-30T21:54:04+00:00"
"time": "2019-04-30T12:38:16+00:00"
},
{
"name": "zendframework/zend-diactoros",
"version": "2.0.0",
"version": "2.1.3",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-diactoros.git",
"reference": "0bae78192e634774b5584f0210c1232da82cb1ff"
"reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/0bae78192e634774b5584f0210c1232da82cb1ff",
"reference": "0bae78192e634774b5584f0210c1232da82cb1ff",
"url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1",
"reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1",
"shasum": ""
},
"require": {
@@ -607,8 +604,8 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev",
"dev-develop": "2.1.x-dev",
"dev-master": "2.1.x-dev",
"dev-develop": "2.2.x-dev",
"dev-release-1.8": "1.8.x-dev"
}
},
@@ -637,7 +634,7 @@
"psr",
"psr-7"
],
"time": "2018-09-27T19:49:04+00:00"
"time": "2019-07-10T16:13:25+00:00"
}
],
"aliases": [],

View File

@@ -14,16 +14,33 @@ use OAuth2ServerExamples\Entities\ClientEntity;
class ClientRepository implements ClientRepositoryInterface
{
const CLIENT_NAME = 'My Awesome App';
const REDIRECT_URI = 'http://foo/bar';
/**
* {@inheritdoc}
*/
public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true)
public function getClientEntity($clientIdentifier)
{
$client = new ClientEntity();
$client->setIdentifier($clientIdentifier);
$client->setName(self::CLIENT_NAME);
$client->setRedirectUri(self::REDIRECT_URI);
return $client;
}
/**
* {@inheritdoc}
*/
public function validateClient($clientIdentifier, $clientSecret, $grantType)
{
$clients = [
'myawesomeapp' => [
'secret' => password_hash('abc123', PASSWORD_BCRYPT),
'name' => 'My Awesome App',
'redirect_uri' => 'http://foo/bar',
'name' => self::CLIENT_NAME,
'redirect_uri' => self::REDIRECT_URI,
'is_confidential' => true,
],
];
@@ -34,18 +51,10 @@ class ClientRepository implements ClientRepositoryInterface
}
if (
$mustValidateSecret === true
&& $clients[$clientIdentifier]['is_confidential'] === true
$clients[$clientIdentifier]['is_confidential'] === true
&& password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false
) {
return;
}
$client = new ClientEntity();
$client->setIdentifier($clientIdentifier);
$client->setName($clients[$clientIdentifier]['name']);
$client->setRedirectUri($clients[$clientIdentifier]['redirect_uri']);
return $client;
}
}

View File

@@ -1,8 +1,6 @@
includes:
- vendor/phpstan/phpstan-phpunit/extension.neon
- vendor/phpstan/phpstan-phpunit/rules.neon
- vendor/phpstan/phpstan-phpunit/strictRules.neon
- vendor/phpstan/phpstan-strict-rules/rules.neon
services:
-
class: LeagueTests\PHPStan\AbstractGrantExtension

View File

@@ -125,7 +125,7 @@ class AuthorizationServer implements EmitterAwareInterface
*/
public function enableGrantType(GrantTypeInterface $grantType, DateInterval $accessTokenTTL = null)
{
if ($accessTokenTTL instanceof DateInterval === false) {
if ($accessTokenTTL === null) {
$accessTokenTTL = new DateInterval('PT1H');
}

View File

@@ -63,7 +63,7 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
}
$header = $request->getHeader('authorization');
$jwt = trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0]));
$jwt = trim((string) preg_replace('/^(?:\s+)?Bearer\s/', '', $header[0]));
try {
// Attempt to parse and validate the JWT

View File

@@ -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);
}

View 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);
}
}

View 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
);
}
}

View File

@@ -19,7 +19,7 @@ use LogicException;
trait CryptTrait
{
/**
* @var string|Key
* @var string|Key|null
*/
protected $encryptionKey;
@@ -39,9 +39,13 @@ trait CryptTrait
return Crypto::encrypt($unencryptedData, $this->encryptionKey);
}
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
if (is_string($this->encryptionKey)) {
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
}
throw new LogicException('Encryption key not set when attempting to encrypt');
} catch (Exception $e) {
throw new LogicException($e->getMessage(), null, $e);
throw new LogicException($e->getMessage(), 0, $e);
}
}
@@ -61,9 +65,13 @@ trait CryptTrait
return Crypto::decrypt($encryptedData, $this->encryptionKey);
}
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
if (is_string($this->encryptionKey)) {
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
}
throw new LogicException('Encryption key not set when attempting to decrypt');
} catch (Exception $e) {
throw new LogicException($e->getMessage(), null, $e);
throw new LogicException($e->getMessage(), 0, $e);
}
}

View File

@@ -9,17 +9,17 @@
namespace League\OAuth2\Server\Entities;
use Lcobucci\JWT\Token;
use League\OAuth2\Server\CryptKey;
interface AccessTokenEntityInterface extends TokenInterface
{
/**
* Generate a JWT from the access token
*
* @param CryptKey $privateKey
*
* @return Token
* Set a private key used to encrypt the access token.
*/
public function convertToJWT(CryptKey $privateKey);
public function setPrivateKey(CryptKey $privateKey);
/**
* Generate a string representation of the access token.
*/
public function __toString();
}

View File

@@ -33,4 +33,11 @@ interface ClientEntityInterface
* @return string|string[]
*/
public function getRedirectUri();
/**
* Returns true if the client is confidential.
*
* @return bool
*/
public function isConfidential();
}

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
namespace League\OAuth2\Server\Entities\Traits;
use DateTime;
use DateTimeImmutable;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;
@@ -20,6 +20,19 @@ use League\OAuth2\Server\Entities\ScopeEntityInterface;
trait AccessTokenTrait
{
/**
* @var CryptKey
*/
private $privateKey;
/**
* Set the private key used to encrypt this access token.
*/
public function setPrivateKey(CryptKey $privateKey)
{
$this->privateKey = $privateKey;
}
/**
* Generate a JWT from the access token
*
@@ -27,7 +40,7 @@ trait AccessTokenTrait
*
* @return Token
*/
public function convertToJWT(CryptKey $privateKey)
private function convertToJWT(CryptKey $privateKey)
{
return (new Builder())
->setAudience($this->getClient()->getIdentifier())
@@ -35,19 +48,27 @@ trait AccessTokenTrait
->setIssuedAt(time())
->setNotBefore(time())
->setExpiration($this->getExpiryDateTime()->getTimestamp())
->setSubject($this->getUserIdentifier())
->setSubject((string) $this->getUserIdentifier())
->set('scopes', $this->getScopes())
->sign(new Sha256(), new Key($privateKey->getKeyPath(), $privateKey->getPassPhrase()))
->getToken();
}
/**
* Generate a string representation from the access token
*/
public function __toString()
{
return (string) $this->convertToJWT($this->privateKey);
}
/**
* @return ClientEntityInterface
*/
abstract public function getClient();
/**
* @return DateTime
* @return DateTimeImmutable
*/
abstract public function getExpiryDateTime();

View File

@@ -21,6 +21,11 @@ trait ClientTrait
*/
protected $redirectUri;
/**
* @var bool
*/
protected $isConfidential = false;
/**
* Get the client's name.
*
@@ -43,4 +48,14 @@ trait ClientTrait
{
return $this->redirectUri;
}
/**
* Returns true if the client is confidential.
*
* @return bool
*/
public function isConfidential()
{
return $this->isConfidential;
}
}

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ namespace League\OAuth2\Server\Exception;
use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
class OAuthServerException extends Exception
@@ -40,6 +41,11 @@ class OAuthServerException extends Exception
*/
private $payload;
/**
* @var ServerRequestInterface
*/
private $serverRequest;
/**
* Throw a new exception.
*
@@ -95,6 +101,16 @@ class OAuthServerException extends Exception
$this->payload = $payload;
}
/**
* Set the server request that is responsible for generating the exception
*
* @param ServerRequestInterface $serverRequest
*/
public function setServerRequest(ServerRequestInterface $serverRequest)
{
$this->serverRequest = $serverRequest;
}
/**
* Unsupported grant type error.
*
@@ -129,13 +145,17 @@ class OAuthServerException extends Exception
/**
* Invalid client error.
*
* @param ServerRequestInterface $serverRequest
*
* @return static
*/
public static function invalidClient()
public static function invalidClient(ServerRequestInterface $serverRequest)
{
$errorMessage = 'Client authentication failed';
$exception = new static('Client authentication failed', 4, 'invalid_client', 401);
return new static($errorMessage, 4, 'invalid_client', 401);
$exception->setServerRequest($serverRequest);
return $exception;
}
/**
@@ -288,7 +308,9 @@ class OAuthServerException extends Exception
$response = $response->withHeader($header, $content);
}
$response->getBody()->write(json_encode($payload, $jsonOptions));
$responseBody = json_encode($payload, $jsonOptions) ?: 'JSON encoding of payload failed';
$response->getBody()->write($responseBody);
return $response->withStatus($this->getHttpStatusCode());
}
@@ -313,8 +335,8 @@ class OAuthServerException extends Exception
// include the "WWW-Authenticate" response header field
// matching the authentication scheme used by the client.
// @codeCoverageIgnoreStart
if ($this->errorType === 'invalid_client' && array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false) {
$authScheme = strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0 ? 'Bearer' : 'Basic';
if ($this->errorType === 'invalid_client' && $this->serverRequest->hasHeader('Authorization') === true) {
$authScheme = strpos($this->serverRequest->getHeader('Authorization')[0], 'Bearer') === 0 ? 'Bearer' : 'Basic';
$headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
}

View File

@@ -11,7 +11,7 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use Error;
use Exception;
use League\Event\EmitterAwareTrait;
@@ -177,28 +177,17 @@ abstract class AbstractGrant implements GrantTypeInterface
*/
protected function validateClient(ServerRequestInterface $request)
{
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
list($clientId, $clientSecret) = $this->getClientCredentials($request);
$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
if ($clientId === null) {
throw OAuthServerException::invalidRequest('client_id');
}
// If the client is confidential require the client secret
$clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
$client = $this->clientRepository->getClientEntity(
$clientId,
$this->getIdentifier(),
$clientSecret,
true
);
if ($client instanceof ClientEntityInterface === false) {
if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
throw OAuthServerException::invalidClient($request);
}
$client = $this->getClientEntityOrFail($clientId, $request);
// If a redirect URI is provided ensure it matches what is pre-registered
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
if ($redirectUri !== null) {
@@ -208,6 +197,56 @@ abstract class AbstractGrant implements GrantTypeInterface
return $client;
}
/**
* Wrapper around ClientRepository::getClientEntity() that ensures we emit
* an event and throw an exception if the repo doesn't return a client
* entity.
*
* This is a bit of defensive coding because the interface contract
* doesn't actually enforce non-null returns/exception-on-no-client so
* getClientEntity might return null. By contrast, this method will
* always either return a ClientEntityInterface or throw.
*
* @param string $clientId
* @param ServerRequestInterface $request
*
* @return ClientEntityInterface
*/
protected function getClientEntityOrFail($clientId, ServerRequestInterface $request)
{
$client = $this->clientRepository->getClientEntity($clientId);
if ($client instanceof ClientEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient($request);
}
return $client;
}
/**
* Gets the client credentials from the request from the request body or
* the Http Basic Authorization header
*
* @param ServerRequestInterface $request
*
* @return array
*/
protected function getClientCredentials(ServerRequestInterface $request)
{
list($basicAuthUser, $basicAuthPassword) = $this->getBasicAuthCredentials($request);
$clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);
if (is_null($clientId)) {
throw OAuthServerException::invalidRequest('client_id');
}
$clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);
return [$clientId, $clientSecret];
}
/**
* Validate redirectUri from the request.
* If a redirect URI is provided ensure it matches what is pre-registered
@@ -227,12 +266,12 @@ abstract class AbstractGrant implements GrantTypeInterface
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
throw OAuthServerException::invalidClient($request);
} elseif (\is_array($client->getRedirectUri())
&& \in_array($redirectUri, $client->getRedirectUri(), true) === false
) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
throw OAuthServerException::invalidClient($request);
}
}
@@ -394,13 +433,8 @@ abstract class AbstractGrant implements GrantTypeInterface
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
$accessToken->setClient($client);
$accessToken->setUserIdentifier($userIdentifier);
$accessToken->setExpiryDateTime((new DateTime())->add($accessTokenTTL));
foreach ($scopes as $scope) {
$accessToken->addScope($scope);
}
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL));
$accessToken->setPrivateKey($this->privateKey);
while ($maxGenerationAttempts-- > 0) {
$accessToken->setIdentifier($this->generateUniqueIdentifier());
@@ -440,7 +474,7 @@ abstract class AbstractGrant implements GrantTypeInterface
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;
$authCode = $this->authCodeRepository->getNewAuthCode();
$authCode->setExpiryDateTime((new DateTime())->add($authCodeTTL));
$authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL));
$authCode->setClient($client);
$authCode->setUserIdentifier($userIdentifier);
@@ -482,7 +516,7 @@ abstract class AbstractGrant implements GrantTypeInterface
return null;
}
$refreshToken->setExpiryDateTime((new DateTime())->add($this->refreshTokenTTL));
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL));
$refreshToken->setAccessToken($accessToken);
$maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;

View File

@@ -10,8 +10,11 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use DateTimeImmutable;
use Exception;
use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface;
use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier;
use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -35,7 +38,12 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
/**
* @var bool
*/
private $enableCodeExchangeProof = false;
private $requireCodeChallengeForPublicClients = true;
/**
* @var CodeChallengeVerifierInterface[]
*/
private $codeChallengeVerifiers = [];
/**
* @param AuthCodeRepositoryInterface $authCodeRepository
@@ -53,11 +61,22 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$this->setRefreshTokenRepository($refreshTokenRepository);
$this->authCodeTTL = $authCodeTTL;
$this->refreshTokenTTL = new DateInterval('P1M');
if (in_array('sha256', hash_algos(), true)) {
$s256Verifier = new S256Verifier();
$this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier;
}
$plainVerifier = new PlainVerifier();
$this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier;
}
public function enableCodeExchangeProof()
/**
* Disable the requirement for a code challenge for public clients.
*/
public function disableRequireCodeChallengeForPublicClients()
{
$this->enableCodeExchangeProof = true;
$this->requireCodeChallengeForPublicClients = false;
}
/**
@@ -76,8 +95,15 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
ResponseTypeInterface $responseType,
DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
list($clientId) = $this->getClientCredentials($request);
$client = $this->getClientEntityOrFail($clientId, $request);
// Only validate the client if it is confidential
if ($client->isConfidential()) {
$this->validateClient($request);
}
$encryptedAuthCode = $this->getRequestParameter('code', $request, null);
if ($encryptedAuthCode === null) {
@@ -100,7 +126,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
}
// Validate code challenge
if ($this->enableCodeExchangeProof === true) {
if (!empty($authCodePayload->code_challenge)) {
$codeVerifier = $this->getRequestParameter('code_verifier', $request, null);
if ($codeVerifier === null) {
@@ -116,32 +142,21 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
);
}
switch ($authCodePayload->code_challenge_method) {
case 'plain':
if (hash_equals($codeVerifier, $authCodePayload->code_challenge) === false) {
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
}
if (property_exists($authCodePayload, 'code_challenge_method')) {
if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) {
$codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method];
break;
case 'S256':
if (
hash_equals(
strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'),
$authCodePayload->code_challenge
) === false
) {
if ($codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) {
throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
}
// @codeCoverageIgnoreStart
break;
default:
} else {
throw OAuthServerException::serverError(
sprintf(
'Unsupported code challenge method `%s`',
$authCodePayload->code_challenge_method
)
);
// @codeCoverageIgnoreEnd
}
}
}
@@ -236,17 +251,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
throw OAuthServerException::invalidRequest('client_id');
}
$client = $this->clientRepository->getClientEntity(
$clientId,
$this->getIdentifier(),
null,
false
);
if ($client instanceof ClientEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
$client = $this->getClientEntityOrFail($clientId, $request);
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
@@ -255,7 +260,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
} elseif (empty($client->getRedirectUri()) ||
(\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
throw OAuthServerException::invalidClient($request);
} else {
$redirectUri = \is_array($client->getRedirectUri())
? $client->getRedirectUri()[0]
@@ -280,18 +285,20 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$authorizationRequest->setScopes($scopes);
if ($this->enableCodeExchangeProof === true) {
$codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
if ($codeChallenge === null) {
throw OAuthServerException::invalidRequest('code_challenge');
}
$codeChallenge = $this->getQueryStringParameter('code_challenge', $request);
if ($codeChallenge !== null) {
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
if (\in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) {
if (array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) {
throw OAuthServerException::invalidRequest(
'code_challenge_method',
'Code challenge method must be `plain` or `S256`'
'Code challenge method must be one of ' . implode(', ', array_map(
function ($method) {
return '`' . $method . '`';
},
array_keys($this->codeChallengeVerifiers)
))
);
}
@@ -306,6 +313,8 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
$authorizationRequest->setCodeChallenge($codeChallenge);
$authorizationRequest->setCodeChallengeMethod($codeChallengeMethod);
} elseif ($this->requireCodeChallengeForPublicClients && !$client->isConfidential()) {
throw OAuthServerException::invalidRequest('code_challenge', 'Code challenge must be provided for public clients');
}
return $authorizationRequest;
@@ -339,21 +348,23 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
'auth_code_id' => $authCode->getIdentifier(),
'scopes' => $authCode->getScopes(),
'user_id' => $authCode->getUserIdentifier(),
'expire_time' => (new DateTime())->add($this->authCodeTTL)->format('U'),
'expire_time' => (new DateTimeImmutable())->add($this->authCodeTTL)->getTimestamp(),
'code_challenge' => $authorizationRequest->getCodeChallenge(),
'code_challenge_method' => $authorizationRequest->getCodeChallengeMethod(),
];
$jsonPayload = json_encode($payload);
if ($jsonPayload === false) {
throw new LogicException('An error was encountered when JSON encoding the authorization request response');
}
$response = new RedirectResponse();
$response->setRedirectUri(
$this->makeRedirectUri(
$finalRedirectUri,
[
'code' => $this->encrypt(
json_encode(
$payload
)
),
'code' => $this->encrypt($jsonPayload),
'state' => $authorizationRequest->getState(),
]
)

View File

@@ -10,8 +10,6 @@
namespace League\OAuth2\Server\Grant;
use DateInterval;
use DateTime;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
@@ -126,17 +124,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
throw OAuthServerException::invalidRequest('client_id');
}
$client = $this->clientRepository->getClientEntity(
$clientId,
$this->getIdentifier(),
null,
false
);
if ($client instanceof ClientEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
}
$client = $this->getClientEntityOrFail($clientId, $request);
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
@@ -145,7 +133,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|| empty($client->getRedirectUri())) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidClient();
throw OAuthServerException::invalidClient($request);
} else {
$redirectUri = is_array($client->getRedirectUri())
? $client->getRedirectUri()[0]
@@ -210,9 +198,9 @@ class ImplicitGrant extends AbstractAuthorizeGrant
$this->makeRedirectUri(
$finalRedirectUri,
[
'access_token' => (string) $accessToken->convertToJWT($this->privateKey),
'access_token' => (string) $accessToken,
'token_type' => 'Bearer',
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - (new DateTime())->getTimestamp(),
'expires_in' => $accessToken->getExpiryDateTime()->getTimestamp() - \time(),
'state' => $authorizationRequest->getState(),
],
$this->queryDelimiter

View File

@@ -83,11 +83,13 @@ class PasswordGrant extends AbstractGrant
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
{
$username = $this->getRequestParameter('username', $request);
if (is_null($username)) {
throw OAuthServerException::invalidRequest('username');
}
$password = $this->getRequestParameter('password', $request);
if (is_null($password)) {
throw OAuthServerException::invalidRequest('password');
}
@@ -98,10 +100,11 @@ class PasswordGrant extends AbstractGrant
$this->getIdentifier(),
$client
);
if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
throw OAuthServerException::invalidGrant();
}
return $user;

View File

@@ -19,13 +19,20 @@ interface ClientRepositoryInterface extends RepositoryInterface
/**
* Get a client.
*
* @param string $clientIdentifier The client's identifier
* @param null|string $grantType The grant type used (if sent)
* @param null|string $clientSecret The client's secret (if sent)
* @param bool $mustValidateSecret If true the client must attempt to validate the secret if the client
* is confidential
* @param string $clientIdentifier The client's identifier
*
* @return ClientEntityInterface
* @return ClientEntityInterface|null
*/
public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true);
public function getClientEntity($clientIdentifier);
/**
* Validate a client's secret.
*
* @param string $clientIdentifier The client's identifier
* @param null|string $clientSecret The client's secret (if sent)
* @param null|string $grantType The type of grant the client is using (if sent)
*
* @return bool
*/
public function validateClient($clientIdentifier, $clientSecret, $grantType);
}

View File

@@ -22,7 +22,7 @@ interface ScopeRepositoryInterface extends RepositoryInterface
*
* @param string $identifier The scope identifier
*
* @return ScopeEntityInterface
* @return ScopeEntityInterface|null
*/
public function getScopeEntityByIdentifier($identifier);

View File

@@ -22,7 +22,7 @@ interface UserRepositoryInterface extends RepositoryInterface
* @param string $grantType The grant type used
* @param ClientEntityInterface $clientEntity
*
* @return UserEntityInterface
* @return UserEntityInterface|null
*/
public function getUserEntityByUserCredentials(
$username,

View File

@@ -111,7 +111,7 @@ class AuthorizationRequest
}
/**
* @return UserEntityInterface
* @return UserEntityInterface|null
*/
public function getUser()
{

View File

@@ -11,9 +11,9 @@
namespace League\OAuth2\Server\ResponseTypes;
use DateTime;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use LogicException;
use Psr\Http\Message\ResponseInterface;
class BearerTokenResponse extends AbstractResponseType
@@ -25,32 +25,34 @@ class BearerTokenResponse extends AbstractResponseType
{
$expireDateTime = $this->accessToken->getExpiryDateTime()->getTimestamp();
$jwtAccessToken = $this->accessToken->convertToJWT($this->privateKey);
$responseParams = [
'token_type' => 'Bearer',
'expires_in' => $expireDateTime - (new DateTime())->getTimestamp(),
'access_token' => (string) $jwtAccessToken,
'expires_in' => $expireDateTime - \time(),
'access_token' => (string) $this->accessToken,
];
if ($this->refreshToken instanceof RefreshTokenEntityInterface) {
$refreshToken = $this->encrypt(
json_encode(
[
'client_id' => $this->accessToken->getClient()->getIdentifier(),
'refresh_token_id' => $this->refreshToken->getIdentifier(),
'access_token_id' => $this->accessToken->getIdentifier(),
'scopes' => $this->accessToken->getScopes(),
'user_id' => $this->accessToken->getUserIdentifier(),
'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(),
]
)
);
$refreshTokenPayload = json_encode([
'client_id' => $this->accessToken->getClient()->getIdentifier(),
'refresh_token_id' => $this->refreshToken->getIdentifier(),
'access_token_id' => $this->accessToken->getIdentifier(),
'scopes' => $this->accessToken->getScopes(),
'user_id' => $this->accessToken->getUserIdentifier(),
'expire_time' => $this->refreshToken->getExpiryDateTime()->getTimestamp(),
]);
$responseParams['refresh_token'] = $refreshToken;
if ($refreshTokenPayload === false) {
throw new LogicException('Error encountered JSON encoding the refresh token payload');
}
$responseParams['refresh_token'] = $this->encrypt($refreshTokenPayload);
}
$responseParams = array_merge($this->getExtraParams($this->accessToken), $responseParams);
$responseParams = json_encode(array_merge($this->getExtraParams($this->accessToken), $responseParams));
if ($responseParams === false) {
throw new LogicException('Error encountered JSON encoding response parameters');
}
$response = $response
->withStatus(200)
@@ -58,7 +60,7 @@ class BearerTokenResponse extends AbstractResponseType
->withHeader('cache-control', 'no-store')
->withHeader('content-type', 'application/json; charset=UTF-8');
$response->getBody()->write(json_encode($responseParams));
$response->getBody()->write($responseParams);
return $response;
}

View File

@@ -2,6 +2,7 @@
namespace LeagueTests;
use DateInterval;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -30,7 +31,7 @@ class AuthorizationServerTest extends TestCase
{
const DEFAULT_SCOPE = 'basic';
public function setUp()
public function setUp(): void
{
// Make sure the keys have the correct permissions.
chmod(__DIR__ . '/Stubs/private.key', 0600);
@@ -49,7 +50,7 @@ class AuthorizationServerTest extends TestCase
new StubResponseType()
);
$server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M'));
$server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M'));
try {
$server->respondToAccessTokenRequest(ServerRequestFactory::fromGlobals(), new Response);
@@ -82,7 +83,7 @@ class AuthorizationServerTest extends TestCase
);
$server->setDefaultScope(self::DEFAULT_SCOPE);
$server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M'));
$server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M'));
$_POST['grant_type'] = 'client_credentials';
$_POST['client_id'] = 'foo';
@@ -116,35 +117,31 @@ class AuthorizationServerTest extends TestCase
$privateKey = 'file://' . __DIR__ . '/Stubs/private.key';
$encryptionKey = 'file://' . __DIR__ . '/Stubs/public.key';
$server = new class($clientRepository, $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(), $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(), $privateKey, $encryptionKey) extends AuthorizationServer {
protected function getResponseType()
{
$this->responseType = new class extends BearerTokenResponse {
/* @return null|CryptKey */
public function getPrivateKey()
{
return $this->privateKey;
}
public function getEncryptionKey()
{
return $this->encryptionKey;
}
};
return parent::getResponseType();
}
};
$server = new AuthorizationServer(
$clientRepository,
$this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(),
$this->getMockBuilder(ScopeRepositoryInterface::class)->getMock(),
'file://' . __DIR__ . '/Stubs/private.key',
'file://' . __DIR__ . '/Stubs/public.key'
);
$abstractGrantReflection = new \ReflectionClass($server);
$method = $abstractGrantReflection->getMethod('getResponseType');
$method->setAccessible(true);
$responseType = $method->invoke($server);
$this->assertInstanceOf(BearerTokenResponse::class, $responseType);
$responseTypeReflection = new \ReflectionClass($responseType);
$privateKeyProperty = $responseTypeReflection->getProperty('privateKey');
$privateKeyProperty->setAccessible(true);
$encryptionKeyProperty = $responseTypeReflection->getProperty('encryptionKey');
$encryptionKeyProperty->setAccessible(true);
// generated instances should have keys setup
$this->assertSame($privateKey, $responseType->getPrivateKey()->getKeyPath());
$this->assertSame($encryptionKey, $responseType->getEncryptionKey());
$this->assertSame($privateKey, $privateKeyProperty->getValue($responseType)->getKeyPath());
$this->assertSame($encryptionKey, $encryptionKeyProperty->getValue($responseType));
}
public function testMultipleRequestsGetDifferentResponseTypeInstances()
@@ -217,7 +214,7 @@ class AuthorizationServerTest extends TestCase
$grant = new AuthCodeGrant(
$authCodeRepository,
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
new \DateInterval('PT10M')
new DateInterval('PT10M')
);
$server->enableGrantType($grant);
@@ -238,6 +235,7 @@ class AuthorizationServerTest extends TestCase
{
$client = new ClientEntity();
$client->setRedirectUri('http://foo/bar');
$client->setConfidential();
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
@@ -248,7 +246,7 @@ class AuthorizationServerTest extends TestCase
$grant = new AuthCodeGrant(
$this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(),
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
new \DateInterval('PT10M')
new DateInterval('PT10M')
);
$grant->setClientRepository($clientRepositoryMock);
@@ -289,7 +287,7 @@ class AuthorizationServerTest extends TestCase
$grant = new AuthCodeGrant(
$this->getMockBuilder(AuthCodeRepositoryInterface::class)->getMock(),
$this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock(),
new \DateInterval('PT10M')
new DateInterval('PT10M')
);
$grant->setClientRepository($clientRepositoryMock);
@@ -324,10 +322,6 @@ class AuthorizationServerTest extends TestCase
}
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 2
*/
public function testValidateAuthorizationRequestUnregistered()
{
$server = new AuthorizationServer(
@@ -338,19 +332,13 @@ class AuthorizationServerTest extends TestCase
'file://' . __DIR__ . '/Stubs/public.key'
);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
'client_id' => 'foo',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'code',
'client_id' => 'foo',
]);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(2);
$server->validateAuthorizationRequest($request);
}

View File

@@ -11,10 +11,6 @@ use Zend\Diactoros\ServerRequest;
class BearerTokenValidatorTest extends TestCase
{
/**
* @expectedException League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 9
*/
public function testThrowExceptionWhenAccessTokenIsNotSigned()
{
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
@@ -32,8 +28,10 @@ class BearerTokenValidatorTest extends TestCase
->set('scopes', 'scope1 scope2 scope3 scope4')
->getToken();
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(9);
$bearerTokenValidator->validateAuthorization($request);
}

View 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'));
}
}

View 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)), '='), '+/', '-_');
}
}

View File

@@ -4,10 +4,68 @@ namespace LeagueTests\Exception;
use Exception;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AbstractGrant;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use PHPUnit\Framework\TestCase;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequest;
class OAuthServerExceptionTest extends TestCase
{
public function testInvalidClientExceptionSetsAuthenticateHeader()
{
$serverRequest = (new ServerRequest())
->withParsedBody([
'client_id' => 'foo',
])
->withAddedHeader('Authorization', 'Basic fakeauthdetails');
try {
$this->issueInvalidClientException($serverRequest);
} catch (OAuthServerException $e) {
$response = $e->generateHttpResponse(new Response());
$this->assertTrue($response->hasHeader('WWW-Authenticate'));
}
}
public function testInvalidClientExceptionOmitsAuthenticateHeader()
{
$serverRequest = (new ServerRequest())
->withParsedBody([
'client_id' => 'foo',
]);
try {
$this->issueInvalidClientException($serverRequest);
} catch (OAuthServerException $e) {
$response = $e->generateHttpResponse(new Response());
$this->assertFalse($response->hasHeader('WWW-Authenticate'));
}
}
/**
* Issue an invalid client exception
*
* @throws OAuthServerException
*/
private function issueInvalidClientException($serverRequest)
{
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('validateClient')->willReturn(false);
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->setClientRepository($clientRepositoryMock);
$abstractGrantReflection = new \ReflectionClass($grantMock);
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$validateClientMethod->invoke($grantMock, $serverRequest);
}
public function testHasRedirect()
{
$exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error');
@@ -27,7 +85,9 @@ class OAuthServerExceptionTest extends TestCase
$previous = new Exception('This is the previous');
$exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $previous);
$this->assertSame('This is the previous', $exceptionWithPrevious->getPrevious()->getMessage());
$previousMessage = $exceptionWithPrevious->getPrevious() !== null ? $exceptionWithPrevious->getPrevious()->getMessage() : null;
$this->assertSame('This is the previous', $previousMessage);
}
public function testDoesNotHavePrevious()

View File

@@ -2,7 +2,8 @@
namespace LeagueTests\Grant;
use League\Event\Emitter;
use DateInterval;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
@@ -23,21 +24,13 @@ use Zend\Diactoros\ServerRequest;
class AbstractGrantTest extends TestCase
{
public function testGetSet()
{
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->setEmitter(new Emitter());
}
public function testHttpBasicWithPassword()
{
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('Open:Sesame'));
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:Sesame'));
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
$basicAuthMethod->setAccessible(true);
@@ -50,8 +43,7 @@ class AbstractGrantTest extends TestCase
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('Open:'));
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('Open:'));
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
$basicAuthMethod->setAccessible(true);
@@ -64,8 +56,7 @@ class AbstractGrantTest extends TestCase
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withHeader('Authorization', 'Foo ' . base64_encode('Open:Sesame'));
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Foo ' . base64_encode('Open:Sesame'));
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
$basicAuthMethod->setAccessible(true);
@@ -78,8 +69,7 @@ class AbstractGrantTest extends TestCase
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ||');
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ||');
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
$basicAuthMethod->setAccessible(true);
@@ -92,8 +82,7 @@ class AbstractGrantTest extends TestCase
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame'));
$serverRequest = (new ServerRequest())->withHeader('Authorization', 'Basic ' . base64_encode('OpenSesame'));
$basicAuthMethod = $abstractGrantReflection->getMethod('getBasicAuthCredentials');
$basicAuthMethod->setAccessible(true);
@@ -113,16 +102,14 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
]);
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$result = $validateClientMethod->invoke($grantMock, $serverRequest, true, true);
$result = $validateClientMethod->invoke($grantMock, $serverRequest);
$this->assertEquals($client, $result);
}
@@ -139,14 +126,12 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'redirect_uri' => 'http://foo/bar',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'redirect_uri' => 'http://foo/bar',
]);
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
@@ -154,9 +139,6 @@ class AbstractGrantTest extends TestCase
$this->assertEquals($client, $result);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateClientMissingClientId()
{
$client = new ClientEntity();
@@ -173,16 +155,15 @@ class AbstractGrantTest extends TestCase
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateClientMissingClientSecret()
{
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
$clientRepositoryMock->method('validateClient')->willReturn(false);
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
@@ -190,24 +171,22 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
]);
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateClientInvalidClientSecret()
{
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
$clientRepositoryMock->method('validateClient')->willReturn(false);
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
@@ -215,8 +194,7 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'foo',
]);
@@ -224,12 +202,11 @@ class AbstractGrantTest extends TestCase
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateClientInvalidRedirectUri()
{
$client = new ClientEntity();
@@ -243,8 +220,7 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'redirect_uri' => 'http://bar/foo',
]);
@@ -252,12 +228,11 @@ class AbstractGrantTest extends TestCase
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateClientInvalidRedirectUriArray()
{
$client = new ClientEntity();
@@ -271,8 +246,7 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'redirect_uri' => 'http://bar/foo',
]);
@@ -280,16 +254,15 @@ class AbstractGrantTest extends TestCase
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$validateClientMethod->invoke($grantMock, $serverRequest, true, true);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateClientBadClient()
{
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
$clientRepositoryMock->method('validateClient')->willReturn(false);
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
@@ -297,8 +270,7 @@ class AbstractGrantTest extends TestCase
$abstractGrantReflection = new \ReflectionClass($grantMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
]);
@@ -306,6 +278,8 @@ class AbstractGrantTest extends TestCase
$validateClientMethod = $abstractGrantReflection->getMethod('validateClient');
$validateClientMethod->setAccessible(true);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$validateClientMethod->invoke($grantMock, $serverRequest, true);
}
@@ -314,8 +288,7 @@ class AbstractGrantTest extends TestCase
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->method('getIdentifier')->willReturn('foobar');
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'grant_type' => 'foobar',
]);
@@ -332,7 +305,7 @@ class AbstractGrantTest extends TestCase
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->setRefreshTokenTTL(new \DateInterval('PT1M'));
$grantMock->setRefreshTokenTTL(new DateInterval('PT1M'));
$grantMock->setRefreshTokenRepository($refreshTokenRepoMock);
$abstractGrantReflection = new \ReflectionClass($grantMock);
@@ -374,6 +347,7 @@ class AbstractGrantTest extends TestCase
/** @var AbstractGrant $grantMock */
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$grantMock->setAccessTokenRepository($accessTokenRepoMock);
$abstractGrantReflection = new \ReflectionClass($grantMock);
@@ -383,7 +357,7 @@ class AbstractGrantTest extends TestCase
/** @var AccessTokenEntityInterface $accessToken */
$accessToken = $issueAccessTokenMethod->invoke(
$grantMock,
new \DateInterval('PT1H'),
new DateInterval('PT1H'),
new ClientEntity(),
123,
[new ScopeEntity()]
@@ -408,7 +382,7 @@ class AbstractGrantTest extends TestCase
AuthCodeEntityInterface::class,
$issueAuthCodeMethod->invoke(
$grantMock,
new \DateInterval('PT1H'),
new DateInterval('PT1H'),
new ClientEntity(),
123,
'http://foo/bar',
@@ -426,8 +400,7 @@ class AbstractGrantTest extends TestCase
$method = $abstractGrantReflection->getMethod('getCookieParameter');
$method->setAccessible(true);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withCookieParams([
$serverRequest = (new ServerRequest())->withCookieParams([
'foo' => 'bar',
]);
@@ -444,8 +417,7 @@ class AbstractGrantTest extends TestCase
$method = $abstractGrantReflection->getMethod('getQueryStringParameter');
$method->setAccessible(true);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withQueryParams([
$serverRequest = (new ServerRequest())->withQueryParams([
'foo' => 'bar',
]);
@@ -466,9 +438,6 @@ class AbstractGrantTest extends TestCase
$this->assertEquals([$scope], $grantMock->validateScopes('basic '));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testValidateScopesBadScope()
{
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
@@ -478,6 +447,8 @@ class AbstractGrantTest extends TestCase
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$grantMock->setScopeRepository($scopeRepositoryMock);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$grantMock->validateScopes('basic ');
}
@@ -489,7 +460,7 @@ class AbstractGrantTest extends TestCase
$method = $abstractGrantReflection->getMethod('generateUniqueIdentifier');
$method->setAccessible(true);
$this->assertInternalType('string', $method->invoke($grantMock));
$this->assertIsString($method->invoke($grantMock));
}
public function testCanRespondToAuthorizationRequest()
@@ -498,21 +469,21 @@ class AbstractGrantTest extends TestCase
$this->assertFalse($grantMock->canRespondToAuthorizationRequest(new ServerRequest()));
}
/**
* @expectedException \LogicException
*/
public function testValidateAuthorizationRequest()
{
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$this->expectException(\LogicException::class);
$grantMock->validateAuthorizationRequest(new ServerRequest());
}
/**
* @expectedException \LogicException
*/
public function testCompleteAuthorizationRequest()
{
$grantMock = $this->getMockForAbstractClass(AbstractGrant::class);
$this->expectException(\LogicException::class);
$grantMock->completeAuthorizationRequest(new AuthorizationRequest());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
namespace LeagueTests\Grant;
use DateInterval;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
@@ -44,17 +46,15 @@ class ClientCredentialsGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
}

View File

@@ -2,6 +2,7 @@
namespace LeagueTests\Grant;
use DateInterval;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
@@ -30,56 +31,47 @@ class ImplicitGrantTest extends TestCase
*/
protected $cryptStub;
public function setUp()
public function setUp(): void
{
$this->cryptStub = new CryptTraitStub();
}
public function testGetIdentifier()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$this->assertEquals('implicit', $grant->getIdentifier());
}
public function testCanRespondToAccessTokenRequest()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$this->assertFalse(
$grant->canRespondToAccessTokenRequest(new ServerRequest())
);
}
/**
* @expectedException \LogicException
*/
public function testRespondToAccessTokenRequest()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$this->expectException(\LogicException::class);
$grant->respondToAccessTokenRequest(
new ServerRequest(),
new StubResponseType(),
new \DateInterval('PT10M')
new DateInterval('PT10M')
);
}
public function testCanRespondToAuthorizationRequest()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'token',
'client_id' => 'foo',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'token',
'client_id' => 'foo',
]);
$this->assertTrue($grant->canRespondToAuthorizationRequest($request));
}
@@ -95,25 +87,16 @@ class ImplicitGrantTest extends TestCase
$scopeEntity = new ScopeEntity();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://foo/bar',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://foo/bar',
]);
$this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request));
}
@@ -129,89 +112,54 @@ class ImplicitGrantTest extends TestCase
$scopeEntity = new ScopeEntity();
$scopeRepositoryMock->method('getScopeEntityByIdentifier')->willReturn($scopeEntity);
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://foo/bar',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://foo/bar',
]);
$this->assertInstanceOf(AuthorizationRequest::class, $grant->validateAuthorizationRequest($request));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 3
*/
public function testValidateAuthorizationRequestMissingClientId()
{
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
]
);
$request = (new ServerRequest())->withQueryParams(['response_type' => 'code']);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(3);
$grant->validateAuthorizationRequest($request);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 4
*/
public function testValidateAuthorizationRequestInvalidClientId()
{
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn(null);
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
'client_id' => 'foo',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'code',
'client_id' => 'foo',
]);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(4);
$grant->validateAuthorizationRequest($request);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 4
*/
public function testValidateAuthorizationRequestBadRedirectUriString()
{
$client = new ClientEntity();
@@ -219,31 +167,21 @@ class ImplicitGrantTest extends TestCase
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://bar',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://bar',
]);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(4);
$grant->validateAuthorizationRequest($request);
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 4
*/
public function testValidateAuthorizationRequestBadRedirectUriArray()
{
$client = new ClientEntity();
@@ -251,37 +189,37 @@ class ImplicitGrantTest extends TestCase
$clientRepositoryMock = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepositoryMock->method('getClientEntity')->willReturn($client);
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$grant->setClientRepository($clientRepositoryMock);
$request = new ServerRequest(
[],
[],
null,
null,
'php://input',
$headers = [],
$cookies = [],
$queryParams = [
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://bar',
]
);
$request = (new ServerRequest())->withQueryParams([
'response_type' => 'code',
'client_id' => 'foo',
'redirect_uri' => 'http://bar',
]);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(4);
$grant->validateAuthorizationRequest($request);
}
public function testCompleteAuthorizationRequest()
{
$client = new ClientEntity();
$client->setIdentifier('identifier');
$authRequest = new AuthorizationRequest();
$authRequest->setAuthorizationApproved(true);
$authRequest->setClient(new ClientEntity());
$authRequest->setClient($client);
$authRequest->setGrantTypeId('authorization_code');
$authRequest->setUser(new UserEntity());
$accessToken = new AccessTokenEntity();
$accessToken->setClient($client);
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken);
$accessTokenRepositoryMock->method('persistNewAccessToken')->willReturnSelf();
$scopeRepositoryMock = $this->getMockBuilder(ScopeRepositoryInterface::class)->getMock();
@@ -295,10 +233,6 @@ class ImplicitGrantTest extends TestCase
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 9
*/
public function testCompleteAuthorizationRequestDenied()
{
$authRequest = new AuthorizationRequest();
@@ -319,20 +253,29 @@ class ImplicitGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(9);
$grant->completeAuthorizationRequest($authRequest);
}
public function testAccessTokenRepositoryUniqueConstraintCheck()
{
$client = new ClientEntity();
$client->setIdentifier('identifier');
$authRequest = new AuthorizationRequest();
$authRequest->setAuthorizationApproved(true);
$authRequest->setClient(new ClientEntity());
$authRequest->setClient($client);
$authRequest->setGrantTypeId('authorization_code');
$authRequest->setUser(new UserEntity());
/** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */
$accessToken = new AccessTokenEntity();
$accessToken->setClient($client);
/** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('getNewToken')->willReturn($accessToken);
$accessTokenRepositoryMock->expects($this->at(0))->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
$accessTokenRepositoryMock->expects($this->at(1))->method('persistNewAccessToken')->willReturnSelf();
@@ -347,10 +290,6 @@ class ImplicitGrantTest extends TestCase
$this->assertInstanceOf(RedirectResponse::class, $grant->completeAuthorizationRequest($authRequest));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 7
*/
public function testAccessTokenRepositoryFailToPersist()
{
$authRequest = new AuthorizationRequest();
@@ -359,7 +298,7 @@ class ImplicitGrantTest extends TestCase
$authRequest->setGrantTypeId('authorization_code');
$authRequest->setUser(new UserEntity());
/** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */
/** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(OAuthServerException::serverError('something bad happened'));
@@ -372,13 +311,12 @@ class ImplicitGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(7);
$grant->completeAuthorizationRequest($authRequest);
}
/**
* @expectedException \League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException
* @expectedExceptionCode 100
*/
public function testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop()
{
$authRequest = new AuthorizationRequest();
@@ -387,7 +325,7 @@ class ImplicitGrantTest extends TestCase
$authRequest->setGrantTypeId('authorization_code');
$authRequest->setUser(new UserEntity());
/** @var AccessTokenRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $accessTokenRepositoryMock */
/** @var AccessTokenRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject $accessTokenRepositoryMock */
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
$accessTokenRepositoryMock->method('getNewToken')->willReturn(new AccessTokenEntity());
$accessTokenRepositoryMock->method('persistNewAccessToken')->willThrowException(UniqueTokenIdentifierConstraintViolationException::create());
@@ -400,34 +338,38 @@ class ImplicitGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$this->expectException(\League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException::class);
$this->expectExceptionCode(100);
$grant->completeAuthorizationRequest($authRequest);
}
/**
* @expectedException \LogicException
*/
public function testSetRefreshTokenTTL()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant->setRefreshTokenTTL(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$this->expectException(\LogicException::class);
$grant->setRefreshTokenTTL(new DateInterval('PT10M'));
}
/**
* @expectedException \LogicException
*/
public function testSetRefreshTokenRepository()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$refreshTokenRepositoryMock = $this->getMockBuilder(RefreshTokenRepositoryInterface::class)->getMock();
$this->expectException(\LogicException::class);
$grant->setRefreshTokenRepository($refreshTokenRepositoryMock);
}
/**
* @expectedException \LogicException
*/
public function testCompleteAuthorizationRequestNoUser()
{
$grant = new ImplicitGrant(new \DateInterval('PT10M'));
$grant = new ImplicitGrant(new DateInterval('PT10M'));
$this->expectException(\LogicException::class);
$grant->completeAuthorizationRequest(new AuthorizationRequest());
}
}

View File

@@ -2,6 +2,8 @@
namespace LeagueTests\Grant;
use DateInterval;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Grant\PasswordGrant;
@@ -60,19 +62,17 @@ class PasswordGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'foo',
'password' => 'bar',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'foo',
'password' => 'bar',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
@@ -105,16 +105,14 @@ class PasswordGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'foo',
'password' => 'bar',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'foo',
'password' => 'bar',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
@@ -123,9 +121,6 @@ class PasswordGrantTest extends TestCase
$this->assertNull($responseType->getRefreshToken());
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testRespondToRequestMissingUsername()
{
$client = new ClientEntity();
@@ -142,21 +137,18 @@ class PasswordGrantTest extends TestCase
$grant->setClientRepository($clientRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
]
);
$serverRequest = (new ServerRequest())->withQueryParams([
'client_id' => 'foo',
'client_secret' => 'bar',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testRespondToRequestMissingPassword()
{
$client = new ClientEntity();
@@ -173,22 +165,19 @@ class PasswordGrantTest extends TestCase
$grant->setClientRepository($clientRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'alex',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'alex',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testRespondToRequestBadCredentials()
{
$client = new ClientEntity();
@@ -206,17 +195,18 @@ class PasswordGrantTest extends TestCase
$grant->setClientRepository($clientRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'alex',
'password' => 'whisky',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'username' => 'alex',
'password' => 'whisky',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(10);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
}

View File

@@ -2,6 +2,7 @@
namespace LeagueTests\Grant;
use DateInterval;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
@@ -26,7 +27,7 @@ class RefreshTokenGrantTest extends TestCase
*/
protected $cryptStub;
public function setUp()
public function setUp(): void
{
$this->cryptStub = new CryptTraitStub();
}
@@ -79,8 +80,7 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
@@ -88,7 +88,7 @@ class RefreshTokenGrantTest extends TestCase
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
@@ -136,8 +136,7 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody([
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
@@ -191,27 +190,20 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
'scope' => 'foo',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
'scope' => 'foo',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 5
*/
public function testRespondToUnexpectedScope()
{
$client = new ClientEntity();
@@ -250,24 +242,21 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
'scope' => 'foobar',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
'scope' => 'foobar',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(5);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 3
*/
public function testRespondToRequestMissingOldToken()
{
$client = new ClientEntity();
@@ -284,22 +273,19 @@ class RefreshTokenGrantTest extends TestCase
$grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(3);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestInvalidOldToken()
{
$client = new ClientEntity();
@@ -318,23 +304,20 @@ class RefreshTokenGrantTest extends TestCase
$oldRefreshToken = 'foobar';
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestClientMismatch()
{
$client = new ClientEntity();
@@ -367,23 +350,20 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestExpiredToken()
{
$client = new ClientEntity();
@@ -413,23 +393,20 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestRevokedToken()
{
$client = new ClientEntity();
@@ -460,16 +437,17 @@ class RefreshTokenGrantTest extends TestCase
)
);
$serverRequest = new ServerRequest();
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]
);
$serverRequest = (new ServerRequest())->withParsedBody([
'client_id' => 'foo',
'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken,
]);
$responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
}
}

View File

@@ -2,6 +2,7 @@
namespace LeagueTests\Middleware;
use DateInterval;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
@@ -66,7 +67,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
public function testOAuthErrorResponse()
{
$clientRepository = $this->getMockBuilder(ClientRepositoryInterface::class)->getMock();
$clientRepository->method('getClientEntity')->willReturn(null);
$clientRepository->method('validateClient')->willReturn(false);
$server = new AuthorizationServer(
$clientRepository,
@@ -77,7 +78,7 @@ class AuthorizationServerMiddlewareTest extends TestCase
new StubResponseType()
);
$server->enableGrantType(new ClientCredentialsGrant(), new \DateInterval('PT1M'));
$server->enableGrantType(new ClientCredentialsGrant(), new DateInterval('PT1M'));
$_POST['grant_type'] = 'client_credentials';
$_POST['client_id'] = 'foo';

View File

@@ -2,6 +2,8 @@
namespace LeagueTests\Middleware;
use DateInterval;
use DateTimeImmutable;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
@@ -27,13 +29,13 @@ class ResourceServerMiddlewareTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('test');
$accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$token = (string) $accessToken;
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $token));
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token));
$middleware = new ResourceServerMiddleware($server);
$response = $middleware->__invoke(
@@ -62,13 +64,13 @@ class ResourceServerMiddlewareTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('test');
$accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->sub(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->sub(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$token = $accessToken->convertToJWT(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$token = (string) $accessToken;
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $token));
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $token));
$middleware = new ResourceServerMiddleware($server);
$response = $middleware->__invoke(
@@ -91,8 +93,7 @@ class ResourceServerMiddlewareTest extends TestCase
'file://' . __DIR__ . '/../Stubs/public.key'
);
$request = new ServerRequest();
$request = $request->withHeader('authorization', '');
$request = (new ServerRequest())->withHeader('authorization', '');
$middleware = new ResourceServerMiddleware($server);
$response = $middleware->__invoke(

View File

@@ -2,6 +2,8 @@
namespace LeagueTests\ResponseTypes;
use DateInterval;
use DateTimeImmutable;
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Exception\OAuthServerException;
@@ -32,14 +34,15 @@ class BearerResponseTypeTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('abcdef');
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->addScope($scope);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef');
$refreshToken->setAccessToken($accessToken);
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
@@ -54,7 +57,7 @@ class BearerResponseTypeTest extends TestCase
$response->getBody()->rewind();
$json = json_decode($response->getBody()->getContents());
$this->assertAttributeEquals('Bearer', 'token_type', $json);
$this->assertEquals('Bearer', $json->token_type);
$this->assertObjectHasAttribute('expires_in', $json);
$this->assertObjectHasAttribute('access_token', $json);
$this->assertObjectHasAttribute('refresh_token', $json);
@@ -74,14 +77,15 @@ class BearerResponseTypeTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('abcdef');
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->addScope($scope);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef');
$refreshToken->setAccessToken($accessToken);
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
@@ -96,13 +100,13 @@ class BearerResponseTypeTest extends TestCase
$response->getBody()->rewind();
$json = json_decode($response->getBody()->getContents());
$this->assertAttributeEquals('Bearer', 'token_type', $json);
$this->assertEquals('Bearer', $json->token_type);
$this->assertObjectHasAttribute('expires_in', $json);
$this->assertObjectHasAttribute('access_token', $json);
$this->assertObjectHasAttribute('refresh_token', $json);
$this->assertObjectHasAttribute('foo', $json);
$this->assertAttributeEquals('bar', 'foo', $json);
$this->assertEquals('bar', $json->foo);
}
public function testDetermineAccessTokenInHeaderValidToken()
@@ -117,13 +121,14 @@ class BearerResponseTypeTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('abcdef');
$accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef');
$refreshToken->setAccessToken($accessToken);
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
@@ -137,8 +142,7 @@ class BearerResponseTypeTest extends TestCase
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
$request = $authorizationValidator->validateAuthorization($request);
@@ -162,13 +166,14 @@ class BearerResponseTypeTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('abcdef');
$accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef');
$refreshToken->setAccessToken($accessToken);
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
@@ -179,8 +184,7 @@ class BearerResponseTypeTest extends TestCase
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token . 'foo'));
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token . 'foo'));
try {
$authorizationValidator->validateAuthorization($request);
@@ -204,13 +208,14 @@ class BearerResponseTypeTest extends TestCase
$accessToken = new AccessTokenEntity();
$accessToken->setIdentifier('abcdef');
$accessToken->setUserIdentifier(123);
$accessToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$accessToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$accessToken->setClient($client);
$accessToken->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$refreshToken = new RefreshTokenEntity();
$refreshToken->setIdentifier('abcdef');
$refreshToken->setAccessToken($accessToken);
$refreshToken->setExpiryDateTime((new \DateTime())->add(new \DateInterval('PT1H')));
$refreshToken->setExpiryDateTime((new DateTimeImmutable())->add(new DateInterval('PT1H')));
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
@@ -224,8 +229,7 @@ class BearerResponseTypeTest extends TestCase
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$request = new ServerRequest();
$request = $request->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
$request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $json->access_token));
try {
$authorizationValidator->validateAuthorization($request);
@@ -248,8 +252,7 @@ class BearerResponseTypeTest extends TestCase
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$request = new ServerRequest();
$request = $request->withHeader('authorization', 'Bearer blah');
$request = (new ServerRequest())->withHeader('authorization', 'Bearer blah');
try {
$authorizationValidator->validateAuthorization($request);
@@ -272,8 +275,7 @@ class BearerResponseTypeTest extends TestCase
$authorizationValidator = new BearerTokenValidator($accessTokenRepositoryMock);
$authorizationValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
$request = new ServerRequest();
$request = $request->withHeader('authorization', 'Bearer blah.blah.blah');
$request = (new ServerRequest())->withHeader('authorization', 'Bearer blah.blah.blah');
try {
$authorizationValidator->validateAuthorization($request);

View File

@@ -15,8 +15,8 @@ class ClientEntity implements ClientEntityInterface
$this->redirectUri = $uri;
}
public function setName($name)
public function setConfidential()
{
$this->name = $name;
$this->isConfidential = true;
}
}

View File

@@ -7,11 +7,10 @@ use PHPUnit\Framework\TestCase;
class CryptKeyTest extends TestCase
{
/**
* @expectedException \LogicException
*/
public function testNoFile()
{
$this->expectException(\LogicException::class);
new CryptKey('undefined file');
}
@@ -27,6 +26,11 @@ class CryptKeyTest extends TestCase
public function testKeyFileCreation()
{
$keyContent = file_get_contents(__DIR__ . '/../Stubs/public.key');
if (!is_string($keyContent)) {
$this->fail('The public key stub is not a string');
}
$key = new CryptKey($keyContent);
$this->assertEquals(
@@ -35,6 +39,11 @@ class CryptKeyTest extends TestCase
);
$keyContent = file_get_contents(__DIR__ . '/../Stubs/private.key.crlf');
if (!is_string($keyContent)) {
$this->fail('The private key (crlf) stub is not a string');
}
$key = new CryptKey($keyContent);
$this->assertEquals(

View File

@@ -10,7 +10,7 @@ class CryptTraitTest extends TestCase
{
protected $cryptStub;
protected function setUp()
protected function setUp(): void
{
$this->cryptStub = new CryptTraitStub();
}