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
18namespace Google\Auth;
19
20use Firebase\JWT\JWT;
21use Firebase\JWT\Key;
22use Google\Auth\HttpHandler\HttpClientCache;
23use Google\Auth\HttpHandler\HttpHandlerFactory;
24use GuzzleHttp\Psr7\Query;
25use GuzzleHttp\Psr7\Request;
26use GuzzleHttp\Psr7\Utils;
27use InvalidArgumentException;
28use Psr\Http\Message\RequestInterface;
29use Psr\Http\Message\ResponseInterface;
30use 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 */
39class 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 = array(
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 = array(
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 = array())
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 = array('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 = array();
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