Merge pull request #1033 from thephpleague/8.0.0

8.0.0
This commit is contained in:
Andrew Millington 2019-07-13 19:46:10 +01:00 committed by GitHub
commit 1a3107b4fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 1328 additions and 1032 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased] ## [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)
- Remove JTI claim from JWT header (PR #1031)
## [7.4.0] - released 2019-05-05 ## [7.4.0] - released 2019-05-05
### Changed ### 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: The following versions of PHP are supported:
* PHP 7.0
* PHP 7.1 * PHP 7.1
* PHP 7.2 * PHP 7.2
* PHP 7.3 * 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. 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. 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 ## Security
If you discover any security related issues, please email `hello@alexbilbie.com` instead of using the issue tracker. If you discover any security related issues, please email `andrew@noexceptions.io` instead of using the issue tracker.
## License ## License
@ -100,7 +95,7 @@ This package is released under the MIT License. See the bundled [LICENSE](https:
## Credits ## Credits
This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster) 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/). 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/", "homepage": "https://oauth2.thephpleague.com/",
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": ">=7.0.0", "php": ">=7.1.0",
"ext-openssl": "*", "ext-openssl": "*",
"league/event": "^2.1", "league/event": "^2.2",
"lcobucci/jwt": "^3.2.2", "lcobucci/jwt": "^3.3.1",
"psr/http-message": "^1.0.1", "psr/http-message": "^1.0.1",
"defuse/php-encryption": "^2.1" "defuse/php-encryption": "^2.2.1",
"ext-json": "*"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^6.3 || ^7.0", "phpunit/phpunit": "^7.5.13 || ^8.2.3",
"zendframework/zend-diactoros": "^1.3.2", "zendframework/zend-diactoros": "^2.1.2",
"phpstan/phpstan": "^0.9.2", "phpstan/phpstan": "^0.11.8",
"phpstan/phpstan-phpunit": "^0.9.4", "phpstan/phpstan-phpunit": "^0.11.2",
"phpstan/phpstan-strict-rules": "^0.9.0",
"roave/security-advisories": "dev-master" "roave/security-advisories": "dev-master"
}, },
"repositories": [ "repositories": [

View File

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

69
examples/composer.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

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 trait CryptTrait
{ {
/** /**
* @var string|Key * @var string|Key|null
*/ */
protected $encryptionKey; protected $encryptionKey;
@ -39,9 +39,13 @@ trait CryptTrait
return Crypto::encrypt($unencryptedData, $this->encryptionKey); return Crypto::encrypt($unencryptedData, $this->encryptionKey);
} }
if (is_string($this->encryptionKey)) {
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey); return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
}
throw new LogicException('Encryption key not set when attempting to encrypt');
} catch (Exception $e) { } 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::decrypt($encryptedData, $this->encryptionKey);
} }
if (is_string($this->encryptionKey)) {
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey); return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
}
throw new LogicException('Encryption key not set when attempting to decrypt');
} catch (Exception $e) { } 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; namespace League\OAuth2\Server\Entities;
use Lcobucci\JWT\Token;
use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptKey;
interface AccessTokenEntityInterface extends TokenInterface interface AccessTokenEntityInterface extends TokenInterface
{ {
/** /**
* Generate a JWT from the access token * Set a private key used to encrypt the access token.
*
* @param CryptKey $privateKey
*
* @return 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[] * @return string|string[]
*/ */
public function getRedirectUri(); 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; namespace League\OAuth2\Server\Entities;
use DateTime; use DateTimeImmutable;
interface RefreshTokenEntityInterface interface RefreshTokenEntityInterface
{ {
@ -30,16 +30,16 @@ interface RefreshTokenEntityInterface
/** /**
* Get the token's expiry date time. * Get the token's expiry date time.
* *
* @return DateTime * @return DateTimeImmutable
*/ */
public function getExpiryDateTime(); public function getExpiryDateTime();
/** /**
* Set the date time when the token expires. * Set the date time when the token expires.
* *
* @param DateTime $dateTime * @param DateTimeImmutable $dateTime
*/ */
public function setExpiryDateTime(DateTime $dateTime); public function setExpiryDateTime(DateTimeImmutable $dateTime);
/** /**
* Set the access token that the refresh token was associated with. * Set the access token that the refresh token was associated with.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,12 +20,19 @@ interface ClientRepositoryInterface extends RepositoryInterface
* Get a client. * Get a client.
* *
* @param string $clientIdentifier The client's identifier * @param string $clientIdentifier The client's identifier
* @param 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
* *
* @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 * @param string $identifier The scope identifier
* *
* @return ScopeEntityInterface * @return ScopeEntityInterface|null
*/ */
public function getScopeEntityByIdentifier($identifier); public function getScopeEntityByIdentifier($identifier);

View File

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

View File

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

View File

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

View File

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

View File

@ -11,10 +11,6 @@ use Zend\Diactoros\ServerRequest;
class BearerTokenValidatorTest extends TestCase class BearerTokenValidatorTest extends TestCase
{ {
/**
* @expectedException League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 9
*/
public function testThrowExceptionWhenAccessTokenIsNotSigned() public function testThrowExceptionWhenAccessTokenIsNotSigned()
{ {
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock(); $accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
@ -32,8 +28,10 @@ class BearerTokenValidatorTest extends TestCase
->set('scopes', 'scope1 scope2 scope3 scope4') ->set('scopes', 'scope1 scope2 scope3 scope4')
->getToken(); ->getToken();
$request = new ServerRequest(); $request = (new ServerRequest())->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
$request = $request->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(9);
$bearerTokenValidator->validateAuthorization($request); $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 Exception;
use League\OAuth2\Server\Exception\OAuthServerException; use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AbstractGrant;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Zend\Diactoros\Response;
use Zend\Diactoros\ServerRequest;
class OAuthServerExceptionTest extends TestCase 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() public function testHasRedirect()
{ {
$exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error'); $exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error');
@ -27,7 +85,9 @@ class OAuthServerExceptionTest extends TestCase
$previous = new Exception('This is the previous'); $previous = new Exception('This is the previous');
$exceptionWithPrevious = OAuthServerException::accessDenied(null, null, $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() public function testDoesNotHavePrevious()

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -2,6 +2,8 @@
namespace LeagueTests\Grant; namespace LeagueTests\Grant;
use DateInterval;
use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Grant\PasswordGrant; use League\OAuth2\Server\Grant\PasswordGrant;
@ -60,19 +62,17 @@ class PasswordGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'username' => 'foo', 'username' => 'foo',
'password' => 'bar', 'password' => 'bar',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
@ -105,16 +105,14 @@ class PasswordGrantTest extends TestCase
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$grant->setScopeRepository($scopeRepositoryMock); $grant->setScopeRepository($scopeRepositoryMock);
$grant->setDefaultScope(self::DEFAULT_SCOPE); $grant->setDefaultScope(self::DEFAULT_SCOPE);
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'username' => 'foo', 'username' => 'foo',
'password' => 'bar', 'password' => 'bar',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
@ -123,9 +121,6 @@ class PasswordGrantTest extends TestCase
$this->assertNull($responseType->getRefreshToken()); $this->assertNull($responseType->getRefreshToken());
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testRespondToRequestMissingUsername() public function testRespondToRequestMissingUsername()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -142,21 +137,18 @@ class PasswordGrantTest extends TestCase
$grant->setClientRepository($clientRepositoryMock); $grant->setClientRepository($clientRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withQueryParams([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testRespondToRequestMissingPassword() public function testRespondToRequestMissingPassword()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -173,22 +165,19 @@ class PasswordGrantTest extends TestCase
$grant->setClientRepository($clientRepositoryMock); $grant->setClientRepository($clientRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'username' => 'alex', 'username' => 'alex',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
*/
public function testRespondToRequestBadCredentials() public function testRespondToRequestBadCredentials()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -206,17 +195,18 @@ class PasswordGrantTest extends TestCase
$grant->setClientRepository($clientRepositoryMock); $grant->setClientRepository($clientRepositoryMock);
$grant->setAccessTokenRepository($accessTokenRepositoryMock); $grant->setAccessTokenRepository($accessTokenRepositoryMock);
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'username' => 'alex', 'username' => 'alex',
'password' => 'whisky', 'password' => 'whisky',
] ]);
);
$responseType = new StubResponseType(); $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; namespace LeagueTests\Grant;
use DateInterval;
use League\OAuth2\Server\CryptKey; use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
@ -26,7 +27,7 @@ class RefreshTokenGrantTest extends TestCase
*/ */
protected $cryptStub; protected $cryptStub;
public function setUp() public function setUp(): void
{ {
$this->cryptStub = new CryptTraitStub(); $this->cryptStub = new CryptTraitStub();
} }
@ -79,8 +80,7 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody([
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
@ -88,7 +88,7 @@ class RefreshTokenGrantTest extends TestCase
]); ]);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
@ -136,8 +136,7 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody([
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
@ -191,27 +190,20 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
'scope' => 'foo', 'scope' => 'foo',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M')); $grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
$this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken()); $this->assertInstanceOf(AccessTokenEntityInterface::class, $responseType->getAccessToken());
$this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken()); $this->assertInstanceOf(RefreshTokenEntityInterface::class, $responseType->getRefreshToken());
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 5
*/
public function testRespondToUnexpectedScope() public function testRespondToUnexpectedScope()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -250,24 +242,21 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
'scope' => 'foobar', 'scope' => 'foobar',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(5);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 3
*/
public function testRespondToRequestMissingOldToken() public function testRespondToRequestMissingOldToken()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -284,22 +273,19 @@ class RefreshTokenGrantTest extends TestCase
$grant->setEncryptionKey($this->cryptStub->getKey()); $grant->setEncryptionKey($this->cryptStub->getKey());
$grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key')); $grant->setPrivateKey(new CryptKey('file://' . __DIR__ . '/../Stubs/private.key'));
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(3);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestInvalidOldToken() public function testRespondToRequestInvalidOldToken()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -318,23 +304,20 @@ class RefreshTokenGrantTest extends TestCase
$oldRefreshToken = 'foobar'; $oldRefreshToken = 'foobar';
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestClientMismatch() public function testRespondToRequestClientMismatch()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -367,23 +350,20 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestExpiredToken() public function testRespondToRequestExpiredToken()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -413,23 +393,20 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
/**
* @expectedException \League\OAuth2\Server\Exception\OAuthServerException
* @expectedExceptionCode 8
*/
public function testRespondToRequestRevokedToken() public function testRespondToRequestRevokedToken()
{ {
$client = new ClientEntity(); $client = new ClientEntity();
@ -460,16 +437,17 @@ class RefreshTokenGrantTest extends TestCase
) )
); );
$serverRequest = new ServerRequest(); $serverRequest = (new ServerRequest())->withParsedBody([
$serverRequest = $serverRequest->withParsedBody(
[
'client_id' => 'foo', 'client_id' => 'foo',
'client_secret' => 'bar', 'client_secret' => 'bar',
'refresh_token' => $oldRefreshToken, 'refresh_token' => $oldRefreshToken,
] ]);
);
$responseType = new StubResponseType(); $responseType = new StubResponseType();
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new \DateInterval('PT5M'));
$this->expectException(\League\OAuth2\Server\Exception\OAuthServerException::class);
$this->expectExceptionCode(8);
$grant->respondToAccessTokenRequest($serverRequest, $responseType, new DateInterval('PT5M'));
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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