Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/google/auth/_default.py: 17%

218 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 07:30 +0000

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 

26import six 

27 

28from google.auth import environment_vars 

29from google.auth import exceptions 

30import google.auth.transport._http_client 

31 

32_LOGGER = logging.getLogger(__name__) 

33 

34# Valid types accepted for file-based credentials. 

35_AUTHORIZED_USER_TYPE = "authorized_user" 

36_SERVICE_ACCOUNT_TYPE = "service_account" 

37_EXTERNAL_ACCOUNT_TYPE = "external_account" 

38_EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE = "external_account_authorized_user" 

39_IMPERSONATED_SERVICE_ACCOUNT_TYPE = "impersonated_service_account" 

40_GDCH_SERVICE_ACCOUNT_TYPE = "gdch_service_account" 

41_VALID_TYPES = ( 

42 _AUTHORIZED_USER_TYPE, 

43 _SERVICE_ACCOUNT_TYPE, 

44 _EXTERNAL_ACCOUNT_TYPE, 

45 _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE, 

46 _IMPERSONATED_SERVICE_ACCOUNT_TYPE, 

47 _GDCH_SERVICE_ACCOUNT_TYPE, 

48) 

49 

50# Help message when no credentials can be found. 

51_CLOUD_SDK_MISSING_CREDENTIALS = """\ 

52Your default credentials were not found. To set up Application Default Credentials, \ 

53see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.\ 

54""" 

55 

56# Warning when using Cloud SDK user credentials 

57_CLOUD_SDK_CREDENTIALS_WARNING = """\ 

58Your application has authenticated using end user credentials from Google \ 

59Cloud SDK without a quota project. You might receive a "quota exceeded" \ 

60or "API not enabled" error. See the following page for troubleshooting: \ 

61https://cloud.google.com/docs/authentication/adc-troubleshooting/user-creds. \ 

62""" 

63 

64# The subject token type used for AWS external_account credentials. 

65_AWS_SUBJECT_TOKEN_TYPE = "urn:ietf:params:aws:token-type:aws4_request" 

66 

67 

68def _warn_about_problematic_credentials(credentials): 

69 """Determines if the credentials are problematic. 

70 

71 Credentials from the Cloud SDK that are associated with Cloud SDK's project 

72 are problematic because they may not have APIs enabled and have limited 

73 quota. If this is the case, warn about it. 

74 """ 

75 from google.auth import _cloud_sdk 

76 

77 if credentials.client_id == _cloud_sdk.CLOUD_SDK_CLIENT_ID: 

78 warnings.warn(_CLOUD_SDK_CREDENTIALS_WARNING) 

79 

80 

81def load_credentials_from_file( 

82 filename, scopes=None, default_scopes=None, quota_project_id=None, request=None 

83): 

84 """Loads Google credentials from a file. 

85 

86 The credentials file must be a service account key, stored authorized 

87 user credentials, external account credentials, or impersonated service 

88 account credentials. 

89 

90 Args: 

91 filename (str): The full path to the credentials file. 

92 scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If 

93 specified, the credentials will automatically be scoped if 

94 necessary 

95 default_scopes (Optional[Sequence[str]]): Default scopes passed by a 

96 Google client library. Use 'scopes' for user-defined scopes. 

97 quota_project_id (Optional[str]): The project ID used for 

98 quota and billing. 

99 request (Optional[google.auth.transport.Request]): An object used to make 

100 HTTP requests. This is used to determine the associated project ID 

101 for a workload identity pool resource (external account credentials). 

102 If not specified, then it will use a 

103 google.auth.transport.requests.Request client to make requests. 

104 

105 Returns: 

106 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded 

107 credentials and the project ID. Authorized user credentials do not 

108 have the project ID information. External account credentials project 

109 IDs may not always be determined. 

110 

111 Raises: 

112 google.auth.exceptions.DefaultCredentialsError: if the file is in the 

113 wrong format or is missing. 

114 """ 

115 if not os.path.exists(filename): 

116 raise exceptions.DefaultCredentialsError( 

117 "File {} was not found.".format(filename) 

118 ) 

119 

120 with io.open(filename, "r") as file_obj: 

