<?php
// phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
namespace Yoast\WP\SEO\MyYoast_Client\Application;
use Exception;
use SensitiveParameter;
use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Client_Authenticator_Interface;
use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Client_Registration_Interface;
use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Discovery_Interface;
use Yoast\WP\SEO\MyYoast_Client\Application\Ports\OAuth_Server_Client_Interface;
use Yoast\WP\SEO\MyYoast_Client\Domain\Token_Type_Hint;
use YoastSEO_Vendor\Psr\Log\LoggerAwareInterface;
use YoastSEO_Vendor\Psr\Log\LoggerAwareTrait;
use YoastSEO_Vendor\Psr\Log\NullLogger;
/**
* Revokes tokens at the authorization server (RFC 7009).
*
* Token revocation is best-effort: failures are logged as warnings and
* signalled to the caller via a `false` return value rather than rethrown.
*/
class Token_Revocation_Handler implements LoggerAwareInterface {
use LoggerAwareTrait;
/**
* The discovery port.
*
* @var Discovery_Interface
*/
private $discovery;
/**
* The client registration port.
*
* @var Client_Registration_Interface
*/
private $client_registration;
/**
* The client authenticator port.
*
* @var Client_Authenticator_Interface
*/
private $client_authenticator;
/**
* The token endpoint client port.
*
* @var OAuth_Server_Client_Interface
*/
private $oauth_server_client;
/**
* Token_Revocation_Handler constructor.
*
* @param Discovery_Interface $discovery The discovery port.
* @param Client_Registration_Interface $client_registration The client registration port.
* @param Client_Authenticator_Interface $client_authenticator The client authenticator port.
* @param OAuth_Server_Client_Interface $oauth_server_client The token endpoint client port.
*/
public function __construct(
Discovery_Interface $discovery,
Client_Registration_Interface $client_registration,
Client_Authenticator_Interface $client_authenticator,
OAuth_Server_Client_Interface $oauth_server_client
) {
$this->discovery = $discovery;
$this->client_registration = $client_registration;
$this->client_authenticator = $client_authenticator;
$this->oauth_server_client = $oauth_server_client;
$this->logger = new NullLogger();
}
/**
* Revokes a token at the authorization server.
*
* @param string $token The token to revoke.
* @param string $token_type_hint A Token_Type_Hint constant.
*
* @return bool True if the revocation request was sent (regardless of server response).
*/
public function revoke(
// phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPNativeAttributeFound -- No-op on PHP < 8.2; redacts parameter from stack traces on PHP 8.2+.
#[SensitiveParameter]
string $token,
string $token_type_hint = Token_Type_Hint::REFRESH_TOKEN
): bool {
try {
$registered_client = $this->client_registration->get_registered_client();
if ( $registered_client === null ) {
return false;
}
$revocation_endpoint = $this->discovery->get_document()->get_revocation_endpoint();
$client_assertion = $this->client_authenticator->create_client_assertion(
$registered_client->get_client_id(),
$revocation_endpoint,
);
$body = [
'token' => $token,
'token_type_hint' => $token_type_hint,
'client_id' => $registered_client->get_client_id(),
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
'client_assertion' => $client_assertion,
];
$this->oauth_server_client->request(
'POST',
$revocation_endpoint,
[
'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
'body' => $body,
'dpop' => true,
],
);
return true;
}
catch ( Exception $e ) {
$this->logger->warning(
'Token revocation failed ({token_type_hint}): {error}',
[
'token_type_hint' => $token_type_hint,
'error' => $e->getMessage(),
],
);
return false;
}
}
}