| @@ -13,13 +13,16 @@ | ||
| 13 | 13 | use Yoast\WP\SEO\MyYoast_Client\Application\Grants\Client_Credentials_Grant; |
| 14 | 14 | use Yoast\WP\SEO\MyYoast_Client\Application\Grants\Refresh_Token_Grant; |
| 15 | 15 | use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Client_Registration_Interface; |
| 16 | 16 | use Yoast\WP\SEO\MyYoast_Client\Application\Ports\OAuth_Server_Client_Interface; |
| 17 | +use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Redirect_URI_Provider_Interface; | |
| 17 | 18 | use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Site_URL_Provider_Interface; |
| 18 | 19 | use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Token_Storage_Interface; |
| 19 | 20 | use Yoast\WP\SEO\MyYoast_Client\Application\Ports\User_Token_Storage_Interface; |
| 21 | +use Yoast\WP\SEO\MyYoast_Client\Domain\Exceptions\Invalid_Resource_Exception; | |
| 20 | 22 | use Yoast\WP\SEO\MyYoast_Client\Domain\HTTP_Response; |
| 21 | 23 | use Yoast\WP\SEO\MyYoast_Client\Domain\Registered_Client; |
| 24 | +use Yoast\WP\SEO\MyYoast_Client\Domain\Resource_Indicator; | |
| 22 | 25 | use Yoast\WP\SEO\MyYoast_Client\Domain\Token_Set; |
| 23 | 26 | use Yoast\WP\SEO\MyYoast_Client\Domain\Token_Type_Hint; |
| 24 | 27 | use YoastSEO_Vendor\Psr\Log\LoggerAwareInterface; |
| 25 | 28 | use YoastSEO_Vendor\Psr\Log\LoggerAwareTrait; |
| @@ -101,19 +104,27 @@ | ||
| 101 | 104 | */ |
| 102 | 105 | private $site_url_provider; |
| 103 | 106 | |
| 104 | 107 | /** |
| 108 | + * The redirect URI provider port. | |
| 109 | + * | |
| 110 | + * @var Redirect_URI_Provider_Interface | |
| 111 | + */ | |
| 112 | + private $redirect_uri_provider; | |
| 113 | + | |
| 114 | + /** | |
| 105 | 115 | * MyYoast_Client constructor. |
| 106 | 116 | * |
| 107 | - * @param Client_Registration_Interface $client_registration The client registration port. | |
| 108 | - * @param Authorization_Code_Handler $auth_code_handler The authorization code handler. | |
| 109 | - * @param OAuth_Grant_Handler $grant_handler The OAuth grant handler. | |
| 110 | - * @param Token_Revocation_Handler $revocation_handler The token revocation handler. | |
| 111 | - * @param OAuth_Server_Client_Interface $http_client The OAuth server client port. | |
| 112 | - * @param Lock_Helper $lock_helper The lock helper. | |
| 113 | - * @param Token_Storage_Interface $token_storage The site-level token storage port. | |
| 114 | - * @param User_Token_Storage_Interface $user_token_storage The user-level token storage port. | |
| 115 | - * @param Site_URL_Provider_Interface $site_url_provider The site URL provider port. | |
| 117 | + * @param Client_Registration_Interface $client_registration The client registration port. | |
| 118 | + * @param Authorization_Code_Handler $auth_code_handler The authorization code handler. | |
| 119 | + * @param OAuth_Grant_Handler $grant_handler The OAuth grant handler. | |
| 120 | + * @param Token_Revocation_Handler $revocation_handler The token revocation handler. | |
| 121 | + * @param OAuth_Server_Client_Interface $http_client The OAuth server client port. | |
| 122 | + * @param Lock_Helper $lock_helper The lock helper. | |
| 123 | + * @param Token_Storage_Interface $token_storage The site-level token storage port. | |
| 124 | + * @param User_Token_Storage_Interface $user_token_storage The user-level token storage port. | |
| 125 | + * @param Site_URL_Provider_Interface $site_url_provider The site URL provider port. | |
| 126 | + * @param Redirect_URI_Provider_Interface $redirect_uri_provider The redirect URI provider port. | |
| 116 | 127 | */ |
| 117 | 128 | public function __construct( |
| 118 | 129 | Client_Registration_Interface $client_registration, |
| 119 | 130 | Authorization_Code_Handler $auth_code_handler, |
| @@ -122,54 +133,67 @@ | ||
| 122 | 133 | OAuth_Server_Client_Interface $http_client, |
| 123 | 134 | Lock_Helper $lock_helper, |
| 124 | 135 | Token_Storage_Interface $token_storage, |
| 125 | 136 | User_Token_Storage_Interface $user_token_storage, |
| 126 | - Site_URL_Provider_Interface $site_url_provider | |
| 137 | + Site_URL_Provider_Interface $site_url_provider, | |
| 138 | + Redirect_URI_Provider_Interface $redirect_uri_provider | |
| 127 | 139 | ) { |
| 128 | - $this->client_registration = $client_registration; | |
| 129 | - $this->auth_code_handler = $auth_code_handler; | |
| 130 | - $this->grant_handler = $grant_handler; | |
| 131 | - $this->revocation_handler = $revocation_handler; | |
| 132 | - $this->http_client = $http_client; | |
| 133 | - $this->lock_helper = $lock_helper; | |
| 134 | - $this->token_storage = $token_storage; | |
| 135 | - $this->user_token_storage = $user_token_storage; | |
| 136 | - $this->site_url_provider = $site_url_provider; | |
| 137 | - $this->logger = new NullLogger(); | |
| 140 | + $this->client_registration = $client_registration; | |
| 141 | + $this->auth_code_handler = $auth_code_handler; | |
| 142 | + $this->grant_handler = $grant_handler; | |
| 143 | + $this->revocation_handler = $revocation_handler; | |
| 144 | + $this->http_client = $http_client; | |
| 145 | + $this->lock_helper = $lock_helper; | |
| 146 | + $this->token_storage = $token_storage; | |
| 147 | + $this->user_token_storage = $user_token_storage; | |
| 148 | + $this->site_url_provider = $site_url_provider; | |
| 149 | + $this->redirect_uri_provider = $redirect_uri_provider; | |
| 150 | + $this->logger = new NullLogger(); | |
| 138 | 151 | } |
| 139 | 152 | |
| 140 | 153 | /** |
| 141 | - * Ensures the plugin is registered as an OAuth client. | |
| 154 | + * Ensures the plugin is registered as an OAuth client with the provider's redirect URIs. | |
| 142 | 155 | * |
| 143 | - * @param string[] $redirect_uris The OAuth redirect URIs to register with. | |
| 144 | - * | |
| 145 | 156 | * @return Registered_Client The registered client. |
| 146 | 157 | * |
| 147 | 158 | * @throws Registration_Failed_Exception If registration fails. |
| 148 | 159 | */ |
| 149 | - public function ensure_registered( array $redirect_uris = [] ): Registered_Client { | |
| 150 | - return $this->client_registration->ensure_registered( $redirect_uris ); | |
| 160 | + public function ensure_registered(): Registered_Client { | |
| 161 | + return $this->client_registration->ensure_registered( $this->redirect_uri_provider->get_redirect_uris() ); | |
| 151 | 162 | } |
| 152 | 163 | |
| 153 | 164 | /** |
| 165 | + * Returns the stored registered client, or null if not registered. | |
| 166 | + * | |
| 167 | + * The returned client exposes per-URI verification state via Registered_Client::is_uri_validated(). | |
| 168 | + * | |
| 169 | + * @return Registered_Client|null The registered client, or null if not registered. | |
| 170 | + */ | |
| 171 | + public function get_registered_client(): ?Registered_Client { | |
| 172 | + return $this->client_registration->get_registered_client(); | |
| 173 | + } | |
| 174 | + | |
| 175 | + /** | |
| 154 | 176 | * Whether the plugin is registered as an OAuth client. |
| 155 | 177 | * |
| 156 | - * @param string[] $redirect_uris Optional redirect URIs to verify against the stored registration. | |
| 157 | - * | |
| 158 | 178 | * @return bool |
| 159 | 179 | */ |
| 160 | - public function is_registered( array $redirect_uris = [] ): bool { | |
| 161 | - return $this->client_registration->is_registered( $redirect_uris ); | |
| 180 | + public function is_registered(): bool { | |
| 181 | + return $this->client_registration->get_registered_client() !== null; | |
| 162 | 182 | } |
| 163 | 183 | |
| 164 | 184 | /** |
| 165 | - * Reads the current client registration from the server. | |
| 185 | + * Refreshes the local registration status against the server. | |
| 166 | 186 | * |
| 187 | + * Reads the current client registration (RFC 7592 GET) to confirm it is | |
| 188 | + * still live; the underlying read self-heals by forgetting the local | |
| 189 | + * registration when the server reports it gone. | |
| 190 | + * | |
| 167 | 191 | * @return array<string, string|string[]> The registration metadata. |
| 168 | 192 | * |
| 169 | 193 | * @throws Registration_Failed_Exception If the read fails. |
| 170 | 194 | */ |
| 171 | - public function verify_registration(): array { | |
| 195 | + public function refresh_registration_status(): array { | |
| 172 | 196 | return $this->client_registration->read_registration(); |
| 173 | 197 | } |
| 174 | 198 | |
| 175 | 199 | /** |
| @@ -203,19 +227,20 @@ | ||
| 203 | 227 | |
| 204 | 228 | /** |
| 205 | 229 | * Builds the authorization URL for the user authorization flow. |
| 206 | 230 | * |
| 207 | - * @param int $user_id The WordPress user ID. | |
| 208 | - * @param string $redirect_uri The callback redirect URI. | |
| 209 | - * @param string[] $scopes The scopes to request. | |
| 210 | - * @param string|null $return_url The URL to return the user to after authorization completes. | |
| 231 | + * @param int $user_id The WordPress user ID. | |
| 232 | + * @param string[] $scopes The scopes to request. | |
| 233 | + * @param string|null $resource_indicator The RFC 8707 resource indicator the issued token should be bound to. | |
| 234 | + * @param string|null $return_url The URL to return the user to after authorization completes. | |
| 211 | 235 | * |
| 212 | 236 | * @return string The authorization URL. |
| 213 | 237 | * |
| 214 | 238 | * @throws Authorization_Flow_Exception If registration, discovery, or parameter validation fails. |
| 239 | + * @throws Invalid_Resource_Exception If the resource indicator is malformed. | |
| 215 | 240 | */ |
| 216 | - public function get_authorization_url( int $user_id, string $redirect_uri, array $scopes = [], ?string $return_url = null ): string { | |
| 217 | - return $this->auth_code_handler->get_authorization_url( $user_id, $redirect_uri, $scopes, $return_url ); | |
| 241 | + public function get_authorization_url( int $user_id, array $scopes = [], ?string $resource_indicator = null, ?string $return_url = null ): string { | |
| 242 | + return $this->auth_code_handler->get_authorization_url( $user_id, $scopes, new Resource_Indicator( $resource_indicator ), $return_url ); | |
| 218 | 243 | } |
| 219 | 244 | |
| 220 | 245 | /** |
| 221 | 246 | * Exchanges an authorization code for tokens and stores them for the user. |
| @@ -234,42 +259,63 @@ | ||
| 234 | 259 | $this->user_token_storage->store( $user_id, $token_set ); |
| 235 | 260 | return $token_set; |
| 236 | 261 | } |
| 237 | 262 | |
| 263 | + // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- Token_Storage_Exception is thrown by an injected service, not directly here. | |
| 264 | + | |
| 238 | 265 | /** |
| 239 | 266 | * Returns a valid site-level access token (client_credentials). |
| 240 | 267 | * |
| 241 | - * @param string[] $scopes The service:* scopes to request. | |
| 268 | + * @param string[] $scopes The service:* scopes to request. | |
| 269 | + * @param string|null $resource_indicator The RFC 8707 resource indicator the token should be bound to, or null for the default resource. | |
| 242 | 270 | * |
| 243 | 271 | * @return Token_Set The site-level token set. |
| 244 | 272 | * |
| 245 | - * @throws Token_Request_Failed_Exception If the token request fails. | |
| 246 | - * @throws Token_Storage_Exception If encrypting the token set for storage fails. | |
| 273 | + * @throws Invalid_Resource_Exception If the resource indicator is malformed. | |
| 274 | + * @throws Token_Request_Failed_Exception If the site is not registered or the token request fails. May also throw Token_Storage_Exception when encrypting the token set for storage fails. | |
| 247 | 275 | */ |
| 248 | - public function get_site_token( array $scopes = [] ): Token_Set { | |
| 249 | - $cached = $this->token_storage->get(); | |
| 276 | + public function get_site_token( array $scopes = [], ?string $resource_indicator = null ): Token_Set { | |
| 277 | + $indicator = new Resource_Indicator( $resource_indicator ); | |
| 278 | + | |
| 279 | + $cached = $this->token_storage->get( $indicator ); | |
| 250 | 280 | if ( $cached !== null && ! $cached->is_expired() && $cached->has_scopes( $scopes ) ) { |
| 281 | + $this->logger->debug( 'MyYoast site token: cache hit; reusing cached token (scopes: {scopes}).', [ 'scopes' => \implode( ' ', $scopes ) ] ); | |
| 251 | 282 | return $cached; |
| 252 | 283 | } |
| 284 | + $this->logger->debug( 'MyYoast site token: cache miss; preparing to request a fresh token (scopes: {scopes}).', [ 'scopes' => \implode( ' ', $scopes ) ] ); | |
| 253 | 285 | |
| 286 | + if ( ! $this->is_registered() ) { | |
| 287 | + $this->logger->debug( 'MyYoast site token: site not registered; refusing to request a token. Run the connect flow first.' ); | |
| 288 | + throw new Token_Request_Failed_Exception( 'not_registered', 'Site is not registered with MyYoast; complete the connect flow first.' ); | |
| 289 | + } | |
| 290 | + $this->logger->debug( 'MyYoast site token: site already registered; proceeding with token request.' ); | |
| 291 | + | |
| 254 | 292 | $grant = new Client_Credentials_Grant( $scopes, $this->site_url_provider->get() ); |
| 255 | - $token_set = $this->grant_handler->request_token( $grant ); | |
| 293 | + $token_set = $this->grant_handler->request_token( $grant, $indicator ); | |
| 256 | 294 | $this->token_storage->store( $token_set ); |
| 257 | 295 | |
| 296 | + $this->logger->debug( 'MyYoast site token: fresh token issued and cached.' ); | |
| 297 | + | |
| 258 | 298 | return $token_set; |
| 259 | 299 | } |
| 260 | 300 | |
| 301 | + // phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber | |
| 302 | + | |
| 261 | 303 | /** |
| 262 | 304 | * Returns a valid user-level access token, auto-refreshing if expired. |
| 263 | 305 | * |
| 264 | - * @param int $user_id The WordPress user ID. | |
| 265 | - * @param string[] $required_scopes Optional scopes required for the token; if provided, no token will be returned unless it has at least these scopes. | |
| 266 | - * This is to avoid refreshing a token that would trigger an immediate re-authorization due to missing scopes. | |
| 306 | + * @param int $user_id The WordPress user ID. | |
| 307 | + * @param string[] $required_scopes Optional scopes required for the token; if provided, no token will be returned unless it has at least these scopes. | |
| 308 | + * This is to avoid refreshing a token that would trigger an immediate re-authorization due to missing scopes. | |
| 309 | + * @param string|null $resource_indicator The RFC 8707 resource indicator the token should be bound to, or null for the default resource. | |
| 267 | 310 | * |
| 268 | 311 | * @return Token_Set|null The user token set, or null if the user hasn't authorized. |
| 312 | + * | |
| 313 | + * @throws Invalid_Resource_Exception If the resource indicator is malformed. | |
| 269 | 314 | */ |
| 270 | - public function get_user_token( int $user_id, array $required_scopes = [] ): ?Token_Set { | |
| 271 | - $token_set = $this->user_token_storage->get( $user_id ); | |
| 315 | + public function get_user_token( int $user_id, array $required_scopes = [], ?string $resource_indicator = null ): ?Token_Set { | |
| 316 | + $indicator = new Resource_Indicator( $resource_indicator ); | |
| 317 | + $token_set = $this->user_token_storage->get( $user_id, $indicator ); | |
| 272 | 318 | if ( $token_set === null ) { |
| 273 | 319 | return null; |
| 274 | 320 | } |
| 275 | 321 | |
| @@ -286,28 +332,24 @@ | ||
| 286 | 332 | if ( $refresh_token === null ) { |
| 287 | 333 | return null; |
| 288 | 334 | } |
| 289 | 335 | |
| 336 | + // Refresh must keep the audience binding (RFC 8707 §2); pull it from the stored token. | |
| 337 | + $bound_resource = $token_set->get_resource_indicator(); | |
| 338 | + | |
| 339 | + // If the client was just re-registered, this refresh will fail with invalid_grant. | |
| 340 | + // error_count is 0 on first attempt; after one invalid_grant it becomes 1. | |
| 341 | + // On the second consecutive invalid_grant (error_count >= 1), clear and give up. | |
| 342 | + $grant = new Refresh_Token_Grant( $refresh_token ); | |
| 343 | + $lock_key = 'wpseo_myyoast_refresh:' . \hash( 'sha256', $refresh_token ); | |
| 290 | 344 | try { |
| 291 | - // If the client was just re-registered, this refresh will fail with invalid_grant. | |
| 292 | - // error_count is 0 on first attempt; after one invalid_grant it becomes 1. | |
| 293 | - // On the second consecutive invalid_grant (error_count >= 1), clear and give up. | |
| 294 | - $grant = new Refresh_Token_Grant( $refresh_token ); | |
| 295 | - $lock_key = 'wpseo_myyoast_refresh:' . \hash( 'sha256', $refresh_token ); | |
| 296 | 345 | $new_token_set = $this->lock_helper->execute( |
| 297 | 346 | $lock_key, |
| 298 | - function () use ( $grant ) { | |
| 299 | - return $this->grant_handler->request_token( $grant ); | |
| 347 | + function () use ( $grant, $bound_resource ) { | |
| 348 | + return $this->grant_handler->request_token( $grant, $bound_resource ); | |
| 300 | 349 | }, |
| 301 | 350 | self::REFRESH_LOCK_TTL_IN_SECONDS, |
| 302 | 351 | ); |
| 303 | - try { | |
| 304 | - $this->user_token_storage->store( $user_id, $new_token_set ); | |
| 305 | - } catch ( Token_Storage_Exception $e ) { | |
| 306 | - // Next request will re-refresh from the old stored token. | |
| 307 | - $this->logger->warning( 'Failed to persist refreshed token: {error}', [ 'error' => $e->getMessage() ] ); | |
| 308 | - } | |
| 309 | - return $new_token_set; | |
| 310 | 352 | } catch ( Lock_Timeout_Exception $e ) { |
| 311 | 353 | // Concurrent refresh in progress, treat as transient failure. |
| 312 | 354 | $this->logger->debug( 'Skipping token refresh for user {user_id}: concurrent refresh in progress.', [ 'user_id' => $user_id ] ); |
| 313 | 355 | return null; |
| @@ -314,9 +356,9 @@ | ||
| 314 | 356 | } catch ( Token_Request_Failed_Exception $e ) { |
| 315 | 357 | if ( $e->get_error_code() === 'invalid_grant' ) { |
| 316 | 358 | if ( $token_set->get_error_count() >= 1 ) { |
| 317 | 359 | $this->logger->warning( 'Repeated invalid_grant for user {user_id}, clearing stored tokens.', [ 'user_id' => $user_id ] ); |
| 318 | - $this->user_token_storage->delete( $user_id ); | |
| 360 | + $this->user_token_storage->delete( $user_id, $indicator ); | |
| 319 | 361 | return null; |
| 320 | 362 | } |
| 321 | 363 | |
| 322 | 364 | try { |
| @@ -328,30 +370,44 @@ | ||
| 328 | 370 | } |
| 329 | 371 | |
| 330 | 372 | return null; |
| 331 | 373 | } |
| 374 | + try { | |
| 375 | + $this->user_token_storage->store( $user_id, $new_token_set ); | |
| 376 | + } catch ( Token_Storage_Exception $e ) { | |
| 377 | + // Next request will re-refresh from the old stored token. | |
| 378 | + $this->logger->warning( 'Failed to persist refreshed token: {error}', [ 'error' => $e->getMessage() ] ); | |
| 379 | + } | |
| 380 | + return $new_token_set; | |
| 332 | 381 | } |
| 333 | 382 | |
| 334 | 383 | /** |
| 335 | - * Whether the given user has authorized with MyYoast. | |
| 384 | + * Whether the given user has authorized with MyYoast for a resource. | |
| 336 | 385 | * |
| 337 | - * @param int $user_id The WordPress user ID. | |
| 386 | + * @param int $user_id The WordPress user ID. | |
| 387 | + * @param string|null $resource_indicator The RFC 8707 resource indicator, or null for the default resource. | |
| 338 | 388 | * |
| 339 | 389 | * @return bool |
| 390 | + * | |
| 391 | + * @throws Invalid_Resource_Exception If the resource indicator is malformed. | |
| 340 | 392 | */ |
| 341 | - public function has_user_token( int $user_id ): bool { | |
| 342 | - return $this->user_token_storage->get( $user_id ) !== null; | |
| 393 | + public function has_user_token( int $user_id, ?string $resource_indicator = null ): bool { | |
| 394 | + return $this->user_token_storage->get( $user_id, new Resource_Indicator( $resource_indicator ) ) !== null; | |
| 343 | 395 | } |
| 344 | 396 | |
| 345 | 397 | /** |
| 346 | - * Revokes the user's tokens and clears storage. | |
| 398 | + * Revokes the user's tokens for a resource and clears storage. | |
| 347 | 399 | * |
| 348 | - * @param int $user_id The WordPress user ID. | |
| 400 | + * @param int $user_id The WordPress user ID. | |
| 401 | + * @param string|null $resource_indicator The RFC 8707 resource indicator, or null for the default resource. | |
| 349 | 402 | * |
| 350 | 403 | * @return void |
| 404 | + * | |
| 405 | + * @throws Invalid_Resource_Exception If the resource indicator is malformed. | |
| 351 | 406 | */ |
| 352 | - public function revoke_user_token( int $user_id ): void { | |
| 353 | - $token_set = $this->user_token_storage->get( $user_id ); | |
| 407 | + public function revoke_user_token( int $user_id, ?string $resource_indicator = null ): void { | |
| 408 | + $indicator = new Resource_Indicator( $resource_indicator ); | |
| 409 | + $token_set = $this->user_token_storage->get( $user_id, $indicator ); | |
| 354 | 410 | if ( $token_set === null ) { |
| 355 | 411 | return; |
| 356 | 412 | } |
| 357 | 413 | // Assume tokens are opaque for forwards compatibility. This operation is noop for JWTs, as they are only revoked upon expiration. |
| @@ -359,12 +415,30 @@ | ||
| 359 | 415 | if ( $token_set->get_refresh_token() !== null ) { |
| 360 | 416 | $this->revocation_handler->revoke( $token_set->get_refresh_token(), Token_Type_Hint::REFRESH_TOKEN ); |
| 361 | 417 | } |
| 362 | 418 | |
| 363 | - $this->user_token_storage->delete( $user_id ); | |
| 419 | + $this->user_token_storage->delete( $user_id, $indicator ); | |
| 364 | 420 | } |
| 365 | 421 | |
| 366 | 422 | /** |
| 423 | + * Revokes every user token across all resource buckets ("log out everywhere"). | |
| 424 | + * | |
| 425 | + * @param int $user_id The WordPress user ID. | |
| 426 | + * | |
| 427 | + * @return void | |
| 428 | + */ | |
| 429 | + public function revoke_all_user_tokens( int $user_id ): void { | |
| 430 | + $tokens = $this->user_token_storage->get_all( $user_id ); | |
| 431 | + foreach ( $tokens as $token_set ) { | |
| 432 | + $this->revocation_handler->revoke( $token_set->get_access_token(), Token_Type_Hint::ACCESS_TOKEN ); | |
| 433 | + if ( $token_set->get_refresh_token() !== null ) { | |
| 434 | + $this->revocation_handler->revoke( $token_set->get_refresh_token(), Token_Type_Hint::REFRESH_TOKEN ); | |
| 435 | + } | |
| 436 | + $this->user_token_storage->delete( $user_id, $token_set->get_resource_indicator() ); | |
| 437 | + } | |
| 438 | + } | |
| 439 | + | |
| 440 | + /** | |
| 367 | 441 | * Revokes a token at the authorization server. |
| 368 | 442 | * |
| 369 | 443 | * @param string $token The token to revoke. |
| 370 | 444 | * @param string $token_type_hint A Token_Type_Hint constant. |
| @@ -380,14 +454,27 @@ | ||
| 380 | 454 | return $this->revocation_handler->revoke( $token, $token_type_hint ); |
| 381 | 455 | } |
| 382 | 456 | |
| 383 | 457 | /** |
| 384 | - * Clears the site-level token. | |
| 458 | + * Clears the site-level token for a resource bucket. | |
| 385 | 459 | * |
| 460 | + * @param string|null $resource_indicator The RFC 8707 resource indicator, or null for the default resource. | |
| 461 | + * | |
| 386 | 462 | * @return void |
| 463 | + * | |
| 464 | + * @throws Invalid_Resource_Exception If the resource indicator is malformed. | |
| 387 | 465 | */ |
| 388 | - public function clear_site_token(): void { | |
| 389 | - $this->token_storage->delete(); | |
| 466 | + public function clear_site_token( ?string $resource_indicator = null ): void { | |
| 467 | + $this->token_storage->delete( new Resource_Indicator( $resource_indicator ) ); | |
| 468 | + } | |
| 469 | + | |
| 470 | + /** | |
| 471 | + * Clears every site-level token across resource buckets. | |
| 472 | + * | |
| 473 | + * @return void | |
| 474 | + */ | |
| 475 | + public function clear_all_site_tokens(): void { | |
| 476 | + $this->token_storage->delete_all(); | |
| 390 | 477 | } |
| 391 | 478 | |
| 392 | 479 | /** |
| 393 | 480 | * Makes an authenticated DPoP-bound request to a resource server. |