121 try: 

122 info = json.load(file_obj) 

123 except ValueError as caught_exc: 

124 new_exc = exceptions.DefaultCredentialsError( 

125 "File {} is not a valid json file.".format(filename), caught_exc 

126 ) 

127 six.raise_from(new_exc, caught_exc) 

128 return _load_credentials_from_info( 

129 filename, info, scopes, default_scopes, quota_project_id, request 

130 ) 

131 

132 

133def _load_credentials_from_info( 

134 filename, info, scopes, default_scopes, quota_project_id, request 

135): 

136 from google.auth.credentials import CredentialsWithQuotaProject 

137 

138 credential_type = info.get("type") 

139 

140 if credential_type == _AUTHORIZED_USER_TYPE: 

141 credentials, project_id = _get_authorized_user_credentials( 

142 filename, info, scopes 

143 ) 

144 

145 elif credential_type == _SERVICE_ACCOUNT_TYPE: 

146 credentials, project_id = _get_service_account_credentials( 

147 filename, info, scopes, default_scopes 

148 ) 

149 

150 elif credential_type == _EXTERNAL_ACCOUNT_TYPE: 

151 credentials, project_id = _get_external_account_credentials( 

152 info, 

153 filename, 

154 scopes=scopes, 

155 default_scopes=default_scopes, 

156 request=request, 

157 ) 

158 

159 elif credential_type == _EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE: 

160 credentials, project_id = _get_external_account_authorized_user_credentials( 

161 filename, info, request 

162 ) 

163 

164 elif credential_type == _IMPERSONATED_SERVICE_ACCOUNT_TYPE: 

165 credentials, project_id = _get_impersonated_service_account_credentials( 

166 filename, info, scopes 

167 ) 

168 elif credential_type == _GDCH_SERVICE_ACCOUNT_TYPE: 

169 credentials, project_id = _get_gdch_service_account_credentials(filename, info) 

170 else: 

171 raise exceptions.DefaultCredentialsError( 

172 "The file {file} does not have a valid type. " 

173 "Type is {type}, expected one of {valid_types}.".format( 

174 file=filename, type=credential_type, valid_types=_VALID_TYPES 

175 ) 

176 ) 

177 if isinstance(credentials, CredentialsWithQuotaProject): 

178 credentials = _apply_quota_project_id(credentials, quota_project_id) 

179 return credentials, project_id 

180 

181 

182def _get_gcloud_sdk_credentials(quota_project_id=None): 

183 """Gets the credentials and project ID from the Cloud SDK.""" 

184 from google.auth import _cloud_sdk 

185 

186 _LOGGER.debug("Checking Cloud SDK credentials as part of auth process...") 

187 

188 # Check if application default credentials exist. 

189 credentials_filename = _cloud_sdk.get_application_default_credentials_path() 

190 

191 if not os.path.isfile(credentials_filename): 

192 _LOGGER.debug("Cloud SDK credentials not found on disk; not using them") 

193 return None, None 

194 

195 credentials, project_id = load_credentials_from_file( 

196 credentials_filename, quota_project_id=quota_project_id 

197 ) 

198 

199 if not project_id: 

200 project_id = _cloud_sdk.get_project_id() 

201 

202 return credentials, project_id 

203 

204 

205def _get_explicit_environ_credentials(quota_project_id=None): 

206 """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment 

207 variable.""" 

208 from google.auth import _cloud_sdk 

209 

210 cloud_sdk_adc_path = _cloud_sdk.get_application_default_credentials_path() 

211 explicit_file = os.environ.get(environment_vars.CREDENTIALS) 

212 

213 _LOGGER.debug( 

214 "Checking %s for explicit credentials as part of auth process...", explicit_file 

215 ) 

216 

217 if explicit_file is not None and explicit_file == cloud_sdk_adc_path: 

218 # Cloud sdk flow calls gcloud to fetch project id, so if the explicit 

219 # file path is cloud sdk credentials path, then we should fall back 

220 # to cloud sdk flow, otherwise project id cannot be obtained. 

221 _LOGGER.debug( 

222 "Explicit credentials path %s is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...", 

223 explicit_file, 

224 ) 

