1# Copyright 2015 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Application default credentials.
16
17Implements application default credentials and project ID detection.
18"""
19
20import io
21import json
22import logging
23import os
24import warnings
25
26from google.auth import environment_vars
27from google.auth import exceptions
28import google.auth.transport._http_client
29
30_LOGGER = logging.getLogger(__name__)
31
32# Valid types accepted for file-based credentials.
33_AUTHORIZED_USER_TYPE = "authorized_user"
34_SERVICE_ACCOUNT_TYPE = "service_account"
35_EXTERNAL_ACCOUNT_TYPE = "external_account"
36_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = "external_account_authorized_user"
37_IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account"
38_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account"
39_VALID_TYPES = (
40 _AUTHORIZED_USER_TYPE,
41 _SERVICE_ACCOUNT_TYPE,
42 _EXTERNAL_ACCOUNT_TYPE,
43 _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE,
44 _IMPERSONATED_SERVICE_ACCOUNT_TYPE,
45 _GDCH_SERVICE_ACCOUNT_TYPE,
46)
47
48# Help message when no credentials can be found.
49_CLOUD_SDK_MISSING_CREDENTIALS = """\
50Your default credentials were not found. To set up Application Default Credentials, \
51see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.\
52"""
53
54# Warning when using Cloud SDK user credentials
55_CLOUD_SDK_CREDENTIALS_WARNING = """\
56Your application has authenticated using end user credentials from Google \
57Cloud SDK without a quota project. You might receive a "quota exceeded" \
58or "API not enabled" error. See the following page for troubleshooting: \
59https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \
60"""
61
62# The subject token type used for AWS external_account credentials.
63_AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request"
64
65
66def _warn_about_problematic_credentials(credentials):
67 """Determines if the credentials are problematic.
68
69 Credentials from the Cloud SDK that are associated with Cloud SDK's project
70 are problematic because they may not have APIs enabled and have limited
71 quota. If this is the case, warn about it.
72 """
73 from google.auth import _cloud_sdk
74
75 if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID:
76 warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING)
77
78
79def load_credentials_from_file(
80 filename, scopes=None, default_scopes=None, quota_project_id=None, request=None
81):
82 """Loads Google credentials from a file.
83
84 The credentials file must be a service account key, stored authorized
85 user credentials, external account credentials, or impersonated service
86 account credentials.
87
88 Args:
89 filename (str): The full path to the credentials file.
90 scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
91 specified, the credentials will automatically be scoped if
92 necessary
93 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
94 Google client library. Use 'scopes' for user-defined scopes.
95 quota_project_id (Optional[str]): The project ID used for
96 quota and billing.
97 request (Optional[google.auth.transport.Request]): An object used to make
98 HTTP requests. This is used to determine the associated project ID
99 for a workload identity pool resource (external account credentials).
100 If not specified, then it will use a
101 google.auth.transport.requests.Request client to make requests.
102
103 Returns:
104 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
105 credentials and the project ID. Authorized user credentials do not
106 have the project ID information. External account credentials project
107 IDs may not always be determined.
108
109 Raises:
110 google.auth.exceptions.DefaultCredentialsError: if the file is in the
111 wrong format or is missing.
112 """
113 if not os.path.exists(filename):
114 raise exceptions.DefaultCredentialsError(
115 "File {} was not found.".format(filename)
116 )
117
118 with io.open(filename, "r") as file_obj:
119 try:
120 info = json.load(file_obj)
121 except ValueError as caught_exc:
122 new_exc = exceptions.DefaultCredentialsError(
123 "File {} is not a valid json file.".format(filename), caught_exc
124 )
125 raise new_exc from caught_exc
126 return _load_credentials_from_info(
127 filename, info, scopes, default_scopes, quota_project_id, request
128 )
129
130
131def load_credentials_from_dict(
132 info, scopes=None, default_scopes=None, quota_project_id=None, request=None
133):
134 """Loads Google credentials from a dict.
135
136 The credentials file must be a service account key, stored authorized
137 user credentials, external account credentials, or impersonated service
138 account credentials.
139
140 Args:
141 info (Dict[str, Any]): A dict object containing the credentials
142 scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
143 specified, the credentials will automatically be scoped if
144 necessary
145 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
146 Google client library. Use 'scopes' for user-defined scopes.
147 quota_project_id (Optional[str]): The project ID used for
148 quota and billing.
149 request (Optional[google.auth.transport.Request]): An object used to make
150 HTTP requests. This is used to determine the associated project ID
151 for a workload identity pool resource (external account credentials).
152 If not specified, then it will use a
153 google.auth.transport.requests.Request client to make requests.
154
155 Returns:
156 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
157 credentials and the project ID. Authorized user credentials do not
158 have the project ID information. External account credentials project
159 IDs may not always be determined.
160
161 Raises:
162 google.auth.exceptions.DefaultCredentialsError: if the file is in the
163 wrong format or is missing.
164 """
165 if not isinstance(info, dict):
166 raise exceptions.DefaultCredentialsError(
167 "info object was of type {} but dict type was expected.".format(type(info))
168 )
169
170 return _load_credentials_from_info(
171 "dict object", info, scopes, default_scopes, quota_project_id, request
172 )
173
174
175def _load_credentials_from_info(
176 filename, info, scopes, default_scopes, quota_project_id, request
177):
178 from google.auth.credentials import CredentialsWithQuotaProject
179
180 credential_type = info.get("type")
181
182 if credential_type == _AUTHORIZED_USER_TYPE:
183 credentials, project_id = _get_authorized_user_credentials(
184 filename, info, scopes
185 )
186
187 elif credential_type == _SERVICE_ACCOUNT_TYPE:
188 credentials, project_id = _get_service_account_credentials(
189 filename, info, scopes, default_scopes
190 )
191
192 elif credential_type == _EXTERNAL_ACCOUNT_TYPE:
193 credentials, project_id = _get_external_account_credentials(
194 info,
195 filename,
196 scopes=scopes,
197 default_scopes=default_scopes,
198 request=request,
199 )
200
201 elif credential_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE:
202 credentials, project_id = _get_external_account_authorized_user_credentials(
203 filename, info, request
204 )
205
206 elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE:
207 credentials, project_id = _get_impersonated_service_account_credentials(
208 filename, info, scopes
209 )
210 elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE:
211 credentials, project_id = _get_gdch_service_account_credentials(filename, info)
212 else:
213 raise exceptions.DefaultCredentialsError(
214 "The file {file} does not have a valid type. "
215 "Type is {type}, expected one of {valid_types}.".format(
216 file=filename, type=credential_type, valid_types=_VALID_TYPES
217 )
218 )
219 if isinstance(credentials, CredentialsWithQuotaProject):
220 credentials = _apply_quota_project_id(credentials, quota_project_id)
221 return credentials, project_id
222
223
224def _get_gcloud_sdk_credentials(quota_project_id=None):
225 """Gets the credentials and project ID from the Cloud SDK."""
226 from google.auth import _cloud_sdk
227
228 _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...")
229
230 # Check if application default credentials exist.
231 credentials_filename = _cloud_sdk.get_application_default_credentials_path()
232
233 if not os.path.isfile(credentials_filename):
234 _LOGGER.debug("Cloud SDK credentials not found on disk; not using them")
235 return None, None
236
237 credentials, project_id = load_credentials_from_file(
238 credentials_filename, quota_project_id=quota_project_id
239 )
240 credentials._cred_file_path = credentials_filename
241
242 if not project_id:
243 project_id = _cloud_sdk.get_project_id()
244
245 return credentials, project_id
246
247
248def _get_explicit_environ_credentials(quota_project_id=None):
249 """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment
250 variable."""
251 from google.auth import _cloud_sdk
252
253 cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path()
254 explicit_file = os.environ.get(environment_vars.CREDENTIALS)
255
256 _LOGGER.debug(
257 "Checking %s for explicit credentials as part of auth process...", explicit_file
258 )
259
260 if explicit_file is not None and explicit_file == cloud_sdk_adc_path:
261 # Cloud sdk flow calls gcloud to fetch project id, so if the explicit
262 # file path is cloud sdk credentials path, then we should fall back
263 # to cloud sdk flow, otherwise project id cannot be obtained.
264 _LOGGER.debug(
265 "Explicit credentials path %s is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...",
266 explicit_file,
267 )
268 return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id)
269
270 if explicit_file is not None:
271 credentials, project_id = load_credentials_from_file(
272 os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id
273 )
274 credentials._cred_file_path = f"{explicit_file} file via the GOOGLE_APPLICATION_CREDENTIALS environment variable"
275
276 return credentials, project_id
277
278 else:
279 return None, None
280
281
282def _get_gae_credentials():
283 """Gets Google App Engine App Identity credentials and project ID."""
284 # If not GAE gen1, prefer the metadata service even if the GAE APIs are
285 # available as per https://google.aip.dev/auth/4115.
286 if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27":
287 return None, None
288
289 # While this library is normally bundled with app_engine, there are
290 # some cases where it's not available, so we tolerate ImportError.
291 try:
292 _LOGGER.debug("Checking for App Engine runtime as part of auth process...")
293 import google.auth.app_engine as app_engine
294 except ImportError:
295 _LOGGER.warning("Import of App Engine auth library failed.")
296 return None, None
297
298 try:
299 credentials = app_engine.Credentials()
300 project_id = app_engine.get_project_id()
301 return credentials, project_id
302 except EnvironmentError:
303 _LOGGER.debug(
304 "No App Engine library was found so cannot authentication via App Engine Identity Credentials."
305 )
306 return None, None
307
308
309def _get_gce_credentials(request=None, quota_project_id=None):
310 """Gets credentials and project ID from the GCE Metadata Service."""
311 # Ping requires a transport, but we want application default credentials
312 # to require no arguments. So, we'll use the _http_client transport which
313 # uses http.client. This is only acceptable because the metadata server
314 # doesn't do SSL and never requires proxies.
315
316 # While this library is normally bundled with compute_engine, there are
317 # some cases where it's not available, so we tolerate ImportError.
318 try:
319 from google.auth import compute_engine
320 from google.auth.compute_engine import _metadata
321 except ImportError:
322 _LOGGER.warning("Import of Compute Engine auth library failed.")
323 return None, None
324
325 if request is None:
326 request = google.auth.transport._http_client.Request()
327
328 if _metadata.is_on_gce(request=request):
329 # Get the project ID.
330 try:
331 project_id = _metadata.get_project_id(request=request)
332 except exceptions.TransportError:
333 project_id = None
334
335 cred = compute_engine.Credentials()
336 cred = _apply_quota_project_id(cred, quota_project_id)
337
338 return cred, project_id
339 else:
340 _LOGGER.warning(
341 "Authentication failed using Compute Engine authentication due to unavailable metadata server."
342 )
343 return None, None
344
345
346def _get_external_account_credentials(
347 info, filename, scopes=None, default_scopes=None, request=None
348):
349 """Loads external account Credentials from the parsed external account info.
350
351 The credentials information must correspond to a supported external account
352 credentials.
353
354 Args:
355 info (Mapping[str, str]): The external account info in Google format.
356 filename (str): The full path to the credentials file.
357 scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If
358 specified, the credentials will automatically be scoped if
359 necessary.
360 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
361 Google client library. Use 'scopes' for user-defined scopes.
362 request (Optional[google.auth.transport.Request]): An object used to make
363 HTTP requests. This is used to determine the associated project ID
364 for a workload identity pool resource (external account credentials).
365 If not specified, then it will use a
366 google.auth.transport.requests.Request client to make requests.
367
368 Returns:
369 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded
370 credentials and the project ID. External account credentials project
371 IDs may not always be determined.
372
373 Raises:
374 google.auth.exceptions.DefaultCredentialsError: if the info dictionary
375 is in the wrong format or is missing required information.
376 """
377 # There are currently 3 types of external_account credentials.
378 if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE:
379 # Check if configuration corresponds to an AWS credentials.
380 from google.auth import aws
381
382 credentials = aws.Credentials.from_info(
383 info, scopes=scopes, default_scopes=default_scopes
384 )
385 elif (
386 info.get("credential_source") is not None
387 and info.get("credential_source").get("executable") is not None
388 ):
389 from google.auth import pluggable
390
391 credentials = pluggable.Credentials.from_info(
392 info, scopes=scopes, default_scopes=default_scopes
393 )
394 else:
395 try:
396 # Check if configuration corresponds to an Identity Pool credentials.
397 from google.auth import identity_pool
398
399 credentials = identity_pool.Credentials.from_info(
400 info, scopes=scopes, default_scopes=default_scopes
401 )
402 except ValueError:
403 # If the configuration is invalid or does not correspond to any
404 # supported external_account credentials, raise an error.
405 raise exceptions.DefaultCredentialsError(
406 "Failed to load external account credentials from {}".format(filename)
407 )
408 if request is None:
409 import google.auth.transport.requests
410
411 request = google.auth.transport.requests.Request()
412
413 return credentials, credentials.get_project_id(request=request)
414
415
416def _get_external_account_authorized_user_credentials(
417 filename, info, scopes=None, default_scopes=None, request=None
418):
419 try:
420 from google.auth import external_account_authorized_user
421
422 credentials = external_account_authorized_user.Credentials.from_info(info)
423 except ValueError:
424 raise exceptions.DefaultCredentialsError(
425 "Failed to load external account authorized user credentials from {}".format(
426 filename
427 )
428 )
429
430 return credentials, None
431
432
433def _get_authorized_user_credentials(filename, info, scopes=None):
434 from google.oauth2 import credentials
435
436 try:
437 credentials = credentials.Credentials.from_authorized_user_info(
438 info, scopes=scopes
439 )
440 except ValueError as caught_exc:
441 msg = "Failed to load authorized user credentials from {}".format(filename)
442 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
443 raise new_exc from caught_exc
444 return credentials, None
445
446
447def _get_service_account_credentials(filename, info, scopes=None, default_scopes=None):
448 from google.oauth2 import service_account
449
450 try:
451 credentials = service_account.Credentials.from_service_account_info(
452 info, scopes=scopes, default_scopes=default_scopes
453 )
454 except ValueError as caught_exc:
455 msg = "Failed to load service account credentials from {}".format(filename)
456 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
457 raise new_exc from caught_exc
458 return credentials, info.get("project_id")
459
460
461def _get_impersonated_service_account_credentials(filename, info, scopes):
462 from google.auth import impersonated_credentials
463
464 try:
465 source_credentials_info = info.get("source_credentials")
466 source_credentials_type = source_credentials_info.get("type")
467 if source_credentials_type == _AUTHORIZED_USER_TYPE:
468 source_credentials, _ = _get_authorized_user_credentials(
469 filename, source_credentials_info
470 )
471 elif source_credentials_type == _SERVICE_ACCOUNT_TYPE:
472 source_credentials, _ = _get_service_account_credentials(
473 filename, source_credentials_info
474 )
475 else:
476 raise exceptions.InvalidType(
477 "source credential of type {} is not supported.".format(
478 source_credentials_type
479 )
480 )
481 impersonation_url = info.get("service_account_impersonation_url")
482 start_index = impersonation_url.rfind("/")
483 end_index = impersonation_url.find(":generateAccessToken")
484 if start_index == -1 or end_index == -1 or start_index > end_index:
485 raise exceptions.InvalidValue(
486 "Cannot extract target principal from {}".format(impersonation_url)
487 )
488 target_principal = impersonation_url[start_index + 1 : end_index]
489 delegates = info.get("delegates")
490 quota_project_id = info.get("quota_project_id")
491 credentials = impersonated_credentials.Credentials(
492 source_credentials,
493 target_principal,
494 scopes,
495 delegates,
496 quota_project_id=quota_project_id,
497 )
498 except ValueError as caught_exc:
499 msg = "Failed to load impersonated service account credentials from {}".format(
500 filename
501 )
502 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
503 raise new_exc from caught_exc
504 return credentials, None
505
506
507def _get_gdch_service_account_credentials(filename, info):
508 from google.oauth2 import gdch_credentials
509
510 try:
511 credentials = gdch_credentials.ServiceAccountCredentials.from_service_account_info(
512 info
513 )
514 except ValueError as caught_exc:
515 msg = "Failed to load GDCH service account credentials from {}".format(filename)
516 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
517 raise new_exc from caught_exc
518 return credentials, info.get("project")
519
520
521def get_api_key_credentials(key):
522 """Return credentials with the given API key."""
523 from google.auth import api_key
524
525 return api_key.Credentials(key)
526
527
528def _apply_quota_project_id(credentials, quota_project_id):
529 if quota_project_id:
530 credentials = credentials.with_quota_project(quota_project_id)
531 else:
532 credentials = credentials.with_quota_project_from_environment()
533
534 from google.oauth2 import credentials as authorized_user_credentials
535
536 if isinstance(credentials, authorized_user_credentials.Credentials) and (
537 not credentials.quota_project_id
538 ):
539 _warn_about_problematic_credentials(credentials)
540 return credentials
541
542
543def default(scopes=None, request=None, quota_project_id=None, default_scopes=None):
544 """Gets the default credentials for the current environment.
545
546 `Application Default Credentials`_ provides an easy way to obtain
547 credentials to call Google APIs for server-to-server or local applications.
548 This function acquires credentials from the environment in the following
549 order:
550
551 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set
552 to the path of a valid service account JSON private key file, then it is
553 loaded and returned. The project ID returned is the project ID defined
554 in the service account file if available (some older files do not
555 contain project ID information).
556
557 If the environment variable is set to the path of a valid external
558 account JSON configuration file (workload identity federation), then the
559 configuration file is used to determine and retrieve the external
560 credentials from the current environment (AWS, Azure, etc).
561 These will then be exchanged for Google access tokens via the Google STS
562 endpoint.
563 The project ID returned in this case is the one corresponding to the
564 underlying workload identity pool resource if determinable.
565
566 If the environment variable is set to the path of a valid GDCH service
567 account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH
568 credential will be returned. The project ID returned is the project
569 specified in the JSON file.
570 2. If the `Google Cloud SDK`_ is installed and has application default
571 credentials set they are loaded and returned.
572
573 To enable application default credentials with the Cloud SDK run::
574
575 gcloud auth application-default login
576
577 If the Cloud SDK has an active project, the project ID is returned. The
578 active project can be set using::
579
580 gcloud config set project
581
582 3. If the application is running in the `App Engine standard environment`_
583 (first generation) then the credentials and project ID from the
584 `App Identity Service`_ are used.
585 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or
586 the `App Engine flexible environment`_ or the `App Engine standard
587 environment`_ (second generation) then the credentials and project ID
588 are obtained from the `Metadata Service`_.
589 5. If no credentials are found,
590 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised.
591
592 .. _Application Default Credentials: https://developers.google.com\
593 /identity/protocols/application-default-credentials
594 .. _Google Cloud SDK: https://cloud.google.com/sdk
595 .. _App Engine standard environment: https://cloud.google.com/appengine
596 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\
597 /appidentity/
598 .. _Compute Engine: https://cloud.google.com/compute
599 .. _App Engine flexible environment: https://cloud.google.com\
600 /appengine/flexible
601 .. _Metadata Service: https://cloud.google.com/compute/docs\
602 /storing-retrieving-metadata
603 .. _Cloud Run: https://cloud.google.com/run
604 .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\
605 /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted
606
607 Example::
608
609 import google.auth
610
611 credentials, project_id = google.auth.default()
612
613 Args:
614 scopes (Sequence[str]): The list of scopes for the credentials. If
615 specified, the credentials will automatically be scoped if
616 necessary.
617 request (Optional[google.auth.transport.Request]): An object used to make
618 HTTP requests. This is used to either detect whether the application
619 is running on Compute Engine or to determine the associated project
620 ID for a workload identity pool resource (external account
621 credentials). If not specified, then it will either use the standard
622 library http client to make requests for Compute Engine credentials
623 or a google.auth.transport.requests.Request client for external
624 account credentials.
625 quota_project_id (Optional[str]): The project ID used for
626 quota and billing.
627 default_scopes (Optional[Sequence[str]]): Default scopes passed by a
628 Google client library. Use 'scopes' for user-defined scopes.
629 Returns:
630 Tuple[~google.auth.credentials.Credentials, Optional[str]]:
631 the current environment's credentials and project ID. Project ID
632 may be None, which indicates that the Project ID could not be
633 ascertained from the environment.
634
635 Raises:
636 ~google.auth.exceptions.DefaultCredentialsError:
637 If no credentials were found, or if the credentials found were
638 invalid.
639 """
640 from google.auth.credentials import with_scopes_if_required
641 from google.auth.credentials import CredentialsWithQuotaProject
642
643 explicit_project_id = os.environ.get(
644 environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT)
645 )
646
647 checkers = (
648 # Avoid passing scopes here to prevent passing scopes to user credentials.
649 # with_scopes_if_required() below will ensure scopes/default scopes are
650 # safely set on the returned credentials since requires_scopes will
651 # guard against setting scopes on user credentials.
652 lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id),
653 lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id),
654 _get_gae_credentials,
655 lambda: _get_gce_credentials(request, quota_project_id=quota_project_id),
656 )
657
658 for checker in checkers:
659 credentials, project_id = checker()
660 if credentials is not None:
661 credentials = with_scopes_if_required(
662 credentials, scopes, default_scopes=default_scopes
663 )
664
665 effective_project_id = explicit_project_id or project_id
666
667 # For external account credentials, scopes are required to determine
668 # the project ID. Try to get the project ID again if not yet
669 # determined.
670 if not effective_project_id and callable(
671 getattr(credentials, "get_project_id", None)
672 ):
673 if request is None:
674 import google.auth.transport.requests
675
676 request = google.auth.transport.requests.Request()
677 effective_project_id = credentials.get_project_id(request=request)
678
679 if quota_project_id and isinstance(
680 credentials, CredentialsWithQuotaProject
681 ):
682 credentials = credentials.with_quota_project(quota_project_id)
683
684 if not effective_project_id:
685 _LOGGER.warning(
686 "No project ID could be determined. Consider running "
687 "`gcloud config set project` or setting the %s "
688 "environment variable",
689 environment_vars.PROJECT,
690 )
691 return credentials, effective_project_id
692
693 raise exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS)