PluginProbe ʕ •ᴥ•ʔ
ShareThis Dashboard for Google Analytics / 3.1.7
ShareThis Dashboard for Google Analytics v3.1.7
3.3.2 trunk 1.0.7 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1 2.1.2 2.1.3 2.1.4 2.1.5 2.2.5 2.3.5 2.3.6 2.3.7 2.3.8 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 3.0.0 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.3.0 3.3.1
googleanalytics / lib / analytics-admin / vendor / google / apiclient / src / AccessToken / Verify.php
googleanalytics / lib / analytics-admin / vendor / google / apiclient / src / AccessToken Last commit date
Revoke.php 3 years ago Verify.php 3 years ago
Verify.php
321 lines
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
19 namespace Google\AccessToken;
20
21 use DateTime;
22 use DomainException;
23 use Exception;
24 use ExpiredException;
25 use Firebase\JWT\ExpiredException as ExpiredExceptionV3;
26 use Firebase\JWT\Key;
27 use Firebase\JWT\SignatureInvalidException;
28 use Google\Auth\Cache\MemoryCacheItemPool;
29 use Google\Exception as GoogleException;
30 use GuzzleHttp\Client;
31 use GuzzleHttp\ClientInterface;
32 use InvalidArgumentException;
33 use LogicException;
34 use phpseclib3\Crypt\PublicKeyLoader;
35 use phpseclib3\Crypt\RSA\PublicKey; // Firebase v2
36 use Psr\Cache\CacheItemPoolInterface;
37
38 /**
39 * Wrapper around Google Access Tokens which provides convenience functions
40 *
41 */
42 class Verify
43 {
44 const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
45 const OAUTH2_ISSUER = 'accounts.google.com';
46 const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
47
48 /**
49 * @var ClientInterface The http client
50 */
51 private $http;
52
53 /**
54 * @var CacheItemPoolInterface cache class
55 */
56 private $cache;
57
58 /**
59 * @var \Firebase\JWT\JWT
60 */
61 public $jwt;
62
63 /**
64 * Instantiates the class, but does not initiate the login flow, leaving it
65 * to the discretion of the caller.
66 */
67 public function __construct(
68 ClientInterface $http = null,
69 CacheItemPoolInterface $cache = null,
70 $jwt = null
71 ) {
72 if (null === $http) {
73 $http = new Client();
74 }
75
76 if (null === $cache) {
77 $cache = new MemoryCacheItemPool();
78 }
79
80 $this->http = $http;
81 $this->cache = $cache;
82 $this->jwt = $jwt ?: $this->getJwtService();
83 }
84
85 /**
86 * Verifies an id token and returns the authenticated apiLoginTicket.
87 * Throws an exception if the id token is not valid.
88 * The audience parameter can be used to control which id tokens are
89 * accepted. By default, the id token must have been issued to this OAuth2 client.
90 *
91 * @param string $idToken the ID token in JWT format
92 * @param string $audience Optional. The audience to verify against JWt "aud"
93 * @return array|false the token payload, if successful
94 */
95 public function verifyIdToken($idToken, $audience = null)
96 {
97 if (empty($idToken)) {
98 throw new LogicException('id_token cannot be null');
99 }
100
101 // set phpseclib constants if applicable
102 $this->setPhpsecConstants();
103
104 // Check signature
105 $certs = $this->getFederatedSignOnCerts();
106 foreach ($certs as $cert) {
107 try {
108 $args = [$idToken];
109 $publicKey = $this->getPublicKey($cert);
110 if (class_exists(Key::class)) {
111 $args[] = new Key($publicKey, 'RS256');
112 } else {
113 $args[] = $publicKey;
114 $args[] = ['RS256'];
115 }
116 $payload = \call_user_func_array([$this->jwt, 'decode'], $args);
117
118 if (property_exists($payload, 'aud')) {
119 if ($audience && $payload->aud != $audience) {
120 return false;
121 }
122 }
123
124 // support HTTP and HTTPS issuers
125 // @see https://developers.google.com/identity/sign-in/web/backend-auth
126 $issuers = [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS];
127 if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {
128 return false;
129 }
130
131 return (array) $payload;
132 } catch (ExpiredException $e) { // @phpstan-ignore-line
133 return false;
134 } catch (ExpiredExceptionV3 $e) {
135 return false;
136 } catch (SignatureInvalidException $e) {
137 // continue
138 } catch (DomainException $e) {
139 // continue
140 }
141 }
142
143 return false;
144 }
145
146 private function getCache()
147 {
148 return $this->cache;
149 }
150
151 /**
152 * Retrieve and cache a certificates file.
153 *
154 * @param string $url location
155 * @throws \Google\Exception
156 * @return array certificates
157 */
158 private function retrieveCertsFromLocation($url)
159 {
160 // If we're retrieving a local file, just grab it.
161 if (0 !== strpos($url, 'http')) {
162 if (!$file = file_get_contents($url)) {
163 throw new GoogleException(
164 "Failed to retrieve verification certificates: '" .
165 $url . "'."
166 );
167 }
168
169 return json_decode($file, true);
170 }
171
172 // @phpstan-ignore-next-line
173 $response = $this->http->get($url);
174
175 if ($response->getStatusCode() == 200) {
176 return json_decode((string) $response->getBody(), true);
177 }
178 throw new GoogleException(
179 sprintf(
180 'Failed to retrieve verification certificates: "%s".',
181 $response->getBody()->getContents()
182 ),
183 $response->getStatusCode()
184 );
185 }
186
187 // Gets federated sign-on certificates to use for verifying identity tokens.
188 // Returns certs as array structure, where keys are key ids, and values
189 // are PEM encoded certificates.
190 private function getFederatedSignOnCerts()
191 {
192 $certs = null;
193 if ($cache = $this->getCache()) {
194 $cacheItem = $cache->getItem('federated_signon_certs_v3');
195 $certs = $cacheItem->get();
196 }
197
198
199 if (!$certs) {
200 $certs = $this->retrieveCertsFromLocation(
201 self::FEDERATED_SIGNON_CERT_URL
202 );
203
204 if ($cache) {
205 $cacheItem->expiresAt(new DateTime('+1 hour'));
206 $cacheItem->set($certs);
207 $cache->save($cacheItem);
208 }
209 }
210
211 if (!isset($certs['keys'])) {
212 throw new InvalidArgumentException(
213 'federated sign-on certs expects "keys" to be set'
214 );
215 }
216
217 return $certs['keys'];
218 }
219
220 private function getJwtService()
221 {
222 $jwtClass = 'JWT';
223 if (class_exists('\Firebase\JWT\JWT')) {
224 $jwtClass = 'Firebase\JWT\JWT';
225 }
226
227 if (property_exists($jwtClass, 'leeway') && $jwtClass::$leeway < 1) {
228 // Ensures JWT leeway is at least 1
229 // @see https://github.com/google/google-api-php-client/issues/827
230 $jwtClass::$leeway = 1;
231 }
232
233 // @phpstan-ignore-next-line
234 return new $jwtClass();
235 }
236
237 private function getPublicKey($cert)
238 {
239 $bigIntClass = $this->getBigIntClass();
240 $modulus = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['n']), 256);
241 $exponent = new $bigIntClass($this->jwt->urlsafeB64Decode($cert['e']), 256);
242 $component = ['n' => $modulus, 'e' => $exponent];
243
244 if (class_exists('phpseclib3\Crypt\RSA\PublicKey')) {
245 /** @var PublicKey $loader */
246 $loader = PublicKeyLoader::load($component);
247
248 return $loader->toString('PKCS8');
249 }
250
251 $rsaClass = $this->getRsaClass();
252 $rsa = new $rsaClass();
253 $rsa->loadKey($component);
254
255 return $rsa->getPublicKey();
256 }
257
258 private function getRsaClass()
259 {
260 if (class_exists('phpseclib3\Crypt\RSA')) {
261 return 'phpseclib3\Crypt\RSA';
262 }
263
264 if (class_exists('phpseclib\Crypt\RSA')) {
265 return 'phpseclib\Crypt\RSA';
266 }
267
268 return 'Crypt_RSA';
269 }
270
271 private function getBigIntClass()
272 {
273 if (class_exists('phpseclib3\Math\BigInteger')) {
274 return 'phpseclib3\Math\BigInteger';
275 }
276
277 if (class_exists('phpseclib\Math\BigInteger')) {
278 return 'phpseclib\Math\BigInteger';
279 }
280
281 return 'Math_BigInteger';
282 }
283
284 private function getOpenSslConstant()
285 {
286 if (class_exists('phpseclib3\Crypt\AES')) {
287 return 'phpseclib3\Crypt\AES::ENGINE_OPENSSL';
288 }
289
290 if (class_exists('phpseclib\Crypt\RSA')) {
291 return 'phpseclib\Crypt\RSA::MODE_OPENSSL';
292 }
293
294 if (class_exists('Crypt_RSA')) {
295 return 'CRYPT_RSA_MODE_OPENSSL';
296 }
297
298 throw new Exception('Cannot find RSA class');
299 }
300
301 /**
302 * phpseclib calls "phpinfo" by default, which requires special
303 * whitelisting in the AppEngine VM environment. This function
304 * sets constants to bypass the need for phpseclib to check phpinfo
305 *
306 * @see phpseclib/Math/BigInteger
307 * @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85
308 */
309 private function setPhpsecConstants()
310 {
311 if (filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN)) {
312 if (!defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
313 define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
314 }
315 if (!defined('CRYPT_RSA_MODE')) {
316 define('CRYPT_RSA_MODE', constant($this->getOpenSslConstant()));
317 }
318 }
319 }
320 }
321