PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.5
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.5
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
← All changes | src/config/oauth-client.php +56 -8 18.228.5 View file →
@@ -47,11 +47,11 @@
47 47
48 48 /**
49 49 * OAuth_Client constructor.
50 50 *
51 - * @param string $token_option The option's name to save the token as.
52 - * @param Wincher_PKCE_Provider|GenericProvider $provider The provider.
53 - * @param Options_Helper $options_helper The Options_Helper instance.
51 + * @param string $token_option The option's name to save the token as.
52 + * @param Wincher_PKCE_Provider|GenericProvider $provider The provider.
53 + * @param Options_Helper $options_helper The Options_Helper instance.
54 54 *
55 55 * @throws Empty_Property_Exception Exception thrown if a token property is empty.
56 56 */
57 57 public function __construct(
@@ -70,9 +70,10 @@
70 70 $tokens['access_token'],
71 71 $tokens['refresh_token'],
72 72 $tokens['expires'],
73 73 $tokens['has_expired'],
74 - $tokens['created_at']
74 + $tokens['created_at'],
75 + ( $tokens['error_count'] ?? 0 ),
75 76 );
76 77 }
77 78 }
78 79
@@ -91,9 +92,9 @@
91 92 ->getAccessToken(
92 93 'authorization_code',
93 94 [
94 95 'code' => $code,
95 - ]
96 + ],
96 97 );
97 98
98 99 $token = OAuth_Token::from_response( $response );
99 100
@@ -201,8 +202,25 @@
201 202 return $token;
202 203 }
203 204
204 205 /**
206 + * Clears the stored token from storage.
207 + *
208 + * @return bool The stored token.
209 + *
210 + * @throws Failed_Storage_Exception Exception thrown if clearing of the token fails.
211 + */
212 + public function clear_token() {
213 + $saved = $this->options_helper->set( $this->token_option, [] );
214 +
215 + if ( $saved === false ) {
216 + throw new Failed_Storage_Exception();
217 + }
218 +
219 + return true;
220 + }
221 +
222 + /**
205 223 * Performs the specified request.
206 224 *
207 225 * @param string $method The HTTP method to use.
208 226 * @param string $url The URL to send the request to.
@@ -241,20 +259,50 @@
241 259 *
242 260 * @throws Authentication_Failed_Exception Exception thrown if authentication has failed.
243 261 */
244 262 protected function refresh_tokens( OAuth_Token $tokens ) {
263 + // We do this dance with transients since we need to make sure we don't
264 + // delete valid tokens because of a race condition when two calls are
265 + // made simultaneously to this function and refresh token rotation is
266 + // turned on in the OAuth server. This is not 100% safe, but should at
267 + // least be much better than not having any lock at all.
268 + $lock_name = \sprintf( 'lock:%s', $this->token_option );
269 + $can_lock = \get_transient( $lock_name ) === false;
270 + $has_lock = $can_lock && \set_transient( $lock_name, true, 30 );
271 +
245 272 try {
246 273 $new_tokens = $this->provider->getAccessToken(
247 274 'refresh_token',
248 275 [
249 276 'refresh_token' => $tokens->refresh_token,
250 - ]
277 + ],
251 278 );
252 279
253 - $token = OAuth_Token::from_response( $new_tokens );
280 + $token_obj = OAuth_Token::from_response( $new_tokens );
254 281
255 - return $this->store_token( $token );
282 + return $this->store_token( $token_obj );
256 283 } catch ( Exception $exception ) {
284 + // If we tried to refresh but the refresh token is invalid, delete
285 + // the tokens so that we don't try again. Only do this if we got the
286 + // lock at the beginning of the call.
287 + if ( $has_lock && $exception->getMessage() === 'invalid_grant' ) {
288 + try {
289 + // To protect from race conditions, only do this if we've
290 + // seen an error before with the same token.
291 + if ( $tokens->error_count >= 1 ) {
292 + $this->clear_token();
293 + }
294 + else {
295 + $tokens->error_count += 1;
296 + $this->store_token( $tokens );
297 + }
298 + } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch
299 + // Pass through.
300 + }
301 + }
302 +
257 303 throw new Authentication_Failed_Exception( $exception );
304 + } finally {
305 + \delete_transient( $lock_name );
258 306 }
259 307 }
260 308 }