Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit b8f48d0

Browse files
author
Jon Wayne Parrott
authored
Add jwt.Credentials.from_signing_credentials, remove serivce_account.Credentials.to_jwt_credentials (#120)
1 parent 0c5503e commit b8f48d0

5 files changed

Lines changed: 66 additions & 58 deletions

File tree

google/auth/jwt.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@
4747

4848
from google.auth import _helpers
4949
from google.auth import _service_account_info
50-
from google.auth import credentials
5150
from google.auth import crypt
51+
import google.auth.credentials
5252

5353

5454
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
@@ -239,8 +239,8 @@ def decode(token, certs=None, verify=True, audience=None):
239239
return payload
240240

241241

242-
class Credentials(credentials.Signing,
243-
credentials.Credentials):
242+
class Credentials(google.auth.credentials.Signing,
243+
google.auth.credentials.Credentials):
244244
"""Credentials that use a JWT as the bearer token.
245245
246246
These credentials require an "audience" claim. This claim identifies the
@@ -253,23 +253,24 @@ class Credentials(credentials.Signing,
253253
To create JWT credentials using a Google service account private key
254254
JSON file::
255255
256+
audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
256257
credentials = jwt.Credentials.from_service_account_file(
257258
'service-account.json',
258-
audience='https://speech.googleapis.com')
259+
audience=audience)
259260
260261
If you already have the service account file loaded and parsed::
261262
262263
service_account_info = json.load(open('service_account.json'))
263264
credentials = jwt.Credentials.from_service_account_info(
264265
service_account_info,
265-
audience='https://speech.googleapis.com')
266+
audience=audience)
266267
267268
Both helper methods pass on arguments to the constructor, so you can
268269
specify the JWT claims::
269270
270271
credentials = jwt.Credentials.from_service_account_file(
271272
'service-account.json',
272-
audience='https://speech.googleapis.com',
273+
audience=audience,
273274
additional_claims={'meta': 'data'})
274275
275276
You can also construct the credentials directly if you have a
@@ -279,13 +280,14 @@ class Credentials(credentials.Signing,
279280
signer,
280281
issuer='your-issuer',
281282
subject='your-subject',
282-
audience=''https://speech.googleapis.com'')
283+
audience=audience)
283284
284285
The claims are considered immutable. If you want to modify the claims,
285286
you can easily create another instance using :meth:`with_claims`::
286287
287-
new_credentials = credentials.with_claims(
288-
audience='https://vision.googleapis.com')
288+
new_audience = (
289+
'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
290+
new_credentials = credentials.with_claims(audience=new_audience)
289291
"""
290292

291293
def __init__(self, signer, issuer, subject, audience,
@@ -371,6 +373,41 @@ def from_service_account_file(cls, filename, **kwargs):
371373
filename, require=['client_email'])
372374
return cls._from_signer_and_info(signer, info, **kwargs)
373375

376+
@classmethod
377+
def from_signing_credentials(cls, credentials, audience, **kwargs):
378+
"""Creates a new :class:`google.auth.jwt.Credentials` instance from an
379+
existing :class:`google.auth.credentials.Signing` instance.
380+
381+
The new instance will use the same signer as the existing instance and
382+
will use the existing instance's signer email as the issuer and
383+
subject by default.
384+
385+
Example::
386+
387+
svc_creds = service_account.Credentials.from_service_account_file(
388+
'service_account.json')
389+
audience = (
390+
'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
391+
jwt_creds = jwt.Credentials.from_signing_credentials(
392+
svc_creds, audience=audience)
393+
394+
Args:
395+
credentials (google.auth.credentials.Signing): The credentials to
396+
use to construct the new credentials.
397+
audience (str): the `aud` claim. The intended audience for the
398+
credentials.
399+
kwargs: Additional arguments to pass to the constructor.
400+
401+
Returns:
402+
google.auth.jwt.Credentials: A new Credentials instance.
403+
"""
404+
kwargs.setdefault('issuer', credentials.signer_email)
405+
kwargs.setdefault('subject', credentials.signer_email)
406+
return cls(
407+
credentials.signer,
408+
audience=audience,
409+
**kwargs)
410+
374411
def with_claims(self, issuer=None, subject=None, audience=None,
375412
additional_claims=None):
376413
"""Returns a copy of these credentials with modified claims.
@@ -431,16 +468,16 @@ def refresh(self, request):
431468
# (pylint doesn't correctly recognize overridden methods.)
432469
self.token, self.expiry = self._make_jwt()
433470

434-
@_helpers.copy_docstring(credentials.Signing)
471+
@_helpers.copy_docstring(google.auth.credentials.Signing)
435472
def sign_bytes(self, message):
436473
return self._signer.sign(message)
437474

438475
@property
439-
@_helpers.copy_docstring(credentials.Signing)
476+
@_helpers.copy_docstring(google.auth.credentials.Signing)
440477
def signer_email(self):
441478
return self._issuer
442479

443480
@property
444-
@_helpers.copy_docstring(credentials.Signing)
481+
@_helpers.copy_docstring(google.auth.credentials.Signing)
445482
def signer(self):
446483
return self._signer

google/oauth2/service_account.py

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -204,38 +204,6 @@ def from_service_account_file(cls, filename, **kwargs):
204204
filename, require=['client_email', 'token_uri'])
205205
return cls._from_signer_and_info(signer, info, **kwargs)
206206

207-
def to_jwt_credentials(self, audience):
208-
"""Creates a :class:`google.auth.jwt.Credentials` instance from this
209-
instance.
210-
211-
The new instance will use the same private key as this instance and
212-
will use this instance's service account email as the issuer and
213-
subject.
214-
215-
This is the same as calling
216-
:meth:`jwt.Credentials.from_service_account_file` with the same
217-
file used to create these credentials::
218-
219-
svc_creds = service_account.Credentials.from_service_account_file(
220-
'service_account.json')
221-
jwt_from_svc = svc_credentials.to_jwt_credentials()
222-
# is the same as:
223-
jwt_creds = jwt.Credentials.from_service_account_file(
224-
'service_account.json')
225-
226-
Args:
227-
audience (str): the `aud` claim. The intended audience for the
228-
credentials.
229-
230-
Returns:
231-
google.auth.jwt.Credentials: A new Credentials instance.
232-
"""
233-
return jwt.Credentials(
234-
self._signer,
235-
issuer=self._service_account_email,
236-
subject=self._service_account_email,
237-
audience=audience)
238-
239207
@property
240208
def service_account_email(self):
241209
"""The service account email."""

system_tests/test_grpc.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import google.auth
1616
import google.auth.credentials
17+
import google.auth.jwt
1718
import google.auth.transport.grpc
1819
from google.cloud.gapic.pubsub.v1 import publisher_client
1920

@@ -42,7 +43,8 @@ def test_grpc_request_with_jwt_credentials(http_request):
4243
credentials, project_id = google.auth.default()
4344
audience = 'https://{}/google.pubsub.v1.Publisher'.format(
4445
publisher_client.PublisherClient.SERVICE_ADDRESS)
45-
credentials = credentials.to_jwt_credentials(
46+
credentials = google.auth.jwt.Credentials.from_signing_credentials(
47+
credentials,
4648
audience=audience)
4749

4850
channel = google.auth.transport.grpc.secure_authorized_channel(

tests/oauth2/test_service_account.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,19 +111,6 @@ def test_from_service_account_file_args(self):
111111
assert credentials._subject == subject
112112
assert credentials._additional_claims == additional_claims
113113

114-
def test_to_jwt_credentials(self):
115-
jwt_from_svc = self.credentials.to_jwt_credentials(
116-
audience=mock.sentinel.audience)
117-
jwt_from_info = jwt.Credentials.from_service_account_info(
118-
SERVICE_ACCOUNT_INFO,
119-
audience=mock.sentinel.audience)
120-
121-
assert isinstance(jwt_from_svc, jwt.Credentials)
122-
assert jwt_from_svc._signer.key_id == jwt_from_info._signer.key_id
123-
assert jwt_from_svc._issuer == jwt_from_info._issuer
124-
assert jwt_from_svc._subject == jwt_from_info._subject
125-
assert jwt_from_svc._audience == jwt_from_info._audience
126-
127114
def test_default_state(self):
128115
assert not self.credentials.valid
129116
# Expiration hasn't been set yet

tests/test_jwt.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,20 @@ def test_from_service_account_file_args(self):
258258
assert credentials._audience == self.AUDIENCE
259259
assert credentials._additional_claims == self.ADDITIONAL_CLAIMS
260260

261+
def test_from_signing_credentials(self):
262+
jwt_from_signing = self.credentials.from_signing_credentials(
263+
self.credentials,
264+
audience=mock.sentinel.new_audience)
265+
jwt_from_info = jwt.Credentials.from_service_account_info(
266+
SERVICE_ACCOUNT_INFO,
267+
audience=mock.sentinel.new_audience)
268+
269+
assert isinstance(jwt_from_signing, jwt.Credentials)
270+
assert jwt_from_signing._signer.key_id == jwt_from_info._signer.key_id
271+
assert jwt_from_signing._issuer == jwt_from_info._issuer
272+
assert jwt_from_signing._subject == jwt_from_info._subject
273+
assert jwt_from_signing._audience == jwt_from_info._audience
274+
261275
def test_default_state(self):
262276
assert not self.credentials.valid
263277
# Expiration hasn't been set yet

0 commit comments

Comments
 (0)