OAuth2.php 38 KB

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