diff --git a/.phpstan/baseline-lt-8.2.neon b/.phpstan/baseline-lt-8.2.neon index 443422a..17982aa 100644 --- a/.phpstan/baseline-lt-8.2.neon +++ b/.phpstan/baseline-lt-8.2.neon @@ -1,17 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Call to method getBytesFromString\\(\\) on an unknown class Random\\\\Randomizer\\.$#" - count: 1 - path: ../src/Core/OAuth2Provider.php - - - - message: "#^Instantiated class Random\\\\Engine\\\\Secure not found\\.$#" - count: 1 - path: ../src/Core/OAuth2Provider.php - - - - message: "#^Instantiated class Random\\\\Randomizer not found\\.$#" - count: 1 - path: ../src/Core/OAuth2Provider.php - + - message: "#^Call to method getBytesFromString\\(\\) on an unknown class Random\\\\Randomizer\\.$#" + - message: "#^Instantiated class Random\\\\Engine\\\\Secure not found\\.$#" + - message: "#^Instantiated class Random\\\\Randomizer not found\\.$#" diff --git a/.phpstan/baseline-lt-8.3.neon b/.phpstan/baseline-lt-8.3.neon index 95a742d..0ff5105 100644 --- a/.phpstan/baseline-lt-8.3.neon +++ b/.phpstan/baseline-lt-8.3.neon @@ -1,7 +1,3 @@ parameters: ignoreErrors: - - - message: "#^Call to an undefined method Random\\\\Randomizer\\:\\:getBytesFromString\\(\\)\\.$#" - count: 1 - path: ../src/Core/OAuth2Provider.php - + - message: "#^Call to an undefined method Random\\\\Randomizer\\:\\:getBytesFromString\\(\\)\\.$#" diff --git a/src/Core/ClientCredentialsTrait.php b/src/Core/ClientCredentialsTrait.php new file mode 100644 index 0000000..56437c1 --- /dev/null +++ b/src/Core/ClientCredentialsTrait.php @@ -0,0 +1,93 @@ + + * @copyright 2024 smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\OAuth\Core; + +use chillerlan\HTTP\Utils\QueryUtil; +use Psr\Http\Message\ResponseInterface; +use function implode; +use const PHP_QUERY_RFC1738; + +/** + * Implements Client Credentials functionality + * + * @see \chillerlan\OAuth\Core\ClientCredentials + */ +trait ClientCredentialsTrait{ + + /** + * implements ClientCredentials::getClientCredentialsToken() + * + * @see \chillerlan\OAuth\Core\ClientCredentials::getClientCredentialsToken() + * + * @param string[]|null $scopes + * @throws \chillerlan\OAuth\Providers\ProviderException + */ + public function getClientCredentialsToken(array|null $scopes = null):AccessToken{ + $body = $this->getClientCredentialsTokenRequestBodyParams($scopes); + $response = $this->sendClientCredentialsTokenRequest(($this->clientCredentialsTokenURL ?? $this->accessTokenURL), $body); + $token = $this->parseTokenResponse($response); + + // provider didn't send a set of scopes with the token response, so add the given ones manually + if(empty($token->scopes)){ + $token->scopes = ($scopes ?? []); + } + + $this->storage->storeAccessToken($token, $this->name); + + return $token; + } + + /** + * prepares the request body parameters for the client credentials token request + * + * @see \chillerlan\OAuth\Core\OAuth2Provider::getClientCredentialsToken() + * + * @param string[]|null $scopes + * @return array + */ + protected function getClientCredentialsTokenRequestBodyParams(array|null $scopes):array{ + $body = ['grant_type' => 'client_credentials']; + + if(!empty($scopes)){ + $body['scope'] = implode($this::SCOPES_DELIMITER, $scopes); + } + + return $body; + } + + /** + * sends a request to the client credentials endpoint, using basic authentication + * + * @see \chillerlan\OAuth\Core\OAuth2Provider::getClientCredentialsToken() + * + * @param array $body + */ + protected function sendClientCredentialsTokenRequest(string $url, array $body):ResponseInterface{ + + $request = $this->requestFactory + ->createRequest('POST', $url) + ->withHeader('Accept', 'application/json') + ->withHeader('Accept-Encoding', 'identity') + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ->withBody($this->streamFactory->createStream(QueryUtil::build($body, PHP_QUERY_RFC1738))) + ; + + foreach($this::HEADERS_AUTH as $header => $value){ + $request = $request->withHeader($header, $value); + } + + $request = $this->addBasicAuthHeader($request); + + return $this->http->sendRequest($request); + } + +} diff --git a/src/Core/OAuth2Provider.php b/src/Core/OAuth2Provider.php index a0aec2e..7ce5d30 100644 --- a/src/Core/OAuth2Provider.php +++ b/src/Core/OAuth2Provider.php @@ -17,9 +17,8 @@ use chillerlan\OAuth\Providers\ProviderException; use Psr\Http\Message\{RequestInterface, ResponseInterface, UriInterface}; use Throwable; -use function array_merge, date, explode, hash, hash_equals, implode, in_array, is_array, random_int, - sodium_bin2base64, sprintf, str_contains, strtolower, trim; -use const PHP_QUERY_RFC1738, PHP_VERSION_ID, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; +use function array_merge, date, explode, hash_equals, implode, in_array, is_array, sprintf; +use const PHP_QUERY_RFC1738; /** * Implements an abstract OAuth2 provider with all methods required by the OAuth2Interface. @@ -52,6 +51,7 @@ abstract class OAuth2Provider extends OAuthProvider implements OAuth2Interface{ * An optional PAR (Pushed Authorization Request) endpoint URL * * @see \chillerlan\OAuth\Core\PAR::getParRequestUri() + * @see \chillerlan\OAuth\Core\PARTrait::getParRequestUri() */ protected string $parAuthorizationURL = ''; @@ -290,83 +290,6 @@ public function getRequestAuthorization(RequestInterface $request, AccessToken|n } - /* - * ClientCredentials - */ - - /** - * implements ClientCredentials::getClientCredentialsToken() - * - * @see \chillerlan\OAuth\Core\ClientCredentials::getClientCredentialsToken() - * - * @param string[]|null $scopes - * @throws \chillerlan\OAuth\Providers\ProviderException - */ - public function getClientCredentialsToken(array|null $scopes = null):AccessToken{ - - if(!$this instanceof ClientCredentials){ - throw new ProviderException('client credentials token not supported'); - } - - $body = $this->getClientCredentialsTokenRequestBodyParams($scopes); - $response = $this->sendClientCredentialsTokenRequest(($this->clientCredentialsTokenURL ?? $this->accessTokenURL), $body); - $token = $this->parseTokenResponse($response); - - // provider didn't send a set of scopes with the token response, so add the given ones manually - if(empty($token->scopes)){ - $token->scopes = ($scopes ?? []); - } - - $this->storage->storeAccessToken($token, $this->name); - - return $token; - } - - /** - * prepares the request body parameters for the client credentials token request - * - * @see \chillerlan\OAuth\Core\OAuth2Provider::getClientCredentialsToken() - * - * @param string[]|null $scopes - * @return array - */ - protected function getClientCredentialsTokenRequestBodyParams(array|null $scopes):array{ - $body = ['grant_type' => 'client_credentials']; - - if(!empty($scopes)){ - $body['scope'] = implode($this::SCOPES_DELIMITER, $scopes); - } - - return $body; - } - - /** - * sends a request to the client credentials endpoint, using basic authentication - * - * @see \chillerlan\OAuth\Core\OAuth2Provider::getClientCredentialsToken() - * - * @param array $body - */ - protected function sendClientCredentialsTokenRequest(string $url, array $body):ResponseInterface{ - - $request = $this->requestFactory - ->createRequest('POST', $url) - ->withHeader('Accept', 'application/json') - ->withHeader('Accept-Encoding', 'identity') - ->withHeader('Content-Type', 'application/x-www-form-urlencoded') - ->withBody($this->streamFactory->createStream(QueryUtil::build($body, PHP_QUERY_RFC1738))) - ; - - foreach($this::HEADERS_AUTH as $header => $value){ - $request = $request->withHeader($header, $value); - } - - $request = $this->addBasicAuthHeader($request); - - return $this->http->sendRequest($request); - } - - /* * TokenRefresh */ @@ -423,87 +346,6 @@ protected function getRefreshAccessTokenRequestBodyParams(string $refreshToken): } - /* - * TokenInvalidate - */ - - /** - * implements TokenInvalidate::invalidateAccessToken() - * - * @see \chillerlan\OAuth\Core\TokenInvalidate::invalidateAccessToken() - * @throws \chillerlan\OAuth\Providers\ProviderException - */ - public function invalidateAccessToken(AccessToken|null $token = null, string|null $type = null):bool{ - $type = strtolower(trim(($type ?? 'access_token'))); - - // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 - if(!in_array($type, ['access_token', 'refresh_token'], true)){ - throw new ProviderException(sprintf('invalid token type "%s"', $type)); - } - - $tokenToInvalidate = ($token ?? $this->storage->getAccessToken($this->name)); - $body = $this->getInvalidateAccessTokenBodyParams($tokenToInvalidate, $type); - $response = $this->sendTokenInvalidateRequest($this->revokeURL, $body); - - // some endpoints may return 204, others 200 with empty body - if(in_array($response->getStatusCode(), [200, 204], true)){ - - // if the token was given via parameter it cannot be deleted from storage - if($token === null){ - $this->storage->clearAccessToken($this->name); - } - - return true; - } - - // ok, let's see if we got a response body - // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1 - if(str_contains($response->getHeaderLine('content-type'), 'json')){ - $json = MessageUtil::decodeJSON($response); - - if(isset($json['error'])){ - throw new ProviderException($json['error']); - } - } - - return false; - } - - /** - * Prepares and sends a request to the token invalidation endpoint - * - * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() - * - * @param array $body - */ - protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ - - $request = $this->requestFactory - ->createRequest('POST', $url) - ->withHeader('Content-Type', 'application/x-www-form-urlencoded') - ; - - // some enpoints may require a basic auth header here - $request = $this->setRequestBody($body, $request); - - return $this->http->sendRequest($request); - } - - /** - * Prepares the body for a token revocation request - * - * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() - * - * @return array - */ - protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ - return [ - 'token' => $token->accessToken, - 'token_type_hint' => $type, - ]; - } - - /* * CSRFToken */ @@ -561,177 +403,4 @@ final public function checkState(string|null $state = null):void{ } - - /* - * PKCE - */ - - /** - * implements PKCE::setCodeChallenge() - * - * @see \chillerlan\OAuth\Core\PKCE::setCodeChallenge() - * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURLRequestParams() - * - * @param array $params - * @return array - */ - final public function setCodeChallenge(array $params, string $challengeMethod):array{ - - if(!$this instanceof PKCE){ - throw new ProviderException('PKCE challenge not supported'); - } - - if(!isset($params['response_type']) || $params['response_type'] !== 'code'){ - throw new ProviderException('invalid authorization request params'); - } - - $verifier = $this->generateVerifier($this->options->pkceVerifierLength); - - $params['code_challenge'] = $this->generateChallenge($verifier, $challengeMethod); - $params['code_challenge_method'] = $challengeMethod; - - $this->storage->storeCodeVerifier($verifier, $this->name); - - return $params; - } - - /** - * implements PKCE::setCodeVerifier() - * - * @see \chillerlan\OAuth\Core\PKCE::setCodeVerifier() - * @see \chillerlan\OAuth\Core\OAuth2Provider::getAccessTokenRequestBodyParams() - * - * @param array $params - * @return array - */ - final public function setCodeVerifier(array $params):array{ - - if(!$this instanceof PKCE){ - throw new ProviderException('PKCE challenge not supported'); - } - - if(!isset($params['grant_type'], $params['code']) || $params['grant_type'] !== 'authorization_code'){ - throw new ProviderException('invalid authorization request body'); - } - - $params['code_verifier'] = $this->storage->getCodeVerifier($this->name); - - // delete verifier after use - $this->storage->clearCodeVerifier($this->name); - - return $params; - } - - /** - * implements PKCE::generateVerifier() - * - * @see \chillerlan\OAuth\Core\PKCE::generateVerifier() - * @see \chillerlan\OAuth\Core\OAuth2Provider::setCodeChallenge() - * - * @noinspection PhpFullyQualifiedNameUsageInspection - * @SuppressWarnings(PHPMD.MissingImport) - */ - final public function generateVerifier(int $length):string{ - - // use the Randomizer if available - // https://github.com/phpstan/phpstan/issues/7843 - if(PHP_VERSION_ID >= 80300){ - $randomizer = new \Random\Randomizer(new \Random\Engine\Secure); - - return $randomizer->getBytesFromString(PKCE::VERIFIER_CHARSET, $length); - } - - $str = ''; - - for($i = 0; $i < $length; $i++){ - $str .= PKCE::VERIFIER_CHARSET[random_int(0, 65)]; - } - - return $str; - } - - /** - * implements PKCE::generateChallenge() - * - * @see \chillerlan\OAuth\Core\PKCE::generateChallenge() - * @see \chillerlan\OAuth\Core\OAuth2Provider::setCodeChallenge() - */ - final public function generateChallenge(string $verifier, string $challengeMethod):string{ - - if($challengeMethod === PKCE::CHALLENGE_METHOD_PLAIN){ - return $verifier; - } - - $verifier = match($challengeMethod){ - PKCE::CHALLENGE_METHOD_S256 => hash('sha256', $verifier, true), - // no other hash methods yet - default => throw new ProviderException('invalid PKCE challenge method'), - }; - - return sodium_bin2base64($verifier, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); - } - - - /* - * PAR - */ - - /** - * implements PAR::getParRequestUri() - * - * @see \chillerlan\OAuth\Core\PAR::getParRequestUri() - * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURL() - * - * @param array $body - */ - public function getParRequestUri(array $body):UriInterface{ - - if(!$this instanceof PAR){ - throw new ProviderException('PKCE challenge not supported'); - } - - // send the request with the same method and parameters as the token requests - // @link https://datatracker.ietf.org/doc/html/rfc9126#name-request - $response = $this->sendAccessTokenRequest($this->parAuthorizationURL, $body); - $status = $response->getStatusCode(); - $json = MessageUtil::decodeJSON($response, true); - - // something went horribly wrong - if($status !== 200){ - - // @link https://datatracker.ietf.org/doc/html/rfc9126#section-2.3 - if(isset($json['error'], $json['error_description'])){ - throw new ProviderException(sprintf('PAR error: "%s" (%s)', $json['error'], $json['error_description'])); - } - - throw new ProviderException(sprintf('PAR request error: (HTTP/%s)', $status)); // @codeCoverageIgnore - } - - $url = QueryUtil::merge($this->authorizationURL, $this->getParAuthorizationURLRequestParams($json)); - - return $this->uriFactory->createUri($url); - } - - /** - * Parses the response from the PAR request and returns the query parameters for the authorization URL - * - * @see \chillerlan\OAuth\Core\OAuth2Provider::getParRequestUri() - * - * @param array $response - * @return array - * - * @codeCoverageIgnore - */ - protected function getParAuthorizationURLRequestParams(array $response):array{ - - if(!isset($response['request_uri'])){ - throw new ProviderException('PAR response error: "request_uri" missing'); - } - - return [ - 'client_id' => $this->options->key, - 'request_uri' => $response['request_uri'], - ]; - } - } diff --git a/src/Core/OAuthProvider.php b/src/Core/OAuthProvider.php index eb48dfa..a49df03 100644 --- a/src/Core/OAuthProvider.php +++ b/src/Core/OAuthProvider.php @@ -85,6 +85,7 @@ abstract class OAuthProvider implements OAuthInterface{ * An optional URL for application side token revocation * * @see \chillerlan\OAuth\Core\TokenInvalidate + * @see \chillerlan\OAuth\Core\TokenInvalidateTrait::invalidateAccessToken() */ protected string $revokeURL = ''; diff --git a/src/Core/PARTrait.php b/src/Core/PARTrait.php new file mode 100644 index 0000000..705a3e9 --- /dev/null +++ b/src/Core/PARTrait.php @@ -0,0 +1,80 @@ + + * @copyright 2024 smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\OAuth\Core; + +use chillerlan\HTTP\Utils\MessageUtil; +use chillerlan\HTTP\Utils\QueryUtil; +use chillerlan\OAuth\Providers\ProviderException; +use Psr\Http\Message\UriInterface; +use function sprintf; + +/** + * Implements PAR (Pushed Authorization Requests) functionality + * + * @see \chillerlan\OAuth\Core\PAR + */ +trait PARTrait{ + + /** + * implements PAR::getParRequestUri() + * + * @see \chillerlan\OAuth\Core\PAR::getParRequestUri() + * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURL() + * + * @param array $body + */ + public function getParRequestUri(array $body):UriInterface{ + // send the request with the same method and parameters as the token requests + // @link https://datatracker.ietf.org/doc/html/rfc9126#name-request + $response = $this->sendAccessTokenRequest($this->parAuthorizationURL, $body); + $status = $response->getStatusCode(); + $json = MessageUtil::decodeJSON($response, true); + + // something went horribly wrong + if($status !== 200){ + + // @link https://datatracker.ietf.org/doc/html/rfc9126#section-2.3 + if(isset($json['error'], $json['error_description'])){ + throw new ProviderException(sprintf('PAR error: "%s" (%s)', $json['error'], $json['error_description'])); + } + + throw new ProviderException(sprintf('PAR request error: (HTTP/%s)', $status)); // @codeCoverageIgnore + } + + $url = QueryUtil::merge($this->authorizationURL, $this->getParAuthorizationURLRequestParams($json)); + + return $this->uriFactory->createUri($url); + } + + /** + * Parses the response from the PAR request and returns the query parameters for the authorization URL + * + * @see \chillerlan\OAuth\Core\OAuth2Provider::getParRequestUri() + * + * @param array $response + * @return array + * + * @codeCoverageIgnore + */ + protected function getParAuthorizationURLRequestParams(array $response):array{ + + if(!isset($response['request_uri'])){ + throw new ProviderException('PAR response error: "request_uri" missing'); + } + + return [ + 'client_id' => $this->options->key, + 'request_uri' => $response['request_uri'], + ]; + } + +} diff --git a/src/Core/PKCETrait.php b/src/Core/PKCETrait.php new file mode 100644 index 0000000..11ab981 --- /dev/null +++ b/src/Core/PKCETrait.php @@ -0,0 +1,129 @@ + + * @copyright 2024 smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\OAuth\Core; + +use chillerlan\OAuth\Providers\ProviderException; +use function hash; +use function random_int; +use function sodium_bin2base64; +use const PHP_VERSION_ID; +use const SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING; + +/** + * Implements PKCE (Proof Key for Code Exchange) functionality + * + * @see \chillerlan\OAuth\Core\PKCE + */ +trait PKCETrait{ + + /** + * implements PKCE::setCodeChallenge() + * + * @see \chillerlan\OAuth\Core\PKCE::setCodeChallenge() + * @see \chillerlan\OAuth\Core\OAuth2Provider::getAuthorizationURLRequestParams() + * + * @param array $params + * @return array + */ + final public function setCodeChallenge(array $params, string $challengeMethod):array{ + + if(!isset($params['response_type']) || $params['response_type'] !== 'code'){ + throw new ProviderException('invalid authorization request params'); + } + + $verifier = $this->generateVerifier($this->options->pkceVerifierLength); + + $params['code_challenge'] = $this->generateChallenge($verifier, $challengeMethod); + $params['code_challenge_method'] = $challengeMethod; + + $this->storage->storeCodeVerifier($verifier, $this->name); + + return $params; + } + + /** + * implements PKCE::setCodeVerifier() + * + * @see \chillerlan\OAuth\Core\PKCE::setCodeVerifier() + * @see \chillerlan\OAuth\Core\OAuth2Provider::getAccessTokenRequestBodyParams() + * + * @param array $params + * @return array + */ + final public function setCodeVerifier(array $params):array{ + + if(!$this instanceof PKCE){ + throw new ProviderException('PKCE challenge not supported'); + } + + if(!isset($params['grant_type'], $params['code']) || $params['grant_type'] !== 'authorization_code'){ + throw new ProviderException('invalid authorization request body'); + } + + $params['code_verifier'] = $this->storage->getCodeVerifier($this->name); + + // delete verifier after use + $this->storage->clearCodeVerifier($this->name); + + return $params; + } + + /** + * implements PKCE::generateVerifier() + * + * @see \chillerlan\OAuth\Core\PKCE::generateVerifier() + * @see \chillerlan\OAuth\Core\OAuth2Provider::setCodeChallenge() + * + * @noinspection PhpFullyQualifiedNameUsageInspection + * @SuppressWarnings(PHPMD.MissingImport) + */ + final public function generateVerifier(int $length):string{ + + // use the Randomizer if available + // https://github.com/phpstan/phpstan/issues/7843 + if(PHP_VERSION_ID >= 80300){ + $randomizer = new \Random\Randomizer(new \Random\Engine\Secure); + + return $randomizer->getBytesFromString(PKCE::VERIFIER_CHARSET, $length); + } + + $str = ''; + + for($i = 0; $i < $length; $i++){ + $str .= PKCE::VERIFIER_CHARSET[random_int(0, 65)]; + } + + return $str; + } + + /** + * implements PKCE::generateChallenge() + * + * @see \chillerlan\OAuth\Core\PKCE::generateChallenge() + * @see \chillerlan\OAuth\Core\OAuth2Provider::setCodeChallenge() + */ + final public function generateChallenge(string $verifier, string $challengeMethod):string{ + + if($challengeMethod === PKCE::CHALLENGE_METHOD_PLAIN){ + return $verifier; + } + + $verifier = match($challengeMethod){ + PKCE::CHALLENGE_METHOD_S256 => hash('sha256', $verifier, true), + // no other hash methods yet + default => throw new ProviderException('invalid PKCE challenge method'), + }; + + return sodium_bin2base64($verifier, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING); + } + +} diff --git a/src/Core/TokenInvalidateTrait.php b/src/Core/TokenInvalidateTrait.php new file mode 100644 index 0000000..ee78878 --- /dev/null +++ b/src/Core/TokenInvalidateTrait.php @@ -0,0 +1,106 @@ + + * @copyright 2024 smiley + * @license MIT + */ +declare(strict_types=1); + +namespace chillerlan\OAuth\Core; + +use chillerlan\HTTP\Utils\MessageUtil; +use chillerlan\OAuth\Providers\ProviderException; +use Psr\Http\Message\ResponseInterface; +use function in_array; +use function sprintf; +use function str_contains; +use function strtolower; +use function trim; + +/** + * Implements token invalidation functionality + * + * @see \chillerlan\OAuth\Core\TokenInvalidate + */ +trait TokenInvalidateTrait{ + + /** + * implements TokenInvalidate::invalidateAccessToken() + * + * @see \chillerlan\OAuth\Core\TokenInvalidate::invalidateAccessToken() + * @throws \chillerlan\OAuth\Providers\ProviderException + */ + public function invalidateAccessToken(AccessToken|null $token = null, string|null $type = null):bool{ + $type = strtolower(trim(($type ?? 'access_token'))); + + // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 + if(!in_array($type, ['access_token', 'refresh_token'], true)){ + throw new ProviderException(sprintf('invalid token type "%s"', $type)); + } + + $tokenToInvalidate = ($token ?? $this->storage->getAccessToken($this->name)); + $body = $this->getInvalidateAccessTokenBodyParams($tokenToInvalidate, $type); + $response = $this->sendTokenInvalidateRequest($this->revokeURL, $body); + + // some endpoints may return 204, others 200 with empty body + if(in_array($response->getStatusCode(), [200, 204], true)){ + + // if the token was given via parameter it cannot be deleted from storage + if($token === null){ + $this->storage->clearAccessToken($this->name); + } + + return true; + } + + // ok, let's see if we got a response body + // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1 + if(str_contains($response->getHeaderLine('content-type'), 'json')){ + $json = MessageUtil::decodeJSON($response); + + if(isset($json['error'])){ + throw new ProviderException($json['error']); + } + } + + return false; + } + + /** + * Prepares the body for a token revocation request + * + * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() + * + * @return array + */ + protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ + return [ + 'token' => $token->accessToken, + 'token_type_hint' => $type, + ]; + } + + /** + * Prepares and sends a request to the token invalidation endpoint + * + * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() + * + * @param array $body + */ + protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ + + $request = $this->requestFactory + ->createRequest('POST', $url) + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') + ; + + // some enpoints may require a basic auth header here + $request = $this->setRequestBody($body, $request); + + return $this->http->sendRequest($request); + } + +} diff --git a/src/Providers/BattleNet.php b/src/Providers/BattleNet.php index ac4d098..41fc8ef 100644 --- a/src/Providers/BattleNet.php +++ b/src/Providers/BattleNet.php @@ -11,7 +11,7 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, UserInfo}; +use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Provider, UserInfo}; use function in_array, ltrim, rtrim, sprintf, strtolower; /** @@ -20,6 +20,7 @@ * @link https://develop.battle.net/documentation/guides/using-oauth */ class BattleNet extends OAuth2Provider implements ClientCredentials, CSRFToken, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'BATTLENET'; diff --git a/src/Providers/BigCartel.php b/src/Providers/BigCartel.php index a53daaa..e486e95 100644 --- a/src/Providers/BigCartel.php +++ b/src/Providers/BigCartel.php @@ -11,7 +11,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AccessToken, AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, UserInfo}; +use chillerlan\OAuth\Core\{ + AccessToken, AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, UserInfo, +}; use function sprintf; /** @@ -21,6 +23,7 @@ * @link https://bigcartel.wufoo.com/confirm/big-cartel-api-application/ */ class BigCartel extends OAuth2Provider implements CSRFToken, TokenInvalidate, UserInfo{ + use TokenInvalidateTrait; public const IDENTIFIER = 'BIGCARTEL'; diff --git a/src/Providers/Bitbucket.php b/src/Providers/Bitbucket.php index 18634c5..b134501 100644 --- a/src/Providers/Bitbucket.php +++ b/src/Providers/Bitbucket.php @@ -11,7 +11,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo, +}; /** * Bitbucket OAuth2 (Atlassian) @@ -19,6 +21,7 @@ * @link https://developer.atlassian.com/cloud/bitbucket/oauth-2/ */ class Bitbucket extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenRefresh, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'BITBUCKET'; diff --git a/src/Providers/Codeberg.php b/src/Providers/Codeberg.php index f73d567..f55dfd8 100644 --- a/src/Providers/Codeberg.php +++ b/src/Providers/Codeberg.php @@ -13,7 +13,7 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, CSRFToken, OAuth2Provider, PKCE, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{AuthenticatedUser, CSRFToken, OAuth2Provider, PKCE, PKCETrait, TokenRefresh, UserInfo}; use function sprintf; /** @@ -24,6 +24,7 @@ * @link https://codeberg.org/api/swagger */ class Codeberg extends OAuth2Provider implements CSRFToken, PKCE, TokenRefresh, UserInfo{ + use PKCETrait; public const IDENTIFIER = 'CODEBERG'; diff --git a/src/Providers/DeviantArt.php b/src/Providers/DeviantArt.php index 6c8288c..8086e85 100644 --- a/src/Providers/DeviantArt.php +++ b/src/Providers/DeviantArt.php @@ -15,7 +15,8 @@ use chillerlan\HTTP\Utils\MessageUtil; use chillerlan\OAuth\Core\{ - AccessToken, AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo + AccessToken, AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, + CSRFToken, OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo, }; use chillerlan\OAuth\Storage\MemoryStorage; use Throwable; @@ -27,6 +28,7 @@ * @link https://www.deviantart.com/developers/ */ class DeviantArt extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenInvalidate, TokenRefresh, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'DEVIANTART'; diff --git a/src/Providers/Discord.php b/src/Providers/Discord.php index 61e0a54..d0714d3 100644 --- a/src/Providers/Discord.php +++ b/src/Providers/Discord.php @@ -14,7 +14,8 @@ namespace chillerlan\OAuth\Providers; use chillerlan\OAuth\Core\{ - AccessToken, AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo + AccessToken, AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, + OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, TokenRefresh, UserInfo, }; use function sprintf; @@ -24,6 +25,7 @@ * @link https://discord.com/developers/docs/topics/oauth2 */ class Discord extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenInvalidate, TokenRefresh, UserInfo{ + use ClientCredentialsTrait, TokenInvalidateTrait; public const IDENTIFIER = 'DISCORD'; @@ -66,6 +68,7 @@ class Discord extends OAuth2Provider implements ClientCredentials, CSRFToken, To /** * @link https://github.com/discord/discord-api-docs/issues/2259#issuecomment-927180184 + * @return array */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ return [ diff --git a/src/Providers/GitLab.php b/src/Providers/GitLab.php index d36622b..1499694 100644 --- a/src/Providers/GitLab.php +++ b/src/Providers/GitLab.php @@ -11,7 +11,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo, +}; /** * GitLab OAuth2 @@ -19,6 +21,7 @@ * @link https://docs.gitlab.com/ee/api/oauth2.html */ class GitLab extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenRefresh, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'GITLAB'; diff --git a/src/Providers/Gitea.php b/src/Providers/Gitea.php index 34af0a0..5231b8b 100644 --- a/src/Providers/Gitea.php +++ b/src/Providers/Gitea.php @@ -13,7 +13,7 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, CSRFToken, OAuth2Provider, PKCE, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{AuthenticatedUser, CSRFToken, OAuth2Provider, PKCE, PKCETrait, TokenRefresh, UserInfo}; use function sprintf; /** @@ -22,6 +22,7 @@ * @link https://docs.gitea.com/development/oauth2-provider */ class Gitea extends OAuth2Provider implements CSRFToken, PKCE, TokenRefresh, UserInfo{ + use PKCETrait; public const IDENTIFIER = 'GITEA'; diff --git a/src/Providers/Google.php b/src/Providers/Google.php index e291447..17c3a75 100644 --- a/src/Providers/Google.php +++ b/src/Providers/Google.php @@ -13,7 +13,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, CSRFToken, OAuth2Provider, PKCE, TokenInvalidate, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, CSRFToken, OAuth2Provider, PKCE, PKCETrait, TokenInvalidate, TokenInvalidateTrait, UserInfo, +}; /** * Google OAuth2 @@ -23,6 +25,7 @@ * @link https://developers.google.com/oauthplayground/ */ class Google extends OAuth2Provider implements CSRFToken, PKCE, TokenInvalidate, UserInfo{ + use PKCETrait, TokenInvalidateTrait; public const IDENTIFIER = 'GOOGLE'; diff --git a/src/Providers/MusicBrainz.php b/src/Providers/MusicBrainz.php index 0c7ef32..60f03ef 100644 --- a/src/Providers/MusicBrainz.php +++ b/src/Providers/MusicBrainz.php @@ -14,7 +14,7 @@ namespace chillerlan\OAuth\Providers; use chillerlan\OAuth\Core\{ - AccessToken, AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo + AccessToken, AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, TokenRefresh, UserInfo, }; use Psr\Http\Message\{ResponseInterface, StreamInterface}; use function in_array, strtoupper; @@ -26,6 +26,7 @@ * @link https://musicbrainz.org/doc/Development/OAuth2 */ class MusicBrainz extends OAuth2Provider implements CSRFToken, TokenInvalidate, TokenRefresh, UserInfo{ + use TokenInvalidateTrait; public const IDENTIFIER = 'MUSICBRAINZ'; @@ -62,6 +63,9 @@ protected function getRefreshAccessTokenRequestBodyParams(string $refreshToken): ]; } + /** + * @return array + */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ return [ 'client_id' => $this->options->key, diff --git a/src/Providers/NPROne.php b/src/Providers/NPROne.php index ebb7bf1..b175f3e 100644 --- a/src/Providers/NPROne.php +++ b/src/Providers/NPROne.php @@ -13,7 +13,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, TokenRefresh, UserInfo, +}; use function in_array, sprintf, strtolower; /** @@ -23,6 +25,7 @@ * @link https://github.com/npr/npr-one-backend-proxy-php */ class NPROne extends OAuth2Provider implements CSRFToken, TokenRefresh, TokenInvalidate, UserInfo{ + use TokenInvalidateTrait; public const IDENTIFIER = 'NPRONE'; diff --git a/src/Providers/PayPal.php b/src/Providers/PayPal.php index bb51eb6..e127817 100644 --- a/src/Providers/PayPal.php +++ b/src/Providers/PayPal.php @@ -13,7 +13,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo, +}; /** * PayPal OAuth2 @@ -21,6 +23,7 @@ * @link https://developer.paypal.com/api/rest/ */ class PayPal extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenRefresh, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'PAYPAL'; diff --git a/src/Providers/Reddit.php b/src/Providers/Reddit.php index 4a054d3..d501ddd 100644 --- a/src/Providers/Reddit.php +++ b/src/Providers/Reddit.php @@ -14,8 +14,8 @@ namespace chillerlan\OAuth\Providers; use chillerlan\OAuth\Core\{ - AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Interface, - OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo + AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Interface, + OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, TokenRefresh, UserInfo, }; use Psr\Http\Message\ResponseInterface; use function sprintf; @@ -30,6 +30,7 @@ * @link https://www.reddit.com/dev/api */ class Reddit extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenRefresh, TokenInvalidate, UserInfo{ + use ClientCredentialsTrait, TokenInvalidateTrait; public const IDENTIFIER = 'REDDIT'; @@ -89,6 +90,9 @@ class Reddit extends OAuth2Provider implements ClientCredentials, CSRFToken, Tok protected string|null $applicationURL = 'https://www.reddit.com/prefs/apps/'; protected string|null $userRevokeURL = 'https://www.reddit.com/settings/privacy'; + /** + * @param array $body + */ protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ // phpcs:ignore $request = $this->requestFactory diff --git a/src/Providers/SoundCloud.php b/src/Providers/SoundCloud.php index 1cd7fae..ce063ac 100644 --- a/src/Providers/SoundCloud.php +++ b/src/Providers/SoundCloud.php @@ -11,7 +11,7 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, OAuth2Provider, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, OAuth2Provider, TokenRefresh, UserInfo}; /** * SoundCloud OAuth2 @@ -21,6 +21,7 @@ * @link https://developers.soundcloud.com/blog/security-updates-api */ class SoundCloud extends OAuth2Provider implements ClientCredentials, TokenRefresh, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'SOUNDCLOUD'; diff --git a/src/Providers/Spotify.php b/src/Providers/Spotify.php index 66f1fb5..43af95b 100644 --- a/src/Providers/Spotify.php +++ b/src/Providers/Spotify.php @@ -13,7 +13,10 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, PKCE, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, + OAuth2Provider, PKCE, PKCETrait, TokenRefresh, UserInfo, +}; /** * Spotify OAuth2 @@ -24,6 +27,7 @@ * @link https://developer.spotify.com/documentation/web-api/tutorials/code-pkce-flow */ class Spotify extends OAuth2Provider implements ClientCredentials, CSRFToken, PKCE, TokenRefresh, UserInfo{ + use ClientCredentialsTrait, PKCETrait; public const IDENTIFIER = 'SPOTIFY'; diff --git a/src/Providers/Stripe.php b/src/Providers/Stripe.php index f84ea55..fc4c57a 100644 --- a/src/Providers/Stripe.php +++ b/src/Providers/Stripe.php @@ -13,7 +13,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AccessToken, AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AccessToken, AuthenticatedUser, CSRFToken, OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, TokenRefresh, UserInfo, +}; /** * Stripe OAuth2 @@ -25,6 +27,7 @@ * @link https://gist.github.com/amfeng/3507366 */ class Stripe extends OAuth2Provider implements CSRFToken, TokenRefresh, TokenInvalidate, UserInfo{ + use TokenInvalidateTrait; public const IDENTIFIER = 'STRIPE'; @@ -55,6 +58,9 @@ public function me():AuthenticatedUser{ return new AuthenticatedUser($userdata); } + /** + * @return array + */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ $params = $token->extraParams; diff --git a/src/Providers/Tumblr2.php b/src/Providers/Tumblr2.php index a0fdef0..e816367 100644 --- a/src/Providers/Tumblr2.php +++ b/src/Providers/Tumblr2.php @@ -11,7 +11,9 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo}; +use chillerlan\OAuth\Core\{ + AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Provider, TokenRefresh, UserInfo, +}; use function sprintf; /** @@ -20,6 +22,7 @@ * @link https://www.tumblr.com/docs/en/api/v2#oauth2-authorization */ class Tumblr2 extends OAuth2Provider implements CSRFToken, TokenRefresh, ClientCredentials, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'TUMBLR2'; diff --git a/src/Providers/Twitch.php b/src/Providers/Twitch.php index 2dca3e2..f588d1a 100644 --- a/src/Providers/Twitch.php +++ b/src/Providers/Twitch.php @@ -15,8 +15,8 @@ use chillerlan\HTTP\Utils\QueryUtil; use chillerlan\OAuth\Core\{ - AccessToken, AuthenticatedUser, ClientCredentials, CSRFToken, InvalidAccessTokenException, - OAuth2Provider, TokenInvalidate, TokenRefresh, UserInfo + AccessToken, AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, CSRFToken, InvalidAccessTokenException, + OAuth2Provider, TokenInvalidate, TokenInvalidateTrait, TokenRefresh, UserInfo, }; use Psr\Http\Message\{RequestInterface, ResponseInterface}; use function implode, sprintf; @@ -30,6 +30,7 @@ * @link https://dev.twitch.tv/docs/authentication#oauth-client-credentials-flow-app-access-tokens */ class Twitch extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenInvalidate, TokenRefresh, UserInfo{ + use ClientCredentialsTrait, TokenInvalidateTrait; public const IDENTIFIER = 'TWITCH'; @@ -76,6 +77,10 @@ class Twitch extends OAuth2Provider implements ClientCredentials, CSRFToken, Tok protected string|null $apiDocs = 'https://dev.twitch.tv/docs/api/reference/'; protected string|null $applicationURL = 'https://dev.twitch.tv/console/apps/create'; + /** + * @param string[]|null $scopes + * @return array + */ protected function getClientCredentialsTokenRequestBodyParams(array|null $scopes):array{ $params = [ @@ -110,6 +115,9 @@ protected function sendClientCredentialsTokenRequest(string $url, array $body):R return $this->http->sendRequest($request); } + /** + * @return array + */ protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ return [ 'client_id' => $this->options->key, diff --git a/src/Providers/TwitterCC.php b/src/Providers/TwitterCC.php index 41eb5c5..19e850b 100644 --- a/src/Providers/TwitterCC.php +++ b/src/Providers/TwitterCC.php @@ -11,7 +11,7 @@ namespace chillerlan\OAuth\Providers; -use chillerlan\OAuth\Core\{AccessToken, ClientCredentials, OAuth2Provider}; +use chillerlan\OAuth\Core\{AccessToken, ClientCredentials, ClientCredentialsTrait, OAuth2Provider}; use Psr\Http\Message\UriInterface; /** @@ -25,6 +25,7 @@ * @todo: https://developer.twitter.com/en/docs/basics/authentication/api-reference/invalidate_token */ class TwitterCC extends OAuth2Provider implements ClientCredentials{ + use ClientCredentialsTrait; public const IDENTIFIER = 'TWITTERCC'; diff --git a/src/Providers/Vimeo.php b/src/Providers/Vimeo.php index cfb050a..5ad5d0c 100644 --- a/src/Providers/Vimeo.php +++ b/src/Providers/Vimeo.php @@ -14,7 +14,8 @@ namespace chillerlan\OAuth\Providers; use chillerlan\OAuth\Core\{ - AccessToken, AuthenticatedUser, ClientCredentials, CSRFToken, OAuth2Provider, TokenInvalidate, UserInfo + AccessToken, AuthenticatedUser, ClientCredentials, ClientCredentialsTrait, + CSRFToken, OAuth2Provider, TokenInvalidate, UserInfo, }; use chillerlan\OAuth\Storage\MemoryStorage; use function str_replace; @@ -26,6 +27,7 @@ * @link https://developer.vimeo.com/api/authentication */ class Vimeo extends OAuth2Provider implements ClientCredentials, CSRFToken, TokenInvalidate, UserInfo{ + use ClientCredentialsTrait; public const IDENTIFIER = 'VIMEO'; diff --git a/tests/Providers/DummyOAuth1Provider.php b/tests/Providers/DummyOAuth1Provider.php index a282513..4f11ccd 100644 --- a/tests/Providers/DummyOAuth1Provider.php +++ b/tests/Providers/DummyOAuth1Provider.php @@ -11,12 +11,13 @@ namespace chillerlan\OAuthTest\Providers; -use chillerlan\OAuth\Core\{AccessToken, OAuth1Provider, TokenInvalidate}; +use chillerlan\OAuth\Core\{AccessToken, OAuth1Provider, TokenInvalidate, TokenInvalidateTrait}; /** * An OAuth1 provider implementation */ final class DummyOAuth1Provider extends OAuth1Provider implements TokenInvalidate{ + use TokenInvalidateTrait; public const IDENTIFIER = 'DUMMYOAUTH1PROVIDER'; diff --git a/tests/Providers/DummyOAuth2Provider.php b/tests/Providers/DummyOAuth2Provider.php index 468f63e..5c15f2f 100644 --- a/tests/Providers/DummyOAuth2Provider.php +++ b/tests/Providers/DummyOAuth2Provider.php @@ -11,13 +11,17 @@ namespace chillerlan\OAuthTest\Providers; -use chillerlan\OAuth\Core\{AccessToken, ClientCredentials, CSRFToken, OAuth2Provider, PAR, PKCE, TokenInvalidate, TokenRefresh}; +use chillerlan\OAuth\Core\{ + AccessToken, ClientCredentials, ClientCredentialsTrait, CSRFToken, OAuth2Provider, PAR, PARTrait, + PKCE, PKCETrait, TokenInvalidate, TokenInvalidateTrait, TokenRefresh +}; /** * An OAuth2 provider implementation that supports token refresh, csrf tokens and client credentials */ final class DummyOAuth2Provider extends OAuth2Provider implements ClientCredentials, CSRFToken, PAR, PKCE, TokenRefresh, TokenInvalidate{ + use ClientCredentialsTrait, PARTrait, PKCETrait, TokenInvalidateTrait; public const IDENTIFIER = 'DUMMYOAUTH2PROVIDER'; diff --git a/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php b/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php index 2bf6185..1ce5382 100644 --- a/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php +++ b/tests/Providers/Unit/OAuth2ProviderUnitTestAbstract.php @@ -229,9 +229,13 @@ public function testGetAccessToken():void{ } public function testGetAccessTokenRequestBodyParams():void{ - $verifier = $this->provider->generateVerifier($this->options->pkceVerifierLength); + $verifier = ''; - $this->storage->storeCodeVerifier($verifier, $this->provider->getName()); + if($this->provider instanceof PKCE){ + $verifier = $this->provider->generateVerifier($this->options->pkceVerifierLength); + + $this->storage->storeCodeVerifier($verifier, $this->provider->getName()); + } $params = $this->invokeReflectionMethod('getAccessTokenRequestBodyParams', ['*test_code*']); @@ -371,17 +375,6 @@ public function testClientCredentialsTokenRequest():void{ $this::assertSame('foo=bar', $json->body); } - public function testGetClientCredentialsNotSupportedException():void{ - - if($this->provider instanceof ClientCredentials){ - $this->markTestSkipped('ClientCredentials supported'); - } - - $this->expectException(ProviderException::class); - $this->expectExceptionMessage('client credentials token not supported'); - - $this->provider->getClientCredentialsToken(); - } /* * CSRF state @@ -599,28 +592,4 @@ public function testSetCodeVerifierInvalidParams():void{ $this->provider->setCodeVerifier(['param' => 'value']); } - public function testSetCodeChallengeNotSupportedException():void{ - - if($this->provider instanceof PKCE){ - $this->markTestSkipped('PKCE supported'); - } - - $this->expectException(ProviderException::class); - $this->expectExceptionMessage('PKCE challenge not supported'); - - $this->provider->setCodeChallenge([], PKCE::CHALLENGE_METHOD_S256); - } - - public function testSetCodeVerifierNotSupportedException():void{ - - if($this->provider instanceof PKCE){ - $this->markTestSkipped('PKCE supported'); - } - - $this->expectException(ProviderException::class); - $this->expectExceptionMessage('PKCE challenge not supported'); - - $this->provider->setCodeVerifier([]); - } - }