225 return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) 

226 

227 if explicit_file is not None: 

228 credentials, project_id = load_credentials_from_file( 

229 os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id 

230 ) 

231 

232 return credentials, project_id 

233 

234 else: 

235 return None, None 

236 

237 

238def _get_gae_credentials(): 

239 """Gets Google App Engine App Identity credentials and project ID.""" 

240 # If not GAE gen1, prefer the metadata service even if the GAE APIs are 

241 # available as per https://google.aip.dev/auth/4115. 

242 if os.environ.get(environment_vars.LEGACY_APPENGINE_RUNTIME) != "python27": 

243 return None, None 

244 

245 # While this library is normally bundled with app_engine, there are 

246 # some cases where it's not available, so we tolerate ImportError. 

247 try: 

248 _LOGGER.debug("Checking for App Engine runtime as part of auth process...") 

249 import google.auth.app_engine as app_engine 

250 except ImportError: 

251 _LOGGER.warning("Import of App Engine auth library failed.") 

252 return None, None 

253 

254 try: 

255 credentials = app_engine.Credentials() 

256 project_id = app_engine.get_project_id() 

257 return credentials, project_id 

258 except EnvironmentError: 

259 _LOGGER.debug( 

260 "No App Engine library was found so cannot authentication via App Engine Identity Credentials." 

261 ) 

262 return None, None 

263 

264 

265def _get_gce_credentials(request=None, quota_project_id=None): 

266 """Gets credentials and project ID from the GCE Metadata Service.""" 

267 # Ping requires a transport, but we want application default credentials 

268 # to require no arguments. So, we'll use the _http_client transport which 

269 # uses http.client. This is only acceptable because the metadata server 

270 # doesn't do SSL and never requires proxies. 

271 

272 # While this library is normally bundled with compute_engine, there are 

273 # some cases where it's not available, so we tolerate ImportError. 

274 try: 

275 from google.auth import compute_engine 

276 from google.auth.compute_engine import _metadata 

277 except ImportError: 

278 _LOGGER.warning("Import of Compute Engine auth library failed.") 

279 return None, None 

280 

281 if request is None: 

282 request = google.auth.transport._http_client.Request() 

283 

284 if _metadata.ping(request=request): 

285 # Get the project ID. 

286 try: 

287 project_id = _metadata.get_project_id(request=request) 

288 except exceptions.TransportError: 

289 project_id = None 

290 

291 cred = compute_engine.Credentials() 

292 cred = _apply_quota_project_id(cred, quota_project_id) 

293 

294 return cred, project_id 

295 else: 

296 _LOGGER.warning( 

297 "Authentication failed using Compute Engine authentication due to unavailable metadata server." 

298 ) 

299 return None, None 

300 

301 

302def _get_external_account_credentials( 

303 info, filename, scopes=None, default_scopes=None, request=None 

304): 

305 """Loads external account Credentials from the parsed external account info. 

306 

307 The credentials information must correspond to a supported external account 

308 credentials. 

309 

310 Args: 

311 info (Mapping[str, str]): The external account info in Google format. 

312 filename (str): The full path to the credentials file. 

313 scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If 

314 specified, the credentials will automatically be scoped if 

315 necessary. 

316 default_scopes (Optional[Sequence[str]]): Default scopes passed by a 

317 Google client library. Use 'scopes' for user-defined scopes. 

318 request (Optional[google.auth.transport.Request]): An object used to make 

319 HTTP requests. This is used to determine the associated project ID 

320 for a workload identity pool resource (external account credentials). 

321 If not specified, then it will use a 

322 google.auth.transport.requests.Request client to make requests. 

323 

324 Returns: 

325 Tuple[google.auth.credentials.Credentials, Optional[str]]: Loaded 

326 credentials and the project ID. External account credentials project 

327 IDs may not always be determined. 

328 

329 Raises: 

330 google.auth.exceptions.DefaultCredentialsError: if the info dictionary 

331 is in the wrong format or is missing required information. 

332 """ 

333 # There are currently 3 types of external_account credentials. 

