PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.8
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.8
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / myyoast-client / application / oauth-grant-handler.php

oauth-grant-handler.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 27.8, at src/myyoast-client/application/oauth-grant-handler.php

166 lines 6.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
3
4 namespace Yoast\WP\SEO\MyYoast_Client\Application;
5
6 use InvalidArgumentException;
7 use Yoast\WP\SEO\MyYoast_Client\Application\Exceptions\Client_Authentication_Exception;
8 use Yoast\WP\SEO\MyYoast_Client\Application\Exceptions\Discovery_Failed_Exception;
9 use Yoast\WP\SEO\MyYoast_Client\Application\Exceptions\Server_Capability_Exception;
10 use Yoast\WP\SEO\MyYoast_Client\Application\Exceptions\Token_Request_Failed_Exception;
11 use Yoast\WP\SEO\MyYoast_Client\Application\Grants\Grant_Interface;
12 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Client_Authenticator_Interface;
13 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Client_Registration_Interface;
14 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Discovery_Interface;
15 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\OAuth_Server_Client_Interface;
16 use Yoast\WP\SEO\MyYoast_Client\Domain\Token_Set;
17 use YoastSEO_Vendor\Psr\Log\LoggerAwareInterface;
18 use YoastSEO_Vendor\Psr\Log\LoggerAwareTrait;
19 use YoastSEO_Vendor\Psr\Log\NullLogger;
20
21 /**
22 * Central handler for OAuth token endpoint requests.
23 *
24 * Handles client authentication (private_key_jwt) and delegates grant-specific
25 * parameters to the provided Grant_Interface. Executes the token request via
26 * the token endpoint client and handles error responses.
27 */
28 class OAuth_Grant_Handler implements LoggerAwareInterface {
29 use LoggerAwareTrait;
30
31 /**
32 * The discovery port.
33 *
34 * @var Discovery_Interface
35 */
36 private $discovery;
37
38 /**
39 * The client registration port.
40 *
41 * @var Client_Registration_Interface
42 */
43 private $client_registration;
44
45 /**
46 * The client authenticator port.
47 *
48 * @var Client_Authenticator_Interface
49 */
50 private $client_authenticator;
51
52 /**
53 * The token endpoint client port.
54 *
55 * @var OAuth_Server_Client_Interface
56 */
57 private $oauth_server_client;
58
59 /**
60 * OAuth_Grant_Handler constructor.
61 *
62 * @param Discovery_Interface $discovery The discovery port.
63 * @param Client_Registration_Interface $client_registration The client registration port.
64 * @param Client_Authenticator_Interface $client_authenticator The client authenticator port.
65 * @param OAuth_Server_Client_Interface $oauth_server_client The token endpoint client port.
66 */
67 public function __construct(
68 Discovery_Interface $discovery,
69 Client_Registration_Interface $client_registration,
70 Client_Authenticator_Interface $client_authenticator,
71 OAuth_Server_Client_Interface $oauth_server_client
72 ) {
73 $this->discovery = $discovery;
74 $this->client_registration = $client_registration;
75 $this->client_authenticator = $client_authenticator;
76 $this->oauth_server_client = $oauth_server_client;
77 $this->logger = new NullLogger();
78 }
79
80 /**
81 * Executes a token endpoint request using the provided grant strategy.
82 *
83 * Ensures the client is registered, creates a client assertion, merges
84 * grant-specific parameters, and sends the request.
85 *
86 * @param Grant_Interface $grant The grant strategy providing grant-specific parameters.
87 *
88 * @return Token_Set The token set from the response.
89 *
90 * @throws Token_Request_Failed_Exception If the token request fails.
91 */
92 public function request_token( Grant_Interface $grant ): Token_Set {
93 $registered_client = $this->client_registration->ensure_registered();
94
95 try {
96 $token_endpoint = $this->discovery->get_document()->get_token_endpoint();
97 } catch ( Discovery_Failed_Exception |Server_Capability_Exception $e ) {
98 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
99 throw new Token_Request_Failed_Exception( 'discovery_failed', $e->getMessage(), 0, $e );
100 }
101
102 $client_id = $registered_client->get_client_id();
103
104 try {
105 $client_assertion = $this->client_authenticator->create_client_assertion( $client_id, $token_endpoint );
106 } catch ( Client_Authentication_Exception $e ) {
107 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
108 throw new Token_Request_Failed_Exception( 'client_authentication_failed', $e->getMessage(), 0, $e );
109 }
110
111 $body = \array_merge(
112 [
113 'grant_type' => $grant->get_grant_type(),
114 'client_id' => $client_id,
115 'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
116 'client_assertion' => $client_assertion,
117 ],
118 $grant->get_grant_params(),
119 );
120
121 $result = $this->oauth_server_client->request(
122 'POST',
123 $token_endpoint,
124 [
125 'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
126 'body' => $body,
127 'dpop' => true,
128 ],
129 );
130
131 if ( ! $result->is_successful() ) {
132 $error = (string) $result->get_body_value( 'error', 'unknown' );
133 $description = (string) $result->get_body_value( 'error_description', '' );
134 $this->logger->warning(
135 'Token request failed for grant {grant_type}: HTTP {status}, error={error} {description}',
136 [
137 'grant_type' => $grant->get_grant_type(),
138 'status' => $result->get_status(),
139 'error' => $error,
140 'description' => $description,
141 ],
142 );
143
144 $body = $result->get_body();
145 if ( \is_array( $body ) && isset( $body['error'] ) ) {
146 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
147 throw Token_Request_Failed_Exception::from_response( $body, $result->get_status() );
148 }
149 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
150 throw new Token_Request_Failed_Exception( 'token_request_failed', 'HTTP ' . $result->get_status(), $result->get_status() );
151 }
152
153 $body = $result->get_body();
154 if ( ! \is_array( $body ) ) {
155 throw new Token_Request_Failed_Exception( 'invalid_token_response', 'Token endpoint did not return a JSON object.' );
156 }
157
158 try {
159 return Token_Set::from_response( $body );
160 } catch ( InvalidArgumentException $e ) {
161 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
162 throw new Token_Request_Failed_Exception( 'invalid_token_response', $e->getMessage(), 0, $e );
163 }
164 }
165 }
166