From 11ebaeb9d7c0862916154cfb810238574507629a Mon Sep 17 00:00:00 2001 From: arithmetic1728 <58957152+arithmetic1728@users.noreply.github.com> Date: Tue, 7 Sep 2021 14:38:49 -0700 Subject: [PATCH] fix: disable warning if quota project id provided to auth.default() (#856) * fix: disable warning if quota project id provided to auth.default() * add more tests --- google/auth/_default.py | 16 ++++++----- google/auth/_default_async.py | 20 +++++++------ tests/test__default.py | 44 ++++++++++++++++++++++------ tests_async/test__default_async.py | 46 ++++++++++++++++++++++++------ 4 files changed, 93 insertions(+), 33 deletions(-) diff --git a/google/auth/_default.py b/google/auth/_default.py index 7da77a28f..d4ccbc6ec 100644 --- a/google/auth/_default.py +++ b/google/auth/_default.py @@ -172,7 +172,7 @@ def load_credentials_from_file( ) -def _get_gcloud_sdk_credentials(): +def _get_gcloud_sdk_credentials(quota_project_id=None): """Gets the credentials and project ID from the Cloud SDK.""" from google.auth import _cloud_sdk @@ -185,7 +185,9 @@ def _get_gcloud_sdk_credentials(): _LOGGER.debug("Cloud SDK credentials not found on disk; not using them") return None, None - credentials, project_id = load_credentials_from_file(credentials_filename) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) if not project_id: project_id = _cloud_sdk.get_project_id() @@ -193,7 +195,7 @@ def _get_gcloud_sdk_credentials(): return credentials, project_id -def _get_explicit_environ_credentials(): +def _get_explicit_environ_credentials(quota_project_id=None): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment variable.""" from google.auth import _cloud_sdk @@ -213,11 +215,11 @@ def _get_explicit_environ_credentials(): "Explicit credentials path %s is the same as Cloud SDK credentials path, fall back to Cloud SDK credentials flow...", explicit_file, ) - return _get_gcloud_sdk_credentials() + return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) if explicit_file is not None: credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS] + os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id ) return credentials, project_id @@ -447,8 +449,8 @@ def default(scopes=None, request=None, quota_project_id=None, default_scopes=Non # with_scopes_if_required() below will ensure scopes/default scopes are # safely set on the returned credentials since requires_scopes will # guard against setting scopes on user credentials. - _get_explicit_environ_credentials, - _get_gcloud_sdk_credentials, + lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), + lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, lambda: _get_gce_credentials(request), ) diff --git a/google/auth/_default_async.py b/google/auth/_default_async.py index 82e6c432d..3fa125b46 100644 --- a/google/auth/_default_async.py +++ b/google/auth/_default_async.py @@ -73,11 +73,13 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): try: credentials = credentials.Credentials.from_authorized_user_info( info, scopes=scopes - ).with_quota_project(quota_project_id) + ) except ValueError as caught_exc: msg = "Failed to load authorized user credentials from {}".format(filename) new_exc = exceptions.DefaultCredentialsError(msg, caught_exc) raise new_exc from caught_exc + if quota_project_id: + credentials = credentials.with_quota_project(quota_project_id) if not credentials.quota_project_id: _default._warn_about_problematic_credentials(credentials) return credentials, None @@ -104,7 +106,7 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None): ) -def _get_gcloud_sdk_credentials(): +def _get_gcloud_sdk_credentials(quota_project_id=None): """Gets the credentials and project ID from the Cloud SDK.""" from google.auth import _cloud_sdk @@ -114,7 +116,9 @@ def _get_gcloud_sdk_credentials(): if not os.path.isfile(credentials_filename): return None, None - credentials, project_id = load_credentials_from_file(credentials_filename) + credentials, project_id = load_credentials_from_file( + credentials_filename, quota_project_id=quota_project_id + ) if not project_id: project_id = _cloud_sdk.get_project_id() @@ -122,7 +126,7 @@ def _get_gcloud_sdk_credentials(): return credentials, project_id -def _get_explicit_environ_credentials(): +def _get_explicit_environ_credentials(quota_project_id=None): """Gets credentials from the GOOGLE_APPLICATION_CREDENTIALS environment variable.""" from google.auth import _cloud_sdk @@ -134,11 +138,11 @@ def _get_explicit_environ_credentials(): # Cloud sdk flow calls gcloud to fetch project id, so if the explicit # file path is cloud sdk credentials path, then we should fall back # to cloud sdk flow, otherwise project id cannot be obtained. - return _get_gcloud_sdk_credentials() + return _get_gcloud_sdk_credentials(quota_project_id=quota_project_id) if explicit_file is not None: credentials, project_id = load_credentials_from_file( - os.environ[environment_vars.CREDENTIALS] + os.environ[environment_vars.CREDENTIALS], quota_project_id=quota_project_id ) return credentials, project_id @@ -250,8 +254,8 @@ def default_async(scopes=None, request=None, quota_project_id=None): ) checkers = ( - _get_explicit_environ_credentials, - _get_gcloud_sdk_credentials, + lambda: _get_explicit_environ_credentials(quota_project_id=quota_project_id), + lambda: _get_gcloud_sdk_credentials(quota_project_id=quota_project_id), _get_gae_credentials, lambda: _get_gce_credentials(request), ) diff --git a/tests/test__default.py b/tests/test__default.py index a515f3813..c70ceaa57 100644 --- a/tests/test__default.py +++ b/tests/test__default.py @@ -328,15 +328,18 @@ def test__get_explicit_environ_credentials_no_env(): assert _default._get_explicit_environ_credentials() == (None, None) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH -def test__get_explicit_environ_credentials(load, monkeypatch): +def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - credentials, project_id = _default._get_explicit_environ_credentials() + credentials, project_id = _default._get_explicit_environ_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with("filename") + load.assert_called_with("filename", quota_project_id=quota_project_id) @LOAD_FILE_PATCH @@ -350,36 +353,40 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): assert project_id is None +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) @mock.patch("google.auth._default._get_gcloud_sdk_credentials", autospec=True) def test__get_explicit_environ_credentials_fallback_to_gcloud( - get_gcloud_creds, get_adc_path, monkeypatch + get_gcloud_creds, get_adc_path, quota_project_id, monkeypatch ): # Set explicit credentials path to cloud sdk credentials path. get_adc_path.return_value = "filename" monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - _default._get_explicit_environ_credentials() + _default._get_explicit_environ_credentials(quota_project_id=quota_project_id) # Check we fall back to cloud sdk flow since explicit credentials path is # cloud sdk credentials path - get_gcloud_creds.assert_called_once() + get_gcloud_creds.assert_called_with(quota_project_id=quota_project_id) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) -def test__get_gcloud_sdk_credentials(get_adc_path, load): +def test__get_gcloud_sdk_credentials(get_adc_path, load, quota_project_id): get_adc_path.return_value = SERVICE_ACCOUNT_FILE - credentials, project_id = _default._get_gcloud_sdk_credentials() + credentials, project_id = _default._get_gcloud_sdk_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with(SERVICE_ACCOUNT_FILE) + load.assert_called_with(SERVICE_ACCOUNT_FILE, quota_project_id=quota_project_id) @mock.patch( @@ -779,3 +786,22 @@ def test_default_environ_external_credentials_bad_format(monkeypatch, tmpdir): assert excinfo.match( "Failed to load external account credentials from {}".format(str(filename)) ) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_warning_without_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE + + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.default(quota_project_id=None) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = AUTHORIZED_USER_CLOUD_SDK_FILE + + credentials, project_id = _default.default(quota_project_id="project-foo") diff --git a/tests_async/test__default_async.py b/tests_async/test__default_async.py index b67230342..69a50d69a 100644 --- a/tests_async/test__default_async.py +++ b/tests_async/test__default_async.py @@ -165,15 +165,18 @@ def test__get_explicit_environ_credentials_no_env(): assert _default._get_explicit_environ_credentials() == (None, None) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH -def test__get_explicit_environ_credentials(load, monkeypatch): +def test__get_explicit_environ_credentials(load, quota_project_id, monkeypatch): monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - credentials, project_id = _default._get_explicit_environ_credentials() + credentials, project_id = _default._get_explicit_environ_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with("filename") + load.assert_called_with("filename", quota_project_id=quota_project_id) @LOAD_FILE_PATCH @@ -187,36 +190,42 @@ def test__get_explicit_environ_credentials_no_project_id(load, monkeypatch): assert project_id is None +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) @mock.patch("google.auth._default_async._get_gcloud_sdk_credentials", autospec=True) def test__get_explicit_environ_credentials_fallback_to_gcloud( - get_gcloud_creds, get_adc_path, monkeypatch + get_gcloud_creds, get_adc_path, quota_project_id, monkeypatch ): # Set explicit credentials path to cloud sdk credentials path. get_adc_path.return_value = "filename" monkeypatch.setenv(environment_vars.CREDENTIALS, "filename") - _default._get_explicit_environ_credentials() + _default._get_explicit_environ_credentials(quota_project_id=quota_project_id) # Check we fall back to cloud sdk flow since explicit credentials path is # cloud sdk credentials path - get_gcloud_creds.assert_called_once() + get_gcloud_creds.assert_called_with(quota_project_id=quota_project_id) +@pytest.mark.parametrize("quota_project_id", [None, "project-foo"]) @LOAD_FILE_PATCH @mock.patch( "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True ) -def test__get_gcloud_sdk_credentials(get_adc_path, load): +def test__get_gcloud_sdk_credentials(get_adc_path, load, quota_project_id): get_adc_path.return_value = test_default.SERVICE_ACCOUNT_FILE - credentials, project_id = _default._get_gcloud_sdk_credentials() + credentials, project_id = _default._get_gcloud_sdk_credentials( + quota_project_id=quota_project_id + ) assert credentials is MOCK_CREDENTIALS assert project_id is mock.sentinel.project_id - load.assert_called_with(test_default.SERVICE_ACCOUNT_FILE) + load.assert_called_with( + test_default.SERVICE_ACCOUNT_FILE, quota_project_id=quota_project_id + ) @mock.patch( @@ -533,3 +542,22 @@ def test_default_no_app_engine_compute_engine_module(unused_get): sys.modules["google.auth.compute_engine"] = None sys.modules["google.auth.app_engine"] = None assert _default.default_async() == (MOCK_CREDENTIALS, mock.sentinel.project_id) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_warning_without_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE + + with pytest.warns(UserWarning, match="Cloud SDK"): + credentials, project_id = _default.default_async(quota_project_id=None) + + +@mock.patch( + "google.auth._cloud_sdk.get_application_default_credentials_path", autospec=True +) +def test_default_no_warning_with_quota_project_id_for_user_creds(get_adc_path): + get_adc_path.return_value = test_default.AUTHORIZED_USER_CLOUD_SDK_FILE + + credentials, project_id = _default.default_async(quota_project_id="project-foo")