From 3406f6fee0e957402da17693c6cf9247eb510af7 Mon Sep 17 00:00:00 2001 From: Rutger Hertogh Date: Fri, 12 Nov 2021 20:36:38 +0100 Subject: [PATCH] Custom implementation of PKCE for thephpleague/oauth2-client till https://github.com/thephpleague/oauth2-client/pull/901 is merged --- tests/_helpers/ClientTokenProvider.php | 120 +++++++++++++++++- .../functional/AuthorizationCodeGrantCest.php | 5 +- 2 files changed, 120 insertions(+), 5 deletions(-) diff --git a/tests/_helpers/ClientTokenProvider.php b/tests/_helpers/ClientTokenProvider.php index 73b89ce..17eb172 100644 --- a/tests/_helpers/ClientTokenProvider.php +++ b/tests/_helpers/ClientTokenProvider.php @@ -6,7 +6,6 @@ class ClientTokenProvider extends GenericProvider { - public function getAccessTokenRequestWrapper($grant, array $options = []) { $grant = $this->verifyGrant($grant); @@ -24,4 +23,123 @@ public function getAccessTokenRequestWrapper($grant, array $options = []) $params = $grant->prepareRequestParameters($params, $options); return $this->getAccessTokenRequest($params); } + + + + + # region PKCE https://github.com/thephpleague/oauth2-client/pull/901 + const PKCE_METHOD_S256 = 'S256'; + protected $pkceCode; + public $pkceMethod = null; + public function setPkceCode($pkceCode) + { + $this->pkceCode = $pkceCode; + return $this; + } + public function getPkceCode() + { + return $this->pkceCode; + } + protected function getRandomPkceCode($length = 64) + { + return substr( + strtr( + base64_encode(random_bytes($length)), + '+/', + '-_' + ), + 0, + $length + ); + } + protected function getPkceMethod() + { + return $this->pkceMethod; + } + protected function getAuthorizationParameters(array $options) + { + if (empty($options['state'])) { + $options['state'] = $this->getRandomState(); + } + if (empty($options['scope'])) { + $options['scope'] = $this->getDefaultScopes(); + } + $options += [ + 'response_type' => 'code', + 'approval_prompt' => 'auto' + ]; + if (is_array($options['scope'])) { + $separator = $this->getScopeSeparator(); + $options['scope'] = implode($separator, $options['scope']); + } + // Store the state as it may need to be accessed later on. + $this->state = $options['state']; + + $pkceMethod = $this->getPkceMethod(); + if (!empty($pkceMethod)) { + $this->pkceCode = $this->getRandomPkceCode(); + if ($pkceMethod === static::PKCE_METHOD_S256) { + $options['code_challenge'] = trim( + strtr( + base64_encode(hash('sha256', $this->pkceCode, true)), + '+/', + '-_' + ), + '=' + ); + } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) { + $options['code_challenge'] = $this->pkceCode; + } else { + throw new InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".'); + } + $options['code_challenge_method'] = $pkceMethod; + } + + // Business code layer might set a different redirect_uri parameter + // depending on the context, leave it as-is + if (!isset($options['redirect_uri'])) { + $options['redirect_uri'] = $this->redirectUri; + } + $options['client_id'] = $this->clientId; + return $options; + } + public function getAccessToken($grant, array $options = []) + { + $grant = $this->verifyGrant($grant); + $params = [ + 'client_id' => $this->clientId, + 'client_secret' => $this->clientSecret, + 'redirect_uri' => $this->redirectUri, + ]; + + if (!empty($this->pkceCode)) { + $params['code_verifier'] = $this->pkceCode; + } + + $params = $grant->prepareRequestParameters($params, $options); + $request = $this->getAccessTokenRequest($params); + $response = $this->getParsedResponse($request); + if (false === is_array($response)) { + throw new UnexpectedValueException( + 'Invalid response received from Authorization Server. Expected JSON.' + ); + } + $prepared = $this->prepareAccessTokenResponse($response); + $token = $this->createAccessToken($prepared, $grant); + return $token; + } + protected function getConfigurableOptions() + { + return array_merge($this->getRequiredOptions(), [ + 'accessTokenMethod', + 'accessTokenResourceOwnerId', + 'scopeSeparator', + 'responseError', + 'responseCode', + 'responseResourceOwnerId', + 'scopes', + 'pkceMethod', + ]); + } + # endregion } diff --git a/tests/functional/AuthorizationCodeGrantCest.php b/tests/functional/AuthorizationCodeGrantCest.php index 2649553..d053f85 100644 --- a/tests/functional/AuthorizationCodeGrantCest.php +++ b/tests/functional/AuthorizationCodeGrantCest.php @@ -7,16 +7,13 @@ use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa\Sha256; use League\OAuth2\Client\Token\AccessToken; -use rhertogh\Yii2Oauth2Server\helpers\UrlHelper; use rhertogh\Yii2Oauth2Server\interfaces\components\authorization\Oauth2ClientAuthorizationRequestInterface; use rhertogh\Yii2Oauth2Server\Oauth2Module; use Yii; -use yii\helpers\ArrayHelper; use yii\helpers\Json; use Yii2Oauth2ServerTests\_helpers\ClientTokenProvider; -use Yii2Oauth2ServerTests\_helpers\TestUserModel; -use Yii2Oauth2ServerTests\functional\_base\BaseGrantCest; use Yii2Oauth2ServerTests\ApiTester; +use Yii2Oauth2ServerTests\functional\_base\BaseGrantCest; /** * @covers \rhertogh\Yii2Oauth2Server\controllers\web\server\Oauth2AuthorizeAction