@@ -230,7 +230,7 @@ def requires_scopes(self):
230230
231231 @_helpers .copy_docstring (credentials .Scoped )
232232 def with_scopes (self , scopes ):
233- return Credentials (
233+ return self . __class__ (
234234 self ._signer ,
235235 service_account_email = self ._service_account_email ,
236236 scopes = scopes ,
@@ -249,7 +249,7 @@ def with_subject(self, subject):
249249 google.auth.service_account.Credentials: A new credentials
250250 instance.
251251 """
252- return Credentials (
252+ return self . __class__ (
253253 self ._signer ,
254254 service_account_email = self ._service_account_email ,
255255 scopes = self ._scopes ,
@@ -273,7 +273,7 @@ def with_claims(self, additional_claims):
273273 new_additional_claims = copy .deepcopy (self ._additional_claims )
274274 new_additional_claims .update (additional_claims or {})
275275
276- return Credentials (
276+ return self . __class__ (
277277 self ._signer ,
278278 service_account_email = self ._service_account_email ,
279279 scopes = self ._scopes ,
@@ -336,3 +336,207 @@ def signer(self):
336336 @_helpers .copy_docstring (credentials .Signing )
337337 def signer_email (self ):
338338 return self ._service_account_email
339+
340+
341+ class IDTokenCredentials (credentials .Signing , credentials .Credentials ):
342+ """Open ID Connect ID Token-based service account credentials.
343+
344+ These credentials are largely similar to :class:`.Credentials`, but instead
345+ of using an OAuth 2.0 Access Token as the bearer token, they use an Open
346+ ID Connect ID Token as the bearer token. These credentials are useful when
347+ communicating to services that require ID Tokens and can not accept access
348+ tokens.
349+
350+ Usually, you'll create these credentials with one of the helper
351+ constructors. To create credentials using a Google service account
352+ private key JSON file::
353+
354+ credentials = (
355+ service_account.IDTokenCredentials.from_service_account_file(
356+ 'service-account.json'))
357+
358+ Or if you already have the service account file loaded::
359+
360+ service_account_info = json.load(open('service_account.json'))
361+ credentials = (
362+ service_account.IDTokenCredentials.from_service_account_info(
363+ service_account_info))
364+
365+ Both helper methods pass on arguments to the constructor, so you can
366+ specify additional scopes and a subject if necessary::
367+
368+ credentials = (
369+ service_account.IDTokenCredentials.from_service_account_file(
370+ 'service-account.json',
371+ scopes=['email'],
372+ subject='user@example.com'))
373+ `
374+ The credentials are considered immutable. If you want to modify the scopes
375+ or the subject used for delegation, use :meth:`with_scopes` or
376+ :meth:`with_subject`::
377+
378+ scoped_credentials = credentials.with_scopes(['email'])
379+ delegated_credentials = credentials.with_subject(subject)
380+
381+ """
382+ def __init__ (self , signer , service_account_email , token_uri ,
383+ target_audience , additional_claims = None ):
384+ """
385+ Args:
386+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
387+ service_account_email (str): The service account's email.
388+ token_uri (str): The OAuth 2.0 Token URI.
389+ target_audience (str): The intended audience for these credentials,
390+ used when requesting the ID Token. The ID Token's ``aud`` claim
391+ will be set to this string.
392+ additional_claims (Mapping[str, str]): Any additional claims for
393+ the JWT assertion used in the authorization grant.
394+
395+ .. note:: Typically one of the helper constructors
396+ :meth:`from_service_account_file` or
397+ :meth:`from_service_account_info` are used instead of calling the
398+ constructor directly.
399+ """
400+ super (IDTokenCredentials , self ).__init__ ()
401+ self ._signer = signer
402+ self ._service_account_email = service_account_email
403+ self ._token_uri = token_uri
404+ self ._target_audience = target_audience
405+
406+ if additional_claims is not None :
407+ self ._additional_claims = additional_claims
408+ else :
409+ self ._additional_claims = {}
410+
411+ @classmethod
412+ def _from_signer_and_info (cls , signer , info , ** kwargs ):
413+ """Creates a credentials instance from a signer and service account
414+ info.
415+
416+ Args:
417+ signer (google.auth.crypt.Signer): The signer used to sign JWTs.
418+ info (Mapping[str, str]): The service account info.
419+ kwargs: Additional arguments to pass to the constructor.
420+
421+ Returns:
422+ google.auth.jwt.IDTokenCredentials: The constructed credentials.
423+
424+ Raises:
425+ ValueError: If the info is not in the expected format.
426+ """
427+ kwargs .setdefault ('service_account_email' , info ['client_email' ])
428+ kwargs .setdefault ('token_uri' , info ['token_uri' ])
429+ return cls (signer , ** kwargs )
430+
431+ @classmethod
432+ def from_service_account_info (cls , info , ** kwargs ):
433+ """Creates a credentials instance from parsed service account info.
434+
435+ Args:
436+ info (Mapping[str, str]): The service account info in Google
437+ format.
438+ kwargs: Additional arguments to pass to the constructor.
439+
440+ Returns:
441+ google.auth.service_account.IDTokenCredentials: The constructed
442+ credentials.
443+
444+ Raises:
445+ ValueError: If the info is not in the expected format.
446+ """
447+ signer = _service_account_info .from_dict (
448+ info , require = ['client_email' , 'token_uri' ])
449+ return cls ._from_signer_and_info (signer , info , ** kwargs )
450+
451+ @classmethod
452+ def from_service_account_file (cls , filename , ** kwargs ):
453+ """Creates a credentials instance from a service account json file.
454+
455+ Args:
456+ filename (str): The path to the service account json file.
457+ kwargs: Additional arguments to pass to the constructor.
458+
459+ Returns:
460+ google.auth.service_account.IDTokenCredentials: The constructed
461+ credentials.
462+ """
463+ info , signer = _service_account_info .from_filename (
464+ filename , require = ['client_email' , 'token_uri' ])
465+ return cls ._from_signer_and_info (signer , info , ** kwargs )
466+
467+ def with_target_audience (self , target_audience ):
468+ """Create a copy of these credentials with the specified target
469+ audience.
470+
471+ Args:
472+ target_audience (str): The intended audience for these credentials,
473+ used when requesting the ID Token.
474+
475+ Returns:
476+ google.auth.service_account.IDTokenCredentials: A new credentials
477+ instance.
478+ """
479+ return self .__class__ (
480+ self ._signer ,
481+ service_account_email = self ._service_account_email ,
482+ token_uri = self ._token_uri ,
483+ target_audience = target_audience ,
484+ additional_claims = self ._additional_claims .copy ())
485+
486+ def _make_authorization_grant_assertion (self ):
487+ """Create the OAuth 2.0 assertion.
488+
489+ This assertion is used during the OAuth 2.0 grant to acquire an
490+ ID token.
491+
492+ Returns:
493+ bytes: The authorization grant assertion.
494+ """
495+ now = _helpers .utcnow ()
496+ lifetime = datetime .timedelta (seconds = _DEFAULT_TOKEN_LIFETIME_SECS )
497+ expiry = now + lifetime
498+
499+ payload = {
500+ 'iat' : _helpers .datetime_to_secs (now ),
501+ 'exp' : _helpers .datetime_to_secs (expiry ),
502+ # The issuer must be the service account email.
503+ 'iss' : self .service_account_email ,
504+ # The audience must be the auth token endpoint's URI
505+ 'aud' : self ._token_uri ,
506+ # The target audience specifies which service the ID token is
507+ # intended for.
508+ 'target_audience' : self ._target_audience
509+ }
510+
511+ payload .update (self ._additional_claims )
512+
513+ token = jwt .encode (self ._signer , payload )
514+
515+ return token
516+
517+ @_helpers .copy_docstring (credentials .Credentials )
518+ def refresh (self , request ):
519+ assertion = self ._make_authorization_grant_assertion ()
520+ access_token , expiry , _ = _client .id_token_jwt_grant (
521+ request , self ._token_uri , assertion )
522+ self .token = access_token
523+ self .expiry = expiry
524+
525+ @property
526+ def service_account_email (self ):
527+ """The service account email."""
528+ return self ._service_account_email
529+
530+ @_helpers .copy_docstring (credentials .Signing )
531+ def sign_bytes (self , message ):
532+ return self ._signer .sign (message )
533+
534+ @property
535+ @_helpers .copy_docstring (credentials .Signing )
536+ def signer (self ):
537+ return self ._signer
538+
539+ @property
540+ @_helpers .copy_docstring (credentials .Signing )
541+ def signer_email (self ):
542+ return self ._service_account_email
0 commit comments