diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | google/auth/compute_engine/credentials.py | 21 | ||||
-rw-r--r-- | noxfile.py | 1 | ||||
-rw-r--r-- | tests/compute_engine/test_credentials.py | 82 |
4 files changed, 100 insertions, 7 deletions
@@ -31,8 +31,11 @@ tests/data/user-key.json # PyCharm configuration: .idea +venv/ # Generated files pylintrc pylintrc.test pytype_output/ + +.python-version diff --git a/google/auth/compute_engine/credentials.py b/google/auth/compute_engine/credentials.py index fc14fcc..e35907a 100644 --- a/google/auth/compute_engine/credentials.py +++ b/google/auth/compute_engine/credentials.py @@ -136,6 +136,7 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing): token_uri=_DEFAULT_TOKEN_URI, additional_claims=None, service_account_email=None, + signer=None, ): """ Args: @@ -150,6 +151,9 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing): service_account_email (str): Optional explicit service account to use to sign JWT tokens. By default, this is the default GCE service account. + signer (google.auth.crypt.Signer): The signer used to sign JWTs. + In case the signer is specified, the request argument will be + ignored. """ super(IDTokenCredentials, self).__init__() @@ -158,11 +162,13 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing): service_account_email = sa_info["email"] self._service_account_email = service_account_email - self._signer = iam.Signer( - request=request, - credentials=Credentials(), - service_account_email=service_account_email, - ) + if signer is None: + signer = iam.Signer( + request=request, + credentials=Credentials(), + service_account_email=service_account_email, + ) + self._signer = signer self._token_uri = token_uri self._target_audience = target_audience @@ -182,12 +188,15 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing): google.auth.service_account.IDTokenCredentials: A new credentials instance. """ + # since the signer is already instantiated, + # the request is not needed return self.__class__( - self._signer, + None, service_account_email=self._service_account_email, token_uri=self._token_uri, target_audience=target_audience, additional_claims=self._additional_claims.copy(), + signer=self.signer, ) def _make_authorization_grant_assertion(self): @@ -25,6 +25,7 @@ TEST_DEPENDENCIES = [ "requests", "urllib3", "cryptography", + "responses", "grpcio", ] BLACK_VERSION = "black==19.3b0" diff --git a/tests/compute_engine/test_credentials.py b/tests/compute_engine/test_credentials.py index f05a566..b861984 100644 --- a/tests/compute_engine/test_credentials.py +++ b/tests/compute_engine/test_credentials.py @@ -11,17 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import base64 import datetime import mock import pytest +import responses from google.auth import _helpers from google.auth import exceptions from google.auth import jwt from google.auth import transport from google.auth.compute_engine import credentials +from google.auth.transport import requests class TestCredentials(object): @@ -270,6 +272,84 @@ class TestIDTokenCredentials(object): "target_audience": "https://actually.not", } + # Check that the signer have been initialized with a Request object + assert isinstance(self.credentials._signer._request, transport.Request) + + @responses.activate + def test_with_target_audience_integration(self): + """ Test that it is possible to refresh credentials + generated from `with_target_audience`. + + Instead of mocking the methods, the HTTP responses + have been mocked. + """ + + # mock information about credentials + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/default/?recursive=true", + status=200, + content_type="application/json", + json={ + "scopes": "email", + "email": "service-account@example.com", + "aliases": ["default"], + }, + ) + + # mock token for credentials + responses.add( + responses.GET, + "http://metadata.google.internal/computeMetadata/v1/instance/" + "service-accounts/service-account@example.com/token", + status=200, + content_type="application/json", + json={ + "access_token": "some-token", + "expires_in": 3210, + "token_type": "Bearer", + }, + ) + + # mock sign blob endpoint + signature = base64.b64encode(b"some-signature").decode("utf-8") + responses.add( + responses.POST, + "https://iam.googleapis.com/v1/projects/-/serviceAccounts/" + "service-account@example.com:signBlob?alt=json", + status=200, + content_type="application/json", + json={"keyId": "some-key-id", "signature": signature}, + ) + + id_token = "{}.{}.{}".format( + base64.b64encode(b'{"some":"some"}').decode("utf-8"), + base64.b64encode(b'{"exp": 3210}').decode("utf-8"), + base64.b64encode(b"token").decode("utf-8"), + ) + + # mock id token endpoint + responses.add( + responses.POST, + "https://www.googleapis.com/oauth2/v4/token", + status=200, + content_type="application/json", + json={"id_token": id_token, "expiry": 3210}, + ) + + self.credentials = credentials.IDTokenCredentials( + request=requests.Request(), + service_account_email="service-account@example.com", + target_audience="https://audience.com", + ) + + self.credentials = self.credentials.with_target_audience("https://actually.not") + + self.credentials.refresh(requests.Request()) + + assert self.credentials.token is not None + @mock.patch( "google.auth._helpers.utcnow", return_value=datetime.datetime.utcfromtimestamp(0), |