|
17 | 17 | use chillerlan\OAuth\Providers\ProviderException;
|
18 | 18 | use Psr\Http\Message\{RequestInterface, ResponseInterface, UriInterface};
|
19 | 19 | use Throwable;
|
20 |
| -use function array_merge, date, explode, hash, hash_equals, implode, in_array, is_array, random_int, sodium_bin2base64, sprintf; |
| 20 | +use function array_merge, date, explode, hash, hash_equals, implode, in_array, is_array, random_int, |
| 21 | + sodium_bin2base64, sprintf, str_contains, strtolower, trim; |
21 | 22 | use const PHP_QUERY_RFC1738, PHP_VERSION_ID, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING;
|
22 | 23 |
|
23 | 24 | /**
|
@@ -407,6 +408,81 @@ protected function getRefreshAccessTokenRequestBodyParams(string $refreshToken):
|
407 | 408 | }
|
408 | 409 |
|
409 | 410 |
|
| 411 | + /* |
| 412 | + * TokenInvalidate |
| 413 | + */ |
| 414 | + |
| 415 | + /** |
| 416 | + * @implements \chillerlan\OAuth\Core\TokenInvalidate::invalidateAccessToken() |
| 417 | + * @throws \chillerlan\OAuth\Providers\ProviderException |
| 418 | + */ |
| 419 | + public function invalidateAccessToken(AccessToken $token = null, string|null $type = null):bool{ |
| 420 | + $type = strtolower(trim($type ?? 'access_token')); |
| 421 | + |
| 422 | + // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 |
| 423 | + if(!in_array($type, ['access_token', 'refresh_token'])){ |
| 424 | + throw new ProviderException(sprintf('invalid token type "%s"', $type)); |
| 425 | + } |
| 426 | + |
| 427 | + $tokenToInvalidate = ($token ?? $this->storage->getAccessToken($this->name)); |
| 428 | + $body = $this->getInvalidateAccessTokenBodyParams($tokenToInvalidate, $type); |
| 429 | + $response = $this->sendTokenInvalidateRequest($this->revokeURL, $body); |
| 430 | + |
| 431 | + // some endpoints may return 204, others 200 with empty body |
| 432 | + if(in_array($response->getStatusCode(), [200, 204], true)){ |
| 433 | + |
| 434 | + // if the token was given via parameter it cannot be deleted from storage |
| 435 | + if($token === null){ |
| 436 | + $this->storage->clearAccessToken($this->name); |
| 437 | + } |
| 438 | + |
| 439 | + return true; |
| 440 | + } |
| 441 | + |
| 442 | + // ok, let's see if we got a response body |
| 443 | + // @link https://datatracker.ietf.org/doc/html/rfc7009#section-2.2.1 |
| 444 | + if(str_contains($response->getHeaderLine('content-type'), 'json')){ |
| 445 | + $json = MessageUtil::decodeJSON($response); |
| 446 | + |
| 447 | + if(isset($json['error'])){ |
| 448 | + throw new ProviderException($json['error']); |
| 449 | + } |
| 450 | + } |
| 451 | + |
| 452 | + return false; |
| 453 | + } |
| 454 | + |
| 455 | + /** |
| 456 | + * Prepares and sends a request to the token invalidation endpoint |
| 457 | + * |
| 458 | + * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() |
| 459 | + */ |
| 460 | + protected function sendTokenInvalidateRequest(string $url, array $body):ResponseInterface{ |
| 461 | + |
| 462 | + $request = $this->requestFactory |
| 463 | + ->createRequest('POST', $url) |
| 464 | + ->withHeader('Content-Type', 'application/x-www-form-urlencoded') |
| 465 | + ; |
| 466 | + |
| 467 | + // some enpoints may require a basic auth header here |
| 468 | + $request = $this->setRequestBody($body, $request); |
| 469 | + |
| 470 | + return $this->http->sendRequest($request); |
| 471 | + } |
| 472 | + |
| 473 | + /** |
| 474 | + * Prepares the body for a token revocation request |
| 475 | + * |
| 476 | + * @see \chillerlan\OAuth\Core\OAuth2Provider::invalidateAccessToken() |
| 477 | + */ |
| 478 | + protected function getInvalidateAccessTokenBodyParams(AccessToken $token, string $type):array{ |
| 479 | + return [ |
| 480 | + 'token' => $token->accessToken, |
| 481 | + 'token_type_hint' => $type, |
| 482 | + ]; |
| 483 | + } |
| 484 | + |
| 485 | + |
410 | 486 | /*
|
411 | 487 | * CSRFToken
|
412 | 488 | */
|
|
0 commit comments