diff --git a/.styleci.yml b/.styleci.yml index 6caf80c5..d3498157 100644 --- a/.styleci.yml +++ b/.styleci.yml @@ -29,7 +29,6 @@ enabled: - phpdoc_inline_tag - phpdoc_no_access - phpdoc_no_simplified_null_return - - phpdoc_order - phpdoc_property - phpdoc_scalar - phpdoc_separation diff --git a/CHANGELOG.md b/CHANGELOG.md index 08c40982..045e54e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] -## [7.0.0] - released 2018-02-17 +### 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) diff --git a/composer.json b/composer.json index 8e7fd7e6..48a95701 100644 --- a/composer.json +++ b/composer.json @@ -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" }, diff --git a/examples/src/Repositories/ClientRepository.php b/examples/src/Repositories/ClientRepository.php index 8d4b5219..eaa4e5c2 100644 --- a/examples/src/Repositories/ClientRepository.php +++ b/examples/src/Repositories/ClientRepository.php @@ -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' => [ diff --git a/src/AuthorizationServer.php b/src/AuthorizationServer.php index 885776ec..f1e96146 100644 --- a/src/AuthorizationServer.php +++ b/src/AuthorizationServer.php @@ -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( diff --git a/src/CryptTrait.php b/src/CryptTrait.php index 125a757e..c9a6d7a6 100644 --- a/src/CryptTrait.php +++ b/src/CryptTrait.php @@ -12,11 +12,12 @@ namespace League\OAuth2\Server; use Defuse\Crypto\Crypto; +use Defuse\Crypto\Key; trait CryptTrait { /** - * @var string + * @var string|Key */ protected $encryptionKey; @@ -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()); @@ -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) { diff --git a/src/Grant/AuthCodeGrant.php b/src/Grant/AuthCodeGrant.php index 81152338..80e1cd0f 100644 --- a/src/Grant/AuthCodeGrant.php +++ b/src/Grant/AuthCodeGrant.php @@ -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,6 +240,7 @@ class AuthCodeGrant extends AbstractAuthorizeGrant } $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); + if ($redirectUri !== null) { if ( is_string($client->getRedirectUri()) @@ -284,6 +290,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', diff --git a/src/Grant/ClientCredentialsGrant.php b/src/Grant/ClientCredentialsGrant.php index ed157aaf..026ce5e5 100644 --- a/src/Grant/ClientCredentialsGrant.php +++ b/src/Grant/ClientCredentialsGrant.php @@ -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); diff --git a/src/Grant/GrantTypeInterface.php b/src/Grant/GrantTypeInterface.php index 0e721435..2aee367f 100644 --- a/src/Grant/GrantTypeInterface.php +++ b/src/Grant/GrantTypeInterface.php @@ -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); } diff --git a/src/Grant/PasswordGrant.php b/src/Grant/PasswordGrant.php index cfd7e9fe..1d00998b 100644 --- a/src/Grant/PasswordGrant.php +++ b/src/Grant/PasswordGrant.php @@ -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); diff --git a/src/Grant/RefreshTokenGrant.php b/src/Grant/RefreshTokenGrant.php index f8e022b4..519954be 100644 --- a/src/Grant/RefreshTokenGrant.php +++ b/src/Grant/RefreshTokenGrant.php @@ -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); diff --git a/src/RequestEvent.php b/src/RequestEvent.php index 1558e11f..b1ca3f6b 100644 --- a/src/RequestEvent.php +++ b/src/RequestEvent.php @@ -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 */ diff --git a/src/ResponseTypes/ResponseTypeInterface.php b/src/ResponseTypes/ResponseTypeInterface.php index 8ac20b8c..5eddd607 100644 --- a/src/ResponseTypes/ResponseTypeInterface.php +++ b/src/ResponseTypes/ResponseTypeInterface.php @@ -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); } diff --git a/tests/Utils/CryptTraitTest.php b/tests/Utils/CryptTraitTest.php index 26427e59..c517cec2 100644 --- a/tests/Utils/CryptTraitTest.php +++ b/tests/Utils/CryptTraitTest.php @@ -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);