Notice: include(): Read of 17451 bytes failed with errno=12 Cannot allocate memory in /home3/freedo70/public_html/wp-content/plugins/wordpress-seo/vendor/composer/ClassLoader.php on line 576
Notice: include(): Read of 17451 bytes failed with errno=12 Cannot allocate memory in /home3/freedo70/public_html/wp-content/plugins/wordpress-seo/vendor/composer/ClassLoader.php on line 576
Yoast.JsonEncodeAlternative.Found -- Encoding for HTTP request body, not user-facing output.
$json = \wp_json_encode( $request_body );
if ( $json === false ) {
throw new Registration_Failed_Exception( 'Failed to JSON-encode registration request body.' );
}
$result = $this->http_client->authenticated_request(
'PUT',
$registered_client->get_registration_client_uri(),
$registered_client->get_registration_access_token(),
Auth_Token_Type::BEARER,
[
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'body' => $json,
'timeout' => 15,
],
);
if ( ! $result->is_successful() ) {
$error_message = (string) $result->get_body_value( 'error_description', $result->get_body_value( 'error', '' ) );
throw new Registration_Failed_Exception(
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
\sprintf( 'Key rotation returned HTTP %d: %s', $result->get_status(), $error_message ),
);
}
$body = $result->get_body();
if ( ! \is_array( $body ) || empty( $body['client_id'] ) ) {
throw new Registration_Failed_Exception( 'Key rotation returned invalid response.' );
}
// Server confirmed — now persist the new key pair locally.
$this->key_pair_manager->store_key_pair( Key_Pair_Manager::PURPOSE_REGISTRATION, $new_key_pair );
// Store the new RAT atomically.
return $this->store_credentials( $body );
}
/**
* Updates the registered redirect URIs in place (RFC 7592 PUT).
*
* Preserves the client_id, registration access token, and key pair. Verification state for
* URIs that remain in the set is preserved; URIs no longer present are dropped.
*
* @param string[] $redirect_uris The new exact set of redirect URIs.
*
* @return Registered_Client The updated credentials.
*
* @throws Registration_Not_Found_Exception If the server reports the registration is gone (HTTP 401/404).
* @throws Rate_Limited_Exception If the server rate-limited the request (HTTP 429).
* @throws Registration_Failed_Exception If the update fails for any other reason.
*/
private function update_redirect_uris( array $redirect_uris ): Registered_Client {
$registered_client = $this->get_registered_client();
if ( $registered_client === null ) {
throw new Registration_Failed_Exception( 'Not registered.' );
}
// Per RFC 7592 §2.2, server-assigned fields MUST NOT be included; keep the existing key pair.
$request_body = $this->build_update_request_body( $registered_client->get_metadata() );
$request_body['redirect_uris'] = \array_values( $redirect_uris );
$request_body['software_statement'] = $this->issuer_config->get_software_statement();
// phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- Encoding for HTTP request body, not user-facing output.
$json = \wp_json_encode( $request_body );
if ( $json === false ) {
throw new Registration_Failed_Exception( 'Failed to JSON-encode registration request body.' );
}
$result = $this->http_client->authenticated_request(
'PUT',
$registered_client->get_registration_client_uri(),
$registered_client->get_registration_access_token(),
Auth_Token_Type::BEARER,
[
'headers' => [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'body' => $json,
'timeout' => 15,
],
);
if ( $result->get_status() === 401 || $result->get_status() === 404 ) {
$this->logger->warning( 'Registration is no longer valid on update (HTTP {status}), clearing local registration.', [ 'status' => $result->get_status() ] );
$this->forget_registration();
throw new Registration_Not_Found_Exception(
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
'Registration is no longer valid (HTTP ' . $result->get_status() . ').',
);
}
if ( $result->get_status() === 429 ) {
$this->logger->warning( 'Registration update was rate-limited (HTTP 429).' );
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
throw new Rate_Limited_Exception( 'Registration update was rate-limited (HTTP 429).', $this->get_retry_after_seconds( $result ) );
}
if ( ! $result->is_successful() ) {
$error_message = (string) $result->get_body_value( 'error_description', $result->get_body_value( 'error', '' ) );
throw new Registration_Failed_Exception(
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
\sprintf( 'Redirect URI update returned HTTP %d: %s', $result->get_status(), $error_message ),
);
}
$body = $result->get_body();
if ( ! \is_array( $body ) || empty( $body['client_id'] ) ) {
throw new Registration_Failed_Exception( 'Redirect URI update returned invalid response.' );
}
return $this->store_credentials( $body );
}
/**
* Deletes the client registration from the server (RFC 7592 DELETE) and clears local data.
*
* @return bool True if deleted or already not registered, false on network failure.
*/
public function deregister(): bool {
$credentials = $this->get_registered_client();
if ( $credentials === null ) {
return true;
}
$result = $this->http_client->authenticated_request(
'DELETE',
$credentials->get_registration_client_uri(),
$credentials->get_registration_access_token(),
Auth_Token_Type::BEARER,
[ 'timeout' => 10 ],
);
$this->forget_registration();
return ! $result->is_transport_failure();
}
/**
* Deletes the stored registration credentials.
*
* @return void
*/
public function forget_registration(): void {
unset( $this->cached_registered_clients[ $this->get_option_key() ] );
\delete_option( $this->get_option_key() );
}
/**
* Deletes all local registration data (credentials, key pairs, caches).
*
* @return void
*/
public function delete_local_data(): void {
$this->forget_registration();
$this->key_pair_manager->delete_key_pair( Key_Pair_Manager::PURPOSE_REGISTRATION );
$this->key_pair_manager->delete_key_pair( Key_Pair_Manager::PURPOSE_DPOP );
$this->discovery_client->invalidate_cache();
$suffix = $this->issuer_config->get_issuer_key();
\delete_transient( 'wpseo_myyoast_jwks_' . $suffix );
\delete_transient( 'wpseo_myyoast_dpop_nonce_' . $suffix );
}
/**
* Rotates the DPoP key pair (local only, no server coordination).
*
* @return void
*/
public function rotate_dpop_keys(): void {
$this->key_pair_manager->rotate_key_pair( Key_Pair_Manager::PURPOSE_DPOP );
}
/**
* Whether the given redirect URI has completed the OAuth authorization-code flow on this site.
*
* The state lives on the stored registration: it is pruned to the current redirect-URI set
* whenever those change, and invalidated when the client is deregistered.
*
* @param string $redirect_uri The redirect URI to check.
*
* @return bool
*/
public function is_uri_validated( string $redirect_uri ): bool {
$registered_client = $this->get_registered_client();
return $registered_client !== null && $registered_client->is_uri_validated( $redirect_uri );
}
/**
* Records that the given redirect URI has completed the authorization-code flow.
*
* No-op when the site is not registered or the URI was already recorded. Idempotent:
* `update_option()` short-circuits when the stored value is unchanged.
*
* @param string $redirect_uri The redirect URI that completed the auth-code flow.
*
* @return void
*/
public function mark_uri_validated( string $redirect_uri ): void {
$registered_client = $this->get_registered_client();
if ( $registered_client === null ) {
return;
}
$validated_uris = $registered_client->get_validated_uris();
if ( \in_array( $redirect_uri, $validated_uris, true ) ) {
return;
}
$validated_uris[] = $redirect_uri;
$option_key = $this->get_option_key();
$stored = \get_option( $option_key, [] );
if ( \is_array( $stored ) ) {
$stored['validated_uris'] = $validated_uris;
\update_option( $option_key, $stored, false );
}
$this->cached_registered_clients[ $option_key ] = $registered_client->with_validated_uris( $validated_uris );
}
/**
* Stores the DCR response credentials securely.
*
* A registration read (RFC 7592 GET) response carries no registration access token; when the
* body omits the RAT, the existing stored RAT is preserved rather than overwritten with an
* empty value.
*
* @param array> $response_body The parsed DCR response body.
*
* @return Registered_Client The stored credentials.
*/
private function store_credentials( array $response_body ): Registered_Client {
$option_key = $this->get_option_key();
$existing = $this->get_registered_client();
// The RFC 7592 GET response never re-sends the RAT, so a missing key means "keep the stored
// one" — encrypting the absent value would brick every future management call. Only a body
// that explicitly carries a RAT (DCR / PUT) replaces it.
if ( \array_key_exists( 'registration_access_token', $response_body ) ) {
$rat = $response_body['registration_access_token'];
$encrypted_rat = $this->encryption->encrypt( $rat, self::ENCRYPTION_CONTEXT );
}
else {
// Reuse the already-decrypted RAT and its stored ciphertext rather than re-encrypting.
$rat = ( $existing !== null ) ? $existing->get_registration_access_token() : '';
$stored = \get_option( $option_key, [] );
$encrypted_rat = ( \is_array( $stored ) ) ? ( $stored['encrypted_rat'] ?? '' ) : '';
}
// Strip the RAT from metadata — it is stored encrypted separately.
$metadata = $response_body;
unset( $metadata['registration_access_token'] );
// Preserve validation state across an in-place update or key rotation (same client_id), but
// reset it for a fresh registration: a new client_id means the redirect URIs must be
// re-validated from scratch. Always prune to the new redirect-URI set so a removed URI loses
// its verification and an added one starts unverified.
$validated_uris = [];
if ( $existing !== null && $existing->get_client_id() === $response_body['client_id'] ) {
$new_redirect_uris = ( $metadata['redirect_uris'] ?? [] );
if ( \is_array( $new_redirect_uris ) ) {
$validated_uris = \array_values( \array_intersect( $existing->get_validated_uris(), $new_redirect_uris ) );
}
}
\update_option(
$option_key,
[
'client_id' => $response_body['client_id'],
'encrypted_rat' => $encrypted_rat,
'registration_client_uri' => ( $response_body['registration_client_uri'] ?? '' ),
'metadata' => $metadata,
'validated_uris' => $validated_uris,
],
false,
);
$this->cached_registered_clients[ $option_key ] = new Registered_Client(
$response_body['client_id'],
$rat,
( $response_body['registration_client_uri'] ?? '' ),
$metadata,
$validated_uris,
);
return $this->cached_registered_clients[ $option_key ];
}
/**
* Returns the issuer-scoped option key for storing registration data.
*
* @return string The option key.
*/
private function get_option_key(): string {
return self::OPTION_KEY_PREFIX . $this->issuer_config->get_issuer_key();
}
/**
* Extracts the `Retry-After` value (in seconds) from a 429 response, if any.
*
* @param HTTP_Response $result The 429 response.
*
* @return int|null Seconds until retry, or null when absent or unparseable.
*/
private function get_retry_after_seconds( HTTP_Response $result ): ?int {
$headers = $result->get_headers();
return Rate_Limited_Exception::parse_retry_after( ( $headers['retry-after'] ?? null ) );
}
/**
* Performs the actual DCR registration request.
*
* @param string[] $redirect_uris The OAuth redirect URIs to register.
*
* @return Registered_Client The registration result.
*
* @throws Registration_Temporarily_Unavailable_Exception If the server temporarily refuses new registrations (HTTP 503).
* @throws Rate_Limited_Exception If the server rate-limited the request (HTTP 429).
* @throws Registration_Failed_Exception If registration fails for any other reason.
*/
private function do_register( array $redirect_uris ): Registered_Client {
try {
$registration_endpoint = $this->discovery_client->get_document()->get_registration_endpoint();
} catch ( Discovery_Failed_Exception | Server_Capability_Exception $e ) {
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
throw new Registration_Failed_Exception( 'OIDC discovery failed: ' . $e->getMessage(), 0, $e );
}
$software_statement = $this->issuer_config->get_software_statement();
$initial_access_token = $this->issuer_config->get_initial_access_token();
if ( $software_statement === '' || $initial_access_token === '' ) {
throw new Registration_Failed_Exception( 'Software statement and initial access token must be configured.' );
}
// Ensure a registration key pair exists.
$key_pair = $this->key_pair_manager->get_or_create_key_pair( Key_Pair_Manager::PURPOSE_REGISTRATION );
$public_jwk = $this->key_pair_manager->get_public_key_jwk( $key_pair );
$request_body = [
'software_statement' => $software_statement,
'redirect_uris' => $redirect_uris,
'grant_types' => [ 'authorization_code', 'refresh_token', 'client_credentials' ],
'token_endpoint_auth_method' => 'private_key_jwt',
'jwks' => [ 'keys' => [ $public_jwk ] ],
'dpop_bound_access_tokens' => true,
];
// phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- Encoding for HTTP request body, not user-facing output.
$json = \wp_json_encode( $request_body );
if ( $json === false ) {
throw new Registration_Failed_Exception( 'Failed to JSON-encode DCR request body.' );
}
$result = $this->http_client->request(
'POST',
$registration_endpoint,
[
'headers' => [
'Authorization' => 'Bearer ' . $initial_access_token,
'Content-Type' => 'application/json',
'Accept' => 'application/json',
],
'body' => $json,
'timeout' => 15,
],
);
if ( $result->is_transport_failure() ) {
$error_message = (string) $result->get_body_value( 'error_description', '' );
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
throw new Registration_Failed_Exception( 'DCR request failed: ' . $error_message );
}
// The server temporarily refuses new registrations (rollout brake engaged).
// Surface it as a typed transient failure carrying the (display-only) retry hint.
if ( $result->get_status() === 503 && $result->get_body_value( 'error' ) === 'temporarily_unavailable' ) {
$error_message = (string) $result->get_body_value( 'error_description', 'Client registration is temporarily disabled.' );
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
throw new Registration_Temporarily_Unavailable_Exception( $error_message, $this->get_retry_after_seconds( $result ) );
}
if ( $result->get_status() === 429 ) {
$this->logger->warning( 'DCR was rate-limited (HTTP 429).' );
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
throw new Rate_Limited_Exception( 'DCR was rate-limited (HTTP 429).', $this->get_retry_after_seconds( $result ) );
}
if ( $result->get_status() !== 201 ) {
$error_message = (string) $result->get_body_value( 'error_description', $result->get_body_value( 'error', '' ) );
throw new Registration_Failed_Exception(
// phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
\sprintf( 'DCR returned HTTP %d: %s', $result->get_status(), $error_message ),
);
}
$body = $result->get_body();
if ( ! \is_array( $body ) || empty( $body['client_id'] ) ) {
throw new Registration_Failed_Exception( 'DCR returned invalid response.' );
}
return $this->store_credentials( $body );
}
/**
* Strips server-assigned fields from metadata for a RFC 7592 PUT request.
*
* Per RFC 7592 §2.2, the update request body MUST NOT include fields
* that are assigned by the server (e.g. registration_client_uri,
* client_id_issued_at, client_secret, client_secret_expires_at).
* The software_statement is also stripped since a fresh one is provided.
*
* phpcs:disable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint -- OAuth metadata is an associative array with heterogeneous values.
*
* @param array $metadata The stored client metadata.
*
* @return array The metadata suitable for a PUT request body.
*
* phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint
*/
private function build_update_request_body( array $metadata ): array {
unset(
$metadata['registration_access_token'],
$metadata['registration_client_uri'],
$metadata['client_id_issued_at'],
$metadata['client_secret'],
$metadata['client_secret_expires_at'],
$metadata['software_statement'],
);
return $metadata;
}
}
Dogara retains House of Reps seat in Bauchi dedicates victory to God Archives - Freedom Online