PluginProbe
Authorizer / 3.6.0
Authorizer v3.6.0
3.15.3 3.15.2 3.15.1 3.15.0 3.14.3 3.14.4 3.14.2 3.14.1 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.9.0 2.9.1 2.9.10 2.9.11 2.9.12 2.9.13 2.9.2 2.9.3 2.9.6 All 126 releases
authorizer / vendor / google / auth / src / OAuth2.php

OAuth2.php in Authorizer 3.6.0, at vendor/google/auth/src/OAuth2.php

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