Skip to content

Commit 03a9b4a

Browse files
bshafferlmigliobusunkim96
authored
fix: compute engine id token credentials "with_target_audience" method (#438)
Co-authored-by: Lorenzo Migliorino <50544028+lmiglio@users.noreply.github.com> Co-authored-by: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com>
1 parent 3bca955 commit 03a9b4a

File tree

4 files changed

+100
-7
lines changed

4 files changed

+100
-7
lines changed

packages/google-auth/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ tests/data/user-key.json
3131

3232
# PyCharm configuration:
3333
.idea
34+
venv/
3435

3536
# Generated files
3637
pylintrc
3738
pylintrc.test
3839
pytype_output/
40+
41+
.python-version

packages/google-auth/google/auth/compute_engine/credentials.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ def __init__(
136136
token_uri=_DEFAULT_TOKEN_URI,
137137
additional_claims=None,
138138
service_account_email=None,
139+
signer=None,
139140
):
140141
"""
141142
Args:
@@ -150,6 +151,9 @@ def __init__(
150151
service_account_email (str): Optional explicit service account to
151152
use to sign JWT tokens.
152153
By default, this is the default GCE service account.
154+
signer (google.auth.crypt.Signer): The signer used to sign JWTs.
155+
In case the signer is specified, the request argument will be
156+
ignored.
153157
"""
154158
super(IDTokenCredentials, self).__init__()
155159

@@ -158,11 +162,13 @@ def __init__(
158162
service_account_email = sa_info["email"]
159163
self._service_account_email = service_account_email
160164

161-
self._signer = iam.Signer(
162-
request=request,
163-
credentials=Credentials(),
164-
service_account_email=service_account_email,
165-
)
165+
if signer is None:
166+
signer = iam.Signer(
167+
request=request,
168+
credentials=Credentials(),
169+
service_account_email=service_account_email,
170+
)
171+
self._signer = signer
166172

167173
self._token_uri = token_uri
168174
self._target_audience = target_audience
@@ -182,12 +188,15 @@ def with_target_audience(self, target_audience):
182188
google.auth.service_account.IDTokenCredentials: A new credentials
183189
instance.
184190
"""
191+
# since the signer is already instantiated,
192+
# the request is not needed
185193
return self.__class__(
186-
self._signer,
194+
None,
187195
service_account_email=self._service_account_email,
188196
token_uri=self._token_uri,
189197
target_audience=target_audience,
190198
additional_claims=self._additional_claims.copy(),
199+
signer=self.signer,
191200
)
192201

193202
def _make_authorization_grant_assertion(self):

packages/google-auth/noxfile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"requests",
2626
"urllib3",
2727
"cryptography",
28+
"responses",
2829
"grpcio",
2930
]
3031
BLACK_VERSION = "black==19.3b0"

packages/google-auth/tests/compute_engine/test_credentials.py

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,19 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
14+
import base64
1515
import datetime
1616

1717
import mock
1818
import pytest
19+
import responses
1920

2021
from google.auth import _helpers
2122
from google.auth import exceptions
2223
from google.auth import jwt
2324
from google.auth import transport
2425
from google.auth.compute_engine import credentials
26+
from google.auth.transport import requests
2527

2628

2729
class TestCredentials(object):
@@ -270,6 +272,84 @@ def test_with_target_audience(self, sign, get, utcnow):
270272
"target_audience": "https://actually.not",
271273
}
272274

275+
# Check that the signer have been initialized with a Request object
276+
assert isinstance(self.credentials._signer._request, transport.Request)
277+
278+
@responses.activate
279+
def test_with_target_audience_integration(self):
280+
""" Test that it is possible to refresh credentials
281+
generated from `with_target_audience`.
282+
283+
Instead of mocking the methods, the HTTP responses
284+
have been mocked.
285+
"""
286+
287+
# mock information about credentials
288+
responses.add(
289+
responses.GET,
290+
"http://metadata.google.internal/computeMetadata/v1/instance/"
291+
"service-accounts/default/?recursive=true",
292+
status=200,
293+
content_type="application/json",
294+
json={
295+
"scopes": "email",
296+
"email": "service-account@example.com",
297+
"aliases": ["default"],
298+
},
299+
)
300+
301+
# mock token for credentials
302+
responses.add(
303+
responses.GET,
304+
"http://metadata.google.internal/computeMetadata/v1/instance/"
305+
"service-accounts/service-account@example.com/token",
306+
status=200,
307+
content_type="application/json",
308+
json={
309+
"access_token": "some-token",
310+
"expires_in": 3210,
311+
"token_type": "Bearer",
312+
},
313+
)
314+
315+
# mock sign blob endpoint
316+
signature = base64.b64encode(b"some-signature").decode("utf-8")
317+
responses.add(
318+
responses.POST,
319+
"https://iam.googleapis.com/v1/projects/-/serviceAccounts/"
320+
"service-account@example.com:signBlob?alt=json",
321+
status=200,
322+
content_type="application/json",
323+
json={"keyId": "some-key-id", "signature": signature},
324+
)
325+
326+
id_token = "{}.{}.{}".format(
327+
base64.b64encode(b'{"some":"some"}').decode("utf-8"),
328+
base64.b64encode(b'{"exp": 3210}').decode("utf-8"),
329+
base64.b64encode(b"token").decode("utf-8"),
330+
)
331+
332+
# mock id token endpoint
333+
responses.add(
334+
responses.POST,
335+
"https://www.googleapis.com/oauth2/v4/token",
336+
status=200,
337+
content_type="application/json",
338+
json={"id_token": id_token, "expiry": 3210},
339+
)
340+
341+
self.credentials = credentials.IDTokenCredentials(
342+
request=requests.Request(),
343+
service_account_email="service-account@example.com",
344+
target_audience="https://audience.com",
345+
)
346+
347+
self.credentials = self.credentials.with_target_audience("https://actually.not")
348+
349+
self.credentials.refresh(requests.Request())
350+
351+
assert self.credentials.token is not None
352+
273353
@mock.patch(
274354
"google.auth._helpers.utcnow",
275355
return_value=datetime.datetime.utcfromtimestamp(0),

0 commit comments

Comments
 (0)