Index: vendor/google/auth/src/OAuth2.php
===================================================================
--- vendor/google/auth/src/OAuth2.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/auth/src/OAuth2.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,1828 @@
+<?php
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+namespace Google\Auth;
+
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+use Google\Auth\HttpHandler\HttpClientCache;
+use Google\Auth\HttpHandler\HttpHandlerFactory;
+use GuzzleHttp\Psr7\Query;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Psr7\Utils;
+use InvalidArgumentException;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * OAuth2 supports authentication by OAuth2 2-legged flows.
+ *
+ * It primary supports
+ * - service account authorization
+ * - authorization where a user already has an access token
+ */
+class OAuth2 implements FetchAuthTokenInterface
+{
+    const DEFAULT_EXPIRY_SECONDS = 3600; // 1 hour
+    const DEFAULT_SKEW_SECONDS = 60; // 1 minute
+    const JWT_URN = 'urn:ietf:params:oauth:grant-type:jwt-bearer';
+    const STS_URN = 'urn:ietf:params:oauth:grant-type:token-exchange';
+    private const STS_REQUESTED_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:access_token';
+
+    /**
+     * TODO: determine known methods from the keys of JWT::methods.
+     *
+     * @var array<string>
+     */
+    public static $knownSigningAlgorithms = [
+        'HS256',
+        'HS512',
+        'HS384',
+        'RS256',
+    ];
+
+    /**
+     * The well known grant types.
+     *
+     * @var array<string>
+     */
+    public static $knownGrantTypes = [
+        'authorization_code',
+        'refresh_token',
+        'password',
+        'client_credentials',
+    ];
+
+    /**
+     * - authorizationUri
+     *   The authorization server's HTTP endpoint capable of
+     *   authenticating the end-user and obtaining authorization.
+     *
+     * @var ?UriInterface
+     */
+    private $authorizationUri;
+
+    /**
+     * - tokenCredentialUri
+     *   The authorization server's HTTP endpoint capable of issuing
+     *   tokens and refreshing expired tokens.
+     *
+     * @var UriInterface
+     */
+    private $tokenCredentialUri;
+
+    /**
+     * The redirection URI used in the initial request.
+     *
+     * @var ?string
+     */
+    private $redirectUri;
+
+    /**
+     * A unique identifier issued to the client to identify itself to the
+     * authorization server.
+     *
+     * @var string
+     */
+    private $clientId;
+
+    /**
+     * A shared symmetric secret issued by the authorization server, which is
+     * used to authenticate the client.
+     *
+     * @var string
+     */
+    private $clientSecret;
+
+    /**
+     * The resource owner's username.
+     *
+     * @var ?string
+     */
+    private $username;
+
+    /**
+     * The resource owner's password.
+     *
+     * @var ?string
+     */
+    private $password;
+
+    /**
+     * The scope of the access request, expressed either as an Array or as a
+     * space-delimited string.
+     *
+     * @var ?array<string>
+     */
+    private $scope;
+
+    /**
+     * An arbitrary string designed to allow the client to maintain state.
+     *
+     * @var string
+     */
+    private $state;
+
+    /**
+     * The authorization code issued to this client.
+     *
+     * Only used by the authorization code access grant type.
+     *
+     * @var ?string
+     */
+    private $code;
+
+    /**
+     * The issuer ID when using assertion profile.
+     *
+     * @var ?string
+     */
+    private $issuer;
+
+    /**
+     * The target audience for assertions.
+     *
+     * @var string
+     */
+    private $audience;
+
+    /**
+     * The target sub when issuing assertions.
+     *
+     * @var string
+     */
+    private $sub;
+
+    /**
+     * The number of seconds assertions are valid for.
+     *
+     * @var int
+     */
+    private $expiry;
+
+    /**
+     * The signing key when using assertion profile.
+     *
+     * @var ?string
+     */
+    private $signingKey;
+
+    /**
+     * The signing key id when using assertion profile. Param kid in jwt header
+     *
+     * @var string
+     */
+    private $signingKeyId;
+
+    /**
+     * The signing algorithm when using an assertion profile.
+     *
+     * @var ?string
+     */
+    private $signingAlgorithm;
+
+    /**
+     * The refresh token associated with the access token to be refreshed.
+     *
+     * @var ?string
+     */
+    private $refreshToken;
+
+    /**
+     * The current access token.
+     *
+     * @var string
+     */
+    private $accessToken;
+
+    /**
+     * The current ID token.
+     *
+     * @var string
+     */
+    private $idToken;
+
+    /**
+     * The scopes granted to the current access token
+     *
+     * @var string
+     */
+    private $grantedScope;
+
+    /**
+     * The lifetime in seconds of the current access token.
+     *
+     * @var ?int
+     */
+    private $expiresIn;
+
+    /**
+     * The expiration time of the access token as a number of seconds since the
+     * unix epoch.
+     *
+     * @var ?int
+     */
+    private $expiresAt;
+
+    /**
+     * The issue time of the access token as a number of seconds since the unix
+     * epoch.
+     *
+     * @var ?int
+     */
+    private $issuedAt;
+
+    /**
+     * The current grant type.
+     *
+     * @var ?string
+     */
+    private $grantType;
+
+    /**
+     * When using an extension grant type, this is the set of parameters used by
+     * that extension.
+     *
+     * @var array<mixed>
+     */
+    private $extensionParams;
+
+    /**
+     * When using the toJwt function, these claims will be added to the JWT
+     * payload.
+     *
+     * @var array<mixed>
+     */
+    private $additionalClaims;
+
+    /**
+     * The code verifier for PKCE for OAuth 2.0. When set, the authorization
+     * URI will contain the Code Challenge and Code Challenge Method querystring
+     * parameters, and the token URI will contain the Code Verifier parameter.
+     *
+     * @see https://datatracker.ietf.org/doc/html/rfc7636
+     * @var ?string
+     */
+    private $codeVerifier;
+
+    /**
+     * For STS requests.
+     * A URI that indicates the target service or resource where the client
+     * intends to use the requested security token.
+     */
+    private ?string $resource;
+
+    /**
+     * For STS requests.
+     * A fetcher for the "subject_token", which is a security token that
+     * represents the identity of the party on behalf of whom the request is
+     * being made.
+     */
+    private ?ExternalAccountCredentialSourceInterface $subjectTokenFetcher;
+
+    /**
+     * For STS requests.
+     * An identifier, that indicates the type of the security token in the
+     * subjectToken parameter.
+     */
+    private ?string $subjectTokenType;
+
+    /**
+     * For STS requests.
+     * A security token that represents the identity of the acting party.
+     */
+    private ?string $actorToken;
+
+    /**
+     * For STS requests.
+     * An identifier that indicates the type of the security token in the
+     * actorToken parameter.
+     */
+    private ?string $actorTokenType;
+
+    /**
+     * From STS response.
+     * An identifier for the representation of the issued security token.
+     */
+    private ?string $issuedTokenType = null;
+
+    /**
+     * From STS response.
+     * An identifier for the representation of the issued security token.
+     *
+     * @var array<mixed>
+     */
+    private array $additionalOptions;
+
+    /**
+     * Create a new OAuthCredentials.
+     *
+     * The configuration array accepts various options
+     *
+     * - authorizationUri
+     *   The authorization server's HTTP endpoint capable of
+     *   authenticating the end-user and obtaining authorization.
+     *
+     * - tokenCredentialUri
+     *   The authorization server's HTTP endpoint capable of issuing
+     *   tokens and refreshing expired tokens.
+     *
+     * - clientId
+     *   A unique identifier issued to the client to identify itself to the
+     *   authorization server.
+     *
+     * - clientSecret
+     *   A shared symmetric secret issued by the authorization server,
+     *   which is used to authenticate the client.
+     *
+     * - scope
+     *   The scope of the access request, expressed either as an Array
+     *   or as a space-delimited String.
+     *
+     * - state
+     *   An arbitrary string designed to allow the client to maintain state.
+     *
+     * - redirectUri
+     *   The redirection URI used in the initial request.
+     *
+     * - username
+     *   The resource owner's username.
+     *
+     * - password
+     *   The resource owner's password.
+     *
+     * - issuer
+     *   Issuer ID when using assertion profile
+     *
+     * - audience
+     *   Target audience for assertions
+     *
+     * - expiry
+     *   Number of seconds assertions are valid for
+     *
+     * - signingKey
+     *   Signing key when using assertion profile
+     *
+     * - signingKeyId
+     *   Signing key id when using assertion profile
+     *
+     * - refreshToken
+     *   The refresh token associated with the access token
+     *   to be refreshed.
+     *
+     * - accessToken
+     *   The current access token for this client.
+     *
+     * - idToken
+     *   The current ID token for this client.
+     *
+     * - extensionParams
+     *   When using an extension grant type, this is the set of parameters used
+     *   by that extension.
+     *
+     * - codeVerifier
+     *   The code verifier for PKCE for OAuth 2.0.
+     *
+     * - resource
+     *   The target service or resource where the client ntends to use the
+     *   requested security token.
+     *
+     * - subjectTokenFetcher
+     *    A fetcher for the "subject_token", which is a security token that
+     *    represents the identity of the party on behalf of whom the request is
+     *    being made.
+     *
+     * - subjectTokenType
+     *   An identifier that indicates the type of the security token in the
+     *   subjectToken parameter.
+     *
+     * - actorToken
+     *   A security token that represents the identity of the acting party.
+     *
+     * - actorTokenType
+     *   An identifier for the representation of the issued security token.
+     *
+     * @param array<mixed> $config Configuration array
+     */
+    public function __construct(array $config)
+    {
+        $opts = array_merge([
+            'expiry' => self::DEFAULT_EXPIRY_SECONDS,
+            'extensionParams' => [],
+            'authorizationUri' => null,
+            'redirectUri' => null,
+            'tokenCredentialUri' => null,
+            'state' => null,
+            'username' => null,
+            'password' => null,
+            'clientId' => null,
+            'clientSecret' => null,
+            'issuer' => null,
+            'sub' => null,
+            'audience' => null,
+            'signingKey' => null,
+            'signingKeyId' => null,
+            'signingAlgorithm' => null,
+            'scope' => null,
+            'additionalClaims' => [],
+            'codeVerifier' => null,
+            'resource' => null,
+            'subjectTokenFetcher' => null,
+            'subjectTokenType' => null,
+            'actorToken' => null,
+            'actorTokenType' => null,
+            'additionalOptions' => [],
+        ], $config);
+
+        $this->setAuthorizationUri($opts['authorizationUri']);
+        $this->setRedirectUri($opts['redirectUri']);
+        $this->setTokenCredentialUri($opts['tokenCredentialUri']);
+        $this->setState($opts['state']);
+        $this->setUsername($opts['username']);
+        $this->setPassword($opts['password']);
+        $this->setClientId($opts['clientId']);
+        $this->setClientSecret($opts['clientSecret']);
+        $this->setIssuer($opts['issuer']);
+        $this->setSub($opts['sub']);
+        $this->setExpiry($opts['expiry']);
+        $this->setAudience($opts['audience']);
+        $this->setSigningKey($opts['signingKey']);
+        $this->setSigningKeyId($opts['signingKeyId']);
+        $this->setSigningAlgorithm($opts['signingAlgorithm']);
+        $this->setScope($opts['scope']);
+        $this->setExtensionParams($opts['extensionParams']);
+        $this->setAdditionalClaims($opts['additionalClaims']);
+        $this->setCodeVerifier($opts['codeVerifier']);
+
+        // for STS
+        $this->resource = $opts['resource'];
+        $this->subjectTokenFetcher = $opts['subjectTokenFetcher'];
+        $this->subjectTokenType = $opts['subjectTokenType'];
+        $this->actorToken = $opts['actorToken'];
+        $this->actorTokenType = $opts['actorTokenType'];
+        $this->additionalOptions = $opts['additionalOptions'];
+
+        $this->updateToken($opts);
+    }
+
+    /**
+     * Verifies the idToken if present.
+     *
+     * - if none is present, return null
+     * - if present, but invalid, raises DomainException.
+     * - otherwise returns the payload in the idtoken as a PHP object.
+     *
+     * The behavior of this method varies depending on the version of
+     * `firebase/php-jwt` you are using. In versions 6.0 and above, you cannot
+     * provide multiple $allowed_algs, and instead must provide an array of Key
+     * objects as the $publicKey.
+     *
+     * @param string|Key|Key[] $publicKey The public key to use to authenticate the token
+     * @param string|array<string> $allowed_algs algorithm or array of supported verification algorithms.
+     *        Providing more than one algorithm will throw an exception.
+     * @throws \DomainException if the token is missing an audience.
+     * @throws \DomainException if the audience does not match the one set in
+     *         the OAuth2 class instance.
+     * @throws \UnexpectedValueException If the token is invalid
+     * @throws \InvalidArgumentException If more than one value for allowed_algs is supplied
+     * @throws \Firebase\JWT\SignatureInvalidException If the signature is invalid.
+     * @throws \Firebase\JWT\BeforeValidException If the token is not yet valid.
+     * @throws \Firebase\JWT\ExpiredException If the token has expired.
+     * @return null|object
+     */
+    public function verifyIdToken($publicKey = null, $allowed_algs = [])
+    {
+        $idToken = $this->getIdToken();
+        if (is_null($idToken)) {
+            return null;
+        }
+
+        $resp = $this->jwtDecode($idToken, $publicKey, $allowed_algs);
+        if (!property_exists($resp, 'aud')) {
+            throw new \DomainException('No audience found the id token');
+        }
+        if ($resp->aud != $this->getAudience()) {
+            throw new \DomainException('Wrong audience present in the id token');
+        }
+
+        return $resp;
+    }
+
+    /**
+     * Obtains the encoded jwt from the instance data.
+     *
+     * @param array<mixed> $config array optional configuration parameters
+     * @return string
+     */
+    public function toJwt(array $config = [])
+    {
+        if (is_null($this->getSigningKey())) {
+            throw new \DomainException('No signing key available');
+        }
+        if (is_null($this->getSigningAlgorithm())) {
+            throw new \DomainException('No signing algorithm specified');
+        }
+        $now = time();
+
+        $opts = array_merge([
+            'skew' => self::DEFAULT_SKEW_SECONDS,
+        ], $config);
+
+        $assertion = [
+            'iss' => $this->getIssuer(),
+            'exp' => ($now + $this->getExpiry()),
+            'iat' => ($now - $opts['skew']),
+        ];
+        foreach ($assertion as $k => $v) {
+            if (is_null($v)) {
+                throw new \DomainException($k . ' should not be null');
+            }
+        }
+        if (!(is_null($this->getAudience()))) {
+            $assertion['aud'] = $this->getAudience();
+        }
+
+        if (!(is_null($this->getScope()))) {
+            $assertion['scope'] = $this->getScope();
+        }
+
+        if (empty($assertion['scope']) && empty($assertion['aud'])) {
+            throw new \DomainException('one of scope or aud should not be null');
+        }
+
+        if (!(is_null($this->getSub()))) {
+            $assertion['sub'] = $this->getSub();
+        }
+        $assertion += $this->getAdditionalClaims();
+
+        return JWT::encode(
+            $assertion,
+            $this->getSigningKey(),
+            $this->getSigningAlgorithm(),
+            $this->getSigningKeyId()
+        );
+    }
+
+    /**
+     * Generates a request for token credentials.
+     *
+     * @param callable|null $httpHandler callback which delivers psr7 request
+     * @param array<mixed> $headers [optional] Additional headers to pass to
+     *        the token endpoint request.
+     * @return RequestInterface the authorization Url.
+     */
+    public function generateCredentialsRequest(?callable $httpHandler = null, $headers = [])
+    {
+        $uri = $this->getTokenCredentialUri();
+        if (is_null($uri)) {
+            throw new \DomainException('No token credential URI was set.');
+        }
+
+        $grantType = $this->getGrantType();
+        $params = ['grant_type' => $grantType];
+        switch ($grantType) {
+            case 'authorization_code':
+                $params['code'] = $this->getCode();
+                $params['redirect_uri'] = $this->getRedirectUri();
+                if ($this->codeVerifier) {
+                    $params['code_verifier'] = $this->codeVerifier;
+                }
+                $this->addClientCredentials($params);
+                break;
+            case 'password':
+                $params['username'] = $this->getUsername();
+                $params['password'] = $this->getPassword();
+                $this->addClientCredentials($params);
+                break;
+            case 'refresh_token':
+                $params['refresh_token'] = $this->getRefreshToken();
+                if (isset($this->getAdditionalClaims()['target_audience'])) {
+                    $params['target_audience'] = $this->getAdditionalClaims()['target_audience'];
+                }
+                $this->addClientCredentials($params);
+                break;
+            case self::JWT_URN:
+                $params['assertion'] = $this->toJwt();
+                break;
+            case self::STS_URN:
+                $token = $this->subjectTokenFetcher->fetchSubjectToken($httpHandler);
+                $params['subject_token'] = $token;
+                $params['subject_token_type'] = $this->subjectTokenType;
+                $params += array_filter([
+                    'resource'             => $this->resource,
+                    'audience'             => $this->audience,
+                    'scope'                => $this->getScope(),
+                    'requested_token_type' => self::STS_REQUESTED_TOKEN_TYPE,
+                    'actor_token'          => $this->actorToken,
+                    'actor_token_type'     => $this->actorTokenType,
+                ]);
+                if ($this->additionalOptions) {
+                    $params['options'] = json_encode($this->additionalOptions);
+                }
+                break;
+            default:
+                if (!is_null($this->getRedirectUri())) {
+                    # Grant type was supposed to be 'authorization_code', as there
+                    # is a redirect URI.
+                    throw new \DomainException('Missing authorization code');
+                }
+                unset($params['grant_type']);
+                if (!is_null($grantType)) {
+                    $params['grant_type'] = $grantType;
+                }
+                $params = array_merge($params, $this->getExtensionParams());
+        }
+
+        $headers = [
+            'Cache-Control' => 'no-store',
+            'Content-Type' => 'application/x-www-form-urlencoded',
+        ] + $headers;
+
+        return new Request(
+            'POST',
+            $uri,
+            $headers,
+            Query::build($params)
+        );
+    }
+
+    /**
+     * Fetches the auth tokens based on the current state.
+     *
+     * @param callable|null $httpHandler callback which delivers psr7 request
+     * @param array<mixed> $headers [optional] If present, add these headers to the token
+     *        endpoint request.
+     * @return array<mixed> the response
+     */
+    public function fetchAuthToken(?callable $httpHandler = null, $headers = [])
+    {
+        if (is_null($httpHandler)) {
+            $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
+        }
+
+        $response = $httpHandler($this->generateCredentialsRequest($httpHandler, $headers));
+        $credentials = $this->parseTokenResponse($response);
+        $this->updateToken($credentials);
+        if (isset($credentials['scope'])) {
+            $this->setGrantedScope($credentials['scope']);
+        }
+
+        return $credentials;
+    }
+
+    /**
+     * @deprecated
+     *
+     * Obtains a key that can used to cache the results of #fetchAuthToken.
+     *
+     * The key is derived from the scopes.
+     *
+     * @return ?string a key that may be used to cache the auth token.
+     */
+    public function getCacheKey()
+    {
+        if (is_array($this->scope)) {
+            return implode(':', $this->scope);
+        }
+
+        if ($this->audience) {
+            return $this->audience;
+        }
+
+        // If scope has not set, return null to indicate no caching.
+        return null;
+    }
+
+    /**
+     * Gets this instance's SubjectTokenFetcher
+     *
+     * @return null|ExternalAccountCredentialSourceInterface
+     */
+    public function getSubjectTokenFetcher(): ?ExternalAccountCredentialSourceInterface
+    {
+        return $this->subjectTokenFetcher;
+    }
+
+    /**
+     * Parses the fetched tokens.
+     *
+     * @param ResponseInterface $resp the response.
+     * @return array<mixed> the tokens parsed from the response body.
+     * @throws \Exception
+     */
+    public function parseTokenResponse(ResponseInterface $resp)
+    {
+        $body = (string) $resp->getBody();
+        if ($resp->hasHeader('Content-Type') &&
+            $resp->getHeaderLine('Content-Type') == 'application/x-www-form-urlencoded'
+        ) {
+            $res = [];
+            parse_str($body, $res);
+
+            return $res;
+        }
+
+        // Assume it's JSON; if it's not throw an exception
+        if (null === $res = json_decode($body, true)) {
+            throw new \Exception('Invalid JSON response');
+        }
+
+        return $res;
+    }
+
+    /**
+     * Updates an OAuth 2.0 client.
+     *
+     * Example:
+     * ```
+     * $oauth->updateToken([
+     *     'refresh_token' => 'n4E9O119d',
+     *     'access_token' => 'FJQbwq9',
+     *     'expires_in' => 3600
+     * ]);
+     * ```
+     *
+     * @param array<mixed> $config
+     *  The configuration parameters related to the token.
+     *
+     *  - refresh_token
+     *    The refresh token associated with the access token
+     *    to be refreshed.
+     *
+     *  - access_token
+     *    The current access token for this client.
+     *
+     *  - id_token
+     *    The current ID token for this client.
+     *
+     *  - expires_in
+     *    The time in seconds until access token expiration.
+     *
+     *  - expires_at
+     *    The time as an integer number of seconds since the Epoch
+     *
+     *  - issued_at
+     *    The timestamp that the token was issued at.
+     * @return void
+     */
+    public function updateToken(array $config)
+    {
+        $opts = array_merge([
+            'extensionParams' => [],
+            'access_token' => null,
+            'id_token' => null,
+            'expires_in' => null,
+            'expires_at' => null,
+            'issued_at' => null,
+            'scope' => null,
+        ], $config);
+
+        $this->setExpiresAt($opts['expires_at']);
+        $this->setExpiresIn($opts['expires_in']);
+        // By default, the token is issued at `Time.now` when `expiresIn` is set,
+        // but this can be used to supply a more precise time.
+        if (!is_null($opts['issued_at'])) {
+            $this->setIssuedAt($opts['issued_at']);
+        }
+
+        $this->setAccessToken($opts['access_token']);
+        $this->setIdToken($opts['id_token']);
+
+        // The refresh token should only be updated if a value is explicitly
+        // passed in, as some access token responses do not include a refresh
+        // token.
+        if (array_key_exists('refresh_token', $opts)) {
+            $this->setRefreshToken($opts['refresh_token']);
+        }
+
+        // Required for STS response. An identifier for the representation of
+        // the issued security token.
+        if (array_key_exists('issued_token_type', $opts)) {
+            $this->issuedTokenType = $opts['issued_token_type'];
+        }
+    }
+
+    /**
+     * Builds the authorization Uri that the user should be redirected to.
+     *
+     * @param array<mixed> $config configuration options that customize the return url.
+     * @return UriInterface the authorization Url.
+     * @throws InvalidArgumentException
+     */
+    public function buildFullAuthorizationUri(array $config = [])
+    {
+        if (is_null($this->getAuthorizationUri())) {
+            throw new InvalidArgumentException(
+                'requires an authorizationUri to have been set'
+            );
+        }
+
+        $params = array_merge([
+            'response_type' => 'code',
+            'access_type' => 'offline',
+            'client_id' => $this->clientId,
+            'redirect_uri' => $this->redirectUri,
+            'state' => $this->state,
+            'scope' => $this->getScope(),
+        ], $config);
+
+        // Validate the auth_params
+        if (is_null($params['client_id'])) {
+            throw new InvalidArgumentException(
+                'missing the required client identifier'
+            );
+        }
+        if (is_null($params['redirect_uri'])) {
+            throw new InvalidArgumentException('missing the required redirect URI');
+        }
+        if (!empty($params['prompt']) && !empty($params['approval_prompt'])) {
+            throw new InvalidArgumentException(
+                'prompt and approval_prompt are mutually exclusive'
+            );
+        }
+        if ($this->codeVerifier) {
+            $params['code_challenge'] = $this->getCodeChallenge($this->codeVerifier);
+            $params['code_challenge_method'] = $this->getCodeChallengeMethod();
+        }
+
+        // Construct the uri object; return it if it is valid.
+        $result = clone $this->authorizationUri;
+        $existingParams = Query::parse($result->getQuery());
+
+        $result = $result->withQuery(
+            Query::build(array_merge($existingParams, $params))
+        );
+
+        if ($result->getScheme() != 'https') {
+            throw new InvalidArgumentException(
+                'Authorization endpoint must be protected by TLS'
+            );
+        }
+
+        return $result;
+    }
+
+    /**
+     * @return string|null
+     */
+    public function getCodeVerifier(): ?string
+    {
+        return $this->codeVerifier;
+    }
+
+    /**
+     * A cryptographically random string that is used to correlate the
+     * authorization request to the token request.
+     *
+     * The code verifier for PKCE for OAuth 2.0. When set, the authorization
+     * URI will contain the Code Challenge and Code Challenge Method querystring
+     * parameters, and the token URI will contain the Code Verifier parameter.
+     *
+     * @see https://datatracker.ietf.org/doc/html/rfc7636
+     *
+     * @param string|null $codeVerifier
+     */
+    public function setCodeVerifier(?string $codeVerifier): void
+    {
+        $this->codeVerifier = $codeVerifier;
+    }
+
+    /**
+     * Generates a random 128-character string for the "code_verifier" parameter
+     * in PKCE for OAuth 2.0. This is a cryptographically random string that is
+     * determined using random_int, hashed using "hash" and sha256, and base64
+     * encoded.
+     *
+     * When this method is called, the code verifier is set on the object.
+     *
+     * @return string
+     */
+    public function generateCodeVerifier(): string
+    {
+        return $this->codeVerifier = $this->generateRandomString(128);
+    }
+
+    private function getCodeChallenge(string $randomString): string
+    {
+        return rtrim(strtr(base64_encode(hash('sha256', $randomString, true)), '+/', '-_'), '=');
+    }
+
+    private function getCodeChallengeMethod(): string
+    {
+        return 'S256';
+    }
+
+    private function generateRandomString(int $length): string
+    {
+        $validChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~';
+        $validCharsLen = strlen($validChars);
+        $str = '';
+        $i = 0;
+        while ($i++ < $length) {
+            $str .= $validChars[random_int(0, $validCharsLen - 1)];
+        }
+        return $str;
+    }
+
+    /**
+     * Sets the authorization server's HTTP endpoint capable of authenticating
+     * the end-user and obtaining authorization.
+     *
+     * @param string $uri
+     * @return void
+     */
+    public function setAuthorizationUri($uri)
+    {
+        $this->authorizationUri = $this->coerceUri($uri);
+    }
+
+    /**
+     * Gets the authorization server's HTTP endpoint capable of authenticating
+     * the end-user and obtaining authorization.
+     *
+     * @return ?UriInterface
+     */
+    public function getAuthorizationUri()
+    {
+        return $this->authorizationUri;
+    }
+
+    /**
+     * Gets the authorization server's HTTP endpoint capable of issuing tokens
+     * and refreshing expired tokens.
+     *
+     * @return ?UriInterface
+     */
+    public function getTokenCredentialUri()
+    {
+        return $this->tokenCredentialUri;
+    }
+
+    /**
+     * Sets the authorization server's HTTP endpoint capable of issuing tokens
+     * and refreshing expired tokens.
+     *
+     * @param string $uri
+     * @return void
+     */
+    public function setTokenCredentialUri($uri)
+    {
+        $this->tokenCredentialUri = $this->coerceUri($uri);
+    }
+
+    /**
+     * Gets the redirection URI used in the initial request.
+     *
+     * @return ?string
+     */
+    public function getRedirectUri()
+    {
+        return $this->redirectUri;
+    }
+
+    /**
+     * Sets the redirection URI used in the initial request.
+     *
+     * @param ?string $uri
+     * @return void
+     */
+    public function setRedirectUri($uri)
+    {
+        if (is_null($uri)) {
+            $this->redirectUri = null;
+
+            return;
+        }
+        // redirect URI must be absolute
+        if (!$this->isAbsoluteUri($uri)) {
+            // "postmessage" is a reserved URI string in Google-land
+            // @see https://developers.google.com/identity/sign-in/web/server-side-flow
+            if ('postmessage' !== (string) $uri) {
+                throw new InvalidArgumentException(
+                    'Redirect URI must be absolute'
+                );
+            }
+        }
+        $this->redirectUri = (string) $uri;
+    }
+
+    /**
+     * Gets the scope of the access requests as a space-delimited String.
+     *
+     * @return ?string
+     */
+    public function getScope()
+    {
+        if (is_null($this->scope)) {
+            return $this->scope;
+        }
+
+        return implode(' ', $this->scope);
+    }
+
+    /**
+     * Gets the subject token type
+     *
+     * @return ?string
+     */
+    public function getSubjectTokenType(): ?string
+    {
+        return $this->subjectTokenType;
+    }
+
+    /**
+     * Sets the scope of the access request, expressed either as an Array or as
+     * a space-delimited String.
+     *
+     * @param string|array<string>|null $scope
+     * @return void
+     * @throws InvalidArgumentException
+     */
+    public function setScope($scope)
+    {
+        if (is_null($scope)) {
+            $this->scope = null;
+        } elseif (is_string($scope)) {
+            $this->scope = explode(' ', $scope);
+        } elseif (is_array($scope)) {
+            foreach ($scope as $s) {
+                $pos = strpos($s, ' ');
+                if ($pos !== false) {
+                    throw new InvalidArgumentException(
+                        'array scope values should not contain spaces'
+                    );
+                }
+            }
+            $this->scope = $scope;
+        } else {
+            throw new InvalidArgumentException(
+                'scopes should be a string or array of strings'
+            );
+        }
+    }
+
+    /**
+     * Gets the current grant type.
+     *
+     * @return ?string
+     */
+    public function getGrantType()
+    {
+        if (!is_null($this->grantType)) {
+            return $this->grantType;
+        }
+
+        // Returns the inferred grant type, based on the current object instance
+        // state.
+        if (!is_null($this->code)) {
+            return 'authorization_code';
+        }
+
+        if (!is_null($this->refreshToken)) {
+            return 'refresh_token';
+        }
+
+        if (!is_null($this->username) && !is_null($this->password)) {
+            return 'password';
+        }
+
+        if (!is_null($this->issuer) && !is_null($this->signingKey)) {
+            return self::JWT_URN;
+        }
+
+        if (!is_null($this->subjectTokenFetcher) && !is_null($this->subjectTokenType)) {
+            return self::STS_URN;
+        }
+
+        return null;
+    }
+
+    /**
+     * Sets the current grant type.
+     *
+     * @param string $grantType
+     * @return void
+     * @throws InvalidArgumentException
+     */
+    public function setGrantType($grantType)
+    {
+        if (in_array($grantType, self::$knownGrantTypes)) {
+            $this->grantType = $grantType;
+        } else {
+            // validate URI
+            if (!$this->isAbsoluteUri($grantType)) {
+                throw new InvalidArgumentException(
+                    'invalid grant type'
+                );
+            }
+            $this->grantType = (string) $grantType;
+        }
+    }
+
+    /**
+     * Gets an arbitrary string designed to allow the client to maintain state.
+     *
+     * @return string
+     */
+    public function getState()
+    {
+        return $this->state;
+    }
+
+    /**
+     * Sets an arbitrary string designed to allow the client to maintain state.
+     *
+     * @param string $state
+     * @return void
+     */
+    public function setState($state)
+    {
+        $this->state = $state;
+    }
+
+    /**
+     * Gets the authorization code issued to this client.
+     *
+     * @return string
+     */
+    public function getCode()
+    {
+        return $this->code;
+    }
+
+    /**
+     * Sets the authorization code issued to this client.
+     *
+     * @param string $code
+     * @return void
+     */
+    public function setCode($code)
+    {
+        $this->code = $code;
+    }
+
+    /**
+     * Gets the resource owner's username.
+     *
+     * @return string
+     */
+    public function getUsername()
+    {
+        return $this->username;
+    }
+
+    /**
+     * Sets the resource owner's username.
+     *
+     * @param string $username
+     * @return void
+     */
+    public function setUsername($username)
+    {
+        $this->username = $username;
+    }
+
+    /**
+     * Gets the resource owner's password.
+     *
+     * @return string
+     */
+    public function getPassword()
+    {
+        return $this->password;
+    }
+
+    /**
+     * Sets the resource owner's password.
+     *
+     * @param string $password
+     * @return void
+     */
+    public function setPassword($password)
+    {
+        $this->password = $password;
+    }
+
+    /**
+     * Sets a unique identifier issued to the client to identify itself to the
+     * authorization server.
+     *
+     * @return string
+     */
+    public function getClientId()
+    {
+        return $this->clientId;
+    }
+
+    /**
+     * Sets a unique identifier issued to the client to identify itself to the
+     * authorization server.
+     *
+     * @param string $clientId
+     * @return void
+     */
+    public function setClientId($clientId)
+    {
+        $this->clientId = $clientId;
+    }
+
+    /**
+     * Gets a shared symmetric secret issued by the authorization server, which
+     * is used to authenticate the client.
+     *
+     * @return string
+     */
+    public function getClientSecret()
+    {
+        return $this->clientSecret;
+    }
+
+    /**
+     * Sets a shared symmetric secret issued by the authorization server, which
+     * is used to authenticate the client.
+     *
+     * @param string $clientSecret
+     * @return void
+     */
+    public function setClientSecret($clientSecret)
+    {
+        $this->clientSecret = $clientSecret;
+    }
+
+    /**
+     * Gets the Issuer ID when using assertion profile.
+     *
+     * @return ?string
+     */
+    public function getIssuer()
+    {
+        return $this->issuer;
+    }
+
+    /**
+     * Sets the Issuer ID when using assertion profile.
+     *
+     * @param string $issuer
+     * @return void
+     */
+    public function setIssuer($issuer)
+    {
+        $this->issuer = $issuer;
+    }
+
+    /**
+     * Gets the target sub when issuing assertions.
+     *
+     * @return ?string
+     */
+    public function getSub()
+    {
+        return $this->sub;
+    }
+
+    /**
+     * Sets the target sub when issuing assertions.
+     *
+     * @param string $sub
+     * @return void
+     */
+    public function setSub($sub)
+    {
+        $this->sub = $sub;
+    }
+
+    /**
+     * Gets the target audience when issuing assertions.
+     *
+     * @return ?string
+     */
+    public function getAudience()
+    {
+        return $this->audience;
+    }
+
+    /**
+     * Sets the target audience when issuing assertions.
+     *
+     * @param string $audience
+     * @return void
+     */
+    public function setAudience($audience)
+    {
+        $this->audience = $audience;
+    }
+
+    /**
+     * Gets the signing key when using an assertion profile.
+     *
+     * @return ?string
+     */
+    public function getSigningKey()
+    {
+        return $this->signingKey;
+    }
+
+    /**
+     * Sets the signing key when using an assertion profile.
+     *
+     * @param string $signingKey
+     * @return void
+     */
+    public function setSigningKey($signingKey)
+    {
+        $this->signingKey = $signingKey;
+    }
+
+    /**
+     * Gets the signing key id when using an assertion profile.
+     *
+     * @return ?string
+     */
+    public function getSigningKeyId()
+    {
+        return $this->signingKeyId;
+    }
+
+    /**
+     * Sets the signing key id when using an assertion profile.
+     *
+     * @param string $signingKeyId
+     * @return void
+     */
+    public function setSigningKeyId($signingKeyId)
+    {
+        $this->signingKeyId = $signingKeyId;
+    }
+
+    /**
+     * Gets the signing algorithm when using an assertion profile.
+     *
+     * @return ?string
+     */
+    public function getSigningAlgorithm()
+    {
+        return $this->signingAlgorithm;
+    }
+
+    /**
+     * Sets the signing algorithm when using an assertion profile.
+     *
+     * @param ?string $signingAlgorithm
+     * @return void
+     */
+    public function setSigningAlgorithm($signingAlgorithm)
+    {
+        if (is_null($signingAlgorithm)) {
+            $this->signingAlgorithm = null;
+        } elseif (!in_array($signingAlgorithm, self::$knownSigningAlgorithms)) {
+            throw new InvalidArgumentException('unknown signing algorithm');
+        } else {
+            $this->signingAlgorithm = $signingAlgorithm;
+        }
+    }
+
+    /**
+     * Gets the set of parameters used by extension when using an extension
+     * grant type.
+     *
+     * @return array<mixed>
+     */
+    public function getExtensionParams()
+    {
+        return $this->extensionParams;
+    }
+
+    /**
+     * Sets the set of parameters used by extension when using an extension
+     * grant type.
+     *
+     * @param array<mixed> $extensionParams
+     * @return void
+     */
+    public function setExtensionParams($extensionParams)
+    {
+        $this->extensionParams = $extensionParams;
+    }
+
+    /**
+     * Gets the number of seconds assertions are valid for.
+     *
+     * @return int
+     */
+    public function getExpiry()
+    {
+        return $this->expiry;
+    }
+
+    /**
+     * Sets the number of seconds assertions are valid for.
+     *
+     * @param int $expiry
+     * @return void
+     */
+    public function setExpiry($expiry)
+    {
+        $this->expiry = $expiry;
+    }
+
+    /**
+     * Gets the lifetime of the access token in seconds.
+     *
+     * @return int
+     */
+    public function getExpiresIn()
+    {
+        return $this->expiresIn;
+    }
+
+    /**
+     * Sets the lifetime of the access token in seconds.
+     *
+     * @param ?int $expiresIn
+     * @return void
+     */
+    public function setExpiresIn($expiresIn)
+    {
+        if (is_null($expiresIn)) {
+            $this->expiresIn = null;
+            $this->issuedAt = null;
+        } else {
+            $this->issuedAt = time();
+            $this->expiresIn = (int) $expiresIn;
+        }
+    }
+
+    /**
+     * Gets the time the current access token expires at.
+     *
+     * @return ?int
+     */
+    public function getExpiresAt()
+    {
+        if (!is_null($this->expiresAt)) {
+            return $this->expiresAt;
+        }
+
+        if (!is_null($this->issuedAt) && !is_null($this->expiresIn)) {
+            return $this->issuedAt + $this->expiresIn;
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns true if the acccess token has expired.
+     *
+     * @return bool
+     */
+    public function isExpired()
+    {
+        $expiration = $this->getExpiresAt();
+        $now = time();
+
+        return !is_null($expiration) && $now >= $expiration;
+    }
+
+    /**
+     * Sets the time the current access token expires at.
+     *
+     * @param int $expiresAt
+     * @return void
+     */
+    public function setExpiresAt($expiresAt)
+    {
+        $this->expiresAt = $expiresAt;
+    }
+
+    /**
+     * Gets the time the current access token was issued at.
+     *
+     * @return ?int
+     */
+    public function getIssuedAt()
+    {
+        return $this->issuedAt;
+    }
+
+    /**
+     * Sets the time the current access token was issued at.
+     *
+     * @param int $issuedAt
+     * @return void
+     */
+    public function setIssuedAt($issuedAt)
+    {
+        $this->issuedAt = $issuedAt;
+    }
+
+    /**
+     * Gets the current access token.
+     *
+     * @return ?string
+     */
+    public function getAccessToken()
+    {
+        return $this->accessToken;
+    }
+
+    /**
+     * Sets the current access token.
+     *
+     * @param string $accessToken
+     * @return void
+     */
+    public function setAccessToken($accessToken)
+    {
+        $this->accessToken = $accessToken;
+    }
+
+    /**
+     * Gets the current ID token.
+     *
+     * @return ?string
+     */
+    public function getIdToken()
+    {
+        return $this->idToken;
+    }
+
+    /**
+     * Sets the current ID token.
+     *
+     * @param string $idToken
+     * @return void
+     */
+    public function setIdToken($idToken)
+    {
+        $this->idToken = $idToken;
+    }
+
+    /**
+     * Get the granted space-separated scopes (if they exist) for the last
+     * fetched token.
+     *
+     * @return string|null
+     */
+    public function getGrantedScope()
+    {
+        return $this->grantedScope;
+    }
+
+    /**
+     * Sets the current ID token.
+     *
+     * @param string $grantedScope
+     * @return void
+     */
+    public function setGrantedScope($grantedScope)
+    {
+        $this->grantedScope = $grantedScope;
+    }
+
+    /**
+     * Gets the refresh token associated with the current access token.
+     *
+     * @return ?string
+     */
+    public function getRefreshToken()
+    {
+        return $this->refreshToken;
+    }
+
+    /**
+     * Sets the refresh token associated with the current access token.
+     *
+     * @param string $refreshToken
+     * @return void
+     */
+    public function setRefreshToken($refreshToken)
+    {
+        $this->refreshToken = $refreshToken;
+    }
+
+    /**
+     * Sets additional claims to be included in the JWT token
+     *
+     * @param array<mixed> $additionalClaims
+     * @return void
+     */
+    public function setAdditionalClaims(array $additionalClaims)
+    {
+        $this->additionalClaims = $additionalClaims;
+    }
+
+    /**
+     * Gets the additional claims to be included in the JWT token.
+     *
+     * @return array<mixed>
+     */
+    public function getAdditionalClaims()
+    {
+        return $this->additionalClaims;
+    }
+
+    /**
+     * Gets the additional claims to be included in the JWT token.
+     *
+     * @return ?string
+     */
+    public function getIssuedTokenType()
+    {
+        return $this->issuedTokenType;
+    }
+
+    /**
+     * The expiration of the last received token.
+     *
+     * @return array<mixed>|null
+     */
+    public function getLastReceivedToken()
+    {
+        if ($token = $this->getAccessToken()) {
+            // the bare necessity of an auth token
+            $authToken = [
+                'access_token' => $token,
+                'expires_at' => $this->getExpiresAt(),
+            ];
+        } elseif ($idToken = $this->getIdToken()) {
+            $authToken = [
+                'id_token' => $idToken,
+                'expires_at' => $this->getExpiresAt(),
+            ];
+        } else {
+            return null;
+        }
+
+        if ($expiresIn = $this->getExpiresIn()) {
+            $authToken['expires_in'] = $expiresIn;
+        }
+        if ($issuedAt = $this->getIssuedAt()) {
+            $authToken['issued_at'] = $issuedAt;
+        }
+        if ($refreshToken = $this->getRefreshToken()) {
+            $authToken['refresh_token'] = $refreshToken;
+        }
+
+        return $authToken;
+    }
+
+    /**
+     * Get the client ID.
+     *
+     * Alias of {@see Google\Auth\OAuth2::getClientId()}.
+     *
+     * @param callable|null $httpHandler
+     * @return string
+     * @access private
+     */
+    public function getClientName(?callable $httpHandler = null)
+    {
+        return $this->getClientId();
+    }
+
+    /**
+     * @todo handle uri as array
+     *
+     * @param ?string $uri
+     * @return null|UriInterface
+     */
+    private function coerceUri($uri)
+    {
+        if (is_null($uri)) {
+            return null;
+        }
+
+        return Utils::uriFor($uri);
+    }
+
+    /**
+     * @param string $idToken
+     * @param Key|Key[]|string|string[] $publicKey
+     * @param string|string[] $allowedAlgs
+     * @return object
+     */
+    private function jwtDecode($idToken, $publicKey, $allowedAlgs)
+    {
+        $keys = $this->getFirebaseJwtKeys($publicKey, $allowedAlgs);
+
+        // Default exception if none are caught. We are using the same exception
+        // class and message from firebase/php-jwt to preserve backwards
+        // compatibility.
+        $e = new \InvalidArgumentException('Key may not be empty');
+        foreach ($keys as $key) {
+            try {
+                return JWT::decode($idToken, $key);
+            } catch (\Exception $e) {
+                // try next alg
+            }
+        }
+        throw $e;
+    }
+
+    /**
+     * @param Key|Key[]|string|string[] $publicKey
+     * @param string|string[] $allowedAlgs
+     * @return Key[]
+     */
+    private function getFirebaseJwtKeys($publicKey, $allowedAlgs)
+    {
+        // If $publicKey is instance of Key, return it
+        if ($publicKey instanceof Key) {
+            return [$publicKey];
+        }
+
+        // If $allowedAlgs is empty, $publicKey must be Key or Key[].
+        if (empty($allowedAlgs)) {
+            $keys = [];
+            foreach ((array) $publicKey as $kid => $pubKey) {
+                if (!$pubKey instanceof Key) {
+                    throw new \InvalidArgumentException(sprintf(
+                        'When allowed algorithms is empty, the public key must'
+                        . 'be an instance of %s or an array of %s objects',
+                        Key::class,
+                        Key::class
+                    ));
+                }
+                $keys[$kid] = $pubKey;
+            }
+            return $keys;
+        }
+
+        $allowedAlg = null;
+        if (is_string($allowedAlgs)) {
+            $allowedAlg = $allowedAlgs;
+        } elseif (is_array($allowedAlgs)) {
+            if (count($allowedAlgs) > 1) {
+                throw new \InvalidArgumentException(
+                    'To have multiple allowed algorithms, You must provide an'
+                    . ' array of Firebase\JWT\Key objects.'
+                    . ' See https://github.com/firebase/php-jwt for more information.'
+                );
+            }
+            $allowedAlg = array_pop($allowedAlgs);
+        } else {
+            throw new \InvalidArgumentException('allowed algorithms must be a string or array.');
+        }
+
+        if (is_array($publicKey)) {
+            // When publicKey is greater than 1, create keys with the single alg.
+            $keys = [];
+            foreach ($publicKey as $kid => $pubKey) {
+                if ($pubKey instanceof Key) {
+                    $keys[$kid] = $pubKey;
+                } else {
+                    $keys[$kid] = new Key($pubKey, $allowedAlg);
+                }
+            }
+            return $keys;
+        }
+
+        return [new Key($publicKey, $allowedAlg)];
+    }
+
+    /**
+     * Determines if the URI is absolute based on its scheme and host or path
+     * (RFC 3986).
+     *
+     * @param string $uri
+     * @return bool
+     */
+    private function isAbsoluteUri($uri)
+    {
+        $uri = $this->coerceUri($uri);
+
+        return $uri->getScheme() && ($uri->getHost() || $uri->getPath());
+    }
+
+    /**
+     * @param array<mixed> $params
+     * @return array<mixed>
+     */
+    private function addClientCredentials(&$params)
+    {
+        $clientId = $this->getClientId();
+        $clientSecret = $this->getClientSecret();
+
+        if ($clientId && $clientSecret) {
+            $params['client_id'] = $clientId;
+            $params['client_secret'] = $clientSecret;
+        }
+
+        return $params;
+    }
+}
