PluginProbe
Media Cloud Sync / 1.4.2
Media Cloud Sync v1.4.2
1.4.2 1.4.1 1.4.0 1.3.12 1.3.11 1.3.10 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.2.0 1.2.10 1.2.11 1.2.12 1.2.13 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 All 36 releases
media-cloud-sync / includes / sdk / google / google / auth / src / OAuth2.php

OAuth2.php in Media Cloud Sync 1.4.2, at includes/sdk/google/google/auth/src/OAuth2.php

1,548 lines 47.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * Copyright 2015 Google Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 namespace Dudlewebs\WPMCS\GCP\Google\Auth;
19
20 use Dudlewebs\WPMCS\GCP\Firebase\JWT\JWT;
21 use Dudlewebs\WPMCS\GCP\Firebase\JWT\Key;
22 use Dudlewebs\WPMCS\GCP\Google\Auth\HttpHandler\HttpClientCache;
23 use Dudlewebs\WPMCS\GCP\Google\Auth\HttpHandler\HttpHandlerFactory;
24 use Dudlewebs\WPMCS\GCP\GuzzleHttp\Psr7\Query;
25 use Dudlewebs\WPMCS\GCP\GuzzleHttp\Psr7\Request;
26 use Dudlewebs\WPMCS\GCP\GuzzleHttp\Psr7\Utils;
27 use InvalidArgumentException;
28 use Dudlewebs\WPMCS\GCP\Psr\Http\Message\RequestInterface;
29 use Dudlewebs\WPMCS\GCP\Psr\Http\Message\ResponseInterface;
30 use Dudlewebs\WPMCS\GCP\Psr\Http\Message\UriInterface;
31 /**
32 * OAuth2 supports authentication by OAuth2 2-legged flows.
33 *
34 * It primary supports
35 * - service account authorization
36 * - authorization where a user already has an access token
37 */
38 class OAuth2 implements FetchAuthTokenInterface
39 {
40 const DEFAULT_EXPIRY_SECONDS = 3600;
41 // 1 hour
42 const DEFAULT_SKEW_SECONDS = 60;
43 // 1 minute
44 const JWT_URN = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
45 const STS_URN = 'urn:ietf:params:oauth:grant-type:token-exchange';
46 private const STS_REQUESTED_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
47 /**
48 * TODO: determine known methods from the keys of JWT::methods.
49 *
50 * @var array<string>
51 */
52 public static $knownSigningAlgorithms = ['HS256', 'HS512', 'HS384', 'RS256'];
53 /**
54 * The well known grant types.
55 *
56 * @var array<string>
57 */
58 public static $knownGrantTypes = ['authorization_code', 'refresh_token', 'password', 'client_credentials'];
59 /**
60 * - authorizationUri
61 * The authorization server's HTTP endpoint capable of
62 * authenticating the end-user and obtaining authorization.
63 *
64 * @var ?UriInterface
65 */
66 private $authorizationUri;
67 /**
68 * - tokenCredentialUri
69 * The authorization server's HTTP endpoint capable of issuing
70 * tokens and refreshing expired tokens.
71 *
72 * @var UriInterface
73 */
74 private $tokenCredentialUri;
75 /**
76 * The redirection URI used in the initial request.
77 *
78 * @var ?string
79 */
80 private $redirectUri;
81 /**
82 * A unique identifier issued to the client to identify itself to the
83 * authorization server.
84 *
85 * @var string
86 */
87 private $clientId;
88 /**
89 * A shared symmetric secret issued by the authorization server, which is
90 * used to authenticate the client.
91 *
92 * @var string
93 */
94 private $clientSecret;
95 /**
96 * The resource owner's username.
97 *
98 * @var ?string
99 */
100 private $username;
101 /**
102 * The resource owner's password.
103 *
104 * @var ?string
105 */
106 private $password;
107 /**
108 * The scope of the access request, expressed either as an Array or as a
109 * space-delimited string.
110 *
111 * @var ?array<string>
112 */
113 private $scope;
114 /**
115 * An arbitrary string designed to allow the client to maintain state.
116 *
117 * @var string
118 */
119 private $state;
120 /**
121 * The authorization code issued to this client.
122 *
123 * Only used by the authorization code access grant type.
124 *
125 * @var ?string
126 */
127 private $code;
128 /**
129 * The issuer ID when using assertion profile.
130 *
131 * @var ?string
132 */
133 private $issuer;
134 /**
135 * The target audience for assertions.
136 *
137 * @var string
138 */
139 private $audience;
140 /**
141 * The target sub when issuing assertions.
142 *
143 * @var string
144 */
145 private $sub;
146 /**
147 * The number of seconds assertions are valid for.
148 *
149 * @var int
150 */
151 private $expiry;
152 /**
153 * The signing key when using assertion profile.
154 *
155 * @var ?string
156 */
157 private $signingKey;
158 /**
159 * The signing key id when using assertion profile. Param kid in jwt header
160 *
161 * @var string
162 */
163 private $signingKeyId;
164 /**
165 * The signing algorithm when using an assertion profile.
166 *
167 * @var ?string
168 */
169 private $signingAlgorithm;
170 /**
171 * The refresh token associated with the access token to be refreshed.
172 *
173 * @var ?string
174 */
175 private $refreshToken;
176 /**
177 * The current access token.
178 *
179 * @var string
180 */
181 private $accessToken;
182 /**
183 * The current ID token.
184 *
185 * @var string
186 */
187 private $idToken;
188 /**
189 * The scopes granted to the current access token
190 *
191 * @var string
192 */
193 private $grantedScope;
194 /**
195 * The lifetime in seconds of the current access token.
196 *
197 * @var ?int
198 */
199 private $expiresIn;
200 /**
201 * The expiration time of the access token as a number of seconds since the
202 * unix epoch.
203 *
204 * @var ?int
205 */
206 private $expiresAt;
207 /**
208 * The issue time of the access token as a number of seconds since the unix
209 * epoch.
210 *
211 * @var ?int
212 */
213 private $issuedAt;
214 /**
215 * The current grant type.
216 *
217 * @var ?string
218 */
219 private $grantType;
220 /**
221 * When using an extension grant type, this is the set of parameters used by
222 * that extension.
223 *
224 * @var array<mixed>
225 */
226 private $extensionParams;
227 /**
228 * When using the toJwt function, these claims will be added to the JWT
229 * payload.
230 *
231 * @var array<mixed>
232 */
233 private $additionalClaims;
234 /**
235 * The code verifier for PKCE for OAuth 2.0. When set, the authorization
236 * URI will contain the Code Challenge and Code Challenge Method querystring
237 * parameters, and the token URI will contain the Code Verifier parameter.
238 *
239 * @see https://datatracker.ietf.org/doc/html/rfc7636
240 * @var ?string
241 */
242 private $codeVerifier;
243 /**
244 * For STS requests.
245 * A URI that indicates the target service or resource where the client
246 * intends to use the requested security token.
247 */
248 private ?string $resource;
249 /**
250 * For STS requests.
251 * A fetcher for the "subject_token", which is a security token that
252 * represents the identity of the party on behalf of whom the request is
253 * being made.
254 */
255 private ?ExternalAccountCredentialSourceInterface $subjectTokenFetcher;
256 /**
257 * For STS requests.
258 * An identifier, that indicates the type of the security token in the
259 * subjectToken parameter.
260 */
261 private ?string $subjectTokenType;
262 /**
263 * For STS requests.
264 * A security token that represents the identity of the acting party.
265 */
266 private ?string $actorToken;
267 /**
268 * For STS requests.
269 * An identifier that indicates the type of the security token in the
270 * actorToken parameter.
271 */
272 private ?string $actorTokenType;
273 /**
274 * From STS response.
275 * An identifier for the representation of the issued security token.
276 */
277 private ?string $issuedTokenType = null;
278 /**
279 * From STS response.
280 * An identifier for the representation of the issued security token.
281 *
282 * @var array<mixed>
283 */
284 private array $additionalOptions;
285 /**
286 * Create a new OAuthCredentials.
287 *
288 * The configuration array accepts various options
289 *
290 * - authorizationUri
291 * The authorization server's HTTP endpoint capable of
292 * authenticating the end-user and obtaining authorization.
293 *
294 * - tokenCredentialUri
295 * The authorization server's HTTP endpoint capable of issuing
296 * tokens and refreshing expired tokens.
297 *
298 * - clientId
299 * A unique identifier issued to the client to identify itself to the
300 * authorization server.
301 *
302 * - clientSecret
303 * A shared symmetric secret issued by the authorization server,
304 * which is used to authenticate the client.
305 *
306 * - scope
307 * The scope of the access request, expressed either as an Array
308 * or as a space-delimited String.
309 *
310 * - state
311 * An arbitrary string designed to allow the client to maintain state.
312 *
313 * - redirectUri
314 * The redirection URI used in the initial request.
315 *
316 * - username
317 * The resource owner's username.
318 *
319 * - password
320 * The resource owner's password.
321 *
322 * - issuer
323 * Issuer ID when using assertion profile
324 *
325 * - audience
326 * Target audience for assertions
327 *
328 * - expiry
329 * Number of seconds assertions are valid for
330 *
331 * - signingKey
332 * Signing key when using assertion profile
333 *
334 * - signingKeyId
335 * Signing key id when using assertion profile
336 *
337 * - refreshToken
338 * The refresh token associated with the access token
339 * to be refreshed.
340 *
341 * - accessToken
342 * The current access token for this client.
343 *
344 * - idToken
345 * The current ID token for this client.
346 *
347 * - extensionParams
348 * When using an extension grant type, this is the set of parameters used
349 * by that extension.
350 *
351 * - codeVerifier
352 * The code verifier for PKCE for OAuth 2.0.
353 *
354 * - resource
355 * The target service or resource where the client ntends to use the
356 * requested security token.
357 *
358 * - subjectTokenFetcher
359 * A fetcher for the "subject_token", which is a security token that
360 * represents the identity of the party on behalf of whom the request is
361 * being made.
362 *
363 * - subjectTokenType
364 * An identifier that indicates the type of the security token in the
365 * subjectToken parameter.
366 *
367 * - actorToken
368 * A security token that represents the identity of the acting party.
369 *
370 * - actorTokenType
371 * An identifier for the representation of the issued security token.
372 *
373 * @param array<mixed> $config Configuration array
374 */
375 public function __construct(array $config)
376 {
377 $opts = \array_merge(['expiry' => self::DEFAULT_EXPIRY_SECONDS, 'extensionParams' => [], 'authorizationUri' => null, 'redirectUri' => null, 'tokenCredentialUri' => null, 'state' => null, 'username' => null, 'password' => null, 'clientId' => null, 'clientSecret' => null, 'issuer' => null, 'sub' => null, 'audience' => null, 'signingKey' => null, 'signingKeyId' => null, 'signingAlgorithm' => null, 'scope' => null, 'additionalClaims' => [], 'codeVerifier' => null, 'resource' => null, 'subjectTokenFetcher' => null, 'subjectTokenType' => null, 'actorToken' => null, 'actorTokenType' => null, 'additionalOptions' => []], $config);
378 $this->setAuthorizationUri($opts['authorizationUri']);
379 $this->setRedirectUri($opts['redirectUri']);
380 $this->setTokenCredentialUri($opts['tokenCredentialUri']);
381 $this->setState($opts['state']);
382 $this->setUsername($opts['username']);
383 $this->setPassword($opts['password']);
384 $this->setClientId($opts['clientId']);
385 $this->setClientSecret($opts['clientSecret']);
386 $this->setIssuer($opts['issuer']);
387 $this->setSub($opts['sub']);
388 $this->setExpiry($opts['expiry']);
389 $this->setAudience($opts['audience']);
390 $this->setSigningKey($opts['signingKey']);
391 $this->setSigningKeyId($opts['signingKeyId']);
392 $this->setSigningAlgorithm($opts['signingAlgorithm']);
393 $this->setScope($opts['scope']);
394 $this->setExtensionParams($opts['extensionParams']);
395 $this->setAdditionalClaims($opts['additionalClaims']);
396 $this->setCodeVerifier($opts['codeVerifier']);
397 // for STS
398 $this->resource = $opts['resource'];
399 $this->subjectTokenFetcher = $opts['subjectTokenFetcher'];
400 $this->subjectTokenType = $opts['subjectTokenType'];
401 $this->actorToken = $opts['actorToken'];
402 $this->actorTokenType = $opts['actorTokenType'];
403 $this->additionalOptions = $opts['additionalOptions'];
404 $this->updateToken($opts);
405 }
406 /**
407 * Verifies the idToken if present.
408 *
409 * - if none is present, return null
410 * - if present, but invalid, raises DomainException.
411 * - otherwise returns the payload in the idtoken as a PHP object.
412 *
413 * The behavior of this method varies depending on the version of
414 * `firebase/php-jwt` you are using. In versions 6.0 and above, you cannot
415 * provide multiple $allowed_algs, and instead must provide an array of Key
416 * objects as the $publicKey.
417 *
418 * @param string|Key|Key[] $publicKey The public key to use to authenticate the token
419 * @param string|array<string> $allowed_algs algorithm or array of supported verification algorithms.
420 * Providing more than one algorithm will throw an exception.
421 * @throws \DomainException if the token is missing an audience.
422 * @throws \DomainException if the audience does not match the one set in
423 * the OAuth2 class instance.
424 * @throws \UnexpectedValueException If the token is invalid
425 * @throws \InvalidArgumentException If more than one value for allowed_algs is supplied
426 * @throws \Firebase\JWT\SignatureInvalidException If the signature is invalid.
427 * @throws \Firebase\JWT\BeforeValidException If the token is not yet valid.
428 * @throws \Firebase\JWT\ExpiredException If the token has expired.
429 * @return null|object
430 */
431 public function verifyIdToken($publicKey = null, $allowed_algs = [])
432 {
433 $idToken = $this->getIdToken();
434 if (\is_null($idToken)) {
435 return null;
436 }
437 $resp = $this->jwtDecode($idToken, $publicKey, $allowed_algs);
438 if (!\property_exists($resp, 'aud')) {
439 throw new \DomainException('No audience found the id token');
440 }
441 if ($resp->aud != $this->getAudience()) {
442 throw new \DomainException('Wrong audience present in the id token');
443 }
444 return $resp;
445 }
446 /**
447 * Obtains the encoded jwt from the instance data.
448 *
449 * @param array<mixed> $config array optional configuration parameters
450 * @return string
451 */
452 public function toJwt(array $config = [])
453 {
454 if (\is_null($this->getSigningKey())) {
455 throw new \DomainException('No signing key available');
456 }
457 if (\is_null($this->getSigningAlgorithm())) {
458 throw new \DomainException('No signing algorithm specified');
459 }
460 $now = \time();
461 $opts = \array_merge(['skew' => self::DEFAULT_SKEW_SECONDS], $config);
462 $assertion = ['iss' => $this->getIssuer(), 'exp' => $now + $this->getExpiry(), 'iat' => $now - $opts['skew']];
463 foreach ($assertion as $k => $v) {
464 if (\is_null($v)) {
465 throw new \DomainException($k . ' should not be null');
466 }
467 }
468 if (!\is_null($this->getAudience())) {
469 $assertion['aud'] = $this->getAudience();
470 }
471 if (!\is_null($this->getScope())) {
472 $assertion['scope'] = $this->getScope();
473 }
474 if (empty($assertion['scope']) && empty($assertion['aud'])) {
475 throw new \DomainException('one of scope or aud should not be null');
476 }
477 if (!\is_null($this->getSub())) {
478 $assertion['sub'] = $this->getSub();
479 }
480 $assertion += $this->getAdditionalClaims();
481 return JWT::encode($assertion, $this->getSigningKey(), $this->getSigningAlgorithm(), $this->getSigningKeyId());
482 }
483 /**
484 * Generates a request for token credentials.
485 *
486 * @param callable|null $httpHandler callback which delivers psr7 request
487 * @param array<mixed> $headers [optional] Additional headers to pass to
488 * the token endpoint request.
489 * @return RequestInterface the authorization Url.
490 */
491 public function generateCredentialsRequest(?callable $httpHandler = null, array $headers = [])
492 {
493 $uri = $this->getTokenCredentialUri();
494 if (\is_null($uri)) {
495 throw new \DomainException('No token credential URI was set.');
496 }
497 $grantType = $this->getGrantType();
498 $params = ['grant_type' => $grantType];
499 switch ($grantType) {
500 case 'authorization_code':
501 $params['code'] = $this->getCode();
502 $params['redirect_uri'] = $this->getRedirectUri();
503 if ($this->codeVerifier) {
504 $params['code_verifier'] = $this->codeVerifier;
505 }
506 $this->addClientCredentials($params);
507 break;
508 case 'password':
509 $params['username'] = $this->getUsername();
510 $params['password'] = $this->getPassword();
511 $this->addClientCredentials($params);
512 break;
513 case 'refresh_token':
514 $params['refresh_token'] = $this->getRefreshToken();
515 if (isset($this->getAdditionalClaims()['target_audience'])) {
516 $params['target_audience'] = $this->getAdditionalClaims()['target_audience'];
517 }
518 $this->addClientCredentials($params);
519 break;
520 case self::JWT_URN:
521 $params['assertion'] = $this->toJwt();
522 break;
523 case self::STS_URN:
524 $token = $this->subjectTokenFetcher->fetchSubjectToken($httpHandler);
525 $params['subject_token'] = $token;
526 $params['subject_token_type'] = $this->subjectTokenType;
527 $params += \array_filter(['resource' => $this->resource, 'audience' => $this->audience, 'scope' => $this->getScope(), 'requested_token_type' => self::STS_REQUESTED_TOKEN_TYPE, 'actor_token' => $this->actorToken, 'actor_token_type' => $this->actorTokenType]);
528 if ($this->additionalOptions) {
529 $params['options'] = \json_encode($this->additionalOptions);
530 }
531 break;
532 default:
533 if (!\is_null($this->getRedirectUri())) {
534 # Grant type was supposed to be 'authorization_code', as there
535 # is a redirect URI.
536 throw new \DomainException('Missing authorization code');
537 }
538 unset($params['grant_type']);
539 if (!\is_null($grantType)) {
540 $params['grant_type'] = $grantType;
541 }
542 $params = \array_merge($params, $this->getExtensionParams());
543 }
544 $headers = ['Cache-Control' => 'no-store', 'Content-Type' => 'application/x-www-form-urlencoded'] + $headers;
545 return new Request('POST', $uri, $headers, Query::build($params));
546 }
547 /**
548 * Fetches the auth tokens based on the current state.
549 *
550 * @param callable|null $httpHandler callback which delivers psr7 request
551 * @param array<mixed> $headers [optional] If present, add these headers to the token
552 * endpoint request.
553 * @return array<mixed> the response
554 */
555 public function fetchAuthToken(?callable $httpHandler = null, array $headers = [])
556 {
557 if (\is_null($httpHandler)) {
558 $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
559 }
560 $response = $httpHandler($this->generateCredentialsRequest($httpHandler, $headers));
561 $credentials = $this->parseTokenResponse($response);
562 $this->updateToken($credentials);
563 if (isset($credentials['scope'])) {
564 $this->setGrantedScope($credentials['scope']);
565 }
566 return $credentials;
567 }
568 /**
569 * @deprecated
570 *
571 * Obtains a key that can used to cache the results of #fetchAuthToken.
572 *
573 * The key is derived from the scopes.
574 *
575 * @return ?string a key that may be used to cache the auth token.
576 */
577 public function getCacheKey()
578 {
579 if (\is_array($this->scope)) {
580 return \implode(':', $this->scope);
581 }
582 if ($this->audience) {
583 return $this->audience;
584 }
585 // If scope has not set, return null to indicate no caching.
586 return null;
587 }
588 /**
589 * Gets this instance's SubjectTokenFetcher
590 *
591 * @return null|ExternalAccountCredentialSourceInterface
592 */
593 public function getSubjectTokenFetcher() : ?ExternalAccountCredentialSourceInterface
594 {
595 return $this->subjectTokenFetcher;
596 }
597 /**
598 * Parses the fetched tokens.
599 *
600 * @param ResponseInterface $resp the response.
601 * @return array<mixed> the tokens parsed from the response body.
602 * @throws \Exception
603 */
604 public function parseTokenResponse(ResponseInterface $resp)
605 {
606 $body = (string) $resp->getBody();
607 if ($resp->hasHeader('Content-Type') && $resp->getHeaderLine('Content-Type') == 'application/x-www-form-urlencoded') {
608 $res = [];
609 \parse_str($body, $res);
610 return $res;
611 }
612 // Assume it's JSON; if it's not throw an exception
613 if (null === ($res = \json_decode($body, \true))) {
614 throw new \Exception('Invalid JSON response');
615 }
616 return $res;
617 }
618 /**
619 * Updates an OAuth 2.0 client.
620 *
621 * Example:
622 * ```
623 * $oauth->updateToken([
624 * 'refresh_token' => 'n4E9O119d',
625 * 'access_token' => 'FJQbwq9',
626 * 'expires_in' => 3600
627 * ]);
628 * ```
629 *
630 * @param array<mixed> $config
631 * The configuration parameters related to the token.
632 *
633 * - refresh_token
634 * The refresh token associated with the access token
635 * to be refreshed.
636 *
637 * - access_token
638 * The current access token for this client.
639 *
640 * - id_token
641 * The current ID token for this client.
642 *
643 * - expires_in
644 * The time in seconds until access token expiration.
645 *
646 * - expires_at
647 * The time as an integer number of seconds since the Epoch
648 *
649 * - issued_at
650 * The timestamp that the token was issued at.
651 * @return void
652 */
653 public function updateToken(array $config)
654 {
655 $opts = \array_merge(['extensionParams' => [], 'access_token' => null, 'id_token' => null, 'expires_in' => null, 'expires_at' => null, 'issued_at' => null, 'scope' => null], $config);
656 $this->setExpiresAt($opts['expires_at']);
657 $this->setExpiresIn($opts['expires_in']);
658 // By default, the token is issued at `Time.now` when `expiresIn` is set,
659 // but this can be used to supply a more precise time.
660 if (!\is_null($opts['issued_at'])) {
661 $this->setIssuedAt($opts['issued_at']);
662 }
663 $this->setAccessToken($opts['access_token']);
664 $this->setIdToken($opts['id_token']);
665 // The refresh token should only be updated if a value is explicitly
666 // passed in, as some access token responses do not include a refresh
667 // token.
668 if (\array_key_exists('refresh_token', $opts)) {
669 $this->setRefreshToken($opts['refresh_token']);
670 }
671 // Required for STS response. An identifier for the representation of
672 // the issued security token.
673 if (\array_key_exists('issued_token_type', $opts)) {
674 $this->issuedTokenType = $opts['issued_token_type'];
675 }
676 }
677 /**
678 * Builds the authorization Uri that the user should be redirected to.
679 *
680 * @param array<mixed> $config configuration options that customize the return url.
681 * @return UriInterface the authorization Url.
682 * @throws InvalidArgumentException
683 */
684 public function buildFullAuthorizationUri(array $config = [])
685 {
686 if (\is_null($this->getAuthorizationUri())) {
687 throw new InvalidArgumentException('requires an authorizationUri to have been set');
688 }
689 $params = \array_merge(['response_type' => 'code', 'access_type' => 'offline', 'client_id' => $this->clientId, 'redirect_uri' => $this->redirectUri, 'state' => $this->state, 'scope' => $this->getScope()], $config);
690 // Validate the auth_params
691 if (\is_null($params['client_id'])) {
692 throw new InvalidArgumentException('missing the required client identifier');
693 }
694 if (\is_null($params['redirect_uri'])) {
695 throw new InvalidArgumentException('missing the required redirect URI');
696 }
697 if (!empty($params['prompt']) && !empty($params['approval_prompt'])) {
698 throw new InvalidArgumentException('prompt and approval_prompt are mutually exclusive');
699 }
700 if ($this->codeVerifier) {
701 $params['code_challenge'] = $this->getCodeChallenge($this->codeVerifier);
702 $params['code_challenge_method'] = $this->getCodeChallengeMethod();
703 }
704 // Construct the uri object; return it if it is valid.
705 $result = clone $this->authorizationUri;
706 $existingParams = Query::parse($result->getQuery());
707 $result = $result->withQuery(Query::build(\array_merge($existingParams, $params)));
708 if ($result->getScheme() != 'https') {
709 throw new InvalidArgumentException('Authorization endpoint must be protected by TLS');
710 }
711 return $result;
712 }
713 /**
714 * @return string|null
715 */
716 public function getCodeVerifier() : ?string
717 {
718 return $this->codeVerifier;
719 }
720 /**
721 * A cryptographically random string that is used to correlate the
722 * authorization request to the token request.
723 *
724 * The code verifier for PKCE for OAuth 2.0. When set, the authorization
725 * URI will contain the Code Challenge and Code Challenge Method querystring
726 * parameters, and the token URI will contain the Code Verifier parameter.
727 *
728 * @see https://datatracker.ietf.org/doc/html/rfc7636
729 *
730 * @param string|null $codeVerifier
731 */
732 public function setCodeVerifier(?string $codeVerifier) : void
733 {
734 $this->codeVerifier = $codeVerifier;
735 }
736 /**
737 * Generates a random 128-character string for the "code_verifier" parameter
738 * in PKCE for OAuth 2.0. This is a cryptographically random string that is
739 * determined using random_int, hashed using "hash" and sha256, and base64
740 * encoded.
741 *
742 * When this method is called, the code verifier is set on the object.
743 *
744 * @return string
745 */
746 public function generateCodeVerifier() : string
747 {
748 return $this->codeVerifier = $this->generateRandomString(128);
749 }
750 private function getCodeChallenge(string $randomString) : string
751 {
752 return \rtrim(\strtr(\base64_encode(\hash('sha256', $randomString, \true)), '+/', '-_'), '=');
753 }
754 private function getCodeChallengeMethod() : string
755 {
756 return 'S256';
757 }
758 private function generateRandomString(int $length) : string
759 {
760 $validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~';
761 $validCharsLen = \strlen($validChars);
762 $str = '';
763 $i = 0;
764 while ($i++ < $length) {
765 $str .= $validChars[\random_int(0, $validCharsLen - 1)];
766 }
767 return $str;
768 }
769 /**
770 * Sets the authorization server's HTTP endpoint capable of authenticating
771 * the end-user and obtaining authorization.
772 *
773 * @param string $uri
774 * @return void
775 */
776 public function setAuthorizationUri($uri)
777 {
778 $this->authorizationUri = $this->coerceUri($uri);
779 }
780 /**
781 * Gets the authorization server's HTTP endpoint capable of authenticating
782 * the end-user and obtaining authorization.
783 *
784 * @return ?UriInterface
785 */
786 public function getAuthorizationUri()
787 {
788 return $this->authorizationUri;
789 }
790 /**
791 * Gets the authorization server's HTTP endpoint capable of issuing tokens
792 * and refreshing expired tokens.
793 *
794 * @return ?UriInterface
795 */
796 public function getTokenCredentialUri()
797 {
798 return $this->tokenCredentialUri;
799 }
800 /**
801 * Sets the authorization server's HTTP endpoint capable of issuing tokens
802 * and refreshing expired tokens.
803 *
804 * @param string $uri
805 * @return void
806 */
807 public function setTokenCredentialUri($uri)
808 {
809 $this->tokenCredentialUri = $this->coerceUri($uri);
810 }
811 /**
812 * Gets the redirection URI used in the initial request.
813 *
814 * @return ?string
815 */
816 public function getRedirectUri()
817 {
818 return $this->redirectUri;
819 }
820 /**
821 * Sets the redirection URI used in the initial request.
822 *
823 * @param ?string $uri
824 * @return void
825 */
826 public function setRedirectUri($uri)
827 {
828 if (\is_null($uri)) {
829 $this->redirectUri = null;
830 return;
831 }
832 // redirect URI must be absolute
833 if (!$this->isAbsoluteUri($uri)) {
834 // "postmessage" is a reserved URI string in Google-land
835 // @see https://developers.google.com/identity/sign-in/web/server-side-flow
836 if ('postmessage' !== (string) $uri) {
837 throw new InvalidArgumentException('Redirect URI must be absolute');
838 }
839 }
840 $this->redirectUri = (string) $uri;
841 }
842 /**
843 * Gets the scope of the access requests as a space-delimited String.
844 *
845 * @return ?string
846 */
847 public function getScope()
848 {
849 if (\is_null($this->scope)) {
850 return $this->scope;
851 }
852 return \implode(' ', $this->scope);
853 }
854 /**
855 * Gets the subject token type
856 *
857 * @return ?string
858 */
859 public function getSubjectTokenType() : ?string
860 {
861 return $this->subjectTokenType;
862 }
863 /**
864 * Sets the scope of the access request, expressed either as an Array or as
865 * a space-delimited String.
866 *
867 * @param string|array<string>|null $scope
868 * @return void
869 * @throws InvalidArgumentException
870 */
871 public function setScope($scope)
872 {
873 if (\is_null($scope)) {
874 $this->scope = null;
875 } elseif (\is_string($scope)) {
876 $this->scope = \explode(' ', $scope);
877 } elseif (\is_array($scope)) {
878 foreach ($scope as $s) {
879 $pos = \strpos($s, ' ');
880 if ($pos !== \false) {
881 throw new InvalidArgumentException('array scope values should not contain spaces');
882 }
883 }
884 $this->scope = $scope;
885 } else {
886 throw new InvalidArgumentException('scopes should be a string or array of strings');
887 }
888 }
889 /**
890 * Gets the current grant type.
891 *
892 * @return ?string
893 */
894 public function getGrantType()
895 {
896 if (!\is_null($this->grantType)) {
897 return $this->grantType;
898 }
899 // Returns the inferred grant type, based on the current object instance
900 // state.
901 if (!\is_null($this->code)) {
902 return 'authorization_code';
903 }
904 if (!\is_null($this->refreshToken)) {
905 return 'refresh_token';
906 }
907 if (!\is_null($this->username) && !\is_null($this->password)) {
908 return 'password';
909 }
910 if (!\is_null($this->issuer) && !\is_null($this->signingKey)) {
911 return self::JWT_URN;
912 }
913 if (!\is_null($this->subjectTokenFetcher) && !\is_null($this->subjectTokenType)) {
914 return self::STS_URN;
915 }
916 return null;
917 }
918 /**
919 * Sets the current grant type.
920 *
921 * @param string $grantType
922 * @return void
923 * @throws InvalidArgumentException
924 */
925 public function setGrantType($grantType)
926 {
927 if (\in_array($grantType, self::$knownGrantTypes)) {
928 $this->grantType = $grantType;
929 } else {
930 // validate URI
931 if (!$this->isAbsoluteUri($grantType)) {
932 throw new InvalidArgumentException('invalid grant type');
933 }
934 $this->grantType = (string) $grantType;
935 }
936 }
937 /**
938 * Gets an arbitrary string designed to allow the client to maintain state.
939 *
940 * @return string
941 */
942 public function getState()
943 {
944 return $this->state;
945 }
946 /**
947 * Sets an arbitrary string designed to allow the client to maintain state.
948 *
949 * @param string $state
950 * @return void
951 */
952 public function setState($state)
953 {
954 $this->state = $state;
955 }
956 /**
957 * Gets the authorization code issued to this client.
958 *
959 * @return string
960 */
961 public function getCode()
962 {
963 return $this->code;
964 }
965 /**
966 * Sets the authorization code issued to this client.
967 *
968 * @param string $code
969 * @return void
970 */
971 public function setCode($code)
972 {
973 $this->code = $code;
974 }
975 /**
976 * Gets the resource owner's username.
977 *
978 * @return string
979 */
980 public function getUsername()
981 {
982 return $this->username;
983 }
984 /**
985 * Sets the resource owner's username.
986 *
987 * @param string $username
988 * @return void
989 */
990 public function setUsername($username)
991 {
992 $this->username = $username;
993 }
994 /**
995 * Gets the resource owner's password.
996 *
997 * @return string
998 */
999 public function getPassword()
1000 {
1001 return $this->password;
1002 }
1003 /**
1004 * Sets the resource owner's password.
1005 *
1006 * @param string $password
1007 * @return void
1008 */
1009 public function setPassword($password)
1010 {
1011 $this->password = $password;
1012 }
1013 /**
1014 * Sets a unique identifier issued to the client to identify itself to the
1015 * authorization server.
1016 *
1017 * @return string
1018 */
1019 public function getClientId()
1020 {
1021 return $this->clientId;
1022 }
1023 /**
1024 * Sets a unique identifier issued to the client to identify itself to the
1025 * authorization server.
1026 *
1027 * @param string $clientId
1028 * @return void
1029 */
1030 public function setClientId($clientId)
1031 {
1032 $this->clientId = $clientId;
1033 }
1034 /**
1035 * Gets a shared symmetric secret issued by the authorization server, which
1036 * is used to authenticate the client.
1037 *
1038 * @return string
1039 */
1040 public function getClientSecret()
1041 {
1042 return $this->clientSecret;
1043 }
1044 /**
1045 * Sets a shared symmetric secret issued by the authorization server, which
1046 * is used to authenticate the client.
1047 *
1048 * @param string $clientSecret
1049 * @return void
1050 */
1051 public function setClientSecret($clientSecret)
1052 {
1053 $this->clientSecret = $clientSecret;
1054 }
1055 /**
1056 * Gets the Issuer ID when using assertion profile.
1057 *
1058 * @return ?string
1059 */
1060 public function getIssuer()
1061 {
1062 return $this->issuer;
1063 }
1064 /**
1065 * Sets the Issuer ID when using assertion profile.
1066 *
1067 * @param string $issuer
1068 * @return void
1069 */
1070 public function setIssuer($issuer)
1071 {
1072 $this->issuer = $issuer;
1073 }
1074 /**
1075 * Gets the target sub when issuing assertions.
1076 *
1077 * @return ?string
1078 */
1079 public function getSub()
1080 {
1081 return $this->sub;
1082 }
1083 /**
1084 * Sets the target sub when issuing assertions.
1085 *
1086 * @param string $sub
1087 * @return void
1088 */
1089 public function setSub($sub)
1090 {
1091 $this->sub = $sub;
1092 }
1093 /**
1094 * Gets the target audience when issuing assertions.
1095 *
1096 * @return ?string
1097 */
1098 public function getAudience()
1099 {
1100 return $this->audience;
1101 }
1102 /**
1103 * Sets the target audience when issuing assertions.
1104 *
1105 * @param string $audience
1106 * @return void
1107 */
1108 public function setAudience($audience)
1109 {
1110 $this->audience = $audience;
1111 }
1112 /**
1113 * Gets the signing key when using an assertion profile.
1114 *
1115 * @return ?string
1116 */
1117 public function getSigningKey()
1118 {
1119 return $this->signingKey;
1120 }
1121 /**
1122 * Sets the signing key when using an assertion profile.
1123 *
1124 * @param string $signingKey
1125 * @return void
1126 */
1127 public function setSigningKey($signingKey)
1128 {
1129 $this->signingKey = $signingKey;
1130 }
1131 /**
1132 * Gets the signing key id when using an assertion profile.
1133 *
1134 * @return ?string
1135 */
1136 public function getSigningKeyId()
1137 {
1138 return $this->signingKeyId;
1139 }
1140 /**
1141 * Sets the signing key id when using an assertion profile.
1142 *
1143 * @param string $signingKeyId
1144 * @return void
1145 */
1146 public function setSigningKeyId($signingKeyId)
1147 {
1148 $this->signingKeyId = $signingKeyId;
1149 }
1150 /**
1151 * Gets the signing algorithm when using an assertion profile.
1152 *
1153 * @return ?string
1154 */
1155 public function getSigningAlgorithm()
1156 {
1157 return $this->signingAlgorithm;
1158 }
1159 /**
1160 * Sets the signing algorithm when using an assertion profile.
1161 *
1162 * @param ?string $signingAlgorithm
1163 * @return void
1164 */
1165 public function setSigningAlgorithm($signingAlgorithm)
1166 {
1167 if (\is_null($signingAlgorithm)) {
1168 $this->signingAlgorithm = null;
1169 } elseif (!\in_array($signingAlgorithm, self::$knownSigningAlgorithms)) {
1170 throw new InvalidArgumentException('unknown signing algorithm');
1171 } else {
1172 $this->signingAlgorithm = $signingAlgorithm;
1173 }
1174 }
1175 /**
1176 * Gets the set of parameters used by extension when using an extension
1177 * grant type.
1178 *
1179 * @return array<mixed>
1180 */
1181 public function getExtensionParams()
1182 {
1183 return $this->extensionParams;
1184 }
1185 /**
1186 * Sets the set of parameters used by extension when using an extension
1187 * grant type.
1188 *
1189 * @param array<mixed> $extensionParams
1190 * @return void
1191 */
1192 public function setExtensionParams($extensionParams)
1193 {
1194 $this->extensionParams = $extensionParams;
1195 }
1196 /**
1197 * Gets the number of seconds assertions are valid for.
1198 *
1199 * @return int
1200 */
1201 public function getExpiry()
1202 {
1203 return $this->expiry;
1204 }
1205 /**
1206 * Sets the number of seconds assertions are valid for.
1207 *
1208 * @param int $expiry
1209 * @return void
1210 */
1211 public function setExpiry($expiry)
1212 {
1213 $this->expiry = $expiry;
1214 }
1215 /**
1216 * Gets the lifetime of the access token in seconds.
1217 *
1218 * @return int
1219 */
1220 public function getExpiresIn()
1221 {
1222 return $this->expiresIn;
1223 }
1224 /**
1225 * Sets the lifetime of the access token in seconds.
1226 *
1227 * @param ?int $expiresIn
1228 * @return void
1229 */
1230 public function setExpiresIn($expiresIn)
1231 {
1232 if (\is_null($expiresIn)) {
1233 $this->expiresIn = null;
1234 $this->issuedAt = null;
1235 } else {
1236 $this->issuedAt = \time();
1237 $this->expiresIn = (int) $expiresIn;
1238 }
1239 }
1240 /**
1241 * Gets the time the current access token expires at.
1242 *
1243 * @return ?int
1244 */
1245 public function getExpiresAt()
1246 {
1247 if (!\is_null($this->expiresAt)) {
1248 return $this->expiresAt;
1249 }
1250 if (!\is_null($this->issuedAt) && !\is_null($this->expiresIn)) {
1251 return $this->issuedAt + $this->expiresIn;
1252 }
1253 return null;
1254 }
1255 /**
1256 * Returns true if the acccess token has expired.
1257 *
1258 * @return bool
1259 */
1260 public function isExpired()
1261 {
1262 $expiration = $this->getExpiresAt();
1263 $now = \time();
1264 return !\is_null($expiration) && $now >= $expiration;
1265 }
1266 /**
1267 * Sets the time the current access token expires at.
1268 *
1269 * @param int $expiresAt
1270 * @return void
1271 */
1272 public function setExpiresAt($expiresAt)
1273 {
1274 $this->expiresAt = $expiresAt;
1275 }
1276 /**
1277 * Gets the time the current access token was issued at.
1278 *
1279 * @return ?int
1280 */
1281 public function getIssuedAt()
1282 {
1283 return $this->issuedAt;
1284 }
1285 /**
1286 * Sets the time the current access token was issued at.
1287 *
1288 * @param int $issuedAt
1289 * @return void
1290 */
1291 public function setIssuedAt($issuedAt)
1292 {
1293 $this->issuedAt = $issuedAt;
1294 }
1295 /**
1296 * Gets the current access token.
1297 *
1298 * @return ?string
1299 */
1300 public function getAccessToken()
1301 {
1302 return $this->accessToken;
1303 }
1304 /**
1305 * Sets the current access token.
1306 *
1307 * @param string $accessToken
1308 * @return void
1309 */
1310 public function setAccessToken($accessToken)
1311 {
1312 $this->accessToken = $accessToken;
1313 }
1314 /**
1315 * Gets the current ID token.
1316 *
1317 * @return ?string
1318 */
1319 public function getIdToken()
1320 {
1321 return $this->idToken;
1322 }
1323 /**
1324 * Sets the current ID token.
1325 *
1326 * @param string $idToken
1327 * @return void
1328 */
1329 public function setIdToken($idToken)
1330 {
1331 $this->idToken = $idToken;
1332 }
1333 /**
1334 * Get the granted space-separated scopes (if they exist) for the last
1335 * fetched token.
1336 *
1337 * @return string|null
1338 */
1339 public function getGrantedScope()
1340 {
1341 return $this->grantedScope;
1342 }
1343 /**
1344 * Sets the current ID token.
1345 *
1346 * @param string $grantedScope
1347 * @return void
1348 */
1349 public function setGrantedScope($grantedScope)
1350 {
1351 $this->grantedScope = $grantedScope;
1352 }
1353 /**
1354 * Gets the refresh token associated with the current access token.
1355 *
1356 * @return ?string
1357 */
1358 public function getRefreshToken()
1359 {
1360 return $this->refreshToken;
1361 }
1362 /**
1363 * Sets the refresh token associated with the current access token.
1364 *
1365 * @param string $refreshToken
1366 * @return void
1367 */
1368 public function setRefreshToken($refreshToken)
1369 {
1370 $this->refreshToken = $refreshToken;
1371 }
1372 /**
1373 * Sets additional claims to be included in the JWT token
1374 *
1375 * @param array<mixed> $additionalClaims
1376 * @return void
1377 */
1378 public function setAdditionalClaims(array $additionalClaims)
1379 {
1380 $this->additionalClaims = $additionalClaims;
1381 }
1382 /**
1383 * Gets the additional claims to be included in the JWT token.
1384 *
1385 * @return array<mixed>
1386 */
1387 public function getAdditionalClaims()
1388 {
1389 return $this->additionalClaims;
1390 }
1391 /**
1392 * Gets the additional claims to be included in the JWT token.
1393 *
1394 * @return ?string
1395 */
1396 public function getIssuedTokenType()
1397 {
1398 return $this->issuedTokenType;
1399 }
1400 /**
1401 * The expiration of the last received token.
1402 *
1403 * @return array<mixed>|null
1404 */
1405 public function getLastReceivedToken()
1406 {
1407 if ($token = $this->getAccessToken()) {
1408 // the bare necessity of an auth token
1409 $authToken = ['access_token' => $token, 'expires_at' => $this->getExpiresAt()];
1410 } elseif ($idToken = $this->getIdToken()) {
1411 $authToken = ['id_token' => $idToken, 'expires_at' => $this->getExpiresAt()];
1412 } else {
1413 return null;
1414 }
1415 if ($expiresIn = $this->getExpiresIn()) {
1416 $authToken['expires_in'] = $expiresIn;
1417 }
1418 if ($issuedAt = $this->getIssuedAt()) {
1419 $authToken['issued_at'] = $issuedAt;
1420 }
1421 if ($refreshToken = $this->getRefreshToken()) {
1422 $authToken['refresh_token'] = $refreshToken;
1423 }
1424 return $authToken;
1425 }
1426 /**
1427 * Get the client ID.
1428 *
1429 * Alias of {@see OAuth2::getClientId()}.
1430 *
1431 * @param callable|null $httpHandler
1432 * @return string
1433 * @access private
1434 */
1435 public function getClientName(?callable $httpHandler = null)
1436 {
1437 return $this->getClientId();
1438 }
1439 /**
1440 * @todo handle uri as array
1441 *
1442 * @param ?string $uri
1443 * @return null|UriInterface
1444 */
1445 private function coerceUri($uri)
1446 {
1447 if (\is_null($uri)) {
1448 return null;
1449 }
1450 return Utils::uriFor($uri);
1451 }
1452 /**
1453 * @param string $idToken
1454 * @param Key|Key[]|string|string[] $publicKey
1455 * @param string|string[] $allowedAlgs
1456 * @return object
1457 */
1458 private function jwtDecode($idToken, $publicKey, $allowedAlgs)
1459 {
1460 $keys = $this->getFirebaseJwtKeys($publicKey, $allowedAlgs);
1461 // Default exception if none are caught. We are using the same exception
1462 // class and message from firebase/php-jwt to preserve backwards
1463 // compatibility.
1464 $e = new \InvalidArgumentException('Key may not be empty');
1465 foreach ($keys as $key) {
1466 try {
1467 return JWT::decode($idToken, $key);
1468 } catch (\Exception $e) {
1469 // try next alg
1470 }
1471 }
1472 throw $e;
1473 }
1474 /**
1475 * @param Key|Key[]|string|string[] $publicKey
1476 * @param string|string[] $allowedAlgs
1477 * @return Key[]
1478 */
1479 private function getFirebaseJwtKeys($publicKey, $allowedAlgs)
1480 {
1481 // If $publicKey is instance of Key, return it
1482 if ($publicKey instanceof Key) {
1483 return [$publicKey];
1484 }
1485 // If $allowedAlgs is empty, $publicKey must be Key or Key[].
1486 if (empty($allowedAlgs)) {
1487 $keys = [];
1488 foreach ((array) $publicKey as $kid => $pubKey) {
1489 if (!$pubKey instanceof Key) {
1490 throw new \InvalidArgumentException(\sprintf('When allowed algorithms is empty, the public key must' . 'be an instance of %s or an array of %s objects', Key::class, Key::class));
1491 }
1492 $keys[$kid] = $pubKey;
1493 }
1494 return $keys;
1495 }
1496 $allowedAlg = null;
1497 if (\is_string($allowedAlgs)) {
1498 $allowedAlg = $allowedAlgs;
1499 } elseif (\is_array($allowedAlgs)) {
1500 if (\count($allowedAlgs) > 1) {
1501 throw new \InvalidArgumentException('To have multiple allowed algorithms, You must provide an' . ' array of Firebase\\JWT\\Key objects.' . ' See https://github.com/firebase/php-jwt for more information.');
1502 }
1503 $allowedAlg = \array_pop($allowedAlgs);
1504 } else {
1505 throw new \InvalidArgumentException('allowed algorithms must be a string or array.');
1506 }
1507 if (\is_array($publicKey)) {
1508 // When publicKey is greater than 1, create keys with the single alg.
1509 $keys = [];
1510 foreach ($publicKey as $kid => $pubKey) {
1511 if ($pubKey instanceof Key) {
1512 $keys[$kid] = $pubKey;
1513 } else {
1514 $keys[$kid] = new Key($pubKey, $allowedAlg);
1515 }
1516 }
1517 return $keys;
1518 }
1519 return [new Key($publicKey, $allowedAlg)];
1520 }
1521 /**
1522 * Determines if the URI is absolute based on its scheme and host or path
1523 * (RFC 3986).
1524 *
1525 * @param string $uri
1526 * @return bool
1527 */
1528 private function isAbsoluteUri($uri)
1529 {
1530 $uri = $this->coerceUri($uri);
1531 return $uri->getScheme() && ($uri->getHost() || $uri->getPath());
1532 }
1533 /**
1534 * @param array<mixed> $params
1535 * @return array<mixed>
1536 */
1537 private function addClientCredentials(&$params)
1538 {
1539 $clientId = $this->getClientId();
1540 $clientSecret = $this->getClientSecret();
1541 if ($clientId && $clientSecret) {
1542 $params['client_id'] = $clientId;
1543 $params['client_secret'] = $clientSecret;
1544 }
1545 return $params;
1546 }
1547 }
1548