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

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($body, true)) {
  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($grantType, self::$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($signingAlgorithm, self::$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. }