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