PluginProbe
WP-Stateless – Google Cloud Storage / trunk
WP-Stateless – Google Cloud Storage vtrunk
4.4.3 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.3.0 2.3.1 2.3.2 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 All 62 releases
wp-stateless / lib / Google / src / AccessToken / Verify.php

Verify.php in WP-Stateless – Google Cloud Storage trunk, at lib/Google/src/AccessToken/Verify.php

265 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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\JWT;
27 use Firebase\JWT\Key;
28 use Firebase\JWT\SignatureInvalidException;
29 use Google\Auth\Cache\MemoryCacheItemPool;
30 use Google\Exception as GoogleException;
31 use GuzzleHttp\Client;
32 use GuzzleHttp\ClientInterface;
33 use InvalidArgumentException;
34 use LogicException;
35 use phpseclib3\Crypt\AES;
36 use phpseclib3\Crypt\PublicKeyLoader;
37 use phpseclib3\Math\BigInteger;
38 use Psr\Cache\CacheItemPoolInterface;
39
40 /**
41 * Wrapper around Google Access Tokens which provides convenience functions
42 *
43 */
44 class 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 ?JWT $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 }
265