PluginProbe ʕ •ᴥ•ʔ
WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin / 4.7.0
WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin v4.7.0
0.9.6 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.2 1.7.0 1.7.1 1.8.0 1.8.1 1.9.0 2.0.0 2.0.1 2.1.1 2.2.1 2.3.1 2.4.0 2.5.0 2.5.1 2.6.0 2.7.0 2.8.0 2.9.0 3.0.1 3.0.2 3.0.3 3.1.0 3.10.0 3.11.0 3.11.1 3.2.0 3.2.1 3.3.0 3.4.0 3.5.0 3.5.1 3.5.2 3.6.1 3.7.0 3.8.0 3.8.2 3.9.0 4.0.1 4.1.0 4.1.1 4.2.0 4.3.0 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.8.0 trunk 0.10.0 0.10.1 0.11.1 0.11.2 0.3.1 0.3.2 0.4 0.4.1 0.4.2 0.5.0 0.5.1 0.5.2 0.6 0.7 0.8 0.8.2 0.8.3 0.8.4 0.8.5 0.8.6 0.8.7 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5
wp-mail-smtp / vendor_prefixed / google / auth / src / AccessToken.php
wp-mail-smtp / vendor_prefixed / google / auth / src Last commit date
Cache 6 months ago CredentialSource 6 months ago Credentials 6 months ago HttpHandler 6 months ago Middleware 6 months ago AccessToken.php 6 months ago ApplicationDefaultCredentials.php 6 months ago CacheTrait.php 6 months ago CredentialsLoader.php 6 months ago ExternalAccountCredentialSourceInterface.php 6 months ago FetchAuthTokenCache.php 6 months ago FetchAuthTokenInterface.php 6 months ago GCECache.php 6 months ago GetQuotaProjectInterface.php 6 months ago GetUniverseDomainInterface.php 6 months ago Iam.php 6 months ago IamSignerTrait.php 6 months ago OAuth2.php 6 months ago ProjectIdProviderInterface.php 6 months ago ServiceAccountSignerTrait.php 6 months ago SignBlobInterface.php 6 months ago UpdateMetadataInterface.php 6 months ago UpdateMetadataTrait.php 6 months ago
AccessToken.php
391 lines
1 <?php
2
3 /*
4 * Copyright 2019 Google LLC
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 namespace WPMailSMTP\Vendor\Google\Auth;
19
20 use DateTime;
21 use WPMailSMTP\Vendor\Firebase\JWT\ExpiredException;
22 use WPMailSMTP\Vendor\Firebase\JWT\JWT;
23 use WPMailSMTP\Vendor\Firebase\JWT\Key;
24 use WPMailSMTP\Vendor\Firebase\JWT\SignatureInvalidException;
25 use WPMailSMTP\Vendor\Google\Auth\Cache\MemoryCacheItemPool;
26 use WPMailSMTP\Vendor\Google\Auth\HttpHandler\HttpClientCache;
27 use WPMailSMTP\Vendor\Google\Auth\HttpHandler\HttpHandlerFactory;
28 use WPMailSMTP\Vendor\GuzzleHttp\Psr7\Request;
29 use WPMailSMTP\Vendor\GuzzleHttp\Psr7\Utils;
30 use InvalidArgumentException;
31 use WPMailSMTP\Vendor\phpseclib3\Crypt\PublicKeyLoader;
32 use WPMailSMTP\Vendor\phpseclib3\Crypt\RSA;
33 use WPMailSMTP\Vendor\phpseclib3\Math\BigInteger;
34 use WPMailSMTP\Vendor\Psr\Cache\CacheItemPoolInterface;
35 use RuntimeException;
36 use WPMailSMTP\Vendor\SimpleJWT\InvalidTokenException;
37 use WPMailSMTP\Vendor\SimpleJWT\JWT as SimpleJWT;
38 use WPMailSMTP\Vendor\SimpleJWT\Keys\KeyFactory;
39 use WPMailSMTP\Vendor\SimpleJWT\Keys\KeySet;
40 use TypeError;
41 use UnexpectedValueException;
42 /**
43 * Wrapper around Google Access Tokens which provides convenience functions.
44 *
45 * @experimental
46 */
47 class AccessToken
48 {
49 const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
50 const IAP_CERT_URL = 'https://www.gstatic.com/iap/verify/public_key-jwk';
51 const IAP_ISSUER = 'https://cloud.google.com/iap';
52 const OAUTH2_ISSUER = 'accounts.google.com';
53 const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
54 const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke';
55 /**
56 * @var callable
57 */
58 private $httpHandler;
59 /**
60 * @var CacheItemPoolInterface
61 */
62 private $cache;
63 /**
64 * @param callable $httpHandler [optional] An HTTP Handler to deliver PSR-7 requests.
65 * @param CacheItemPoolInterface $cache [optional] A PSR-6 compatible cache implementation.
66 */
67 public function __construct(?callable $httpHandler = null, ?CacheItemPoolInterface $cache = null)
68 {
69 $this->httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
70 $this->cache = $cache ?: new MemoryCacheItemPool();
71 }
72 /**
73 * Verifies an id token and returns the authenticated apiLoginTicket.
74 * Throws an exception if the id token is not valid.
75 * The audience parameter can be used to control which id tokens are
76 * accepted. By default, the id token must have been issued to this OAuth2 client.
77 *
78 * @param string $token The JSON Web Token to be verified.
79 * @param array<mixed> $options [optional] {
80 * Configuration options.
81 * @type string $audience The indended recipient of the token.
82 * @type string $issuer The intended issuer of the token.
83 * @type string $cacheKey The cache key of the cached certs. Defaults to
84 * the sha1 of $certsLocation if provided, otherwise is set to
85 * "federated_signon_certs_v3".
86 * @type string $certsLocation The location (remote or local) from which
87 * to retrieve certificates, if not cached. This value should only be
88 * provided in limited circumstances in which you are sure of the
89 * behavior.
90 * @type bool $throwException Whether the function should throw an
91 * exception if the verification fails. This is useful for
92 * determining the reason verification failed.
93 * }
94 * @return array<mixed>|false the token payload, if successful, or false if not.
95 * @throws InvalidArgumentException If certs could not be retrieved from a local file.
96 * @throws InvalidArgumentException If received certs are in an invalid format.
97 * @throws InvalidArgumentException If the cert alg is not supported.
98 * @throws RuntimeException If certs could not be retrieved from a remote location.
99 * @throws UnexpectedValueException If the token issuer does not match.
100 * @throws UnexpectedValueException If the token audience does not match.
101 */
102 public function verify($token, array $options = [])
103 {
104 $audience = $options['audience'] ?? null;
105 $issuer = $options['issuer'] ?? null;
106 $certsLocation = $options['certsLocation'] ?? self::FEDERATED_SIGNON_CERT_URL;
107 $cacheKey = $options['cacheKey'] ?? $this->getCacheKeyFromCertLocation($certsLocation);
108 $throwException = $options['throwException'] ?? \false;
109 // for backwards compatibility
110 // Check signature against each available cert.
111 $certs = $this->getCerts($certsLocation, $cacheKey, $options);
112 $alg = $this->determineAlg($certs);
113 if (!\in_array($alg, ['RS256', 'ES256'])) {
114 throw new InvalidArgumentException('unrecognized "alg" in certs, expected ES256 or RS256');
115 }
116 try {
117 if ($alg == 'RS256') {
118 return $this->verifyRs256($token, $certs, $audience, $issuer);
119 }
120 return $this->verifyEs256($token, $certs, $audience, $issuer);
121 } catch (ExpiredException $e) {
122 // firebase/php-jwt 5+
123 } catch (SignatureInvalidException $e) {
124 // firebase/php-jwt 5+
125 } catch (InvalidTokenException $e) {
126 // simplejwt
127 } catch (InvalidArgumentException $e) {
128 } catch (UnexpectedValueException $e) {
129 }
130 if ($throwException) {
131 throw $e;
132 }
133 return \false;
134 }
135 /**
136 * Identifies the expected algorithm to verify by looking at the "alg" key
137 * of the provided certs.
138 *
139 * @param array<mixed> $certs Certificate array according to the JWK spec (see
140 * https://tools.ietf.org/html/rfc7517).
141 * @return string The expected algorithm, such as "ES256" or "RS256".
142 */
143 private function determineAlg(array $certs)
144 {
145 $alg = null;
146 foreach ($certs as $cert) {
147 if (empty($cert['alg'])) {
148 throw new InvalidArgumentException('certs expects "alg" to be set');
149 }
150 $alg = $alg ?: $cert['alg'];
151 if ($alg != $cert['alg']) {
152 throw new InvalidArgumentException('More than one alg detected in certs');
153 }
154 }
155 return $alg;
156 }
157 /**
158 * Verifies an ES256-signed JWT.
159 *
160 * @param string $token The JSON Web Token to be verified.
161 * @param array<mixed> $certs Certificate array according to the JWK spec (see
162 * https://tools.ietf.org/html/rfc7517).
163 * @param string|null $audience If set, returns false if the provided
164 * audience does not match the "aud" claim on the JWT.
165 * @param string|null $issuer If set, returns false if the provided
166 * issuer does not match the "iss" claim on the JWT.
167 * @return array<mixed> the token payload, if successful, or false if not.
168 */
169 private function verifyEs256($token, array $certs, $audience = null, $issuer = null)
170 {
171 $this->checkSimpleJwt();
172 $jwkset = new KeySet();
173 foreach ($certs as $cert) {
174 $jwkset->add(KeyFactory::create($cert, 'php'));
175 }
176 // Validate the signature using the key set and ES256 algorithm.
177 $jwt = $this->callSimpleJwtDecode([$token, $jwkset, 'ES256']);
178 $payload = $jwt->getClaims();
179 if ($audience) {
180 if (!isset($payload['aud']) || $payload['aud'] != $audience) {
181 throw new UnexpectedValueException('Audience does not match');
182 }
183 }
184 // @see https://cloud.google.com/iap/docs/signed-headers-howto#verifying_the_jwt_payload
185 $issuer = $issuer ?: self::IAP_ISSUER;
186 if (!isset($payload['iss']) || $payload['iss'] !== $issuer) {
187 throw new UnexpectedValueException('Issuer does not match');
188 }
189 return $payload;
190 }
191 /**
192 * Verifies an RS256-signed JWT.
193 *
194 * @param string $token The JSON Web Token to be verified.
195 * @param array<mixed> $certs Certificate array according to the JWK spec (see
196 * https://tools.ietf.org/html/rfc7517).
197 * @param string|null $audience If set, returns false if the provided
198 * audience does not match the "aud" claim on the JWT.
199 * @param string|null $issuer If set, returns false if the provided
200 * issuer does not match the "iss" claim on the JWT.
201 * @return array<mixed> the token payload, if successful, or false if not.
202 */
203 private function verifyRs256($token, array $certs, $audience = null, $issuer = null)
204 {
205 $this->checkAndInitializePhpsec();
206 $keys = [];
207 foreach ($certs as $cert) {
208 if (empty($cert['kid'])) {
209 throw new InvalidArgumentException('certs expects "kid" to be set');
210 }
211 if (empty($cert['n']) || empty($cert['e'])) {
212 throw new InvalidArgumentException('RSA certs expects "n" and "e" to be set');
213 }
214 $publicKey = $this->loadPhpsecPublicKey($cert['n'], $cert['e']);
215 // create an array of key IDs to certs for the JWT library
216 $keys[$cert['kid']] = new Key($publicKey, 'RS256');
217 }
218 $payload = $this->callJwtStatic('decode', [$token, $keys]);
219 if ($audience) {
220 if (!\property_exists($payload, 'aud') || $payload->aud != $audience) {
221 throw new UnexpectedValueException('Audience does not match');
222 }
223 }
224 // support HTTP and HTTPS issuers
225 // @see https://developers.google.com/identity/sign-in/web/backend-auth
226 $issuers = $issuer ? [$issuer] : [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS];
227 if (!isset($payload->iss) || !\in_array($payload->iss, $issuers)) {
228 throw new UnexpectedValueException('Issuer does not match');
229 }
230 return (array) $payload;
231 }
232 /**
233 * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
234 * token, if a token isn't provided.
235 *
236 * @param string|array<mixed> $token The token (access token or a refresh token) that should be revoked.
237 * @param array<mixed> $options [optional] Configuration options.
238 * @return bool Returns True if the revocation was successful, otherwise False.
239 */
240 public function revoke($token, array $options = [])
241 {
242 if (\is_array($token)) {
243 if (isset($token['refresh_token'])) {
244 $token = $token['refresh_token'];
245 } else {
246 $token = $token['access_token'];
247 }
248 }
249 $body = Utils::streamFor(\http_build_query(['token' => $token]));
250 $request = new Request('POST', self::OAUTH2_REVOKE_URI, ['Cache-Control' => 'no-store', 'Content-Type' => 'application/x-www-form-urlencoded'], $body);
251 $httpHandler = $this->httpHandler;
252 $response = $httpHandler($request, $options);
253 return $response->getStatusCode() == 200;
254 }
255 /**
256 * Gets federated sign-on certificates to use for verifying identity tokens.
257 * Returns certs as array structure, where keys are key ids, and values
258 * are PEM encoded certificates.
259 *
260 * @param string $location The location from which to retrieve certs.
261 * @param string $cacheKey The key under which to cache the retrieved certs.
262 * @param array<mixed> $options [optional] Configuration options.
263 * @return array<mixed>
264 * @throws InvalidArgumentException If received certs are in an invalid format.
265 */
266 private function getCerts($location, $cacheKey, array $options = [])
267 {
268 $cacheItem = $this->cache->getItem($cacheKey);
269 $certs = $cacheItem ? $cacheItem->get() : null;
270 $expireTime = null;
271 if (!$certs) {
272 list($certs, $expireTime) = $this->retrieveCertsFromLocation($location, $options);
273 }
274 if (!isset($certs['keys'])) {
275 if ($location !== self::IAP_CERT_URL) {
276 throw new InvalidArgumentException('federated sign-on certs expects "keys" to be set');
277 }
278 throw new InvalidArgumentException('certs expects "keys" to be set');
279 }
280 // Push caching off until after verifying certs are in a valid format.
281 // Don't want to cache bad data.
282 if ($expireTime) {
283 $cacheItem->expiresAt(new DateTime($expireTime));
284 $cacheItem->set($certs);
285 $this->cache->save($cacheItem);
286 }
287 return $certs['keys'];
288 }
289 /**
290 * Retrieve and cache a certificates file.
291 *
292 * @param string $url location
293 * @param array<mixed> $options [optional] Configuration options.
294 * @return array{array<mixed>, string}
295 * @throws InvalidArgumentException If certs could not be retrieved from a local file.
296 * @throws RuntimeException If certs could not be retrieved from a remote location.
297 */
298 private function retrieveCertsFromLocation($url, array $options = [])
299 {
300 // If we're retrieving a local file, just grab it.
301 $expireTime = '+1 hour';
302 if (\strpos($url, 'http') !== 0) {
303 if (!\file_exists($url)) {
304 throw new InvalidArgumentException(\sprintf('Failed to retrieve verification certificates from path: %s.', $url));
305 }
306 return [\json_decode((string) \file_get_contents($url), \true), $expireTime];
307 }
308 $httpHandler = $this->httpHandler;
309 $response = $httpHandler(new Request('GET', $url), $options);
310 if ($response->getStatusCode() == 200) {
311 if ($cacheControl = $response->getHeaderLine('Cache-Control')) {
312 \array_map(function ($value) use(&$expireTime) {
313 list($key, $value) = \explode('=', $value) + [null, null];
314 if (\trim($key) == 'max-age') {
315 $expireTime = '+' . $value . ' seconds';
316 }
317 }, \explode(',', $cacheControl));
318 }
319 return [\json_decode((string) $response->getBody(), \true), $expireTime];
320 }
321 throw new RuntimeException(\sprintf('Failed to retrieve verification certificates: "%s".', $response->getBody()->getContents()), $response->getStatusCode());
322 }
323 /**
324 * @return void
325 */
326 private function checkAndInitializePhpsec()
327 {
328 if (!\class_exists(RSA::class)) {
329 throw new RuntimeException('Please require phpseclib/phpseclib v3 to use this utility.');
330 }
331 }
332 /**
333 * @return string
334 * @throws TypeError If the key cannot be initialized to a string.
335 */
336 private function loadPhpsecPublicKey(string $modulus, string $exponent) : string
337 {
338 $key = PublicKeyLoader::load(['n' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [$modulus]), 256), 'e' => new BigInteger($this->callJwtStatic('urlsafeB64Decode', [$exponent]), 256)]);
339 $formattedPublicKey = $key->toString('PKCS8');
340 if (!\is_string($formattedPublicKey)) {
341 throw new TypeError('Failed to initialize the key');
342 }
343 return $formattedPublicKey;
344 }
345 /**
346 * @return void
347 */
348 private function checkSimpleJwt()
349 {
350 // @codeCoverageIgnoreStart
351 if (!\class_exists(SimpleJwt::class)) {
352 throw new RuntimeException('Please require kelvinmo/simplejwt ^0.2 to use this utility.');
353 }
354 // @codeCoverageIgnoreEnd
355 }
356 /**
357 * Provide a hook to mock calls to the JWT static methods.
358 *
359 * @param string $method
360 * @param array<mixed> $args
361 * @return mixed
362 */
363 protected function callJwtStatic($method, array $args = [])
364 {
365 return \call_user_func_array([JWT::class, $method], $args);
366 // @phpstan-ignore-line
367 }
368 /**
369 * Provide a hook to mock calls to the JWT static methods.
370 *
371 * @param array<mixed> $args
372 * @return mixed
373 */
374 protected function callSimpleJwtDecode(array $args = [])
375 {
376 return \call_user_func_array([SimpleJwt::class, 'decode'], $args);
377 }
378 /**
379 * Generate a cache key based on the cert location using sha1 with the
380 * exception of using "federated_signon_certs_v3" to preserve BC.
381 *
382 * @param string $certsLocation
383 * @return string
384 */
385 private function getCacheKeyFromCertLocation($certsLocation)
386 {
387 $key = $certsLocation === self::FEDERATED_SIGNON_CERT_URL ? 'federated_signon_certs_v3' : \sha1($certsLocation);
388 return 'google_auth_certs_cache|' . $key;
389 }
390 }
391