334 if info.get("subject_token_type") == _AWS_SUBJECT_TOKEN_TYPE: 

335 # Check if configuration corresponds to an AWS credentials. 

336 from google.auth import aws 

337 

338 credentials = aws.Credentials.from_info( 

339 info, scopes=scopes, default_scopes=default_scopes 

340 ) 

341 elif ( 

342 info.get("credential_source") is not None 

343 and info.get("credential_source").get("executable") is not None 

344 ): 

345 from google.auth import pluggable 

346 

347 credentials = pluggable.Credentials.from_info( 

348 info, scopes=scopes, default_scopes=default_scopes 

349 ) 

350 else: 

351 try: 

352 # Check if configuration corresponds to an Identity Pool credentials. 

353 from google.auth import identity_pool 

354 

355 credentials = identity_pool.Credentials.from_info( 

356 info, scopes=scopes, default_scopes=default_scopes 

357 ) 

358 except ValueError: 

359 # If the configuration is invalid or does not correspond to any 

360 # supported external_account credentials, raise an error. 

361 raise exceptions.DefaultCredentialsError( 

362 "Failed to load external account credentials from {}".format(filename) 

363 ) 

364 if request is None: 

365 import google.auth.transport.requests 

366 

367 request = google.auth.transport.requests.Request() 

368 

369 return credentials, credentials.get_project_id(request=request) 

370 

371 

372def _get_external_account_authorized_user_credentials( 

373 filename, info, scopes=None, default_scopes=None, request=None 

374): 

375 try: 

376 from google.auth import external_account_authorized_user 

377 

378 credentials = external_account_authorized_user.Credentials.from_info(info) 

379 except ValueError: 

380 raise exceptions.DefaultCredentialsError( 

381 "Failed to load external account authorized user credentials from {}".format( 

382 filename 

383 ) 

384 ) 

385 

386 return credentials, None 

387 

388 

389def _get_authorized_user_credentials(filename, info, scopes=None): 

390 from google.oauth2 import credentials 

391 

392 try: 

393 credentials = credentials.Credentials.from_authorized_user_info( 

394 info, scopes=scopes 

395 ) 

396 except ValueError as caught_exc: 

397 msg = "Failed to load authorized user credentials from {}".format(filename) 

398 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) 

399 six.raise_from(new_exc, caught_exc) 

400 return credentials, None 

401 

402 

403def _get_service_account_credentials(filename, info, scopes=None, default_scopes=None): 

404 from google.oauth2 import service_account 

405 

406 try: 

407 credentials = service_account.Credentials.from_service_account_info( 

408 info, scopes=scopes, default_scopes=default_scopes 

409 ) 

410 except ValueError as caught_exc: 

411 msg = "Failed to load service account credentials from {}".format(filename) 

412 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) 

413 six.raise_from(new_exc, caught_exc) 

414 return credentials, info.get("project_id") 

415 

416 

417def _get_impersonated_service_account_credentials(filename, info, scopes): 

418 from google.auth import impersonated_credentials 

419 

420 try: 

421 source_credentials_info = info.get("source_credentials") 

422 source_credentials_type = source_credentials_info.get("type") 

423 if source_credentials_type == _AUTHORIZED_USER_TYPE: 

424 source_credentials, _ = _get_authorized_user_credentials( 

425 filename, source_credentials_info 

426 ) 

427 elif source_credentials_type == _SERVICE_ACCOUNT_TYPE: 

428 source_credentials, _ = _get_service_account_credentials( 

429 filename, source_credentials_info 

430 ) 

431 else: 

432 raise exceptions.InvalidType( 

433 "source credential of type {} is not supported.".format( 

434 source_credentials_type 

435 ) 

436 ) 

437 impersonation_url = info.get("service_account_impersonation_url") 

438 start_index = impersonation_url.rfind("/") 

439 end_index = impersonation_url.find(":generateAccessToken") 

440 if start_index == -1 or end_index == -1 or start_index > end_index: 

441 raise exceptions.InvalidValue( 

442 "Cannot extract target principal from {}".format(impersonation_url) 

443 ) 

444 target_principal = impersonation_url[start_index + 1 : end_index] 

445 delegates = info.get("delegates") 

