PluginProbe
Authorizer / 3.13.0
Authorizer v3.13.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.13.0, at vendor/google/auth/src/OAuth2.php

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