Skip to content

fix compute engine IDTokenCredentials.with_target_audience method #361

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ tests/data/user-key.json

# PyCharm configuration:
.idea
venv/

# Generated files
pylintrc
pylintrc.test
pytype_output/

.python-version
23 changes: 16 additions & 7 deletions google/auth/compute_engine/credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ class IDTokenCredentials(credentials.Credentials, credentials.Signing):
def __init__(self, request, target_audience,
token_uri=_DEFAULT_TOKEN_URI,
additional_claims=None,
service_account_email=None):
service_account_email=None,
signer=None):
"""
Args:
request (google.auth.transport.Request): The object used to make
Expand All @@ -145,6 +146,9 @@ def __init__(self, request, target_audience,
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__()

Expand All @@ -153,10 +157,12 @@ def __init__(self, request, target_audience,
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
Expand All @@ -176,12 +182,15 @@ def with_target_audience(self, target_audience):
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())
additional_claims=self._additional_claims.copy(),
signer=self.signer)

def _make_authorization_grant_assertion(self):
"""Create the OAuth 2.0 assertion.
Expand Down
87 changes: 86 additions & 1 deletion tests/compute_engine/test_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -257,6 +259,89 @@ def test_with_target_audience(self, sign, get, utcnow):
'iss': 'service-account@example.com',
'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))
Expand Down
2 changes: 2 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ deps =
requests-oauthlib
urllib3
cryptography
responses
grpcio; platform_python_implementation != 'PyPy'

commands =
pytest --cov=google.auth --cov=google.oauth2 --cov=tests {posargs:tests}

Expand Down