1# Copyright 2016 Google LLC 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 15import json 16import os 17 18import mock 19import pytest 20 21from google.auth import _default 22from google.auth import app_engine 23from google.auth import aws 24from google.auth import compute_engine 25from google.auth import credentials 26from google.auth import environment_vars 27from google.auth import exceptions 28from google.auth import external_account 29from google.auth import identity_pool 30from google.oauth2 import service_account 31import google.oauth2.credentials 32 33 34DATA_DIR = os.path.join(os.path.dirname(__file__), "data") 35AUTHORIZED_USER_FILE = os.path.join(DATA_DIR, "authorized_user.json") 36 37with open(AUTHORIZED_USER_FILE) as fh: 38 AUTHORIZED_USER_FILE_DATA = json.load(fh) 39 40AUTHORIZED_USER_CLOUD_SDK_FILE = os.path.join( 41 DATA_DIR, "authorized_user_cloud_sdk.json" 42) 43 44AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE = os.path.join( 45 DATA_DIR, "authorized_user_cloud_sdk_with_quota_project_id.json" 46) 47 48SERVICE_ACCOUNT_FILE = os.path.join(DATA_DIR, "service_account.json") 49 50CLIENT_SECRETS_FILE = os.path.join(DATA_DIR, "client_secrets.json") 51 52with open(SERVICE_ACCOUNT_FILE) as fh: 53 SERVICE_ACCOUNT_FILE_DATA = json.load(fh) 54 55SUBJECT_TOKEN_TEXT_FILE = os.path.join(DATA_DIR, "external_subject_token.txt") 56TOKEN_URL = "https://sts.googleapis.com/v1/token" 57AUDIENCE = "//iam.googleapis.com/projects/123456/locations/global/workloadIdentityPools/POOL_ID/providers/PROVIDER_ID" 58WORKFORCE_AUDIENCE = ( 59 "//iam.googleapis.com/locations/global/workforcePools/POOL_ID/providers/PROVIDER_ID" 60) 61WORKFORCE_POOL_USER_PROJECT = "WORKFORCE_POOL_USER_PROJECT_NUMBER" 62REGION_URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone" 63SECURITY_CREDS_URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials" 64CRED_VERIFICATION_URL = ( 65 "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15" 66) 67IDENTITY_POOL_DATA = { 68 "type": "external_account", 69 "audience": AUDIENCE, 70 "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", 71 "token_url": TOKEN_URL, 72 "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, 73} 74AWS_DATA = { 75 "type": "external_account", 76 "audience": AUDIENCE, 77 "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request", 78 "token_url": TOKEN_URL, 79 "credential_source": { 80 "environment_id": "aws1", 81 "region_url": REGION_URL, 82 "url": SECURITY_CREDS_URL, 83 "regional_cred_verification_url": CRED_VERIFICATION_URL, 84 }, 85} 86SERVICE_ACCOUNT_EMAIL = "service-1234@service-name.iam.gserviceaccount.com" 87SERVICE_ACCOUNT_IMPERSONATION_URL = ( 88 "https://us-east1-iamcredentials.googleapis.com/v1/projects/-" 89 + "/serviceAccounts/{}:generateAccessToken".format(SERVICE_ACCOUNT_EMAIL) 90) 91IMPERSONATED_IDENTITY_POOL_DATA = { 92 "type": "external_account", 93 "audience": AUDIENCE, 94 "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", 95 "token_url": TOKEN_URL, 96 "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, 97 "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, 98} 99IMPERSONATED_AWS_DATA = { 100 "type": "external_account", 101 "audience": AUDIENCE, 102 "subject_token_type": "urn:ietf:params:aws:token-type:aws4_request", 103 "token_url": TOKEN_URL, 104 "credential_source": { 105 "environment_id": "aws1", 106 "region_url": REGION_URL, 107 "url": SECURITY_CREDS_URL, 108 "regional_cred_verification_url": CRED_VERIFICATION_URL, 109 }, 110 "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, 111} 112IDENTITY_POOL_WORKFORCE_DATA = { 113 "type": "external_account", 114 "audience": WORKFORCE_AUDIENCE, 115 "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", 116 "token_url": TOKEN_URL, 117 "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, 118 "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, 119} 120IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA = { 121 "type": "external_account", 122 "audience": WORKFORCE_AUDIENCE, 123 "subject_token_type": "urn:ietf:params:oauth:token-type:id_token", 124 "token_url": TOKEN_URL, 125 "credential_source": {"file": SUBJECT_TOKEN_TEXT_FILE}, 126 "service_account_impersonation_url": SERVICE_ACCOUNT_IMPERSONATION_URL, 127 "workforce_pool_user_project": WORKFORCE_POOL_USER_PROJECT, 128} 129 130MOCK_CREDENTIALS = mock.Mock(spec=credentials.CredentialsWithQuotaProject) 131MOCK_CREDENTIALS.with_quota_project.return_value = MOCK_CREDENTIALS 132 133 134def get_project_id_side_effect(self, request=None): 135 # If no scopes are set, this will always return None. 136 if not self.scopes: 137 return None 138 return mock.sentinel.project_id 139 140 141LOAD_FILE_PATCH = mock.patch( 142 "google.auth._default.load_credentials_from_file", 143 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 144 autospec=True, 145) 146EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH = mock.patch.object( 147 external_account.Credentials, 148 "get_project_id", 149 side_effect=get_project_id_side_effect, 150 autospec=True, 151) 152 153 154def test_load_credentials_from_missing_file(): 155 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 156 _default.load_credentials_from_file("") 157 158 assert excinfo.match(r"not found") 159 160 161def test_load_credentials_from_file_invalid_json(tmpdir): 162 jsonfile = tmpdir.join("invalid.json") 163 jsonfile.write("{") 164 165 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 166 _default.load_credentials_from_file(str(jsonfile)) 167 168 assert excinfo.match(r"not a valid json file") 169 170 171def test_load_credentials_from_file_invalid_type(tmpdir): 172 jsonfile = tmpdir.join("invalid.json") 173 jsonfile.write(json.dumps({"type": "not-a-real-type"})) 174 175 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 176 _default.load_credentials_from_file(str(jsonfile)) 177 178 assert excinfo.match(r"does not have a valid type") 179 180 181def test_load_credentials_from_file_authorized_user(): 182 credentials, project_id = _default.load_credentials_from_file(AUTHORIZED_USER_FILE) 183 assert isinstance(credentials, google.oauth2.credentials.Credentials) 184 assert project_id is None 185 186 187def test_load_credentials_from_file_no_type(tmpdir): 188 # use the client_secrets.json, which is valid json but not a 189 # loadable credentials type 190 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 191 _default.load_credentials_from_file(CLIENT_SECRETS_FILE) 192 193 assert excinfo.match(r"does not have a valid type") 194 assert excinfo.match(r"Type is None") 195 196 197def test_load_credentials_from_file_authorized_user_bad_format(tmpdir): 198 filename = tmpdir.join("authorized_user_bad.json") 199 filename.write(json.dumps({"type": "authorized_user"})) 200 201 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 202 _default.load_credentials_from_file(str(filename)) 203 204 assert excinfo.match(r"Failed to load authorized user") 205 assert excinfo.match(r"missing fields") 206 207 208def test_load_credentials_from_file_authorized_user_cloud_sdk(): 209 with pytest.warns(UserWarning, match="Cloud SDK"): 210 credentials, project_id = _default.load_credentials_from_file( 211 AUTHORIZED_USER_CLOUD_SDK_FILE 212 ) 213 assert isinstance(credentials, google.oauth2.credentials.Credentials) 214 assert project_id is None 215 216 # No warning if the json file has quota project id. 217 credentials, project_id = _default.load_credentials_from_file( 218 AUTHORIZED_USER_CLOUD_SDK_WITH_QUOTA_PROJECT_ID_FILE 219 ) 220 assert isinstance(credentials, google.oauth2.credentials.Credentials) 221 assert project_id is None 222 223 224def test_load_credentials_from_file_authorized_user_cloud_sdk_with_scopes(): 225 with pytest.warns(UserWarning, match="Cloud SDK"): 226 credentials, project_id = _default.load_credentials_from_file( 227 AUTHORIZED_USER_CLOUD_SDK_FILE, 228 scopes=["https://www.google.com/calendar/feeds"], 229 ) 230 assert isinstance(credentials, google.oauth2.credentials.Credentials) 231 assert project_id is None 232 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 233 234 235def test_load_credentials_from_file_authorized_user_cloud_sdk_with_quota_project(): 236 credentials, project_id = _default.load_credentials_from_file( 237 AUTHORIZED_USER_CLOUD_SDK_FILE, quota_project_id="project-foo" 238 ) 239 240 assert isinstance(credentials, google.oauth2.credentials.Credentials) 241 assert project_id is None 242 assert credentials.quota_project_id == "project-foo" 243 244 245def test_load_credentials_from_file_service_account(): 246 credentials, project_id = _default.load_credentials_from_file(SERVICE_ACCOUNT_FILE) 247 assert isinstance(credentials, service_account.Credentials) 248 assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] 249 250 251def test_load_credentials_from_file_service_account_with_scopes(): 252 credentials, project_id = _default.load_credentials_from_file( 253 SERVICE_ACCOUNT_FILE, scopes=["https://www.google.com/calendar/feeds"] 254 ) 255 assert isinstance(credentials, service_account.Credentials) 256 assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] 257 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 258 259 260def test_load_credentials_from_file_service_account_with_quota_project(): 261 credentials, project_id = _default.load_credentials_from_file( 262 SERVICE_ACCOUNT_FILE, quota_project_id="project-foo" 263 ) 264 assert isinstance(credentials, service_account.Credentials) 265 assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"] 266 assert credentials.quota_project_id == "project-foo" 267 268 269def test_load_credentials_from_file_service_account_bad_format(tmpdir): 270 filename = tmpdir.join("serivce_account_bad.json") 271 filename.write(json.dumps({"type": "service_account"})) 272 273 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 274 _default.load_credentials_from_file(str(filename)) 275 276 assert excinfo.match(r"Failed to load service account") 277 assert excinfo.match(r"missing fields") 278 279 280@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 281def test_load_credentials_from_file_external_account_identity_pool( 282 get_project_id, tmpdir 283): 284 config_file = tmpdir.join("config.json") 285 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 286 credentials, project_id = _default.load_credentials_from_file(str(config_file)) 287 288 assert isinstance(credentials, identity_pool.Credentials) 289 # Since no scopes are specified, the project ID cannot be determined. 290 assert project_id is None 291 assert get_project_id.called 292 293 294@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 295def test_load_credentials_from_file_external_account_aws(get_project_id, tmpdir): 296 config_file = tmpdir.join("config.json") 297 config_file.write(json.dumps(AWS_DATA)) 298 credentials, project_id = _default.load_credentials_from_file(str(config_file)) 299 300 assert isinstance(credentials, aws.Credentials) 301 # Since no scopes are specified, the project ID cannot be determined. 302 assert project_id is None 303 assert get_project_id.called 304 305 306@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 307def test_load_credentials_from_file_external_account_identity_pool_impersonated( 308 get_project_id, tmpdir 309): 310 config_file = tmpdir.join("config.json") 311 config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA)) 312 credentials, project_id = _default.load_credentials_from_file(str(config_file)) 313 314 assert isinstance(credentials, identity_pool.Credentials) 315 assert not credentials.is_user 316 assert not credentials.is_workforce_pool 317 # Since no scopes are specified, the project ID cannot be determined. 318 assert project_id is None 319 assert get_project_id.called 320 321 322@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 323def test_load_credentials_from_file_external_account_aws_impersonated( 324 get_project_id, tmpdir 325): 326 config_file = tmpdir.join("config.json") 327 config_file.write(json.dumps(IMPERSONATED_AWS_DATA)) 328 credentials, project_id = _default.load_credentials_from_file(str(config_file)) 329 330 assert isinstance(credentials, aws.Credentials) 331 assert not credentials.is_user 332 assert not credentials.is_workforce_pool 333 # Since no scopes are specified, the project ID cannot be determined. 334 assert project_id is None 335 assert get_project_id.called 336 337 338@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 339def test_load_credentials_from_file_external_account_workforce(get_project_id, tmpdir): 340 config_file = tmpdir.join("config.json") 341 config_file.write(json.dumps(IDENTITY_POOL_WORKFORCE_DATA)) 342 credentials, project_id = _default.load_credentials_from_file(str(config_file)) 343 344 assert isinstance(credentials, identity_pool.Credentials) 345 assert credentials.is_user 346 assert credentials.is_workforce_pool 347 # Since no scopes are specified, the project ID cannot be determined. 348 assert project_id is None 349 assert get_project_id.called 350 351 352@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 353def test_load_credentials_from_file_external_account_workforce_impersonated( 354 get_project_id, tmpdir 355): 356 config_file = tmpdir.join("config.json") 357 config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA)) 358 credentials, project_id = _default.load_credentials_from_file(str(config_file)) 359 360 assert isinstance(credentials, identity_pool.Credentials) 361 assert not credentials.is_user 362 assert credentials.is_workforce_pool 363 # Since no scopes are specified, the project ID cannot be determined. 364 assert project_id is None 365 assert get_project_id.called 366 367 368@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 369def test_load_credentials_from_file_external_account_with_user_and_default_scopes( 370 get_project_id, tmpdir 371): 372 config_file = tmpdir.join("config.json") 373 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 374 credentials, project_id = _default.load_credentials_from_file( 375 str(config_file), 376 scopes=["https://www.google.com/calendar/feeds"], 377 default_scopes=["https://www.googleapis.com/auth/cloud-platform"], 378 ) 379 380 assert isinstance(credentials, identity_pool.Credentials) 381 # Since scopes are specified, the project ID can be determined. 382 assert project_id is mock.sentinel.project_id 383 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 384 assert credentials.default_scopes == [ 385 "https://www.googleapis.com/auth/cloud-platform" 386 ] 387 388 389@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 390def test_load_credentials_from_file_external_account_with_quota_project( 391 get_project_id, tmpdir 392): 393 config_file = tmpdir.join("config.json") 394 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 395 credentials, project_id = _default.load_credentials_from_file( 396 str(config_file), quota_project_id="project-foo" 397 ) 398 399 assert isinstance(credentials, identity_pool.Credentials) 400 # Since no scopes are specified, the project ID cannot be determined. 401 assert project_id is None 402 assert credentials.quota_project_id == "project-foo" 403 404 405def test_load_credentials_from_file_external_account_bad_format(tmpdir): 406 filename = tmpdir.join("external_account_bad.json") 407 filename.write(json.dumps({"type": "external_account"})) 408 409 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 410 _default.load_credentials_from_file(str(filename)) 411 412 assert excinfo.match( 413 "Failed to load external account credentials from {}".format(str(filename)) 414 ) 415 416 417@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 418def test_load_credentials_from_file_external_account_explicit_request( 419 get_project_id, tmpdir 420): 421 config_file = tmpdir.join("config.json") 422 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 423 credentials, project_id = _default.load_credentials_from_file( 424 str(config_file), 425 request=mock.sentinel.request, 426 scopes=["https://www.googleapis.com/auth/cloud-platform"], 427 ) 428 429 assert isinstance(credentials, identity_pool.Credentials) 430 # Since scopes are specified, the project ID can be determined. 431 assert project_id is mock.sentinel.project_id 432 get_project_id.assert_called_with(credentials, request=mock.sentinel.request) 433 434 435@mock.patch.dict(os.environ, {}, clear=True) 436def test__get_explicit_environ_credentials_no_env(): 437 assert _default._get_explicit_environ_credentials() == (None, None) 438 439 440@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) 441@LOAD_FILE_PATCH 442def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch): 443 monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") 444 445 credentials, project_id = _default._get_explicit_environ_credentials( 446 quota_project_id=quota_project_id 447 ) 448 449 assert credentials is MOCK_CREDENTIALS 450 assert project_id is mock.sentinel.project_id 451 load.assert_called_with("filename", quota_project_id=quota_project_id) 452 453 454@LOAD_FILE_PATCH 455def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): 456 load.return_value = MOCK_CREDENTIALS, None 457 monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") 458 459 credentials, project_id = _default._get_explicit_environ_credentials() 460 461 assert credentials is MOCK_CREDENTIALS 462 assert project_id is None 463 464 465@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) 466@mock.patch( 467 "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True 468) 469@mock.patch("google.auth._default._get_gcloud_sdk_credentials", autospec=True) 470def test__get_explicit_environ_credentials_fallback_to_gcloud( 471 get_gcloud_creds, get_adc_path, quota_project_id, monkeypatch 472): 473 # Set explicit credentials path to cloud sdk credentials path. 474 get_adc_path.return_value = "filename" 475 monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") 476 477 _default._get_explicit_environ_credentials(quota_project_id=quota_project_id) 478 479 # Check we fall back to cloud sdk flow since explicit credentials path is 480 # cloud sdk credentials path 481 get_gcloud_creds.assert_called_with(quota_project_id=quota_project_id) 482 483 484@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) 485@LOAD_FILE_PATCH 486@mock.patch( 487 "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True 488) 489def test__get_gcloud_sdk_credentials(get_adc_path, load, quota_project_id): 490 get_adc_path.return_value = SERVICE_ACCOUNT_FILE 491 492 credentials, project_id = _default._get_gcloud_sdk_credentials( 493 quota_project_id=quota_project_id 494 ) 495 496 assert credentials is MOCK_CREDENTIALS 497 assert project_id is mock.sentinel.project_id 498 load.assert_called_with(SERVICE_ACCOUNT_FILE, quota_project_id=quota_project_id) 499 500 501@mock.patch( 502 "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True 503) 504def test__get_gcloud_sdk_credentials_non_existent(get_adc_path, tmpdir): 505 non_existent = tmpdir.join("non-existent") 506 get_adc_path.return_value = str(non_existent) 507 508 credentials, project_id = _default._get_gcloud_sdk_credentials() 509 510 assert credentials is None 511 assert project_id is None 512 513 514@mock.patch( 515 "google.auth._cloud_sdk.get_project_id", 516 return_value=mock.sentinel.project_id, 517 autospec=True, 518) 519@mock.patch("os.path.isfile", return_value=True, autospec=True) 520@LOAD_FILE_PATCH 521def test__get_gcloud_sdk_credentials_project_id(load, unused_isfile, get_project_id): 522 # Don't return a project ID from load file, make the function check 523 # the Cloud SDK project. 524 load.return_value = MOCK_CREDENTIALS, None 525 526 credentials, project_id = _default._get_gcloud_sdk_credentials() 527 528 assert credentials == MOCK_CREDENTIALS 529 assert project_id == mock.sentinel.project_id 530 assert get_project_id.called 531 532 533@mock.patch("google.auth._cloud_sdk.get_project_id", return_value=None, autospec=True) 534@mock.patch("os.path.isfile", return_value=True) 535@LOAD_FILE_PATCH 536def test__get_gcloud_sdk_credentials_no_project_id(load, unused_isfile, get_project_id): 537 # Don't return a project ID from load file, make the function check 538 # the Cloud SDK project. 539 load.return_value = MOCK_CREDENTIALS, None 540 541 credentials, project_id = _default._get_gcloud_sdk_credentials() 542 543 assert credentials == MOCK_CREDENTIALS 544 assert project_id is None 545 assert get_project_id.called 546 547 548class _AppIdentityModule(object): 549 """The interface of the App Idenity app engine module. 550 See https://cloud.google.com/appengine/docs/standard/python/refdocs\ 551 /google.appengine.api.app_identity.app_identity 552 """ 553 554 def get_application_id(self): 555 raise NotImplementedError() 556 557 558@pytest.fixture 559def app_identity(monkeypatch): 560 """Mocks the app_identity module for google.auth.app_engine.""" 561 app_identity_module = mock.create_autospec(_AppIdentityModule, instance=True) 562 monkeypatch.setattr(app_engine, "app_identity", app_identity_module) 563 yield app_identity_module 564 565 566@mock.patch.dict(os.environ) 567def test__get_gae_credentials_gen1(app_identity): 568 os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" 569 app_identity.get_application_id.return_value = mock.sentinel.project 570 571 credentials, project_id = _default._get_gae_credentials() 572 573 assert isinstance(credentials, app_engine.Credentials) 574 assert project_id == mock.sentinel.project 575 576 577@mock.patch.dict(os.environ) 578def test__get_gae_credentials_gen2(): 579 os.environ["GAE_RUNTIME"] = "python37" 580 credentials, project_id = _default._get_gae_credentials() 581 assert credentials is None 582 assert project_id is None 583 584 585@mock.patch.dict(os.environ) 586def test__get_gae_credentials_gen2_backwards_compat(): 587 # compat helpers may copy GAE_RUNTIME to APPENGINE_RUNTIME 588 # for backwards compatibility with code that relies on it 589 os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python37" 590 os.environ["GAE_RUNTIME"] = "python37" 591 credentials, project_id = _default._get_gae_credentials() 592 assert credentials is None 593 assert project_id is None 594 595 596def test__get_gae_credentials_env_unset(): 597 assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ 598 assert "GAE_RUNTIME" not in os.environ 599 credentials, project_id = _default._get_gae_credentials() 600 assert credentials is None 601 assert project_id is None 602 603 604@mock.patch.dict(os.environ) 605def test__get_gae_credentials_no_app_engine(): 606 # test both with and without LEGACY_APPENGINE_RUNTIME setting 607 assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ 608 609 import sys 610 611 with mock.patch.dict(sys.modules, {"google.auth.app_engine": None}): 612 credentials, project_id = _default._get_gae_credentials() 613 assert credentials is None 614 assert project_id is None 615 616 os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" 617 credentials, project_id = _default._get_gae_credentials() 618 assert credentials is None 619 assert project_id is None 620 621 622@mock.patch.dict(os.environ) 623@mock.patch.object(app_engine, "app_identity", new=None) 624def test__get_gae_credentials_no_apis(): 625 # test both with and without LEGACY_APPENGINE_RUNTIME setting 626 assert environment_vars.LEGACY_APPENGINE_RUNTIME not in os.environ 627 628 credentials, project_id = _default._get_gae_credentials() 629 assert credentials is None 630 assert project_id is None 631 632 os.environ[environment_vars.LEGACY_APPENGINE_RUNTIME] = "python27" 633 credentials, project_id = _default._get_gae_credentials() 634 assert credentials is None 635 assert project_id is None 636 637 638@mock.patch( 639 "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True 640) 641@mock.patch( 642 "google.auth.compute_engine._metadata.get_project_id", 643 return_value="example-project", 644 autospec=True, 645) 646def test__get_gce_credentials(unused_get, unused_ping): 647 credentials, project_id = _default._get_gce_credentials() 648 649 assert isinstance(credentials, compute_engine.Credentials) 650 assert project_id == "example-project" 651 652 653@mock.patch( 654 "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True 655) 656def test__get_gce_credentials_no_ping(unused_ping): 657 credentials, project_id = _default._get_gce_credentials() 658 659 assert credentials is None 660 assert project_id is None 661 662 663@mock.patch( 664 "google.auth.compute_engine._metadata.ping", return_value=True, autospec=True 665) 666@mock.patch( 667 "google.auth.compute_engine._metadata.get_project_id", 668 side_effect=exceptions.TransportError(), 669 autospec=True, 670) 671def test__get_gce_credentials_no_project_id(unused_get, unused_ping): 672 credentials, project_id = _default._get_gce_credentials() 673 674 assert isinstance(credentials, compute_engine.Credentials) 675 assert project_id is None 676 677 678def test__get_gce_credentials_no_compute_engine(): 679 import sys 680 681 with mock.patch.dict("sys.modules"): 682 sys.modules["google.auth.compute_engine"] = None 683 credentials, project_id = _default._get_gce_credentials() 684 assert credentials is None 685 assert project_id is None 686 687 688@mock.patch( 689 "google.auth.compute_engine._metadata.ping", return_value=False, autospec=True 690) 691def test__get_gce_credentials_explicit_request(ping): 692 _default._get_gce_credentials(mock.sentinel.request) 693 ping.assert_called_with(request=mock.sentinel.request) 694 695 696@mock.patch( 697 "google.auth._default._get_explicit_environ_credentials", 698 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 699 autospec=True, 700) 701def test_default_early_out(unused_get): 702 assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id) 703 704 705@mock.patch( 706 "google.auth._default._get_explicit_environ_credentials", 707 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 708 autospec=True, 709) 710def test_default_explict_project_id(unused_get, monkeypatch): 711 monkeypatch.setenv(environment_vars.PROJECT, "explicit-env") 712 assert _default.default() == (MOCK_CREDENTIALS, "explicit-env") 713 714 715@mock.patch( 716 "google.auth._default._get_explicit_environ_credentials", 717 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 718 autospec=True, 719) 720def test_default_explict_legacy_project_id(unused_get, monkeypatch): 721 monkeypatch.setenv(environment_vars.LEGACY_PROJECT, "explicit-env") 722 assert _default.default() == (MOCK_CREDENTIALS, "explicit-env") 723 724 725@mock.patch("logging.Logger.warning", autospec=True) 726@mock.patch( 727 "google.auth._default._get_explicit_environ_credentials", 728 return_value=(MOCK_CREDENTIALS, None), 729 autospec=True, 730) 731@mock.patch( 732 "google.auth._default._get_gcloud_sdk_credentials", 733 return_value=(MOCK_CREDENTIALS, None), 734 autospec=True, 735) 736@mock.patch( 737 "google.auth._default._get_gae_credentials", 738 return_value=(MOCK_CREDENTIALS, None), 739 autospec=True, 740) 741@mock.patch( 742 "google.auth._default._get_gce_credentials", 743 return_value=(MOCK_CREDENTIALS, None), 744 autospec=True, 745) 746def test_default_without_project_id( 747 unused_gce, unused_gae, unused_sdk, unused_explicit, logger_warning 748): 749 assert _default.default() == (MOCK_CREDENTIALS, None) 750 logger_warning.assert_called_with(mock.ANY, mock.ANY, mock.ANY) 751 752 753@mock.patch( 754 "google.auth._default._get_explicit_environ_credentials", 755 return_value=(None, None), 756 autospec=True, 757) 758@mock.patch( 759 "google.auth._default._get_gcloud_sdk_credentials", 760 return_value=(None, None), 761 autospec=True, 762) 763@mock.patch( 764 "google.auth._default._get_gae_credentials", 765 return_value=(None, None), 766 autospec=True, 767) 768@mock.patch( 769 "google.auth._default._get_gce_credentials", 770 return_value=(None, None), 771 autospec=True, 772) 773def test_default_fail(unused_gce, unused_gae, unused_sdk, unused_explicit): 774 with pytest.raises(exceptions.DefaultCredentialsError): 775 assert _default.default() 776 777 778@mock.patch( 779 "google.auth._default._get_explicit_environ_credentials", 780 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 781 autospec=True, 782) 783@mock.patch( 784 "google.auth.credentials.with_scopes_if_required", 785 return_value=MOCK_CREDENTIALS, 786 autospec=True, 787) 788def test_default_scoped(with_scopes, unused_get): 789 scopes = ["one", "two"] 790 791 credentials, project_id = _default.default(scopes=scopes) 792 793 assert credentials == with_scopes.return_value 794 assert project_id == mock.sentinel.project_id 795 with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes, default_scopes=None) 796 797 798@mock.patch( 799 "google.auth._default._get_explicit_environ_credentials", 800 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 801 autospec=True, 802) 803def test_default_quota_project(with_quota_project): 804 credentials, project_id = _default.default(quota_project_id="project-foo") 805 806 MOCK_CREDENTIALS.with_quota_project.assert_called_once_with("project-foo") 807 assert project_id == mock.sentinel.project_id 808 809 810@mock.patch( 811 "google.auth._default._get_explicit_environ_credentials", 812 return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id), 813 autospec=True, 814) 815def test_default_no_app_engine_compute_engine_module(unused_get): 816 """ 817 google.auth.compute_engine and google.auth.app_engine are both optional 818 to allow not including them when using this package. This verifies 819 that default fails gracefully if these modules are absent 820 """ 821 import sys 822 823 with mock.patch.dict("sys.modules"): 824 sys.modules["google.auth.compute_engine"] = None 825 sys.modules["google.auth.app_engine"] = None 826 assert _default.default() == (MOCK_CREDENTIALS, mock.sentinel.project_id) 827 828 829@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 830def test_default_environ_external_credentials_identity_pool( 831 get_project_id, monkeypatch, tmpdir 832): 833 config_file = tmpdir.join("config.json") 834 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 835 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 836 837 credentials, project_id = _default.default() 838 839 assert isinstance(credentials, identity_pool.Credentials) 840 assert not credentials.is_user 841 assert not credentials.is_workforce_pool 842 # Without scopes, project ID cannot be determined. 843 assert project_id is None 844 845 846@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 847def test_default_environ_external_credentials_identity_pool_impersonated( 848 get_project_id, monkeypatch, tmpdir 849): 850 config_file = tmpdir.join("config.json") 851 config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_DATA)) 852 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 853 854 credentials, project_id = _default.default( 855 scopes=["https://www.google.com/calendar/feeds"] 856 ) 857 858 assert isinstance(credentials, identity_pool.Credentials) 859 assert not credentials.is_user 860 assert not credentials.is_workforce_pool 861 assert project_id is mock.sentinel.project_id 862 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 863 864 865@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 866def test_default_environ_external_credentials_aws_impersonated( 867 get_project_id, monkeypatch, tmpdir 868): 869 config_file = tmpdir.join("config.json") 870 config_file.write(json.dumps(IMPERSONATED_AWS_DATA)) 871 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 872 873 credentials, project_id = _default.default( 874 scopes=["https://www.google.com/calendar/feeds"] 875 ) 876 877 assert isinstance(credentials, aws.Credentials) 878 assert not credentials.is_user 879 assert not credentials.is_workforce_pool 880 assert project_id is mock.sentinel.project_id 881 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 882 883 884@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 885def test_default_environ_external_credentials_workforce( 886 get_project_id, monkeypatch, tmpdir 887): 888 config_file = tmpdir.join("config.json") 889 config_file.write(json.dumps(IDENTITY_POOL_WORKFORCE_DATA)) 890 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 891 892 credentials, project_id = _default.default( 893 scopes=["https://www.google.com/calendar/feeds"] 894 ) 895 896 assert isinstance(credentials, identity_pool.Credentials) 897 assert credentials.is_user 898 assert credentials.is_workforce_pool 899 assert project_id is mock.sentinel.project_id 900 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 901 902 903@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 904def test_default_environ_external_credentials_workforce_impersonated( 905 get_project_id, monkeypatch, tmpdir 906): 907 config_file = tmpdir.join("config.json") 908 config_file.write(json.dumps(IMPERSONATED_IDENTITY_POOL_WORKFORCE_DATA)) 909 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 910 911 credentials, project_id = _default.default( 912 scopes=["https://www.google.com/calendar/feeds"] 913 ) 914 915 assert isinstance(credentials, identity_pool.Credentials) 916 assert not credentials.is_user 917 assert credentials.is_workforce_pool 918 assert project_id is mock.sentinel.project_id 919 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 920 921 922@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 923def test_default_environ_external_credentials_with_user_and_default_scopes_and_quota_project_id( 924 get_project_id, monkeypatch, tmpdir 925): 926 config_file = tmpdir.join("config.json") 927 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 928 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 929 930 credentials, project_id = _default.default( 931 scopes=["https://www.google.com/calendar/feeds"], 932 default_scopes=["https://www.googleapis.com/auth/cloud-platform"], 933 quota_project_id="project-foo", 934 ) 935 936 assert isinstance(credentials, identity_pool.Credentials) 937 assert project_id is mock.sentinel.project_id 938 assert credentials.quota_project_id == "project-foo" 939 assert credentials.scopes == ["https://www.google.com/calendar/feeds"] 940 assert credentials.default_scopes == [ 941 "https://www.googleapis.com/auth/cloud-platform" 942 ] 943 944 945@EXTERNAL_ACCOUNT_GET_PROJECT_ID_PATCH 946def test_default_environ_external_credentials_explicit_request_with_scopes( 947 get_project_id, monkeypatch, tmpdir 948): 949 config_file = tmpdir.join("config.json") 950 config_file.write(json.dumps(IDENTITY_POOL_DATA)) 951 monkeypatch.setenv(environment_vars.CREDENTIALS, str(config_file)) 952 953 credentials, project_id = _default.default( 954 request=mock.sentinel.request, 955 scopes=["https://www.googleapis.com/auth/cloud-platform"], 956 ) 957 958 assert isinstance(credentials, identity_pool.Credentials) 959 assert project_id is mock.sentinel.project_id 960 # default() will initialize new credentials via with_scopes_if_required 961 # and potentially with_quota_project. 962 # As a result the caller of get_project_id() will not match the returned 963 # credentials. 964 get_project_id.assert_called_with(mock.ANY, request=mock.sentinel.request) 965 966 967def test_default_environ_external_credentials_bad_format(monkeypatch, tmpdir): 968 filename = tmpdir.join("external_account_bad.json") 969 filename.write(json.dumps({"type": "external_account"})) 970 monkeypatch.setenv(environment_vars.CREDENTIALS, str(filename)) 971 972 with pytest.raises(exceptions.DefaultCredentialsError) as excinfo: 973 _default.default() 974 975 assert excinfo.match( 976 "Failed to load external account credentials from {}".format(str(filename)) 977 ) 978 979 980@mock.patch( 981 "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True 982) 983def test_default_warning_without_quota_project_id_for_user_creds(get_adc_path): 984 get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE 985 986 with pytest.warns(UserWarning, match="Cloud SDK"): 987 credentials, project_id = _default.default(quota_project_id=None) 988 989 990@mock.patch( 991 "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True 992) 993def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): 994 get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE 995 996 credentials, project_id = _default.default(quota_project_id="project-foo") 997