| @@ -13,8 +13,9 @@ | ||
| 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; |
| 20 | 21 | use Yoast\WP\SEO\MyYoast_Client\Domain\Exceptions\Invalid_Resource_Exception; |
| @@ -103,19 +104,27 @@ | ||
| 103 | 104 | */ |
| 104 | 105 | private $site_url_provider; |
| 105 | 106 | |
| 106 | 107 | /** |
| 108 | + * The redirect URI provider port. | |
| 109 | + * | |
| 110 | + * @var Redirect_URI_Provider_Interface | |
| 111 | + */ | |
| 112 | + private $redirect_uri_provider; | |
| 113 | + | |
| 114 | + /** | |
| 107 | 115 | * MyYoast_Client constructor. |
| 108 | 116 | * |
| 109 | - * @param Client_Registration_Interface $client_registration The client registration port. | |
| 110 | - * @param Authorization_Code_Handler $auth_code_handler The authorization code handler. | |
| 111 | - * @param OAuth_Grant_Handler $grant_handler The OAuth grant handler. | |
| 112 | - * @param Token_Revocation_Handler $revocation_handler The token revocation handler. | |
| 113 | - * @param OAuth_Server_Client_Interface $http_client The OAuth server client port. | |
| 114 | - * @param Lock_Helper $lock_helper The lock helper. | |
| 115 | - * @param Token_Storage_Interface $token_storage The site-level token storage port. | |
| 116 | - * @param User_Token_Storage_Interface $user_token_storage The user-level token storage port. | |
| 117 | - * @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. | |
| 118 | 127 | */ |
| 119 | 128 | public function __construct( |
| 120 | 129 | Client_Registration_Interface $client_registration, |
| 121 | 130 | Authorization_Code_Handler $auth_code_handler, |
| @@ -124,54 +133,67 @@ | ||
| 124 | 133 | OAuth_Server_Client_Interface $http_client, |
| 125 | 134 | Lock_Helper $lock_helper, |
| 126 | 135 | Token_Storage_Interface $token_storage, |
| 127 | 136 | User_Token_Storage_Interface $user_token_storage, |
| 128 | - Site_URL_Provider_Interface $site_url_provider | |
| 137 | + Site_URL_Provider_Interface $site_url_provider, | |
| 138 | + Redirect_URI_Provider_Interface $redirect_uri_provider | |
| 129 | 139 | ) { |
| 130 | - $this->client_registration = $client_registration; | |
| 131 | - $this->auth_code_handler = $auth_code_handler; | |
| 132 | - $this->grant_handler = $grant_handler; | |
| 133 | - $this->revocation_handler = $revocation_handler; | |
| 134 | - $this->http_client = $http_client; | |
| 135 | - $this->lock_helper = $lock_helper; | |
| 136 | - $this->token_storage = $token_storage; | |
| 137 | - $this->user_token_storage = $user_token_storage; | |
| 138 | - $this->site_url_provider = $site_url_provider; | |
| 139 | - $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(); | |
| 140 | 151 | } |
| 141 | 152 | |
| 142 | 153 | /** |
| 143 | - * 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. | |
| 144 | 155 | * |
| 145 | - * @param string[] $redirect_uris The OAuth redirect URIs to register with. | |
| 146 | - * | |
| 147 | 156 | * @return Registered_Client The registered client. |
| 148 | 157 | * |
| 149 | 158 | * @throws Registration_Failed_Exception If registration fails. |
| 150 | 159 | */ |
| 151 | - public function ensure_registered( array $redirect_uris = [] ): Registered_Client { | |
| 152 | - 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() ); | |
| 153 | 162 | } |
| 154 | 163 | |
| 155 | 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 | + /** | |
| 156 | 176 | * Whether the plugin is registered as an OAuth client. |
| 157 | 177 | * |
| 158 | - * @param string[] $redirect_uris Optional redirect URIs to verify against the stored registration. | |
| 159 | - * | |
| 160 | 178 | * @return bool |
| 161 | 179 | */ |
| 162 | - public function is_registered( array $redirect_uris = [] ): bool { | |
| 163 | - return $this->client_registration->is_registered( $redirect_uris ); | |
| 180 | + public function is_registered(): bool { | |
| 181 | + return $this->client_registration->get_registered_client() !== null; | |
| 164 | 182 | } |
| 165 | 183 | |
| 166 | 184 | /** |
| 167 | - * Reads the current client registration from the server. | |
| 185 | + * Refreshes the local registration status against the server. | |
| 168 | 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 | + * | |
| 169 | 191 | * @return array<string, string|string[]> The registration metadata. |
| 170 | 192 | * |
| 171 | 193 | * @throws Registration_Failed_Exception If the read fails. |
| 172 | 194 | */ |
| 173 | - public function verify_registration(): array { | |
| 195 | + public function refresh_registration_status(): array { | |
| 174 | 196 | return $this->client_registration->read_registration(); |
| 175 | 197 | } |
| 176 | 198 | |
| 177 | 199 | /** |
| @@ -206,9 +228,8 @@ | ||
| 206 | 228 | /** |
| 207 | 229 | * Builds the authorization URL for the user authorization flow. |
| 208 | 230 | * |
| 209 | 231 | * @param int $user_id The WordPress user ID. |
| 210 | - * @param string $redirect_uri The callback redirect URI. | |
| 211 | 232 | * @param string[] $scopes The scopes to request. |
| 212 | 233 | * @param string|null $resource_indicator The RFC 8707 resource indicator the issued token should be bound to. |
| 213 | 234 | * @param string|null $return_url The URL to return the user to after authorization completes. |
| 214 | 235 | * |
| @@ -216,10 +237,10 @@ | ||
| 216 | 237 | * |
| 217 | 238 | * @throws Authorization_Flow_Exception If registration, discovery, or parameter validation fails. |
| 218 | 239 | * @throws Invalid_Resource_Exception If the resource indicator is malformed. |
| 219 | 240 | */ |
| 220 | - public function get_authorization_url( int $user_id, string $redirect_uri, array $scopes = [], ?string $resource_indicator = null, ?string $return_url = null ): string { | |
| 221 | - return $this->auth_code_handler->get_authorization_url( $user_id, $redirect_uri, $scopes, new Resource_Indicator( $resource_indicator ), $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 ); | |
| 222 | 243 | } |
| 223 | 244 | |
| 224 | 245 | /** |
| 225 | 246 | * Exchanges an authorization code for tokens and stores them for the user. |
| @@ -238,8 +259,10 @@ | ||
| 238 | 259 | $this->user_token_storage->store( $user_id, $token_set ); |
| 239 | 260 | return $token_set; |
| 240 | 261 | } |
| 241 | 262 | |
| 263 | + // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber -- Token_Storage_Exception is thrown by an injected service, not directly here. | |
| 264 | + | |
| 242 | 265 | /** |
| 243 | 266 | * Returns a valid site-level access token (client_credentials). |
| 244 | 267 | * |
| 245 | 268 | * @param string[] $scopes The service:* scopes to request. |
| @@ -247,9 +270,9 @@ | ||
| 247 | 270 | * |
| 248 | 271 | * @return Token_Set The site-level token set. |
| 249 | 272 | * |
| 250 | 273 | * @throws Invalid_Resource_Exception If the resource indicator is malformed. |
| 251 | - * @throws Token_Request_Failed_Exception If the token request fails. May also throw Token_Storage_Exception when encrypting the token set for storage fails. | |
| 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. | |
| 252 | 275 | */ |
| 253 | 276 | public function get_site_token( array $scopes = [], ?string $resource_indicator = null ): Token_Set { |
| 254 | 277 | $indicator = new Resource_Indicator( $resource_indicator ); |
| 255 | 278 | |
| @@ -254,17 +277,29 @@ | ||
| 254 | 277 | $indicator = new Resource_Indicator( $resource_indicator ); |
| 255 | 278 | |
| 256 | 279 | $cached = $this->token_storage->get( $indicator ); |
| 257 | 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 ) ] ); | |
| 258 | 282 | return $cached; |
| 259 | 283 | } |
| 284 | + $this->logger->debug( 'MyYoast site token: cache miss; preparing to request a fresh token (scopes: {scopes}).', [ 'scopes' => \implode( ' ', $scopes ) ] ); | |
| 260 | 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 | + | |
| 261 | 292 | $grant = new Client_Credentials_Grant( $scopes, $this->site_url_provider->get() ); |
| 262 | 293 | $token_set = $this->grant_handler->request_token( $grant, $indicator ); |
| 263 | 294 | $this->token_storage->store( $token_set ); |
| 264 | 295 | |
| 296 | + $this->logger->debug( 'MyYoast site token: fresh token issued and cached.' ); | |
| 297 | + | |
| 265 | 298 | return $token_set; |
| 266 | 299 | } |
| 300 | + | |
| 301 | + // phpcs:enable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber | |
| 267 | 302 | |
| 268 | 303 | /** |
| 269 | 304 | * Returns a valid user-level access token, auto-refreshing if expired. |
| 270 | 305 | * |