vendor/google/auth/src/OAuth2.php line 772

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