暫無描述

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