PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / trunk
Yoast SEO – Advanced SEO with real-time guidance and built-in AI vtrunk
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 | vendor_prefixed/league/oauth2-client/src/Provider/AbstractProvider.php +102 -9 18.3 → trunk View file →
@@ -16,8 +16,10 @@
16 16
17 17 use YoastSEO_Vendor\GuzzleHttp\Client as HttpClient;
18 18 use YoastSEO_Vendor\GuzzleHttp\ClientInterface as HttpClientInterface;
19 19 use YoastSEO_Vendor\GuzzleHttp\Exception\BadResponseException;
20 +use YoastSEO_Vendor\GuzzleHttp\Exception\GuzzleException;
21 +use InvalidArgumentException;
20 22 use YoastSEO_Vendor\League\OAuth2\Client\Grant\AbstractGrant;
21 23 use YoastSEO_Vendor\League\OAuth2\Client\Grant\GrantFactory;
22 24 use YoastSEO_Vendor\League\OAuth2\Client\OptionProvider\OptionProviderInterface;
23 25 use YoastSEO_Vendor\League\OAuth2\Client\OptionProvider\PostAuthOptionProvider;
@@ -41,9 +43,9 @@
41 43 use ArrayAccessorTrait;
42 44 use GuardedPropertyTrait;
43 45 use QueryBuilderTrait;
44 46 /**
45 - * @var string Key used in a token response to identify the resource owner.
47 + * @var string|null Key used in a token response to identify the resource owner.
46 48 */
47 49 const ACCESS_TOKEN_RESOURCE_OWNER_ID = null;
48 50 /**
49 51 * @var string HTTP method used to fetch access tokens.
@@ -53,8 +55,19 @@
53 55 * @var string HTTP method used to fetch access tokens.
54 56 */
55 57 const METHOD_POST = 'POST';
56 58 /**
59 + * @var string PKCE method used to fetch authorization token.
60 + * The PKCE code challenge will be hashed with sha256 (recommended).
61 + */
62 + const PKCE_METHOD_S256 = 'S256';
63 + /**
64 + * @var string PKCE method used to fetch authorization token.
65 + * The PKCE code challenge will be sent as plain text, this is NOT recommended.
66 + * Only use `plain` if no other option is possible.
67 + */
68 + const PKCE_METHOD_PLAIN = 'plain';
69 + /**
57 70 * @var string
58 71 */
59 72 protected $clientId;
60 73 /**
@@ -69,8 +82,12 @@
69 82 * @var string
70 83 */
71 84 protected $state;
72 85 /**
86 + * @var string|null
87 + */
88 + protected $pkceCode = null;
89 + /**
73 90 * @var GrantFactory
74 91 */
75 92 protected $grantFactory;
76 93 /**
@@ -227,8 +244,32 @@
227 244 {
228 245 return $this->state;
229 246 }
230 247 /**
248 + * Set the value of the pkceCode parameter.
249 + *
250 + * When using PKCE this should be set before requesting an access token.
251 + *
252 + * @param string $pkceCode
253 + * @return self
254 + */
255 + public function setPkceCode($pkceCode)
256 + {
257 + $this->pkceCode = $pkceCode;
258 + return $this;
259 + }
260 + /**
261 + * Returns the current value of the pkceCode parameter.
262 + *
263 + * This can be accessed by the redirect handler during authorization.
264 + *
265 + * @return string|null
266 + */
267 + public function getPkceCode()
268 + {
269 + return $this->pkceCode;
270 + }
271 + /**
231 272 * Returns the base URL for authorizing a client.
232 273 *
233 274 * Eg. https://oauth.service.com/authorize
234 275 *
@@ -264,8 +305,20 @@
264 305 // the amount of bytes by half to produce the correct length.
265 306 return \bin2hex(\random_bytes($length / 2));
266 307 }
267 308 /**
309 + * Returns a new random string to use as PKCE code_verifier and
310 + * hashed as code_challenge parameters in an authorization flow.
311 + * Must be between 43 and 128 characters long.
312 + *
313 + * @param int $length Length of the random string to be generated.
314 + * @return string
315 + */
316 + protected function getRandomPkceCode($length = 64)
317 + {
318 + return \substr(\strtr(\base64_encode(\random_bytes($length)), '+/', '-_'), 0, $length);
319 + }
320 + /**
268 321 * Returns the default scopes used by this provider.
269 322 *
270 323 * This should only be the scopes that are required to request the details
271 324 * of the resource owner, rather than all the available scopes.
@@ -283,12 +336,20 @@
283 336 {
284 337 return ',';
285 338 }
286 339 /**
340 + * @return string|null
341 + */
342 + protected function getPkceMethod()
343 + {
344 + return null;
345 + }
346 + /**
287 347 * Returns authorization parameters based on provided options.
288 348 *
289 349 * @param array $options
290 350 * @return array Authorization parameters
351 + * @throws InvalidArgumentException
291 352 */
292 353 protected function getAuthorizationParameters(array $options)
293 354 {
294 355 if (empty($options['state'])) {
@@ -303,8 +364,20 @@
303 364 $options['scope'] = \implode($separator, $options['scope']);
304 365 }
305 366 // Store the state as it may need to be accessed later on.
306 367 $this->state = $options['state'];
368 + $pkceMethod = $this->getPkceMethod();
369 + if (!empty($pkceMethod)) {
370 + $this->pkceCode = $this->getRandomPkceCode();
371 + if ($pkceMethod === static::PKCE_METHOD_S256) {
372 + $options['code_challenge'] = \trim(\strtr(\base64_encode(\hash('sha256', $this->pkceCode, \true)), '+/', '-_'), '=');
373 + } elseif ($pkceMethod === static::PKCE_METHOD_PLAIN) {
374 + $options['code_challenge'] = $this->pkceCode;
375 + } else {
376 + throw new \InvalidArgumentException('Unknown PKCE method "' . $pkceMethod . '".');
377 + }
378 + $options['code_challenge_method'] = $pkceMethod;
379 + }
307 380 // Business code layer might set a different redirect_uri parameter
308 381 // depending on the context, leave it as-is
309 382 if (!isset($options['redirect_uri'])) {
310 383 $options['redirect_uri'] = $this->redirectUri;
@@ -326,8 +399,9 @@
326 399 * Builds the authorization URL.
327 400 *
328 401 * @param array $options
329 402 * @return string Authorization URL
403 + * @throws InvalidArgumentException
330 404 */
331 405 public function getAuthorizationUrl(array $options = [])
332 406 {
333 407 $base = $this->getBaseAuthorizationUrl();
@@ -340,10 +414,11 @@
340 414 *
341 415 * @param array $options
342 416 * @param callable|null $redirectHandler
343 417 * @return mixed
418 + * @throws InvalidArgumentException
344 419 */
345 - public function authorize(array $options = [], callable $redirectHandler = null)
420 + public function authorize(array $options = [], ?callable $redirectHandler = null)
346 421 {
347 422 $url = $this->getAuthorizationUrl($options);
348 423 if ($redirectHandler) {
349 424 return $redirectHandler($url, $this);
@@ -442,17 +517,26 @@
442 517 }
443 518 /**
444 519 * Requests an access token using a specified grant and option set.
445 520 *
446 - * @param mixed $grant
447 - * @param array $options
521 + * @param mixed $grant
522 + * @param array<string, mixed> $options
523 + * @return AccessTokenInterface
448 524 * @throws IdentityProviderException
449 - * @return AccessTokenInterface
525 + * @throws UnexpectedValueException
526 + * @throws GuzzleException
450 527 */
451 528 public function getAccessToken($grant, array $options = [])
452 529 {
453 530 $grant = $this->verifyGrant($grant);
531 + if (isset($options['scope']) && \is_array($options['scope'])) {
532 + $separator = $this->getScopeSeparator();
533 + $options['scope'] = \implode($separator, $options['scope']);
534 + }
454 535 $params = ['client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'redirect_uri' => $this->redirectUri];
536 + if (!empty($this->pkceCode)) {
537 + $params['code_verifier'] = $this->pkceCode;
538 + }
455 539 $params = $grant->prepareRequestParameters($params, $options);
456 540 $request = $this->getAccessTokenRequest($params);
457 541 $response = $this->getParsedResponse($request);
458 542 if (\false === \is_array($response)) {
@@ -478,9 +562,9 @@
478 562 * Returns an authenticated PSR-7 request instance.
479 563 *
480 564 * @param string $method
481 565 * @param string $url
482 - * @param AccessTokenInterface|string $token
566 + * @param AccessTokenInterface|string|null $token
483 567 * @param array $options Any of "headers", "body", and "protocolVersion".
484 568 * @return RequestInterface
485 569 */
486 570 public function getAuthenticatedRequest($method, $url, $token, array $options = [])
@@ -510,8 +594,9 @@
510 594 * errors! It is recommended to wrap this method in a try/catch block.
511 595 *
512 596 * @param RequestInterface $request
513 597 * @return ResponseInterface
598 + * @throws GuzzleException
514 599 */
515 600 public function getResponse(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request)
516 601 {
517 602 return $this->getHttpClient()->send($request);
@@ -519,10 +604,12 @@
519 604 /**
520 605 * Sends a request and returns the parsed response.
521 606 *
522 607 * @param RequestInterface $request
608 + * @return mixed
523 609 * @throws IdentityProviderException
524 - * @return mixed
610 + * @throws UnexpectedValueException
611 + * @throws GuzzleException
525 612 */
526 613 public function getParsedResponse(\YoastSEO_Vendor\Psr\Http\Message\RequestInterface $request)
527 614 {
528 615 try {
@@ -556,9 +643,9 @@
556 643 * @return string Semi-colon separated join of content-type headers.
557 644 */
558 645 protected function getContentType(\YoastSEO_Vendor\Psr\Http\Message\ResponseInterface $response)
559 646 {
560 - return \join(';', (array) $response->getHeader('content-type'));
647 + return \implode(';', $response->getHeader('content-type'));
561 648 }
562 649 /**
563 650 * Parses the response according to its content-type header.
564 651 *
@@ -603,9 +690,9 @@
603 690 *
604 691 * Custom mapping of expiration, etc should be done here. Always call the
605 692 * parent method when overloading this method.
606 693 *
607 - * @param mixed $result
694 + * @param array<string, mixed> $result
608 695 * @return array
609 696 */
610 697 protected function prepareAccessTokenResponse(array $result)
611 698 {
@@ -641,8 +728,11 @@
641 728 * Requests and returns the resource owner of given access token.
642 729 *
643 730 * @param AccessToken $token
644 731 * @return ResourceOwnerInterface
732 + * @throws IdentityProviderException
733 + * @throws UnexpectedValueException
734 + * @throws GuzzleException
645 735 */
646 736 public function getResourceOwner(\YoastSEO_Vendor\League\OAuth2\Client\Token\AccessToken $token)
647 737 {
648 738 $response = $this->fetchResourceOwnerDetails($token);
@@ -652,8 +742,11 @@
652 742 * Requests resource owner details.
653 743 *
654 744 * @param AccessToken $token
655 745 * @return mixed
746 + * @throws IdentityProviderException
747 + * @throws UnexpectedValueException
748 + * @throws GuzzleException
656 749 */
657 750 protected function fetchResourceOwnerDetails(\YoastSEO_Vendor\League\OAuth2\Client\Token\AccessToken $token)
658 751 {
659 752 $url = $this->getResourceOwnerDetailsUrl($token);