diff --git a/api/components/OAuth2/Component.php b/api/components/OAuth2/Component.php index 38081a1..125ea57 100644 --- a/api/components/OAuth2/Component.php +++ b/api/components/OAuth2/Component.php @@ -3,21 +3,13 @@ declare(strict_types=1); namespace api\components\OAuth2; -use api\components\OAuth2\Grants\AuthCodeGrant; -use api\components\OAuth2\Grants\RefreshTokenGrant; use api\components\OAuth2\Keys\EmptyKey; use DateInterval; use League\OAuth2\Server\AuthorizationServer; -use League\OAuth2\Server\Grant; use yii\base\Component as BaseComponent; class Component extends BaseComponent { - /** - * @var string|\Defuse\Crypto\Key - */ - public $encryptionKey; - /** * @var AuthorizationServer */ @@ -39,19 +31,21 @@ class Component extends BaseComponent { $accessTokensRepo, new Repositories\EmptyScopeRepository(), new EmptyKey(), - $this->encryptionKey + '', // omit key because we use our own encryption mechanism + new ResponseTypes\BearerTokenResponse() ); - $authCodeGrant = new AuthCodeGrant($authCodesRepo, $refreshTokensRepo, new DateInterval('PT10M')); + /** @noinspection PhpUnhandledExceptionInspection */ + $authCodeGrant = new Grants\AuthCodeGrant($authCodesRepo, $refreshTokensRepo, new DateInterval('PT10M')); $authCodeGrant->disableRequireCodeChallengeForPublicClients(); $authServer->enableGrantType($authCodeGrant, $accessTokenTTL); $authCodeGrant->setScopeRepository($publicScopesRepo); // Change repository after enabling - $refreshTokenGrant = new RefreshTokenGrant($refreshTokensRepo); + $refreshTokenGrant = new Grants\RefreshTokenGrant($refreshTokensRepo); $authServer->enableGrantType($refreshTokenGrant); $refreshTokenGrant->setScopeRepository($publicScopesRepo); // Change repository after enabling // TODO: make these access tokens live longer - $clientCredentialsGrant = new Grant\ClientCredentialsGrant(); + $clientCredentialsGrant = new Grants\ClientCredentialsGrant(); $authServer->enableGrantType($clientCredentialsGrant, $accessTokenTTL); $clientCredentialsGrant->setScopeRepository($internalScopesRepo); // Change repository after enabling diff --git a/api/components/OAuth2/CryptTrait.php b/api/components/OAuth2/CryptTrait.php new file mode 100644 index 0000000..c3728a3 --- /dev/null +++ b/api/components/OAuth2/CryptTrait.php @@ -0,0 +1,26 @@ +tokens->encryptValue($unencryptedData); + } + + protected function decrypt($encryptedData): string { + return Yii::$app->tokens->decryptValue($encryptedData); + } + +} diff --git a/api/components/OAuth2/Grants/AuthCodeGrant.php b/api/components/OAuth2/Grants/AuthCodeGrant.php index 5cc1ea7..09fda6d 100644 --- a/api/components/OAuth2/Grants/AuthCodeGrant.php +++ b/api/components/OAuth2/Grants/AuthCodeGrant.php @@ -3,12 +3,14 @@ declare(strict_types=1); namespace api\components\OAuth2\Grants; +use api\components\OAuth2\CryptTrait; use api\components\OAuth2\Repositories\PublicScopeRepository; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Grant\AuthCodeGrant as BaseAuthCodeGrant; class AuthCodeGrant extends BaseAuthCodeGrant { + use CryptTrait; protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface { foreach ($accessToken->getScopes() as $scope) { diff --git a/api/components/OAuth2/Grants/ClientCredentialsGrant.php b/api/components/OAuth2/Grants/ClientCredentialsGrant.php new file mode 100644 index 0000000..fa72668 --- /dev/null +++ b/api/components/OAuth2/Grants/ClientCredentialsGrant.php @@ -0,0 +1,12 @@ +encryptionKey)); + $cipher = $this->base64UrlEncode($nonce . sodium_crypto_secretbox($rawValue, $nonce, $this->encryptionKey)); sodium_memzero($rawValue); return $cipher; } public function decryptValue(string $encryptedValue): string { - $decoded = base64_decode($encryptedValue); + $decoded = $this->base64UrlDecode($encryptedValue); Assert::true($decoded !== false, 'passed value has an invalid base64 encoding'); Assert::true(mb_strlen($decoded, '8bit') >= (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); @@ -137,4 +137,12 @@ class Component extends BaseComponent { return $value; } + private function base64UrlEncode(string $rawValue): string { + return rtrim(strtr(base64_encode($rawValue), '+/', '-_'), '='); + } + + private function base64UrlDecode(string $encodedValue): string { + return base64_decode(str_pad(strtr($encodedValue, '-_', '+/'), strlen($encodedValue) % 4, '=', STR_PAD_RIGHT)); + } + } diff --git a/api/config/config-test.php b/api/config/config-test.php index 1ebbde8..ae91b69 100644 --- a/api/config/config-test.php +++ b/api/config/config-test.php @@ -1,9 +1,6 @@ [ - 'oauth' => [ - 'encryptionKey' => 'mock-encryption-key', - ], 'tokens' => [ 'hmacKey' => 'tests-secret-key', 'privateKeyPath' => codecept_data_dir('certs/private.pem'), diff --git a/api/config/config.php b/api/config/config.php index 3383e9e..115b8ab 100644 --- a/api/config/config.php +++ b/api/config/config.php @@ -13,7 +13,6 @@ return [ ], 'oauth' => [ 'class' => api\components\OAuth2\Component::class, - 'encryptionKey' => getenv('JWT_ENCRYPTION_KEY'), ], 'tokens' => [ 'class' => api\components\Tokens\Component::class, diff --git a/api/tests/functional/_steps/OauthSteps.php b/api/tests/functional/_steps/OauthSteps.php index c6cffc4..6c09d4a 100644 --- a/api/tests/functional/_steps/OauthSteps.php +++ b/api/tests/functional/_steps/OauthSteps.php @@ -18,7 +18,7 @@ class OauthSteps extends FunctionalTester { ]), ['accept' => true]); $this->canSeeResponseJsonMatchesJsonPath('$.redirectUri'); [$redirectUri] = $this->grabDataFromResponseByJsonPath('$.redirectUri'); - preg_match('/code=([\w-]+)/', $redirectUri, $matches); + preg_match('/code=([^&$]+)/', $redirectUri, $matches); return $matches[1]; }