mirror of
https://github.com/elyby/oauth2-server.git
synced 2025-05-31 14:12:07 +05:30
Compare commits
80 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8184f771d4 | ||
|
51b3b415b4 | ||
|
e3ad09d4a2 | ||
|
aeb1fe48d3 | ||
|
f54980da25 | ||
|
105834af96 | ||
|
ffffc4bfeb | ||
|
a77732e97c | ||
|
614bba2c11 | ||
|
224763cda6 | ||
|
a31bc7d4cc | ||
|
0d20c755d4 | ||
|
e36ff17ad9 | ||
|
a339d99135 | ||
|
a7a1065e38 | ||
|
09bf988922 | ||
|
a571e2262b | ||
|
519543e925 | ||
|
3614f8bd7c | ||
|
e4a7fea834 | ||
|
68c9fbd83c | ||
|
466e1a639d | ||
|
d7ab153500 | ||
|
72ead2e3ce | ||
|
ae4ab26aaf | ||
|
2a7f671a95 | ||
|
02609c37cc | ||
|
9941a96feb | ||
|
2e47fa7fca | ||
|
fc55621f20 | ||
|
beec37d95f | ||
|
5a499bf03c | ||
|
2e3ee60a2a | ||
|
0242d0c996 | ||
|
3ea0cdc936 | ||
|
19d782d223 | ||
|
8a25e0a01b | ||
|
a3d4f583ed | ||
|
28276cb688 | ||
|
35c6f28aef | ||
|
bd47b58f81 | ||
|
e38ea4ab34 | ||
|
8b40858509 | ||
|
aa5cc5eac7 | ||
|
38f26d5229 | ||
|
a5a51fad95 | ||
|
52d7952ba5 | ||
|
2375b8c7f3 | ||
|
f9a91a79c2 | ||
|
242dd4dcfe | ||
|
c94ec122aa | ||
|
05f5d90034 | ||
|
491c23c1e9 | ||
|
27323b5c9a | ||
|
80bc291c51 | ||
|
8a619e5c1e | ||
|
7e07033b10 | ||
|
6991777ff3 | ||
|
9febc32e14 | ||
|
c8b44ff5c7 | ||
|
9fc288ce53 | ||
|
8f1bf88792 | ||
|
cc19da50b4 | ||
|
bec0de16bb | ||
|
a56acc8dd0 | ||
|
c9b07f386c | ||
|
00a7972f74 | ||
|
e3266cb50a | ||
|
2fdd6ce494 | ||
|
6fd3024c48 | ||
|
62e06b7d3a | ||
|
009c109716 | ||
|
6723aadfe8 | ||
|
e24964af07 | ||
|
99e42f6f25 | ||
|
6700b113a8 | ||
|
28e1418f64 | ||
|
143afc9561 | ||
|
292272d128 | ||
|
d8ece093d5 |
@@ -29,7 +29,6 @@ enabled:
|
||||
- phpdoc_inline_tag
|
||||
- phpdoc_no_access
|
||||
- phpdoc_no_simplified_null_return
|
||||
- phpdoc_order
|
||||
- phpdoc_property
|
||||
- phpdoc_scalar
|
||||
- phpdoc_separation
|
||||
|
@@ -21,7 +21,7 @@ install:
|
||||
|
||||
script:
|
||||
- vendor/bin/phpunit --coverage-clover=coverage.clover
|
||||
- vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests
|
||||
- vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests
|
||||
|
||||
after_script:
|
||||
- wget https://scrutinizer-ci.com/ocular.phar
|
||||
|
48
CHANGELOG.md
48
CHANGELOG.md
@@ -6,7 +6,42 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [7.0.0] - released 2018-02-17
|
||||
## [7.2.0] - released 2018-06-23
|
||||
|
||||
### Changed
|
||||
- Added new`validateRedirectUri` method AbstractGrant to remove three instances of code duplication (PR #912)
|
||||
- Allow 640 as a crypt key file permission (PR #917)
|
||||
|
||||
### Added
|
||||
- Function `hasRedirect()` added to `OAuthServerException` (PR #703)
|
||||
|
||||
### Fixed
|
||||
- Catch and handle `BadMethodCallException` from the `verify()` method of the JWT token in the `validateAuthorization` method (PR #904)
|
||||
|
||||
## [4.1.7] - released 2018-06-23
|
||||
|
||||
### Fixed
|
||||
- Ensure `empty()` function call only contains variable to be compatible with PHP 5.4 (PR #918)
|
||||
|
||||
## [7.1.1] - released 2018-05-21
|
||||
|
||||
### Fixed
|
||||
- No longer set a WWW-Authenticate header for invalid clients if the client did not send an Authorization header in the original request (PR #902)
|
||||
|
||||
## [7.1.0] - released 2018-04-22
|
||||
|
||||
### Changed
|
||||
- Changed hint for unsupportedGrantType exception so it no longer references the grant type parameter which isn't always expected (PR #893)
|
||||
- Upgrade PHPStan checks to level 7 (PR #856)
|
||||
|
||||
### Added
|
||||
- Added event emitters for issued access and refresh tokens (PR #860)
|
||||
- Can now use Defuse\Crypto\Key for encryption/decryption of keys which is faster than the Cryto class (PR #812)
|
||||
|
||||
### Removed
|
||||
- Remove paragone/random_compat from dependencies
|
||||
|
||||
## [7.0.0] - released 2018-02-18
|
||||
|
||||
### Added
|
||||
- Use PHPStan for static analysis of code (PR #848)
|
||||
@@ -79,6 +114,10 @@ To address feedback from the security release the following change has been made
|
||||
|
||||
- Fixed `finalizeScopes` call (Issue #650)
|
||||
|
||||
## [4.1.6] - 2016-09-13
|
||||
|
||||
- Less restrictive on Authorization header check (Issue #652)
|
||||
|
||||
## [5.1.1] - 2016-07-26
|
||||
|
||||
- Improved test suite (Issue #614)
|
||||
@@ -371,7 +410,10 @@ Version 5 is a complete code rewrite.
|
||||
|
||||
- First major release
|
||||
|
||||
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.0.0...HEAD
|
||||
[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...HEAD
|
||||
[7.2.0]: https://github.com/thephpleague/oauth2-server/compare/7.1.1...7.2.0
|
||||
[7.1.1]: https://github.com/thephpleague/oauth2-server/compare/7.1.0...7.1.1
|
||||
[7.1.0]: https://github.com/thephpleague/oauth2-server/compare/7.0.0...7.1.0
|
||||
[7.0.0]: https://github.com/thephpleague/oauth2-server/compare/6.1.1...7.0.0
|
||||
[6.1.1]: https://github.com/thephpleague/oauth2-server/compare/6.0.0...6.1.1
|
||||
[6.1.0]: https://github.com/thephpleague/oauth2-server/compare/6.0.2...6.1.0
|
||||
@@ -389,6 +431,8 @@ Version 5 is a complete code rewrite.
|
||||
[5.0.0]: https://github.com/thephpleague/oauth2-server/compare/5.0.0-RC2...5.0.0
|
||||
[5.0.0-RC2]: https://github.com/thephpleague/oauth2-server/compare/5.0.0-RC1...5.0.0-RC2
|
||||
[5.0.0-RC1]: https://github.com/thephpleague/oauth2-server/compare/4.1.5...5.0.0-RC1
|
||||
[4.1.7]: https://github.com/thephpleague/oauth2-server/compare/4.1.6...4.1.7
|
||||
[4.1.6]: https://github.com/thephpleague/oauth2-server/compare/4.1.5...4.1.6
|
||||
[4.1.5]: https://github.com/thephpleague/oauth2-server/compare/4.1.4...4.1.5
|
||||
[4.1.4]: https://github.com/thephpleague/oauth2-server/compare/4.1.3...4.1.4
|
||||
[4.1.3]: https://github.com/thephpleague/oauth2-server/compare/4.1.2...4.1.3
|
||||
|
73
CODE_OF_CONDUCT.md
Normal file
73
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
education, socio-economic status, nationality, personal appearance, race,
|
||||
religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at andrew@noexceptions.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
22
CONDUCT.md
22
CONDUCT.md
@@ -1,22 +0,0 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community in a direct capacity. Personal views, beliefs and values of individuals do not necessarily reflect those of the organisation or affiliated individuals and organisations.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
@@ -37,6 +37,8 @@ The following versions of PHP are supported:
|
||||
|
||||
The `openssl` extension is also required.
|
||||
|
||||
All HTTP messages passed to the server should be [PSR-7 compliant](https://www.php-fig.org/psr/psr-7/). This ensures interoperability with other packages and frameworks.
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
@@ -54,7 +56,7 @@ The library uses [PHPUnit](https://phpunit.de/) for unit tests and [PHPStan](htt
|
||||
|
||||
```
|
||||
vendor/bin/phpunit
|
||||
vendor/bin/phpstan analyse -l 6 -c phpstan.neon src tests
|
||||
vendor/bin/phpstan analyse -l 7 -c phpstan.neon src tests
|
||||
```
|
||||
|
||||
## Continous Integration
|
||||
@@ -63,6 +65,7 @@ We use [Travis CI](https://travis-ci.org/), [Scrutinizer](https://scrutinizer-ci
|
||||
|
||||
## Community Integrations
|
||||
|
||||
* [Drupal](https://www.drupal.org/project/simple_oauth)
|
||||
* [Laravel Passport](https://github.com/laravel/passport)
|
||||
* [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server)
|
||||
|
||||
@@ -72,7 +75,7 @@ See the [project changelog](https://github.com/thephpleague/oauth2-server/blob/m
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are always welcome. Please see [CONTRIBUTING.md](https://github.com/thephpleague/oauth2-server/blob/master/CONTRIBUTING.md) and [CONDUCT.md](https://github.com/thephpleague/oauth2-server/blob/master/CONDUCT.md) for details.
|
||||
Contributions are always welcome. Please see [CONTRIBUTING.md](https://github.com/thephpleague/oauth2-server/blob/master/CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](https://github.com/thephpleague/oauth2-server/blob/master/CODE_OF_CONDUCT.md) for details.
|
||||
|
||||
## Support
|
||||
|
||||
|
@@ -8,7 +8,6 @@
|
||||
"ext-openssl": "*",
|
||||
"league/event": "^2.1",
|
||||
"lcobucci/jwt": "^3.2.2",
|
||||
"paragonie/random_compat": "^2.0",
|
||||
"psr/http-message": "^1.0.1",
|
||||
"defuse/php-encryption": "^2.1"
|
||||
},
|
||||
|
@@ -17,7 +17,7 @@ class ClientRepository implements ClientRepositoryInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getClientEntity($clientIdentifier, $grantType, $clientSecret = null, $mustValidateSecret = true)
|
||||
public function getClientEntity($clientIdentifier, $grantType = null, $clientSecret = null, $mustValidateSecret = true)
|
||||
{
|
||||
$clients = [
|
||||
'myawesomeapp' => [
|
||||
|
@@ -3,3 +3,8 @@ includes:
|
||||
- vendor/phpstan/phpstan-phpunit/rules.neon
|
||||
- vendor/phpstan/phpstan-phpunit/strictRules.neon
|
||||
- vendor/phpstan/phpstan-strict-rules/rules.neon
|
||||
services:
|
||||
-
|
||||
class: LeagueTests\PHPStan\AbstractGrantExtension
|
||||
tags:
|
||||
- phpstan.broker.dynamicMethodReturnTypeExtension
|
||||
|
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace League\OAuth2\Server;
|
||||
|
||||
use Defuse\Crypto\Key;
|
||||
use League\Event\EmitterAwareInterface;
|
||||
use League\Event\EmitterAwareTrait;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
@@ -68,7 +69,7 @@ class AuthorizationServer implements EmitterAwareInterface
|
||||
private $scopeRepository;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var string|Key
|
||||
*/
|
||||
private $encryptionKey;
|
||||
|
||||
@@ -84,7 +85,7 @@ class AuthorizationServer implements EmitterAwareInterface
|
||||
* @param AccessTokenRepositoryInterface $accessTokenRepository
|
||||
* @param ScopeRepositoryInterface $scopeRepository
|
||||
* @param CryptKey|string $privateKey
|
||||
* @param string $encryptionKey
|
||||
* @param string|Key $encryptionKey
|
||||
* @param null|ResponseTypeInterface $responseType
|
||||
*/
|
||||
public function __construct(
|
||||
|
@@ -65,8 +65,12 @@ class BearerTokenValidator implements AuthorizationValidatorInterface
|
||||
try {
|
||||
// Attempt to parse and validate the JWT
|
||||
$token = (new Parser())->parse($jwt);
|
||||
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
|
||||
throw OAuthServerException::accessDenied('Access token could not be verified');
|
||||
try {
|
||||
if ($token->verify(new Sha256(), $this->publicKey->getKeyPath()) === false) {
|
||||
throw OAuthServerException::accessDenied('Access token could not be verified');
|
||||
}
|
||||
} catch (\BadMethodCallException $exception) {
|
||||
throw OAuthServerException::accessDenied('Access token is not signed');
|
||||
}
|
||||
|
||||
// Ensure access token hasn't expired
|
||||
|
@@ -48,7 +48,7 @@ class CryptKey
|
||||
if ($keyPermissionsCheck === true) {
|
||||
// Verify the permissions of the key
|
||||
$keyPathPerms = decoct(fileperms($keyPath) & 0777);
|
||||
if (in_array($keyPathPerms, ['400', '440', '600', '660'], true) === false) {
|
||||
if (in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
|
||||
trigger_error(sprintf(
|
||||
'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
|
||||
$keyPath,
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Public/private key encryption.
|
||||
* Encrypt/decrypt with encryptionKey.
|
||||
*
|
||||
* @author Alex Bilbie <hello@alexbilbie.com>
|
||||
* @copyright Copyright (c) Alex Bilbie
|
||||
@@ -12,16 +12,17 @@
|
||||
namespace League\OAuth2\Server;
|
||||
|
||||
use Defuse\Crypto\Crypto;
|
||||
use Defuse\Crypto\Key;
|
||||
|
||||
trait CryptTrait
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @var string|Key
|
||||
*/
|
||||
protected $encryptionKey;
|
||||
|
||||
/**
|
||||
* Encrypt data with a private key.
|
||||
* Encrypt data with encryptionKey.
|
||||
*
|
||||
* @param string $unencryptedData
|
||||
*
|
||||
@@ -32,6 +33,10 @@ trait CryptTrait
|
||||
protected function encrypt($unencryptedData)
|
||||
{
|
||||
try {
|
||||
if ($this->encryptionKey instanceof Key) {
|
||||
return Crypto::encrypt($unencryptedData, $this->encryptionKey);
|
||||
}
|
||||
|
||||
return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
|
||||
} catch (\Exception $e) {
|
||||
throw new \LogicException($e->getMessage());
|
||||
@@ -39,7 +44,7 @@ trait CryptTrait
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt data with a public key.
|
||||
* Decrypt data with encryptionKey.
|
||||
*
|
||||
* @param string $encryptedData
|
||||
*
|
||||
@@ -50,6 +55,10 @@ trait CryptTrait
|
||||
protected function decrypt($encryptedData)
|
||||
{
|
||||
try {
|
||||
if ($this->encryptionKey instanceof Key) {
|
||||
return Crypto::decrypt($encryptedData, $this->encryptionKey);
|
||||
}
|
||||
|
||||
return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
|
||||
} catch (\Exception $e) {
|
||||
throw new \LogicException($e->getMessage());
|
||||
@@ -59,7 +68,7 @@ trait CryptTrait
|
||||
/**
|
||||
* Set the encryption key
|
||||
*
|
||||
* @param string $key
|
||||
* @param string|Key $key
|
||||
*/
|
||||
public function setEncryptionKey($key = null)
|
||||
{
|
||||
|
@@ -12,7 +12,7 @@ namespace League\OAuth2\Server\Entities;
|
||||
interface AuthCodeEntityInterface extends TokenInterface
|
||||
{
|
||||
/**
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRedirectUri();
|
||||
|
||||
|
@@ -17,7 +17,7 @@ trait AuthCodeTrait
|
||||
protected $redirectUri;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getRedirectUri()
|
||||
{
|
||||
|
@@ -92,7 +92,7 @@ class OAuthServerException extends \Exception
|
||||
public static function unsupportedGrantType()
|
||||
{
|
||||
$errorMessage = 'The authorization grant type is not supported by the authorization server.';
|
||||
$hint = 'Check the `grant_type` parameter';
|
||||
$hint = 'Check that all required parameters have been provided';
|
||||
|
||||
return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
|
||||
}
|
||||
@@ -294,19 +294,30 @@ class OAuthServerException extends \Exception
|
||||
// include the "WWW-Authenticate" response header field
|
||||
// matching the authentication scheme used by the client.
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->errorType === 'invalid_client') {
|
||||
$authScheme = 'Basic';
|
||||
if (array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false
|
||||
&& strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0
|
||||
) {
|
||||
$authScheme = 'Bearer';
|
||||
}
|
||||
if ($this->errorType === 'invalid_client' && array_key_exists('HTTP_AUTHORIZATION', $_SERVER) !== false) {
|
||||
$authScheme = strpos($_SERVER['HTTP_AUTHORIZATION'], 'Bearer') === 0 ? 'Bearer' : 'Basic';
|
||||
|
||||
$headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
return $headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the exception has an associated redirect URI.
|
||||
*
|
||||
* Returns whether the exception includes a redirect, since
|
||||
* getHttpStatusCode() doesn't return a 302 when there's a
|
||||
* redirect enabled. This helps when you want to override local
|
||||
* error pages but want to let redirects through.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRedirect()
|
||||
{
|
||||
return $this->redirectUri !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the HTTP status code to send when the exceptions is output.
|
||||
*
|
||||
|
@@ -193,27 +193,43 @@ abstract class AbstractGrant implements GrantTypeInterface
|
||||
throw OAuthServerException::invalidClient();
|
||||
}
|
||||
|
||||
// If a redirect URI is provided ensure it matches what is pre-registered
|
||||
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null);
|
||||
|
||||
if ($redirectUri !== null) {
|
||||
if (
|
||||
is_string($client->getRedirectUri())
|
||||
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
} elseif (
|
||||
is_array($client->getRedirectUri())
|
||||
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
}
|
||||
$this->validateRedirectUri($redirectUri, $client, $request);
|
||||
}
|
||||
|
||||
return $client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate redirectUri from the request.
|
||||
* If a redirect URI is provided ensure it matches what is pre-registered
|
||||
*
|
||||
* @param string $redirectUri
|
||||
* @param ClientEntityInterface $client
|
||||
* @param ServerRequestInterface $request
|
||||
*
|
||||
* @throws OAuthServerException
|
||||
*/
|
||||
protected function validateRedirectUri(
|
||||
string $redirectUri,
|
||||
ClientEntityInterface $client,
|
||||
ServerRequestInterface $request
|
||||
) {
|
||||
if (is_string($client->getRedirectUri())
|
||||
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
} elseif (is_array($client->getRedirectUri())
|
||||
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate scopes in the request.
|
||||
*
|
||||
@@ -386,7 +402,7 @@ abstract class AbstractGrant implements GrantTypeInterface
|
||||
* @param \DateInterval $authCodeTTL
|
||||
* @param ClientEntityInterface $client
|
||||
* @param string $userIdentifier
|
||||
* @param string $redirectUri
|
||||
* @param string|null $redirectUri
|
||||
* @param ScopeEntityInterface[] $scopes
|
||||
*
|
||||
* @throws OAuthServerException
|
||||
@@ -407,7 +423,10 @@ abstract class AbstractGrant implements GrantTypeInterface
|
||||
$authCode->setExpiryDateTime((new \DateTime())->add($authCodeTTL));
|
||||
$authCode->setClient($client);
|
||||
$authCode->setUserIdentifier($userIdentifier);
|
||||
$authCode->setRedirectUri($redirectUri);
|
||||
|
||||
if ($redirectUri !== null) {
|
||||
$authCode->setRedirectUri($redirectUri);
|
||||
}
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
$authCode->addScope($scope);
|
||||
|
@@ -176,6 +176,10 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
|
||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||
|
||||
// Send events to emitter
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
|
||||
|
||||
// Inject tokens into response type
|
||||
$responseType->setAccessToken($accessToken);
|
||||
$responseType->setRefreshToken($refreshToken);
|
||||
@@ -218,6 +222,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
||||
$request,
|
||||
$this->getServerParameter('PHP_AUTH_USER', $request)
|
||||
);
|
||||
|
||||
if (is_null($clientId)) {
|
||||
throw OAuthServerException::invalidRequest('client_id');
|
||||
}
|
||||
@@ -235,20 +240,9 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
||||
}
|
||||
|
||||
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
|
||||
|
||||
if ($redirectUri !== null) {
|
||||
if (
|
||||
is_string($client->getRedirectUri())
|
||||
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
} elseif (
|
||||
is_array($client->getRedirectUri())
|
||||
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
}
|
||||
$this->validateRedirectUri($redirectUri, $client, $request);
|
||||
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|
||||
|| empty($client->getRedirectUri())) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
@@ -270,7 +264,11 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
||||
$authorizationRequest->setGrantTypeId($this->getIdentifier());
|
||||
$authorizationRequest->setClient($client);
|
||||
$authorizationRequest->setRedirectUri($redirectUri);
|
||||
$authorizationRequest->setState($stateParameter);
|
||||
|
||||
if ($stateParameter !== null) {
|
||||
$authorizationRequest->setState($stateParameter);
|
||||
}
|
||||
|
||||
$authorizationRequest->setScopes($scopes);
|
||||
|
||||
if ($this->enableCodeExchangeProof === true) {
|
||||
@@ -280,6 +278,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant
|
||||
}
|
||||
|
||||
$codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain');
|
||||
|
||||
if (in_array($codeChallengeMethod, ['plain', 'S256'], true) === false) {
|
||||
throw OAuthServerException::invalidRequest(
|
||||
'code_challenge_method',
|
||||
|
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace League\OAuth2\Server\Grant;
|
||||
|
||||
use League\OAuth2\Server\RequestEvent;
|
||||
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
@@ -37,6 +38,9 @@ class ClientCredentialsGrant extends AbstractGrant
|
||||
// Issue and persist access token
|
||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, null, $finalizedScopes);
|
||||
|
||||
// Send event to emitter
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||
|
||||
// Inject access token into response type
|
||||
$responseType->setAccessToken($accessToken);
|
||||
|
||||
|
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace League\OAuth2\Server\Grant;
|
||||
|
||||
use Defuse\Crypto\Key;
|
||||
use League\Event\EmitterAwareInterface;
|
||||
use League\OAuth2\Server\CryptKey;
|
||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||
@@ -136,7 +137,7 @@ interface GrantTypeInterface extends EmitterAwareInterface
|
||||
/**
|
||||
* Set the encryption key
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param string|Key|null $key
|
||||
*/
|
||||
public function setEncryptionKey($key = null);
|
||||
}
|
||||
|
@@ -118,6 +118,7 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
||||
$request,
|
||||
$this->getServerParameter('PHP_AUTH_USER', $request)
|
||||
);
|
||||
|
||||
if (is_null($clientId)) {
|
||||
throw OAuthServerException::invalidRequest('client_id');
|
||||
}
|
||||
@@ -135,20 +136,9 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
||||
}
|
||||
|
||||
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request);
|
||||
|
||||
if ($redirectUri !== null) {
|
||||
if (
|
||||
is_string($client->getRedirectUri())
|
||||
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0)
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
} elseif (
|
||||
is_array($client->getRedirectUri())
|
||||
&& in_array($redirectUri, $client->getRedirectUri(), true) === false
|
||||
) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
throw OAuthServerException::invalidClient();
|
||||
}
|
||||
$this->validateRedirectUri($redirectUri, $client, $request);
|
||||
} elseif (is_array($client->getRedirectUri()) && count($client->getRedirectUri()) !== 1
|
||||
|| empty($client->getRedirectUri())) {
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
|
||||
@@ -177,7 +167,11 @@ class ImplicitGrant extends AbstractAuthorizeGrant
|
||||
$authorizationRequest->setGrantTypeId($this->getIdentifier());
|
||||
$authorizationRequest->setClient($client);
|
||||
$authorizationRequest->setRedirectUri($redirectUri);
|
||||
$authorizationRequest->setState($stateParameter);
|
||||
|
||||
if ($stateParameter !== null) {
|
||||
$authorizationRequest->setState($stateParameter);
|
||||
}
|
||||
|
||||
$authorizationRequest->setScopes($finalizedScopes);
|
||||
|
||||
return $authorizationRequest;
|
||||
|
@@ -59,6 +59,10 @@ class PasswordGrant extends AbstractGrant
|
||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $finalizedScopes);
|
||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||
|
||||
// Send events to emitter
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
|
||||
|
||||
// Inject tokens into response
|
||||
$responseType->setAccessToken($accessToken);
|
||||
$responseType->setRefreshToken($refreshToken);
|
||||
|
@@ -65,6 +65,10 @@ class RefreshTokenGrant extends AbstractGrant
|
||||
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $oldRefreshToken['user_id'], $scopes);
|
||||
$refreshToken = $this->issueRefreshToken($accessToken);
|
||||
|
||||
// Send events to emitter
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request));
|
||||
$this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request));
|
||||
|
||||
// Inject tokens into response
|
||||
$responseType->setAccessToken($accessToken);
|
||||
$responseType->setRefreshToken($refreshToken);
|
||||
|
@@ -18,6 +18,9 @@ class RequestEvent extends Event
|
||||
const USER_AUTHENTICATION_FAILED = 'user.authentication.failed';
|
||||
const REFRESH_TOKEN_CLIENT_FAILED = 'refresh_token.client.failed';
|
||||
|
||||
const REFRESH_TOKEN_ISSUED = 'refresh_token.issued';
|
||||
const ACCESS_TOKEN_ISSUED = 'access_token.issued';
|
||||
|
||||
/**
|
||||
* @var ServerRequestInterface
|
||||
*/
|
||||
|
@@ -60,7 +60,7 @@ class AuthorizationRequest
|
||||
/**
|
||||
* The state parameter on the authorization request
|
||||
*
|
||||
* @var string
|
||||
* @var string|null
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
@@ -175,7 +175,7 @@ class AuthorizationRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @return string|null
|
||||
*/
|
||||
public function getState()
|
||||
{
|
||||
|
@@ -11,6 +11,7 @@
|
||||
|
||||
namespace League\OAuth2\Server\ResponseTypes;
|
||||
|
||||
use Defuse\Crypto\Key;
|
||||
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
|
||||
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -37,7 +38,7 @@ interface ResponseTypeInterface
|
||||
/**
|
||||
* Set the encryption key
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param string|Key|null $key
|
||||
*/
|
||||
public function setEncryptionKey($key = null);
|
||||
}
|
||||
|
40
tests/AuthorizationValidators/BearerTokenValidatorTest.php
Normal file
40
tests/AuthorizationValidators/BearerTokenValidatorTest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace LeagueTests\AuthorizationValidators;
|
||||
|
||||
use Lcobucci\JWT\Builder;
|
||||
use League\OAuth2\Server\AuthorizationValidators\BearerTokenValidator;
|
||||
use League\OAuth2\Server\CryptKey;
|
||||
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Zend\Diactoros\ServerRequest;
|
||||
|
||||
class BearerTokenValidatorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @expectedException League\OAuth2\Server\Exception\OAuthServerException
|
||||
* @expectedExceptionCode 9
|
||||
*/
|
||||
public function testThrowExceptionWhenAccessTokenIsNotSigned()
|
||||
{
|
||||
$accessTokenRepositoryMock = $this->getMockBuilder(AccessTokenRepositoryInterface::class)->getMock();
|
||||
|
||||
$bearerTokenValidator = new BearerTokenValidator($accessTokenRepositoryMock);
|
||||
$bearerTokenValidator->setPublicKey(new CryptKey('file://' . __DIR__ . '/../Stubs/public.key'));
|
||||
|
||||
$unsignedJwt = (new Builder())
|
||||
->setAudience('client-id')
|
||||
->setId('token-id', true)
|
||||
->setIssuedAt(time())
|
||||
->setNotBefore(time())
|
||||
->setExpiration(time())
|
||||
->setSubject('user-id')
|
||||
->set('scopes', 'scope1 scope2 scope3 scope4')
|
||||
->getToken();
|
||||
|
||||
$request = new ServerRequest();
|
||||
$request = $request->withHeader('authorization', sprintf('Bearer %s', $unsignedJwt));
|
||||
|
||||
$bearerTokenValidator->validateAuthorization($request);
|
||||
}
|
||||
}
|
23
tests/Exception/OAuthServerExceptionTest.php
Normal file
23
tests/Exception/OAuthServerExceptionTest.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace LeagueTests\Exception;
|
||||
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OAuthServerExceptionTest extends TestCase
|
||||
{
|
||||
public function testHasRedirect()
|
||||
{
|
||||
$exceptionWithRedirect = OAuthServerException::accessDenied('some hint', 'https://example.com/error');
|
||||
|
||||
$this->assertTrue($exceptionWithRedirect->hasRedirect());
|
||||
}
|
||||
|
||||
public function testDoesNotHaveRedirect()
|
||||
{
|
||||
$exceptionWithoutRedirect = OAuthServerException::accessDenied('Some hint');
|
||||
|
||||
$this->assertFalse($exceptionWithoutRedirect->hasRedirect());
|
||||
}
|
||||
}
|
39
tests/PHPStan/AbstractGrantExtension.php
Normal file
39
tests/PHPStan/AbstractGrantExtension.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace LeagueTests\PHPStan;
|
||||
|
||||
use League\OAuth2\Server\Grant\AbstractGrant;
|
||||
use PhpParser\Node\Expr\MethodCall;
|
||||
use PHPStan\Analyser\Scope;
|
||||
use PHPStan\Reflection\MethodReflection;
|
||||
use PHPStan\Type\DynamicMethodReturnTypeExtension;
|
||||
use PHPStan\Type\NullType;
|
||||
use PHPStan\Type\StringType;
|
||||
use PHPStan\Type\Type;
|
||||
use PHPStan\Type\TypeCombinator;
|
||||
|
||||
final class AbstractGrantExtension implements DynamicMethodReturnTypeExtension
|
||||
{
|
||||
public function getClass(): string
|
||||
{
|
||||
return AbstractGrant::class;
|
||||
}
|
||||
|
||||
public function isMethodSupported(MethodReflection $methodReflection): bool
|
||||
{
|
||||
return in_array($methodReflection->getName(), [
|
||||
'getRequestParameter',
|
||||
'getQueryStringParameter',
|
||||
'getCookieParameter',
|
||||
], true);
|
||||
}
|
||||
|
||||
public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
|
||||
{
|
||||
return TypeCombinator::union(...[
|
||||
new StringType(),
|
||||
isset($methodCall->args[2]) ? $scope->getType($methodCall->args[2]->value) : new NullType(),
|
||||
]);
|
||||
}
|
||||
}
|
@@ -2,22 +2,34 @@
|
||||
|
||||
namespace LeagueTests\Utils;
|
||||
|
||||
use Defuse\Crypto\Key;
|
||||
use LeagueTests\Stubs\CryptTraitStub;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CryptTraitTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var \LeagueTests\Stubs\CryptTraitStub
|
||||
*/
|
||||
protected $cryptStub;
|
||||
|
||||
public function setUp()
|
||||
protected function setUp()
|
||||
{
|
||||
$this->cryptStub = new CryptTraitStub;
|
||||
$this->cryptStub = new CryptTraitStub();
|
||||
}
|
||||
|
||||
public function testEncryptDecrypt()
|
||||
public function testEncryptDecryptWithPassword()
|
||||
{
|
||||
$this->cryptStub->setEncryptionKey(base64_encode(random_bytes(36)));
|
||||
|
||||
$this->encryptDecrypt();
|
||||
}
|
||||
|
||||
public function testEncryptDecryptWithKey()
|
||||
{
|
||||
$this->cryptStub->setEncryptionKey(Key::createNewRandomKey());
|
||||
|
||||
$this->encryptDecrypt();
|
||||
}
|
||||
|
||||
private function encryptDecrypt()
|
||||
{
|
||||
$payload = 'alex loves whisky';
|
||||
$encrypted = $this->cryptStub->doEncrypt($payload);
|
||||
|
Reference in New Issue
Block a user