446 quota_project_id = info.get("quota_project_id") 

447 credentials = impersonated_credentials.Credentials( 

448 source_credentials, 

449 target_principal, 

450 scopes, 

451 delegates, 

452 quota_project_id=quota_project_id, 

453 ) 

454 except ValueError as caught_exc: 

455 msg = "Failed to load impersonated service account credentials from {}".format( 

456 filename 

457 ) 

458 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) 

459 six.raise_from(new_exc, caught_exc) 

460 return credentials, None 

461 

462 

463def _get_gdch_service_account_credentials(filename, info): 

464 from google.oauth2 import gdch_credentials 

465 

466 try: 

467 credentials = gdch_credentials.ServiceAccountCredentials.from_service_account_info( 

468 info 

469 ) 

470 except ValueError as caught_exc: 

471 msg = "Failed to load GDCH service account credentials from {}".format(filename) 

472 new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) 

473 six.raise_from(new_exc, caught_exc) 

474 return credentials, info.get("project") 

475 

476 

477def get_api_key_credentials(key): 

478 """Return credentials with the given API key.""" 

479 from google.auth import api_key 

480 

481 return api_key.Credentials(key) 

482 

483 

484def _apply_quota_project_id(credentials, quota_project_id): 

485 if quota_project_id: 

486 credentials = credentials.with_quota_project(quota_project_id) 

487 else: 

488 credentials = credentials.with_quota_project_from_environment() 

489 

490 from google.oauth2 import credentials as authorized_user_credentials 

491 

492 if isinstance(credentials, authorized_user_credentials.Credentials) and ( 

493 not credentials.quota_project_id 

494 ): 

495 _warn_about_problematic_credentials(credentials) 

496 return credentials 

497 

498 

499def default(scopes=None, request=None, quota_project_id=None, default_scopes=None): 

500 """Gets the default credentials for the current environment. 

501 

502 `Application Default Credentials`_ provides an easy way to obtain 

503 credentials to call Google APIs for server-to-server or local applications. 

504 This function acquires credentials from the environment in the following 

505 order: 

506 

507 1. If the environment variable ``GOOGLE_APPLICATION_CREDENTIALS`` is set 

508 to the path of a valid service account JSON private key file, then it is 

509 loaded and returned. The project ID returned is the project ID defined 

510 in the service account file if available (some older files do not 

511 contain project ID information). 

512 

513 If the environment variable is set to the path of a valid external 

514 account JSON configuration file (workload identity federation), then the 

515 configuration file is used to determine and retrieve the external 

516 credentials from the current environment (AWS, Azure, etc). 

517 These will then be exchanged for Google access tokens via the Google STS 

518 endpoint. 

519 The project ID returned in this case is the one corresponding to the 

520 underlying workload identity pool resource if determinable. 

521 

522 If the environment variable is set to the path of a valid GDCH service 

523 account JSON file (`Google Distributed Cloud Hosted`_), then a GDCH 

524 credential will be returned. The project ID returned is the project 

525 specified in the JSON file. 

526 2. If the `Google Cloud SDK`_ is installed and has application default 

527 credentials set they are loaded and returned. 

528 

529 To enable application default credentials with the Cloud SDK run:: 

530 

531 gcloud auth application-default login 

532 

533 If the Cloud SDK has an active project, the project ID is returned. The 

534 active project can be set using:: 

535 

536 gcloud config set project 

537 

538 3. If the application is running in the `App Engine standard environment`_ 

539 (first generation) then the credentials and project ID from the 

540 `App Identity Service`_ are used. 

541 4. If the application is running in `Compute Engine`_ or `Cloud Run`_ or 

542 the `App Engine flexible environment`_ or the `App Engine standard 

543 environment`_ (second generation) then the credentials and project ID 

544 are obtained from the `Metadata Service`_. 

545 5. If no credentials are found, 

546 :class:`~google.auth.exceptions.DefaultCredentialsError` will be raised. 

547 

548 .. _Application Default Credentials: https://developers.google.com\ 

549 /identity/protocols/application-default-credentials 

550 .. _Google Cloud SDK: https://cloud.google.com/sdk 

551 .. _App Engine standard environment: https://cloud.google.com/appengine 

552 .. _App Identity Service: https://cloud.google.com/appengine/docs/python\ 

553 /appidentity/ 

554 .. _Compute Engine: https://cloud.google.com/compute 

555 .. _App Engine flexible environment: https://cloud.google.com\ 

556 /appengine/flexible 

557 .. _Metadata Service: https://cloud.google.com/compute/docs\ 

558 /storing-retrieving-metadata 

559 .. _Cloud Run: https://cloud.google.com/run 

560 .. _Google Distributed Cloud Hosted: https://cloud.google.com/blog/topics\ 

561 /hybrid-cloud/announcing-google-distributed-cloud-edge-and-hosted 

562 

563 Example:: 

564 

565 import google.auth 

566 

567 credentials, project_id = google.auth.default() 

568 

569 Args: 

570 scopes (Sequence[str]): The list of scopes for the credentials. If 

571 specified, the credentials will automatically be scoped if 

572 necessary. 

573 request (Optional[google.auth.transport.Request]): An object used to make 

574 HTTP requests. This is used to either detect whether the application 

575 is running on Compute Engine or to determine the associated project 

576 ID for a workload identity pool resource (external account 

577 credentials). If not specified, then it will either use the standard 

578 library http client to make requests for Compute Engine credentials 

579 or a google.auth.transport.requests.Request client for external 

580 account credentials. 

581 quota_project_id (Optional[str]): The project ID used for 

582 quota and billing. 

583 default_scopes (Optional[Sequence[str]]): Default scopes passed by a 

584 Google client library. Use 'scopes' for user-defined scopes. 

585 Returns: 

586 Tuple[~google.auth.credentials.Credentials, Optional[str]]: 

587 the current environment's credentials and project ID. Project ID 

588 may be None, which indicates that the Project ID could not be 

589 ascertained from the environment. 

590 

591 Raises: 

592 ~google.auth.exceptions.DefaultCredentialsError: 

593 If no credentials were found, or if the credentials found were 

594 invalid. 

595 """ 

