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/private.key
build
*.orig

View File

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

View File

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

View File

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

View File

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

View File

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

69
examples/composer.lock generated
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,30 @@
<?php
/**
* @author Lukáš Unger <lookymsc@gmail.com>
* @copyright Copyright (c) Lukáš Unger
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\CodeChallengeVerifiers;
interface CodeChallengeVerifierInterface
{
/**
* Return code challenge method.
*
* @return string
*/
public function getMethod();
/**
* Verify the code challenge.
*
* @param string $codeVerifier
* @param string $codeChallenge
*
* @return bool
*/
public function verifyCodeChallenge($codeVerifier, $codeChallenge);
}

View File

@ -0,0 +1,36 @@
<?php
/**
* @author Lukáš Unger <lookymsc@gmail.com>
* @copyright Copyright (c) Lukáš Unger
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\CodeChallengeVerifiers;
class PlainVerifier implements CodeChallengeVerifierInterface
{
/**
* Return code challenge method.
*
* @return string
*/
public function getMethod()
{
return 'plain';
}
/**
* Verify the code challenge.
*
* @param string $codeVerifier
* @param string $codeChallenge
*
* @return bool
*/
public function verifyCodeChallenge($codeVerifier, $codeChallenge)
{
return hash_equals($codeVerifier, $codeChallenge);
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* @author Lukáš Unger <lookymsc@gmail.com>
* @copyright Copyright (c) Lukáš Unger
* @license http://mit-license.org/
*
* @link https://github.com/thephpleague/oauth2-server
*/
namespace League\OAuth2\Server\CodeChallengeVerifiers;
class S256Verifier implements CodeChallengeVerifierInterface
{
/**
* Return code challenge method.
*
* @return string
*/
public function getMethod()
{
return 'S256';
}
/**
* Verify the code challenge.
*
* @param string $codeVerifier
* @param string $codeChallenge
*
* @return bool
*/
public function verifyCodeChallenge($codeVerifier, $codeChallenge)
{
return hash_equals(
strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'),
$codeChallenge
);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,24 @@
<?php
namespace LeagueTests\CodeChallengeVerifiers;
use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier;
use PHPUnit\Framework\TestCase;
class PlainVerifierTest extends TestCase
{
public function testGetMethod()
{
$verifier = new PlainVerifier();
$this->assertEquals('plain', $verifier->getMethod());
}
public function testVerifyCodeChallenge()
{
$verifier = new PlainVerifier();
$this->assertTrue($verifier->verifyCodeChallenge('foo', 'foo'));
$this->assertFalse($verifier->verifyCodeChallenge('foo', 'bar'));
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace LeagueTests\CodeChallengeVerifiers;
use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier;
use PHPUnit\Framework\TestCase;
class S256VerifierTest extends TestCase
{
public function testGetMethod()
{
$verifier = new S256Verifier();
$this->assertEquals('S256', $verifier->getMethod());
}
public function testVerifyCodeChallengeSucceeds()
{
$codeChallenge = $this->createCodeChallenge('foo');
$verifier = new S256Verifier();
$this->assertTrue($verifier->verifyCodeChallenge('foo', $codeChallenge));
}
public function testVerifyCodeChallengeFails()
{
$codeChallenge = $this->createCodeChallenge('bar');
$verifier = new S256Verifier();
$this->assertFalse($verifier->verifyCodeChallenge('foo', $codeChallenge));
}
private function createCodeChallenge($codeVerifier)
{
return strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_');
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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