PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / google / auth / src / Credentials / GCECredentials.php
ameliabooking / vendor / google / auth / src / Credentials Last commit date
AppIdentityCredentials.php 6 months ago ExternalAccountCredentials.php 6 months ago GCECredentials.php 6 months ago IAMCredentials.php 6 months ago ImpersonatedServiceAccountCredentials.php 6 months ago InsecureCredentials.php 6 months ago ServiceAccountCredentials.php 6 months ago ServiceAccountJwtAccessCredentials.php 6 months ago UserRefreshCredentials.php 6 months ago
GCECredentials.php
623 lines
1 <?php
2 /*
3 * Copyright 2015 Google Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 namespace AmeliaVendor\Google\Auth\Credentials;
19
20 use AmeliaVendor\Google\Auth\CredentialsLoader;
21 use AmeliaVendor\Google\Auth\GetQuotaProjectInterface;
22 use AmeliaVendor\Google\Auth\HttpHandler\HttpClientCache;
23 use AmeliaVendor\Google\Auth\HttpHandler\HttpHandlerFactory;
24 use AmeliaVendor\Google\Auth\Iam;
25 use AmeliaVendor\Google\Auth\IamSignerTrait;
26 use AmeliaVendor\Google\Auth\ProjectIdProviderInterface;
27 use AmeliaVendor\Google\Auth\SignBlobInterface;
28 use AmeliaVendor\GuzzleHttp\Exception\ClientException;
29 use AmeliaVendor\GuzzleHttp\Exception\ConnectException;
30 use AmeliaVendor\GuzzleHttp\Exception\RequestException;
31 use AmeliaVendor\GuzzleHttp\Exception\ServerException;
32 use AmeliaVendor\GuzzleHttp\Psr7\Request;
33 use InvalidArgumentException;
34
35 /**
36 * GCECredentials supports authorization on Google Compute Engine.
37 *
38 * It can be used to authorize requests using the AuthTokenMiddleware, but will
39 * only succeed if being run on GCE:
40 *
41 * use AmeliaVendor\Google\Auth\Credentials\GCECredentials;
42 * use AmeliaVendor\Google\Auth\Middleware\AuthTokenMiddleware;
43 * use AmeliaVendor\GuzzleHttp\Client;
44 * use AmeliaVendor\GuzzleHttp\HandlerStack;
45 *
46 * $gce = new GCECredentials();
47 * $middleware = new AuthTokenMiddleware($gce);
48 * $stack = HandlerStack::create();
49 * $stack->push($middleware);
50 *
51 * $client = new Client([
52 * 'handler' => $stack,
53 * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
54 * 'auth' => 'google_auth'
55 * ]);
56 *
57 * $res = $client->get('myproject/taskqueues/myqueue');
58 */
59 class GCECredentials extends CredentialsLoader implements
60 SignBlobInterface,
61 ProjectIdProviderInterface,
62 GetQuotaProjectInterface
63 {
64 use IamSignerTrait;
65
66 // phpcs:disable
67 const cacheKey = 'GOOGLE_AUTH_PHP_GCE';
68 // phpcs:enable
69
70 /**
71 * The metadata IP address on appengine instances.
72 *
73 * The IP is used instead of the domain 'metadata' to avoid slow responses
74 * when not on Compute Engine.
75 */
76 const METADATA_IP = '169.254.169.254';
77
78 /**
79 * The metadata path of the default token.
80 */
81 const TOKEN_URI_PATH = 'v1/instance/service-accounts/default/token';
82
83 /**
84 * The metadata path of the default id token.
85 */
86 const ID_TOKEN_URI_PATH = 'v1/instance/service-accounts/default/identity';
87
88 /**
89 * The metadata path of the client ID.
90 */
91 const CLIENT_ID_URI_PATH = 'v1/instance/service-accounts/default/email';
92
93 /**
94 * The metadata path of the project ID.
95 */
96 const PROJECT_ID_URI_PATH = 'v1/project/project-id';
97
98 /**
99 * The metadata path of the project ID.
100 */
101 const UNIVERSE_DOMAIN_URI_PATH = 'v1/universe/universe_domain';
102
103 /**
104 * The header whose presence indicates GCE presence.
105 */
106 const FLAVOR_HEADER = 'Metadata-Flavor';
107
108 /**
109 * The Linux file which contains the product name.
110 */
111 private const GKE_PRODUCT_NAME_FILE = '/sys/class/dmi/id/product_name';
112
113 /**
114 * Note: the explicit `timeout` and `tries` below is a workaround. The underlying
115 * issue is that resolving an unknown host on some networks will take
116 * 20-30 seconds; making this timeout short fixes the issue, but
117 * could lead to false negatives in the event that we are on GCE, but
118 * the metadata resolution was particularly slow. The latter case is
119 * "unlikely" since the expected 4-nines time is about 0.5 seconds.
120 * This allows us to limit the total ping maximum timeout to 1.5 seconds
121 * for developer desktop scenarios.
122 */
123 const MAX_COMPUTE_PING_TRIES = 3;
124 const COMPUTE_PING_CONNECTION_TIMEOUT_S = 0.5;
125
126 /**
127 * Flag used to ensure that the onGCE test is only done once;.
128 *
129 * @var bool
130 */
131 private $hasCheckedOnGce = false;
132
133 /**
134 * Flag that stores the value of the onGCE check.
135 *
136 * @var bool
137 */
138 private $isOnGce = false;
139
140 /**
141 * Result of fetchAuthToken.
142 *
143 * @var array<mixed>
144 */
145 protected $lastReceivedToken;
146
147 /**
148 * @var string|null
149 */
150 private $clientName;
151
152 /**
153 * @var string|null
154 */
155 private $projectId;
156
157 /**
158 * @var string
159 */
160 private $tokenUri;
161
162 /**
163 * @var string
164 */
165 private $targetAudience;
166
167 /**
168 * @var string|null
169 */
170 private $quotaProject;
171
172 /**
173 * @var string|null
174 */
175 private $serviceAccountIdentity;
176
177 /**
178 * @var string
179 */
180 private ?string $universeDomain;
181
182 /**
183 * @param Iam $iam [optional] An IAM instance.
184 * @param string|string[] $scope [optional] the scope of the access request,
185 * expressed either as an array or as a space-delimited string.
186 * @param string $targetAudience [optional] The audience for the ID token.
187 * @param string $quotaProject [optional] Specifies a project to bill for access
188 * charges associated with the request.
189 * @param string $serviceAccountIdentity [optional] Specify a service
190 * account identity name to use instead of "default".
191 * @param string $universeDomain [optional] Specify a universe domain to use
192 * instead of fetching one from the metadata server.
193 */
194 public function __construct(
195 ?Iam $iam = null,
196 $scope = null,
197 $targetAudience = null,
198 $quotaProject = null,
199 $serviceAccountIdentity = null,
200 ?string $universeDomain = null
201 ) {
202 $this->iam = $iam;
203
204 if ($scope && $targetAudience) {
205 throw new InvalidArgumentException(
206 'Scope and targetAudience cannot both be supplied'
207 );
208 }
209
210 $tokenUri = self::getTokenUri($serviceAccountIdentity);
211 if ($scope) {
212 if (is_string($scope)) {
213 $scope = explode(' ', $scope);
214 }
215
216 $scope = implode(',', $scope);
217
218 $tokenUri = $tokenUri . '?scopes=' . $scope;
219 } elseif ($targetAudience) {
220 $tokenUri = self::getIdTokenUri($serviceAccountIdentity);
221 $tokenUri = $tokenUri . '?audience=' . $targetAudience;
222 $this->targetAudience = $targetAudience;
223 }
224
225 $this->tokenUri = $tokenUri;
226 $this->quotaProject = $quotaProject;
227 $this->serviceAccountIdentity = $serviceAccountIdentity;
228 $this->universeDomain = $universeDomain;
229 }
230
231 /**
232 * The full uri for accessing the default token.
233 *
234 * @param string $serviceAccountIdentity [optional] Specify a service
235 * account identity name to use instead of "default".
236 * @return string
237 */
238 public static function getTokenUri($serviceAccountIdentity = null)
239 {
240 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
241 $base .= self::TOKEN_URI_PATH;
242
243 if ($serviceAccountIdentity) {
244 return str_replace(
245 '/default/',
246 '/' . $serviceAccountIdentity . '/',
247 $base
248 );
249 }
250 return $base;
251 }
252
253 /**
254 * The full uri for accessing the default service account.
255 *
256 * @param string $serviceAccountIdentity [optional] Specify a service
257 * account identity name to use instead of "default".
258 * @return string
259 */
260 public static function getClientNameUri($serviceAccountIdentity = null)
261 {
262 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
263 $base .= self::CLIENT_ID_URI_PATH;
264
265 if ($serviceAccountIdentity) {
266 return str_replace(
267 '/default/',
268 '/' . $serviceAccountIdentity . '/',
269 $base
270 );
271 }
272
273 return $base;
274 }
275
276 /**
277 * The full uri for accesesing the default identity token.
278 *
279 * @param string $serviceAccountIdentity [optional] Specify a service
280 * account identity name to use instead of "default".
281 * @return string
282 */
283 private static function getIdTokenUri($serviceAccountIdentity = null)
284 {
285 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
286 $base .= self::ID_TOKEN_URI_PATH;
287
288 if ($serviceAccountIdentity) {
289 return str_replace(
290 '/default/',
291 '/' . $serviceAccountIdentity . '/',
292 $base
293 );
294 }
295
296 return $base;
297 }
298
299 /**
300 * The full uri for accessing the default project ID.
301 *
302 * @return string
303 */
304 private static function getProjectIdUri()
305 {
306 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
307
308 return $base . self::PROJECT_ID_URI_PATH;
309 }
310
311 /**
312 * The full uri for accessing the default universe domain.
313 *
314 * @return string
315 */
316 private static function getUniverseDomainUri()
317 {
318 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
319
320 return $base . self::UNIVERSE_DOMAIN_URI_PATH;
321 }
322
323 /**
324 * Determines if this an App Engine Flexible instance, by accessing the
325 * GAE_INSTANCE environment variable.
326 *
327 * @return bool true if this an App Engine Flexible Instance, false otherwise
328 */
329 public static function onAppEngineFlexible()
330 {
331 return substr((string) getenv('GAE_INSTANCE'), 0, 4) === 'aef-';
332 }
333
334 /**
335 * Determines if this a GCE instance, by accessing the expected metadata
336 * host.
337 * If $httpHandler is not specified a the default HttpHandler is used.
338 *
339 * @param callable $httpHandler callback which delivers psr7 request
340 * @return bool True if this a GCEInstance, false otherwise
341 */
342 public static function onGce(?callable $httpHandler = null)
343 {
344 $httpHandler = $httpHandler
345 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
346
347 $checkUri = 'http://' . self::METADATA_IP;
348 for ($i = 1; $i <= self::MAX_COMPUTE_PING_TRIES; $i++) {
349 try {
350 // Comment from: oauth2client/client.py
351 //
352 // Note: the explicit `timeout` below is a workaround. The underlying
353 // issue is that resolving an unknown host on some networks will take
354 // 20-30 seconds; making this timeout short fixes the issue, but
355 // could lead to false negatives in the event that we are on GCE, but
356 // the metadata resolution was particularly slow. The latter case is
357 // "unlikely".
358 $resp = $httpHandler(
359 new Request(
360 'GET',
361 $checkUri,
362 [self::FLAVOR_HEADER => 'AmeliaVendor\Google']
363 ),
364 ['timeout' => self::COMPUTE_PING_CONNECTION_TIMEOUT_S]
365 );
366
367 return $resp->getHeaderLine(self::FLAVOR_HEADER) == 'AmeliaVendor\Google';
368 } catch (ClientException $e) {
369 } catch (ServerException $e) {
370 } catch (RequestException $e) {
371 } catch (ConnectException $e) {
372 }
373 }
374
375 if (PHP_OS === 'Windows') {
376 // @TODO: implement GCE residency detection on Windows
377 return false;
378 }
379
380 // Detect GCE residency on Linux
381 return self::detectResidencyLinux(self::GKE_PRODUCT_NAME_FILE);
382 }
383
384 private static function detectResidencyLinux(string $productNameFile): bool
385 {
386 if (file_exists($productNameFile)) {
387 $productName = trim((string) file_get_contents($productNameFile));
388 return 0 === strpos($productName, 'AmeliaVendor\Google');
389 }
390 return false;
391 }
392
393 /**
394 * Implements FetchAuthTokenInterface#fetchAuthToken.
395 *
396 * Fetches the auth tokens from the GCE metadata host if it is available.
397 * If $httpHandler is not specified a the default HttpHandler is used.
398 *
399 * @param callable $httpHandler callback which delivers psr7 request
400 *
401 * @return array<mixed> {
402 * A set of auth related metadata, based on the token type.
403 *
404 * @type string $access_token for access tokens
405 * @type int $expires_in for access tokens
406 * @type string $token_type for access tokens
407 * @type string $id_token for ID tokens
408 * }
409 * @throws \Exception
410 */
411 public function fetchAuthToken(?callable $httpHandler = null)
412 {
413 $httpHandler = $httpHandler
414 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
415
416 if (!$this->hasCheckedOnGce) {
417 $this->isOnGce = self::onGce($httpHandler);
418 $this->hasCheckedOnGce = true;
419 }
420 if (!$this->isOnGce) {
421 return []; // return an empty array with no access token
422 }
423
424 $response = $this->getFromMetadata($httpHandler, $this->tokenUri);
425
426 if ($this->targetAudience) {
427 return $this->lastReceivedToken = ['id_token' => $response];
428 }
429
430 if (null === $json = json_decode($response, true)) {
431 throw new \Exception('Invalid JSON response');
432 }
433
434 $json['expires_at'] = time() + $json['expires_in'];
435
436 // store this so we can retrieve it later
437 $this->lastReceivedToken = $json;
438
439 return $json;
440 }
441
442 /**
443 * @return string
444 */
445 public function getCacheKey()
446 {
447 return self::cacheKey;
448 }
449
450 /**
451 * @return array<mixed>|null
452 */
453 public function getLastReceivedToken()
454 {
455 if ($this->lastReceivedToken) {
456 if (array_key_exists('id_token', $this->lastReceivedToken)) {
457 return $this->lastReceivedToken;
458 }
459
460 return [
461 'access_token' => $this->lastReceivedToken['access_token'],
462 'expires_at' => $this->lastReceivedToken['expires_at']
463 ];
464 }
465
466 return null;
467 }
468
469 /**
470 * Get the client name from GCE metadata.
471 *
472 * Subsequent calls will return a cached value.
473 *
474 * @param callable $httpHandler callback which delivers psr7 request
475 * @return string
476 */
477 public function getClientName(?callable $httpHandler = null)
478 {
479 if ($this->clientName) {
480 return $this->clientName;
481 }
482
483 $httpHandler = $httpHandler
484 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
485
486 if (!$this->hasCheckedOnGce) {
487 $this->isOnGce = self::onGce($httpHandler);
488 $this->hasCheckedOnGce = true;
489 }
490
491 if (!$this->isOnGce) {
492 return '';
493 }
494
495 $this->clientName = $this->getFromMetadata(
496 $httpHandler,
497 self::getClientNameUri($this->serviceAccountIdentity)
498 );
499
500 return $this->clientName;
501 }
502
503 /**
504 * Fetch the default Project ID from compute engine.
505 *
506 * Returns null if called outside GCE.
507 *
508 * @param callable $httpHandler Callback which delivers psr7 request
509 * @return string|null
510 */
511 public function getProjectId(?callable $httpHandler = null)
512 {
513 if ($this->projectId) {
514 return $this->projectId;
515 }
516
517 $httpHandler = $httpHandler
518 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
519
520 if (!$this->hasCheckedOnGce) {
521 $this->isOnGce = self::onGce($httpHandler);
522 $this->hasCheckedOnGce = true;
523 }
524
525 if (!$this->isOnGce) {
526 return null;
527 }
528
529 $this->projectId = $this->getFromMetadata($httpHandler, self::getProjectIdUri());
530 return $this->projectId;
531 }
532
533 /**
534 * Fetch the default universe domain from the metadata server.
535 *
536 * @param callable $httpHandler Callback which delivers psr7 request
537 * @return string
538 */
539 public function getUniverseDomain(?callable $httpHandler = null): string
540 {
541 if (null !== $this->universeDomain) {
542 return $this->universeDomain;
543 }
544
545 $httpHandler = $httpHandler
546 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
547
548 if (!$this->hasCheckedOnGce) {
549 $this->isOnGce = self::onGce($httpHandler);
550 $this->hasCheckedOnGce = true;
551 }
552
553 try {
554 $this->universeDomain = $this->getFromMetadata(
555 $httpHandler,
556 self::getUniverseDomainUri()
557 );
558 } catch (ClientException $e) {
559 // If the metadata server exists, but returns a 404 for the universe domain, the auth
560 // libraries should safely assume this is an older metadata server running in GCU, and
561 // should return the default universe domain.
562 if (!$e->hasResponse() || 404 != $e->getResponse()->getStatusCode()) {
563 throw $e;
564 }
565 $this->universeDomain = self::DEFAULT_UNIVERSE_DOMAIN;
566 }
567
568 // We expect in some cases the metadata server will return an empty string for the universe
569 // domain. In this case, the auth library MUST return the default universe domain.
570 if ('' === $this->universeDomain) {
571 $this->universeDomain = self::DEFAULT_UNIVERSE_DOMAIN;
572 }
573
574 return $this->universeDomain;
575 }
576
577 /**
578 * Fetch the value of a GCE metadata server URI.
579 *
580 * @param callable $httpHandler An HTTP Handler to deliver PSR7 requests.
581 * @param string $uri The metadata URI.
582 * @return string
583 */
584 private function getFromMetadata(callable $httpHandler, $uri)
585 {
586 $resp = $httpHandler(
587 new Request(
588 'GET',
589 $uri,
590 [self::FLAVOR_HEADER => 'AmeliaVendor\Google']
591 )
592 );
593
594 return (string) $resp->getBody();
595 }
596
597 /**
598 * Get the quota project used for this API request
599 *
600 * @return string|null
601 */
602 public function getQuotaProject()
603 {
604 return $this->quotaProject;
605 }
606
607 /**
608 * Set whether or not we've already checked the GCE environment.
609 *
610 * @param bool $isOnGce
611 *
612 * @return void
613 */
614 public function setIsOnGce($isOnGce)
615 {
616 // Implicitly set hasCheckedGce to true
617 $this->hasCheckedOnGce = true;
618
619 // Set isOnGce
620 $this->isOnGce = $isOnGce;
621 }
622 }
623