Repository: googleapis/google-auth-library-python

Issue:
KeyError when generating IDTokenCredentials with bad permissions
When trying to generate a IDTokenCredentials token, if for some reason there is some sort of permissions error, the library crashes with a KeyError instead of providing useful info to fix the error.

#### Environment details

  - OS: Ubuntu 22.04
  - Python version: 3.10.6
  - pip version: 23.0
  - `google-auth` version: 2.16.0

#### Steps to reproduce

```python
import google.auth
import google.auth.transport.requests
import google.oauth2.id_token
from google.auth import impersonated_credentials

target_principal = "some-bad-name@myprooject.iam.gserviceaccount.com"
target_audience = "https://some_url.appspot.com"

credentials, project = google.auth.default()

target_credentials = impersonated_credentials.Credentials(
    source_credentials=credentials,
    target_principal=target_principal,
    target_scopes=["https://www.googleapis.com/auth/cloud-platform"],
    lifetime=120,
)

credentials = impersonated_credentials.IDTokenCredentials(
    target_credentials, target_audience=target_audience
)
request = google.auth.transport.requests.Request()
credentials.refresh(request)
print(credentials.token)
```

And the traceback is
```
Traceback (most recent call last):
  File "/workspace/test.py", line 23, in <module>
    credentials.refresh(request)
  File "/usr/local/lib/python3.10/dist-packages/google/auth/impersonated_credentials.py", line 445, in refresh
    id_token = response.json()["token"]
KeyError: 'token'
```



Discussion:

Resolving patch:
diff --git a/google/auth/impersonated_credentials.py b/google/auth/impersonated_credentials.py
--- a/google/auth/impersonated_credentials.py
+++ b/google/auth/impersonated_credentials.py
@@ -438,11 +438,19 @@ def refresh(self, request):
             self._target_credentials._source_credentials, auth_request=request
         )
 
-        response = authed_session.post(
-            url=iam_sign_endpoint,
-            headers=headers,
-            data=json.dumps(body).encode("utf-8"),
-        )
+        try:
+            response = authed_session.post(
+                url=iam_sign_endpoint,
+                headers=headers,
+                data=json.dumps(body).encode("utf-8"),
+            )
+        finally:
+            authed_session.close()
+
+        if response.status_code != http_client.OK:
+            raise exceptions.RefreshError(
+                "Error getting ID token: {}".format(response.json())
+            )
 
         id_token = response.json()["token"]
         self.token = id_token


Test patch:
diff --git a/system_tests/secrets.tar.enc b/system_tests/secrets.tar.enc
Binary files a/system_tests/secrets.tar.enc and b/system_tests/secrets.tar.enc differ
diff --git a/tests/test_impersonated_credentials.py b/tests/test_impersonated_credentials.py
--- a/tests/test_impersonated_credentials.py
+++ b/tests/test_impersonated_credentials.py
@@ -318,6 +318,27 @@ def test_refresh_failure_unauthorzed(self, mock_donor_credentials):
         assert not credentials.valid
         assert credentials.expired
 
+    def test_refresh_failure(self):
+        credentials = self.make_credentials(lifetime=None)
+        credentials.expiry = None
+        credentials.token = "token"
+        id_creds = impersonated_credentials.IDTokenCredentials(
+            credentials, target_audience="audience"
+        )
+
+        response = mock.create_autospec(transport.Response, instance=False)
+        response.status_code = http_client.UNAUTHORIZED
+        response.json = mock.Mock(return_value="failed to get ID token")
+
+        with mock.patch(
+            "google.auth.transport.requests.AuthorizedSession.post",
+            return_value=response,
+        ):
+            with pytest.raises(exceptions.RefreshError) as excinfo:
+                id_creds.refresh(None)
+
+        assert excinfo.match("Error getting ID token")
+
     def test_refresh_failure_http_error(self, mock_donor_credentials):
         credentials = self.make_credentials(lifetime=None)