PluginProbe
WP-Stateless – Google Cloud Storage / 3.0.3
WP-Stateless – Google Cloud Storage v3.0.3
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 / vendor / google / auth / src / Credentials / GCECredentials.php

GCECredentials.php in WP-Stateless – Google Cloud Storage 3.0.3, at lib/Google/vendor/google/auth/src/Credentials/GCECredentials.php

486 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 Google\Auth\Credentials;
19
20 use Google\Auth\CredentialsLoader;
21 use Google\Auth\GetQuotaProjectInterface;
22 use Google\Auth\HttpHandler\HttpClientCache;
23 use Google\Auth\HttpHandler\HttpHandlerFactory;
24 use Google\Auth\Iam;
25 use Google\Auth\ProjectIdProviderInterface;
26 use Google\Auth\SignBlobInterface;
27 use GuzzleHttp\Exception\ClientException;
28 use GuzzleHttp\Exception\ConnectException;
29 use GuzzleHttp\Exception\RequestException;
30 use GuzzleHttp\Exception\ServerException;
31 use GuzzleHttp\Psr7\Request;
32 use InvalidArgumentException;
33
34 /**
35 * GCECredentials supports authorization on Google Compute Engine.
36 *
37 * It can be used to authorize requests using the AuthTokenMiddleware, but will
38 * only succeed if being run on GCE:
39 *
40 * use Google\Auth\Credentials\GCECredentials;
41 * use Google\Auth\Middleware\AuthTokenMiddleware;
42 * use GuzzleHttp\Client;
43 * use GuzzleHttp\HandlerStack;
44 *
45 * $gce = new GCECredentials();
46 * $middleware = new AuthTokenMiddleware($gce);
47 * $stack = HandlerStack::create();
48 * $stack->push($middleware);
49 *
50 * $client = new Client([
51 * 'handler' => $stack,
52 * 'base_uri' => 'https://www.googleapis.com/taskqueue/v1beta2/projects/',
53 * 'auth' => 'google_auth'
54 * ]);
55 *
56 * $res = $client->get('myproject/taskqueues/myqueue');
57 */
58 class GCECredentials extends CredentialsLoader implements
59 SignBlobInterface,
60 ProjectIdProviderInterface,
61 GetQuotaProjectInterface
62 {
63 // phpcs:disable
64 const cacheKey = 'GOOGLE_AUTH_PHP_GCE';
65 // phpcs:enable
66
67 /**
68 * The metadata IP address on appengine instances.
69 *
70 * The IP is used instead of the domain 'metadata' to avoid slow responses
71 * when not on Compute Engine.
72 */
73 const METADATA_IP = '169.254.169.254';
74
75 /**
76 * The metadata path of the default token.
77 */
78 const TOKEN_URI_PATH = 'v1/instance/service-accounts/default/token';
79
80 /**
81 * The metadata path of the default id token.
82 */
83 const ID_TOKEN_URI_PATH = 'v1/instance/service-accounts/default/identity';
84
85 /**
86 * The metadata path of the client ID.
87 */
88 const CLIENT_ID_URI_PATH = 'v1/instance/service-accounts/default/email';
89
90 /**
91 * The metadata path of the project ID.
92 */
93 const PROJECT_ID_URI_PATH = 'v1/project/project-id';
94
95 /**
96 * The header whose presence indicates GCE presence.
97 */
98 const FLAVOR_HEADER = 'Metadata-Flavor';
99
100 /**
101 * Note: the explicit `timeout` and `tries` below is a workaround. The underlying
102 * issue is that resolving an unknown host on some networks will take
103 * 20-30 seconds; making this timeout short fixes the issue, but
104 * could lead to false negatives in the event that we are on GCE, but
105 * the metadata resolution was particularly slow. The latter case is
106 * "unlikely" since the expected 4-nines time is about 0.5 seconds.
107 * This allows us to limit the total ping maximum timeout to 1.5 seconds
108 * for developer desktop scenarios.
109 */
110 const MAX_COMPUTE_PING_TRIES = 3;
111 const COMPUTE_PING_CONNECTION_TIMEOUT_S = 0.5;
112
113 /**
114 * Flag used to ensure that the onGCE test is only done once;.
115 *
116 * @var bool
117 */
118 private $hasCheckedOnGce = false;
119
120 /**
121 * Flag that stores the value of the onGCE check.
122 *
123 * @var bool
124 */
125 private $isOnGce = false;
126
127 /**
128 * Result of fetchAuthToken.
129 */
130 protected $lastReceivedToken;
131
132 /**
133 * @var string|null
134 */
135 private $clientName;
136
137 /**
138 * @var string|null
139 */
140 private $projectId;
141
142 /**
143 * @var Iam|null
144 */
145 private $iam;
146
147 /**
148 * @var string
149 */
150 private $tokenUri;
151
152 /**
153 * @var string
154 */
155 private $targetAudience;
156
157 /**
158 * @var string|null
159 */
160 private $quotaProject;
161
162 /**
163 * @param Iam $iam [optional] An IAM instance.
164 * @param string|array $scope [optional] the scope of the access request,
165 * expressed either as an array or as a space-delimited string.
166 * @param string $targetAudience [optional] The audience for the ID token.
167 * @param string $quotaProject [optional] Specifies a project to bill for access
168 * charges associated with the request.
169 */
170 public function __construct(Iam $iam = null, $scope = null, $targetAudience = null, $quotaProject = null)
171 {
172 $this->iam = $iam;
173
174 if ($scope && $targetAudience) {
175 throw new InvalidArgumentException(
176 'Scope and targetAudience cannot both be supplied'
177 );
178 }
179
180 $tokenUri = self::getTokenUri();
181 if ($scope) {
182 if (is_string($scope)) {
183 $scope = explode(' ', $scope);
184 }
185
186 $scope = implode(',', $scope);
187
188 $tokenUri = $tokenUri . '?scopes='. $scope;
189 } elseif ($targetAudience) {
190 $tokenUri = sprintf(
191 'http://%s/computeMetadata/%s?audience=%s',
192 self::METADATA_IP,
193 self::ID_TOKEN_URI_PATH,
194 $targetAudience
195 );
196 $this->targetAudience = $targetAudience;
197 }
198
199 $this->tokenUri = $tokenUri;
200 $this->quotaProject = $quotaProject;
201 }
202
203 /**
204 * The full uri for accessing the default token.
205 *
206 * @return string
207 */
208 public static function getTokenUri()
209 {
210 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
211
212 return $base . self::TOKEN_URI_PATH;
213 }
214
215 /**
216 * The full uri for accessing the default service account.
217 *
218 * @return string
219 */
220 public static function getClientNameUri()
221 {
222 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
223
224 return $base . self::CLIENT_ID_URI_PATH;
225 }
226
227 /**
228 * The full uri for accessing the default project ID.
229 *
230 * @return string
231 */
232 private static function getProjectIdUri()
233 {
234 $base = 'http://' . self::METADATA_IP . '/computeMetadata/';
235
236 return $base . self::PROJECT_ID_URI_PATH;
237 }
238
239 /**
240 * Determines if this an App Engine Flexible instance, by accessing the
241 * GAE_INSTANCE environment variable.
242 *
243 * @return bool true if this an App Engine Flexible Instance, false otherwise
244 */
245 public static function onAppEngineFlexible()
246 {
247 return substr(getenv('GAE_INSTANCE'), 0, 4) === 'aef-';
248 }
249
250 /**
251 * Determines if this a GCE instance, by accessing the expected metadata
252 * host.
253 * If $httpHandler is not specified a the default HttpHandler is used.
254 *
255 * @param callable $httpHandler callback which delivers psr7 request
256 * @return bool True if this a GCEInstance, false otherwise
257 */
258 public static function onGce(callable $httpHandler = null)
259 {
260 $httpHandler = $httpHandler
261 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
262
263 $checkUri = 'http://' . self::METADATA_IP;
264 for ($i = 1; $i <= self::MAX_COMPUTE_PING_TRIES; $i++) {
265 try {
266 // Comment from: oauth2client/client.py
267 //
268 // Note: the explicit `timeout` below is a workaround. The underlying
269 // issue is that resolving an unknown host on some networks will take
270 // 20-30 seconds; making this timeout short fixes the issue, but
271 // could lead to false negatives in the event that we are on GCE, but
272 // the metadata resolution was particularly slow. The latter case is
273 // "unlikely".
274 $resp = $httpHandler(
275 new Request(
276 'GET',
277 $checkUri,
278 [self::FLAVOR_HEADER => 'Google']
279 ),
280 ['timeout' => self::COMPUTE_PING_CONNECTION_TIMEOUT_S]
281 );
282
283 return $resp->getHeaderLine(self::FLAVOR_HEADER) == 'Google';
284 } catch (ClientException $e) {
285 } catch (ServerException $e) {
286 } catch (RequestException $e) {
287 } catch (ConnectException $e) {
288 }
289 }
290 return false;
291 }
292
293 /**
294 * Implements FetchAuthTokenInterface#fetchAuthToken.
295 *
296 * Fetches the auth tokens from the GCE metadata host if it is available.
297 * If $httpHandler is not specified a the default HttpHandler is used.
298 *
299 * @param callable $httpHandler callback which delivers psr7 request
300 *
301 * @return array A set of auth related metadata, based on the token type.
302 *
303 * Access tokens have the following keys:
304 * - access_token (string)
305 * - expires_in (int)
306 * - token_type (string)
307 * ID tokens have the following keys:
308 * - id_token (string)
309 *
310 * @throws \Exception
311 */
312 public function fetchAuthToken(callable $httpHandler = null)
313 {
314 $httpHandler = $httpHandler
315 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
316
317 if (!$this->hasCheckedOnGce) {
318 $this->isOnGce = self::onGce($httpHandler);
319 $this->hasCheckedOnGce = true;
320 }
321 if (!$this->isOnGce) {
322 return array(); // return an empty array with no access token
323 }
324
325 $response = $this->getFromMetadata($httpHandler, $this->tokenUri);
326
327 if ($this->targetAudience) {
328 return ['id_token' => $response];
329 }
330
331 if (null === $json = json_decode($response, true)) {
332 throw new \Exception('Invalid JSON response');
333 }
334
335 // store this so we can retrieve it later
336 $this->lastReceivedToken = $json;
337 $this->lastReceivedToken['expires_at'] = time() + $json['expires_in'];
338
339 return $json;
340 }
341
342 /**
343 * @return string
344 */
345 public function getCacheKey()
346 {
347 return self::cacheKey;
348 }
349
350 /**
351 * @return array|null
352 */
353 public function getLastReceivedToken()
354 {
355 if ($this->lastReceivedToken) {
356 return [
357 'access_token' => $this->lastReceivedToken['access_token'],
358 'expires_at' => $this->lastReceivedToken['expires_at'],
359 ];
360 }
361
362 return null;
363 }
364
365 /**
366 * Get the client name from GCE metadata.
367 *
368 * Subsequent calls will return a cached value.
369 *
370 * @param callable $httpHandler callback which delivers psr7 request
371 * @return string
372 */
373 public function getClientName(callable $httpHandler = null)
374 {
375 if ($this->clientName) {
376 return $this->clientName;
377 }
378
379 $httpHandler = $httpHandler
380 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
381
382 if (!$this->hasCheckedOnGce) {
383 $this->isOnGce = self::onGce($httpHandler);
384 $this->hasCheckedOnGce = true;
385 }
386
387 if (!$this->isOnGce) {
388 return '';
389 }
390
391 $this->clientName = $this->getFromMetadata($httpHandler, self::getClientNameUri());
392
393 return $this->clientName;
394 }
395
396 /**
397 * Sign a string using the default service account private key.
398 *
399 * This implementation uses IAM's signBlob API.
400 *
401 * @see https://cloud.google.com/iam/credentials/reference/rest/v1/projects.serviceAccounts/signBlob SignBlob
402 *
403 * @param string $stringToSign The string to sign.
404 * @param bool $forceOpenSsl [optional] Does not apply to this credentials
405 * type.
406 * @return string
407 */
408 public function signBlob($stringToSign, $forceOpenSsl = false)
409 {
410 $httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
411
412 // Providing a signer is useful for testing, but it's undocumented
413 // because it's not something a user would generally need to do.
414 $signer = $this->iam ?: new Iam($httpHandler);
415
416 $email = $this->getClientName($httpHandler);
417
418 $previousToken = $this->getLastReceivedToken();
419 $accessToken = $previousToken
420 ? $previousToken['access_token']
421 : $this->fetchAuthToken($httpHandler)['access_token'];
422
423 return $signer->signBlob($email, $accessToken, $stringToSign);
424 }
425
426 /**
427 * Fetch the default Project ID from compute engine.
428 *
429 * Returns null if called outside GCE.
430 *
431 * @param callable $httpHandler Callback which delivers psr7 request
432 * @return string|null
433 */
434 public function getProjectId(callable $httpHandler = null)
435 {
436 if ($this->projectId) {
437 return $this->projectId;
438 }
439
440 $httpHandler = $httpHandler
441 ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
442
443 if (!$this->hasCheckedOnGce) {
444 $this->isOnGce = self::onGce($httpHandler);
445 $this->hasCheckedOnGce = true;
446 }
447
448 if (!$this->isOnGce) {
449 return null;
450 }
451
452 $this->projectId = $this->getFromMetadata($httpHandler, self::getProjectIdUri());
453 return $this->projectId;
454 }
455
456 /**
457 * Fetch the value of a GCE metadata server URI.
458 *
459 * @param callable $httpHandler An HTTP Handler to deliver PSR7 requests.
460 * @param string $uri The metadata URI.
461 * @return string
462 */
463 private function getFromMetadata(callable $httpHandler, $uri)
464 {
465 $resp = $httpHandler(
466 new Request(
467 'GET',
468 $uri,
469 [self::FLAVOR_HEADER => 'Google']
470 )
471 );
472
473 return (string) $resp->getBody();
474 }
475
476 /**
477 * Get the quota project used for this API request
478 *
479 * @return string|null
480 */
481 public function getQuotaProject()
482 {
483 return $this->quotaProject;
484 }
485 }
486