596 from google.auth.credentials import with_scopes_if_required 

597 from google.auth.credentials import CredentialsWithQuotaProject 

598 

599 explicit_project_id = os.environ.get( 

600 environment_vars.PROJECT, os.environ.get(environment_vars.LEGACY_PROJECT) 

601 ) 

602 

603 checkers = ( 

604 # Avoid passing scopes here to prevent passing scopes to user credentials. 

605 # with_scopes_if_required() below will ensure scopes/default scopes are 

606 # safely set on the returned credentials since requires_scopes will 

607 # guard against setting scopes on user credentials. 

608 lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), 

609 lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), 

610 _get_gae_credentials, 

611 lambda: _get_gce_credentials(request, quota_project_id=quota_project_id), 

612 ) 

613 

614 for checker in checkers: 

615 credentials, project_id = checker() 

616 if credentials is not None: 

617 credentials = with_scopes_if_required( 

618 credentials, scopes, default_scopes=default_scopes 

619 ) 

620 

621 # For external account credentials, scopes are required to determine 

622 # the project ID. Try to get the project ID again if not yet 

623 # determined. 

624 if not project_id and callable( 

625 getattr(credentials, "get_project_id", None) 

626 ): 

627 if request is None: 

628 import google.auth.transport.requests 

629 

630 request = google.auth.transport.requests.Request() 

631 project_id = credentials.get_project_id(request=request) 

632 

633 if quota_project_id and isinstance( 

634 credentials, CredentialsWithQuotaProject 

635 ): 

636 credentials = credentials.with_quota_project(quota_project_id) 

637 

638 effective_project_id = explicit_project_id or project_id 

639 if not effective_project_id: 

640 _LOGGER.warning( 

641 "No project ID could be determined. Consider running " 

642 "`gcloud config set project` or setting the %s " 

643 "environment variable", 

644 environment_vars.PROJECT, 

645 ) 

646 return credentials, effective_project_id 

647 

648 raise exceptions.DefaultCredentialsError(_CLOUD_SDK_MISSING_CREDENTIALS)