Repository: googleapis/google-auth-library-python

Issue:
_default always sets `credentials.quota_project_id` to None
#546 introduced a bug where the quota project id always gets set to `None`. If no `quota_project_id` is passed to `load_credentials_from_file(...)` or `default(...)`, the existing project should not be overridden


Discussion:

Resolving patch:
diff --git a/google/auth/_default.py b/google/auth/_default.py
--- a/google/auth/_default.py
+++ b/google/auth/_default.py
@@ -116,11 +116,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)
             six.raise_from(new_exc, caught_exc)
+        if quota_project_id:
+            credentials = credentials.with_quota_project(quota_project_id)
         if not credentials.quota_project_id:
             _warn_about_problematic_credentials(credentials)
         return credentials, None
@@ -131,11 +133,13 @@ def load_credentials_from_file(filename, scopes=None, quota_project_id=None):
         try:
             credentials = service_account.Credentials.from_service_account_info(
                 info, scopes=scopes
-            ).with_quota_project(quota_project_id)
+            )
         except ValueError as caught_exc:
             msg = "Failed to load service account credentials from {}".format(filename)
             new_exc = exceptions.DefaultCredentialsError(msg, caught_exc)
             six.raise_from(new_exc, caught_exc)
+        if quota_project_id:
+            credentials = credentials.with_quota_project(quota_project_id)
         return credentials, info.get("project_id")
 
     else:
@@ -317,9 +321,10 @@ def default(scopes=None, request=None, quota_project_id=None):
     for checker in checkers:
         credentials, project_id = checker()
         if credentials is not None:
-            credentials = with_scopes_if_required(
-                credentials, scopes
-            ).with_quota_project(quota_project_id)
+            credentials = with_scopes_if_required(credentials, scopes)
+            if quota_project_id:
+                credentials = credentials.with_quota_project(quota_project_id)
+
             effective_project_id = explicit_project_id or project_id
             if not effective_project_id:
                 _LOGGER.warning(


Test patch:
diff --git a/tests/test__default.py b/tests/test__default.py
--- a/tests/test__default.py
+++ b/tests/test__default.py
@@ -165,6 +165,15 @@ def test_load_credentials_from_file_service_account_with_scopes():
     assert credentials.scopes == ["https://www.google.com/calendar/feeds"]
 
 
+def test_load_credentials_from_file_service_account_with_quota_project():
+    credentials, project_id = _default.load_credentials_from_file(
+        SERVICE_ACCOUNT_FILE, quota_project_id="project-foo"
+    )
+    assert isinstance(credentials, service_account.Credentials)
+    assert project_id == SERVICE_ACCOUNT_FILE_DATA["project_id"]
+    assert credentials.quota_project_id == "project-foo"
+
+
 def test_load_credentials_from_file_service_account_bad_format(tmpdir):
     filename = tmpdir.join("serivce_account_bad.json")
     filename.write(json.dumps({"type": "service_account"}))
@@ -465,6 +474,18 @@ def test_default_scoped(with_scopes, unused_get):
     with_scopes.assert_called_once_with(MOCK_CREDENTIALS, scopes)
 
 
+@mock.patch(
+    "google.auth._default._get_explicit_environ_credentials",
+    return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),
+    autospec=True,
+)
+def test_default_quota_project(with_quota_project):
+    credentials, project_id = _default.default(quota_project_id="project-foo")
+
+    MOCK_CREDENTIALS.with_quota_project.assert_called_once_with("project-foo")
+    assert project_id == mock.sentinel.project_id
+
+
 @mock.patch(
     "google.auth._default._get_explicit_environ_credentials",
     return_value=(MOCK_CREDENTIALS, mock.sentinel.project_id),