source: vendor/google/apiclient/src/AccessToken/Verify.php

Last change on this file was e3d4e0a, checked in by Vlado 222039 <vlado.popovski@…>, 7 days ago

Upload project files

  • Property mode set to 100644
File size: 7.9 KB
RevLine 
[e3d4e0a]1<?php
2
3/*
4 * Copyright 2008 Google Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19namespace Google\AccessToken;
20
21use DateTime;
22use DomainException;
23use Exception;
24use ExpiredException;
25use Firebase\JWT\ExpiredException as ExpiredExceptionV3;
26use Firebase\JWT\JWT;
27use Firebase\JWT\Key;
28use Firebase\JWT\SignatureInvalidException;
29use Google\Auth\Cache\MemoryCacheItemPool;
30use Google\Exception as GoogleException;
31use GuzzleHttp\Client;
32use GuzzleHttp\ClientInterface;
33use InvalidArgumentException;
34use LogicException;
35use phpseclib3\Crypt\AES;
36use phpseclib3\Crypt\PublicKeyLoader;
37use phpseclib3\Math\BigInteger;
38use Psr\Cache\CacheItemPoolInterface;
39
40/**
41 * Wrapper around Google Access Tokens which provides convenience functions
42 *
43 */
44class Verify
45{
46 const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
47 const OAUTH2_ISSUER = 'accounts.google.com';
48 const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
49
50 /**
51 * @var ClientInterface The http client
52 */
53 private $http;
54
55 /**
56 * @var CacheItemPoolInterface cache class
57 */
58 private $cache;
59
60 /**
61 * @var \Firebase\JWT\JWT
62 */
63 public $jwt;
64
65 /**
66 * Instantiates the class, but does not initiate the login flow, leaving it
67 * to the discretion of the caller.
68 */
69 public function __construct(
70 ?ClientInterface $http = null,
71 ?CacheItemPoolInterface $cache = null,
72 ?string $jwt = null
73 ) {
74 if (null === $http) {
75 $http = new Client();
76 }
77
78 if (null === $cache) {
79 $cache = new MemoryCacheItemPool();
80 }
81
82 $this->http = $http;
83 $this->cache = $cache;
84 $this->jwt = $jwt ?: $this->getJwtService();
85 }
86
87 /**
88 * Verifies an id token and returns the authenticated apiLoginTicket.
89 * Throws an exception if the id token is not valid.
90 * The audience parameter can be used to control which id tokens are
91 * accepted. By default, the id token must have been issued to this OAuth2 client.
92 *
93 * @param string $idToken the ID token in JWT format
94 * @param string $audience Optional. The audience to verify against JWt "aud"
95 * @return array|false the token payload, if successful
96 */
97 public function verifyIdToken($idToken, $audience = null)
98 {
99 if (empty($idToken)) {
100 throw new LogicException('id_token cannot be null');
101 }
102
103 // set phpseclib constants if applicable
104 $this->setPhpsecConstants();
105
106 // Check signature
107 $certs = $this->getFederatedSignOnCerts();
108 foreach ($certs as $cert) {
109 try {
110 $args = [$idToken];
111 $publicKey = $this->getPublicKey($cert);
112 if (class_exists(Key::class)) {
113 $args[] = new Key($publicKey, 'RS256');
114 } else {
115 $args[] = $publicKey;
116 $args[] = ['RS256'];
117 }
118 $payload = \call_user_func_array([$this->jwt, 'decode'], $args);
119
120 if (property_exists($payload, 'aud')) {
121 if ($audience && $payload->aud != $audience) {
122 return false;
123 }
124 }
125
126 // support HTTP and HTTPS issuers
127 // @see https://developers.google.com/identity/sign-in/web/backend-auth
128 $issuers = [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS];
129 if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {
130 return false;
131 }
132
133 return (array)$payload;
134 } catch (ExpiredException $e) { // @phpstan-ignore-line
135 return false;
136 } catch (ExpiredExceptionV3 $e) {
137 return false;
138 } catch (SignatureInvalidException $e) {
139 // continue
140 } catch (DomainException $e) {
141 // continue
142 }
143 }
144
145 return false;
146 }
147
148 private function getCache()
149 {
150 return $this->cache;
151 }
152
153 /**
154 * Retrieve and cache a certificates file.
155 *
156 * @param string $url location
157 * @return array certificates
158 * @throws \Google\Exception
159 */
160 private function retrieveCertsFromLocation($url)
161 {
162 // If we're retrieving a local file, just grab it.
163 if (0 !== strpos($url, 'http')) {
164 if (!$file = file_get_contents($url)) {
165 throw new GoogleException(
166 "Failed to retrieve verification certificates: '".
167 $url."'."
168 );
169 }
170
171 return json_decode($file, true);
172 }
173
174 // @phpstan-ignore-next-line
175 $response = $this->http->get($url);
176
177 if ($response->getStatusCode() == 200) {
178 return json_decode((string)$response->getBody(), true);
179 }
180 throw new GoogleException(
181 sprintf(
182 'Failed to retrieve verification certificates: "%s".',
183 $response->getBody()->getContents()
184 ),
185 $response->getStatusCode()
186 );
187 }
188
189 // Gets federated sign-on certificates to use for verifying identity tokens.
190 // Returns certs as array structure, where keys are key ids, and values
191 // are PEM encoded certificates.
192 private function getFederatedSignOnCerts()
193 {
194 $certs = null;
195 if ($cache = $this->getCache()) {
196 $cacheItem = $cache->getItem('federated_signon_certs_v3');
197 $certs = $cacheItem->get();
198 }
199
200
201 if (!$certs) {
202 $certs = $this->retrieveCertsFromLocation(
203 self::FEDERATED_SIGNON_CERT_URL
204 );
205
206 if ($cache) {
207 $cacheItem->expiresAt(new DateTime('+1 hour'));
208 $cacheItem->set($certs);
209 $cache->save($cacheItem);
210 }
211 }
212
213 if (!isset($certs['keys'])) {
214 throw new InvalidArgumentException(
215 'federated sign-on certs expects "keys" to be set'
216 );
217 }
218
219 return $certs['keys'];
220 }
221
222 private function getJwtService()
223 {
224 $jwt = new JWT();
225 if ($jwt::$leeway < 1) {
226 // Ensures JWT leeway is at least 1
227 // @see https://github.com/google/google-api-php-client/issues/827
228 $jwt::$leeway = 1;
229 }
230
231 return $jwt;
232 }
233
234 private function getPublicKey($cert)
235 {
236 $modulus = new BigInteger($this->jwt->urlsafeB64Decode($cert['n']), 256);
237 $exponent = new BigInteger($this->jwt->urlsafeB64Decode($cert['e']), 256);
238 $component = ['n' => $modulus, 'e' => $exponent];
239
240 $loader = PublicKeyLoader::load($component);
241
242 return $loader->toString('PKCS8');
243 }
244
245 /**
246 * phpseclib calls "phpinfo" by default, which requires special
247 * whitelisting in the AppEngine VM environment. This function
248 * sets constants to bypass the need for phpseclib to check phpinfo
249 *
250 * @see phpseclib/Math/BigInteger
251 * @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85
252 */
253 private function setPhpsecConstants()
254 {
255 if (filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN)) {
256 if (!defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
257 define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
258 }
259 if (!defined('CRYPT_RSA_MODE')) {
260 define('CRYPT_RSA_MODE', AES::ENGINE_OPENSSL);
261 }
262 }
263 }
264}
Note: See TracBrowser for help on using the repository browser.