Blame | Last modification | View Log | RSS feed
<?php/** Copyright 2008 Google Inc.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/use Firebase\JWT\ExpiredException as ExpiredExceptionV3;use GuzzleHttp\Client;use GuzzleHttp\ClientInterface;use Psr\Cache\CacheItemPoolInterface;use Google\Auth\Cache\MemoryCacheItemPool;use Stash\Driver\FileSystem;use Stash\Pool;/*** Wrapper around Google Access Tokens which provides convenience functions**/class Google_AccessToken_Verify{const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';const OAUTH2_ISSUER = 'accounts.google.com';const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';/*** @var GuzzleHttp\ClientInterface The http client*/private $http;/*** @var Psr\Cache\CacheItemPoolInterface cache class*/private $cache;/*** Instantiates the class, but does not initiate the login flow, leaving it* to the discretion of the caller.*/public function __construct(ClientInterface $http = null,CacheItemPoolInterface $cache = null,$jwt = null) {if (null === $http) {$http = new Client();}if (null === $cache) {$cache = new MemoryCacheItemPool;}$this->http = $http;$this->cache = $cache;$this->jwt = $jwt ?: $this->getJwtService();}/*** Verifies an id token and returns the authenticated apiLoginTicket.* Throws an exception if the id token is not valid.* The audience parameter can be used to control which id tokens are* accepted. By default, the id token must have been issued to this OAuth2 client.** @param $audience* @return array the token payload, if successful*/public function verifyIdToken($idToken, $audience = null){if (empty($idToken)) {throw new LogicException('id_token cannot be null');}// set phpseclib constants if applicable$this->setPhpsecConstants();// Check signature$certs = $this->getFederatedSignOnCerts();foreach ($certs as $cert) {$bigIntClass = $this->getBigIntClass();$rsaClass = $this->getRsaClass();$modulus = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['n']), 256);$exponent = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['e']), 256);$rsa = new $rsaClass();$rsa->loadKey(array('n' => $modulus, 'e' => $exponent));try {$payload = $this->jwt->decode($idToken,$rsa->getPublicKey(),array('RS256'));if (property_exists($payload, 'aud')) {if ($audience && $payload->aud != $audience) {return false;}}// support HTTP and HTTPS issuers// @see https://developers.google.com/identity/sign-in/web/backend-auth$issuers = array(self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS);if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {return false;}return (array) $payload;} catch (ExpiredException $e) {return false;} catch (ExpiredExceptionV3 $e) {return false;} catch (DomainException $e) {// continue}}return false;}private function getCache(){return $this->cache;}/*** Retrieve and cache a certificates file.** @param $url string location* @throws Google_Exception* @return array certificates*/private function retrieveCertsFromLocation($url){// If we're retrieving a local file, just grab it.if (0 !== strpos($url, 'http')) {if (!$file = file_get_contents($url)) {throw new Google_Exception("Failed to retrieve verification certificates: '" .$url . "'.");}return json_decode($file, true);}$response = $this->http->get($url);if ($response->getStatusCode() == 200) {return json_decode((string) $response->getBody(), true);}throw new Google_Exception(sprintf('Failed to retrieve verification certificates: "%s".',$response->getBody()->getContents()),$response->getStatusCode());}// Gets federated sign-on certificates to use for verifying identity tokens.// Returns certs as array structure, where keys are key ids, and values// are PEM encoded certificates.private function getFederatedSignOnCerts(){$certs = null;if ($cache = $this->getCache()) {$cacheItem = $cache->getItem('federated_signon_certs_v3', 3600);$certs = $cacheItem->get();}if (!$certs) {$certs = $this->retrieveCertsFromLocation(self::FEDERATED_SIGNON_CERT_URL);if ($cache) {$cacheItem->set($certs);$cache->save($cacheItem);}}if (!isset($certs['keys'])) {throw new InvalidArgumentException('federated sign-on certs expects "keys" to be set');}return $certs['keys'];}private function getJwtService(){$jwtClass = 'JWT';if (class_exists('\Firebase\JWT\JWT')) {$jwtClass = 'Firebase\JWT\JWT';}if (property_exists($jwtClass, 'leeway')) {// adds 1 second to JWT leeway// @see https://github.com/google/google-api-php-client/issues/827$jwtClass::$leeway = 1;}return new $jwtClass;}private function getRsaClass(){if (class_exists('phpseclib\Crypt\RSA')) {return 'phpseclib\Crypt\RSA';}return 'Crypt_RSA';}private function getBigIntClass(){if (class_exists('phpseclib\Math\BigInteger')) {return 'phpseclib\Math\BigInteger';}return 'Math_BigInteger';}private function getOpenSslConstant(){if (class_exists('phpseclib\Crypt\RSA')) {return 'phpseclib\Crypt\RSA::MODE_OPENSSL';}if (class_exists('Crypt_RSA')) {return 'CRYPT_RSA_MODE_OPENSSL';}throw new \Exception('Cannot find RSA class');}/*** phpseclib calls "phpinfo" by default, which requires special* whitelisting in the AppEngine VM environment. This function* sets constants to bypass the need for phpseclib to check phpinfo** @see phpseclib/Math/BigInteger* @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85*/private function setPhpsecConstants(){if (filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN)) {if (!defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);}if (!defined('CRYPT_RSA_MODE')) {define('CRYPT_RSA_MODE', constant($this->getOpenSslConstant()));}}}}