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