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