1# Copyright 2016 Google LLC 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""JSON Web Tokens 16 17Provides support for creating (encoding) and verifying (decoding) JWTs, 18especially JWTs generated and consumed by Google infrastructure. 19 20See `rfc7519`_ for more details on JWTs. 21 22To encode a JWT use :func:`encode`:: 23 24 from google.auth import crypt 25 from google.auth import jwt 26 27 signer = crypt.Signer(private_key) 28 payload = {'some': 'payload'} 29 encoded = jwt.encode(signer, payload) 30 31To decode a JWT and verify claims use :func:`decode`:: 32 33 claims = jwt.decode(encoded, certs=public_certs) 34 35You can also skip verification:: 36 37 claims = jwt.decode(encoded, verify=False) 38 39.. _rfc7519: https://tools.ietf.org/html/rfc7519 40 41""" 42 43try: 44 from collections.abc import Mapping 45# Python 2.7 compatibility 46except ImportError: # pragma: NO COVER 47 from collections import Mapping 48import copy 49import datetime 50import json 51 52import cachetools 53import six 54from six.moves import urllib 55 56from google.auth import _helpers 57from google.auth import _service_account_info 58from google.auth import crypt 59from google.auth import exceptions 60import google.auth.credentials 61 62try: 63 from google.auth.crypt import es256 64except ImportError: # pragma: NO COVER 65 es256 = None 66 67_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds 68_DEFAULT_MAX_CACHE_SIZE = 10 69_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier} 70_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"]) 71 72if es256 is not None: # pragma: NO COVER 73 _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier 74 75 76def encode(signer, payload, header=None, key_id=None): 77 """Make a signed JWT. 78 79 Args: 80 signer (google.auth.crypt.Signer): The signer used to sign the JWT. 81 payload (Mapping[str, str]): The JWT payload. 82 header (Mapping[str, str]): Additional JWT header payload. 83 key_id (str): The key id to add to the JWT header. If the 84 signer has a key id it will be used as the default. If this is 85 specified it will override the signer's key id. 86 87 Returns: 88 bytes: The encoded JWT. 89 """ 90 if header is None: 91 header = {} 92 93 if key_id is None: 94 key_id = signer.key_id 95 96 header.update({"typ": "JWT"}) 97 98 if "alg" not in header: 99 if es256 is not None and isinstance(signer, es256.ES256Signer): 100 header.update({"alg": "ES256"}) 101 else: 102 header.update({"alg": "RS256"}) 103 104 if key_id is not None: 105 header["kid"] = key_id 106 107 segments = [ 108 _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")), 109 _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")), 110 ] 111 112 signing_input = b".".join(segments) 113 signature = signer.sign(signing_input) 114 segments.append(_helpers.unpadded_urlsafe_b64encode(signature)) 115 116 return b".".join(segments) 117 118 119def _decode_jwt_segment(encoded_section): 120 """Decodes a single JWT segment.""" 121 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) 122 try: 123 return json.loads(section_bytes.decode("utf-8")) 124 except ValueError as caught_exc: 125 new_exc = ValueError("Can't parse segment: {0}".format(section_bytes)) 126 six.raise_from(new_exc, caught_exc) 127 128 129def _unverified_decode(token): 130 """Decodes a token and does no verification. 131 132 Args: 133 token (Union[str, bytes]): The encoded JWT. 134 135 Returns: 136 Tuple[str, str, str, str]: header, payload, signed_section, and 137 signature. 138 139 Raises: 140 ValueError: if there are an incorrect amount of segments in the token. 141 """ 142 token = _helpers.to_bytes(token) 143 144 if token.count(b".") != 2: 145 raise ValueError("Wrong number of segments in token: {0}".format(token)) 146 147 encoded_header, encoded_payload, signature = token.split(b".") 148 signed_section = encoded_header + b"." + encoded_payload 149 signature = _helpers.padded_urlsafe_b64decode(signature) 150 151 # Parse segments 152 header = _decode_jwt_segment(encoded_header) 153 payload = _decode_jwt_segment(encoded_payload) 154 155 return header, payload, signed_section, signature 156 157 158def decode_header(token): 159 """Return the decoded header of a token. 160 161 No verification is done. This is useful to extract the key id from 162 the header in order to acquire the appropriate certificate to verify 163 the token. 164 165 Args: 166 token (Union[str, bytes]): the encoded JWT. 167 168 Returns: 169 Mapping: The decoded JWT header. 170 """ 171 header, _, _, _ = _unverified_decode(token) 172 return header 173 174 175def _verify_iat_and_exp(payload, clock_skew_in_seconds=0): 176 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token 177 payload. 178 179 Args: 180 payload (Mapping[str, str]): The JWT payload. 181 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` 182 validation. 183 184 Raises: 185 ValueError: if any checks failed. 186 """ 187 now = _helpers.datetime_to_secs(_helpers.utcnow()) 188 189 # Make sure the iat and exp claims are present. 190 for key in ("iat", "exp"): 191 if key not in payload: 192 raise ValueError("Token does not contain required claim {}".format(key)) 193 194 # Make sure the token wasn't issued in the future. 195 iat = payload["iat"] 196 # Err on the side of accepting a token that is slightly early to account 197 # for clock skew. 198 earliest = iat - clock_skew_in_seconds 199 if now < earliest: 200 raise ValueError( 201 "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format( 202 now, iat 203 ) 204 ) 205 206 # Make sure the token wasn't issued in the past. 207 exp = payload["exp"] 208 # Err on the side of accepting a token that is slightly out of date 209 # to account for clow skew. 210 latest = exp + clock_skew_in_seconds 211 if latest < now: 212 raise ValueError("Token expired, {} < {}".format(latest, now)) 213 214 215def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0): 216 """Decode and verify a JWT. 217 218 Args: 219 token (str): The encoded JWT. 220 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The 221 certificate used to validate the JWT signature. If bytes or string, 222 it must the the public key certificate in PEM format. If a mapping, 223 it must be a mapping of key IDs to public key certificates in PEM 224 format. The mapping must contain the same key ID that's specified 225 in the token's header. 226 verify (bool): Whether to perform signature and claim validation. 227 Verification is done by default. 228 audience (str or list): The audience claim, 'aud', that this JWT should 229 contain. Or a list of audience claims. If None then the JWT's 'aud' 230 parameter is not verified. 231 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp` 232 validation. 233 234 Returns: 235 Mapping[str, str]: The deserialized JSON payload in the JWT. 236 237 Raises: 238 ValueError: if any verification checks failed. 239 """ 240 header, payload, signed_section, signature = _unverified_decode(token) 241 242 if not verify: 243 return payload 244 245 # Pluck the key id and algorithm from the header and make sure we have 246 # a verifier that can support it. 247 key_alg = header.get("alg") 248 key_id = header.get("kid") 249 250 try: 251 verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg] 252 except KeyError as exc: 253 if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS: 254 six.raise_from( 255 ValueError( 256 "The key algorithm {} requires the cryptography package " 257 "to be installed.".format(key_alg) 258 ), 259 exc, 260 ) 261 else: 262 six.raise_from( 263 ValueError("Unsupported signature algorithm {}".format(key_alg)), exc 264 ) 265 266 # If certs is specified as a dictionary of key IDs to certificates, then 267 # use the certificate identified by the key ID in the token header. 268 if isinstance(certs, Mapping): 269 if key_id: 270 if key_id not in certs: 271 raise ValueError("Certificate for key id {} not found.".format(key_id)) 272 certs_to_check = [certs[key_id]] 273 # If there's no key id in the header, check against all of the certs. 274 else: 275 certs_to_check = certs.values() 276 else: 277 certs_to_check = certs 278 279 # Verify that the signature matches the message. 280 if not crypt.verify_signature( 281 signed_section, signature, certs_to_check, verifier_cls 282 ): 283 raise ValueError("Could not verify token signature.") 284 285 # Verify the issued at and created times in the payload. 286 _verify_iat_and_exp(payload, clock_skew_in_seconds) 287 288 # Check audience. 289 if audience is not None: 290 claim_audience = payload.get("aud") 291 if isinstance(audience, str): 292 audience = [audience] 293 if claim_audience not in audience: 294 raise ValueError( 295 "Token has wrong audience {}, expected one of {}".format( 296 claim_audience, audience 297 ) 298 ) 299 300 return payload 301 302 303class Credentials( 304 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject 305): 306 """Credentials that use a JWT as the bearer token. 307 308 These credentials require an "audience" claim. This claim identifies the 309 intended recipient of the bearer token. 310 311 The constructor arguments determine the claims for the JWT that is 312 sent with requests. Usually, you'll construct these credentials with 313 one of the helper constructors as shown in the next section. 314 315 To create JWT credentials using a Google service account private key 316 JSON file:: 317 318 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher' 319 credentials = jwt.Credentials.from_service_account_file( 320 'service-account.json', 321 audience=audience) 322 323 If you already have the service account file loaded and parsed:: 324 325 service_account_info = json.load(open('service_account.json')) 326 credentials = jwt.Credentials.from_service_account_info( 327 service_account_info, 328 audience=audience) 329 330 Both helper methods pass on arguments to the constructor, so you can 331 specify the JWT claims:: 332 333 credentials = jwt.Credentials.from_service_account_file( 334 'service-account.json', 335 audience=audience, 336 additional_claims={'meta': 'data'}) 337 338 You can also construct the credentials directly if you have a 339 :class:`~google.auth.crypt.Signer` instance:: 340 341 credentials = jwt.Credentials( 342 signer, 343 issuer='your-issuer', 344 subject='your-subject', 345 audience=audience) 346 347 The claims are considered immutable. If you want to modify the claims, 348 you can easily create another instance using :meth:`with_claims`:: 349 350 new_audience = ( 351 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber') 352 new_credentials = credentials.with_claims(audience=new_audience) 353 """ 354 355 def __init__( 356 self, 357 signer, 358 issuer, 359 subject, 360 audience, 361 additional_claims=None, 362 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, 363 quota_project_id=None, 364 ): 365 """ 366 Args: 367 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 368 issuer (str): The `iss` claim. 369 subject (str): The `sub` claim. 370 audience (str): the `aud` claim. The intended audience for the 371 credentials. 372 additional_claims (Mapping[str, str]): Any additional claims for 373 the JWT payload. 374 token_lifetime (int): The amount of time in seconds for 375 which the token is valid. Defaults to 1 hour. 376 quota_project_id (Optional[str]): The project ID used for quota 377 and billing. 378 """ 379 super(Credentials, self).__init__() 380 self._signer = signer 381 self._issuer = issuer 382 self._subject = subject 383 self._audience = audience 384 self._token_lifetime = token_lifetime 385 self._quota_project_id = quota_project_id 386 387 if additional_claims is None: 388 additional_claims = {} 389 390 self._additional_claims = additional_claims 391 392 @classmethod 393 def _from_signer_and_info(cls, signer, info, **kwargs): 394 """Creates a Credentials instance from a signer and service account 395 info. 396 397 Args: 398 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 399 info (Mapping[str, str]): The service account info. 400 kwargs: Additional arguments to pass to the constructor. 401 402 Returns: 403 google.auth.jwt.Credentials: The constructed credentials. 404 405 Raises: 406 ValueError: If the info is not in the expected format. 407 """ 408 kwargs.setdefault("subject", info["client_email"]) 409 kwargs.setdefault("issuer", info["client_email"]) 410 return cls(signer, **kwargs) 411 412 @classmethod 413 def from_service_account_info(cls, info, **kwargs): 414 """Creates an Credentials instance from a dictionary. 415 416 Args: 417 info (Mapping[str, str]): The service account info in Google 418 format. 419 kwargs: Additional arguments to pass to the constructor. 420 421 Returns: 422 google.auth.jwt.Credentials: The constructed credentials. 423 424 Raises: 425 ValueError: If the info is not in the expected format. 426 """ 427 signer = _service_account_info.from_dict(info, require=["client_email"]) 428 return cls._from_signer_and_info(signer, info, **kwargs) 429 430 @classmethod 431 def from_service_account_file(cls, filename, **kwargs): 432 """Creates a Credentials instance from a service account .json file 433 in Google format. 434 435 Args: 436 filename (str): The path to the service account .json file. 437 kwargs: Additional arguments to pass to the constructor. 438 439 Returns: 440 google.auth.jwt.Credentials: The constructed credentials. 441 """ 442 info, signer = _service_account_info.from_filename( 443 filename, require=["client_email"] 444 ) 445 return cls._from_signer_and_info(signer, info, **kwargs) 446 447 @classmethod 448 def from_signing_credentials(cls, credentials, audience, **kwargs): 449 """Creates a new :class:`google.auth.jwt.Credentials` instance from an 450 existing :class:`google.auth.credentials.Signing` instance. 451 452 The new instance will use the same signer as the existing instance and 453 will use the existing instance's signer email as the issuer and 454 subject by default. 455 456 Example:: 457 458 svc_creds = service_account.Credentials.from_service_account_file( 459 'service_account.json') 460 audience = ( 461 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher') 462 jwt_creds = jwt.Credentials.from_signing_credentials( 463 svc_creds, audience=audience) 464 465 Args: 466 credentials (google.auth.credentials.Signing): The credentials to 467 use to construct the new credentials. 468 audience (str): the `aud` claim. The intended audience for the 469 credentials. 470 kwargs: Additional arguments to pass to the constructor. 471 472 Returns: 473 google.auth.jwt.Credentials: A new Credentials instance. 474 """ 475 kwargs.setdefault("issuer", credentials.signer_email) 476 kwargs.setdefault("subject", credentials.signer_email) 477 return cls(credentials.signer, audience=audience, **kwargs) 478 479 def with_claims( 480 self, issuer=None, subject=None, audience=None, additional_claims=None 481 ): 482 """Returns a copy of these credentials with modified claims. 483 484 Args: 485 issuer (str): The `iss` claim. If unspecified the current issuer 486 claim will be used. 487 subject (str): The `sub` claim. If unspecified the current subject 488 claim will be used. 489 audience (str): the `aud` claim. If unspecified the current 490 audience claim will be used. 491 additional_claims (Mapping[str, str]): Any additional claims for 492 the JWT payload. This will be merged with the current 493 additional claims. 494 495 Returns: 496 google.auth.jwt.Credentials: A new credentials instance. 497 """ 498 new_additional_claims = copy.deepcopy(self._additional_claims) 499 new_additional_claims.update(additional_claims or {}) 500 501 return self.__class__( 502 self._signer, 503 issuer=issuer if issuer is not None else self._issuer, 504 subject=subject if subject is not None else self._subject, 505 audience=audience if audience is not None else self._audience, 506 additional_claims=new_additional_claims, 507 quota_project_id=self._quota_project_id, 508 ) 509 510 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) 511 def with_quota_project(self, quota_project_id): 512 return self.__class__( 513 self._signer, 514 issuer=self._issuer, 515 subject=self._subject, 516 audience=self._audience, 517 additional_claims=self._additional_claims, 518 quota_project_id=quota_project_id, 519 ) 520 521 def _make_jwt(self): 522 """Make a signed JWT. 523 524 Returns: 525 Tuple[bytes, datetime]: The encoded JWT and the expiration. 526 """ 527 now = _helpers.utcnow() 528 lifetime = datetime.timedelta(seconds=self._token_lifetime) 529 expiry = now + lifetime 530 531 payload = { 532 "iss": self._issuer, 533 "sub": self._subject, 534 "iat": _helpers.datetime_to_secs(now), 535 "exp": _helpers.datetime_to_secs(expiry), 536 } 537 if self._audience: 538 payload["aud"] = self._audience 539 540 payload.update(self._additional_claims) 541 542 jwt = encode(self._signer, payload) 543 544 return jwt, expiry 545 546 def refresh(self, request): 547 """Refreshes the access token. 548 549 Args: 550 request (Any): Unused. 551 """ 552 # pylint: disable=unused-argument 553 # (pylint doesn't correctly recognize overridden methods.) 554 self.token, self.expiry = self._make_jwt() 555 556 @_helpers.copy_docstring(google.auth.credentials.Signing) 557 def sign_bytes(self, message): 558 return self._signer.sign(message) 559 560 @property 561 @_helpers.copy_docstring(google.auth.credentials.Signing) 562 def signer_email(self): 563 return self._issuer 564 565 @property 566 @_helpers.copy_docstring(google.auth.credentials.Signing) 567 def signer(self): 568 return self._signer 569 570 571class OnDemandCredentials( 572 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject 573): 574 """On-demand JWT credentials. 575 576 Like :class:`Credentials`, this class uses a JWT as the bearer token for 577 authentication. However, this class does not require the audience at 578 construction time. Instead, it will generate a new token on-demand for 579 each request using the request URI as the audience. It caches tokens 580 so that multiple requests to the same URI do not incur the overhead 581 of generating a new token every time. 582 583 This behavior is especially useful for `gRPC`_ clients. A gRPC service may 584 have multiple audience and gRPC clients may not know all of the audiences 585 required for accessing a particular service. With these credentials, 586 no knowledge of the audiences is required ahead of time. 587 588 .. _grpc: http://www.grpc.io/ 589 """ 590 591 def __init__( 592 self, 593 signer, 594 issuer, 595 subject, 596 additional_claims=None, 597 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, 598 max_cache_size=_DEFAULT_MAX_CACHE_SIZE, 599 quota_project_id=None, 600 ): 601 """ 602 Args: 603 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 604 issuer (str): The `iss` claim. 605 subject (str): The `sub` claim. 606 additional_claims (Mapping[str, str]): Any additional claims for 607 the JWT payload. 608 token_lifetime (int): The amount of time in seconds for 609 which the token is valid. Defaults to 1 hour. 610 max_cache_size (int): The maximum number of JWT tokens to keep in 611 cache. Tokens are cached using :class:`cachetools.LRUCache`. 612 quota_project_id (Optional[str]): The project ID used for quota 613 and billing. 614 615 """ 616 super(OnDemandCredentials, self).__init__() 617 self._signer = signer 618 self._issuer = issuer 619 self._subject = subject 620 self._token_lifetime = token_lifetime 621 self._quota_project_id = quota_project_id 622 623 if additional_claims is None: 624 additional_claims = {} 625 626 self._additional_claims = additional_claims 627 self._cache = cachetools.LRUCache(maxsize=max_cache_size) 628 629 @classmethod 630 def _from_signer_and_info(cls, signer, info, **kwargs): 631 """Creates an OnDemandCredentials instance from a signer and service 632 account info. 633 634 Args: 635 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 636 info (Mapping[str, str]): The service account info. 637 kwargs: Additional arguments to pass to the constructor. 638 639 Returns: 640 google.auth.jwt.OnDemandCredentials: The constructed credentials. 641 642 Raises: 643 ValueError: If the info is not in the expected format. 644 """ 645 kwargs.setdefault("subject", info["client_email"]) 646 kwargs.setdefault("issuer", info["client_email"]) 647 return cls(signer, **kwargs) 648 649 @classmethod 650 def from_service_account_info(cls, info, **kwargs): 651 """Creates an OnDemandCredentials instance from a dictionary. 652 653 Args: 654 info (Mapping[str, str]): The service account info in Google 655 format. 656 kwargs: Additional arguments to pass to the constructor. 657 658 Returns: 659 google.auth.jwt.OnDemandCredentials: The constructed credentials. 660 661 Raises: 662 ValueError: If the info is not in the expected format. 663 """ 664 signer = _service_account_info.from_dict(info, require=["client_email"]) 665 return cls._from_signer_and_info(signer, info, **kwargs) 666 667 @classmethod 668 def from_service_account_file(cls, filename, **kwargs): 669 """Creates an OnDemandCredentials instance from a service account .json 670 file in Google format. 671 672 Args: 673 filename (str): The path to the service account .json file. 674 kwargs: Additional arguments to pass to the constructor. 675 676 Returns: 677 google.auth.jwt.OnDemandCredentials: The constructed credentials. 678 """ 679 info, signer = _service_account_info.from_filename( 680 filename, require=["client_email"] 681 ) 682 return cls._from_signer_and_info(signer, info, **kwargs) 683 684 @classmethod 685 def from_signing_credentials(cls, credentials, **kwargs): 686 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance 687 from an existing :class:`google.auth.credentials.Signing` instance. 688 689 The new instance will use the same signer as the existing instance and 690 will use the existing instance's signer email as the issuer and 691 subject by default. 692 693 Example:: 694 695 svc_creds = service_account.Credentials.from_service_account_file( 696 'service_account.json') 697 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials( 698 svc_creds) 699 700 Args: 701 credentials (google.auth.credentials.Signing): The credentials to 702 use to construct the new credentials. 703 kwargs: Additional arguments to pass to the constructor. 704 705 Returns: 706 google.auth.jwt.Credentials: A new Credentials instance. 707 """ 708 kwargs.setdefault("issuer", credentials.signer_email) 709 kwargs.setdefault("subject", credentials.signer_email) 710 return cls(credentials.signer, **kwargs) 711 712 def with_claims(self, issuer=None, subject=None, additional_claims=None): 713 """Returns a copy of these credentials with modified claims. 714 715 Args: 716 issuer (str): The `iss` claim. If unspecified the current issuer 717 claim will be used. 718 subject (str): The `sub` claim. If unspecified the current subject 719 claim will be used. 720 additional_claims (Mapping[str, str]): Any additional claims for 721 the JWT payload. This will be merged with the current 722 additional claims. 723 724 Returns: 725 google.auth.jwt.OnDemandCredentials: A new credentials instance. 726 """ 727 new_additional_claims = copy.deepcopy(self._additional_claims) 728 new_additional_claims.update(additional_claims or {}) 729 730 return self.__class__( 731 self._signer, 732 issuer=issuer if issuer is not None else self._issuer, 733 subject=subject if subject is not None else self._subject, 734 additional_claims=new_additional_claims, 735 max_cache_size=self._cache.maxsize, 736 quota_project_id=self._quota_project_id, 737 ) 738 739 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject) 740 def with_quota_project(self, quota_project_id): 741 742 return self.__class__( 743 self._signer, 744 issuer=self._issuer, 745 subject=self._subject, 746 additional_claims=self._additional_claims, 747 max_cache_size=self._cache.maxsize, 748 quota_project_id=quota_project_id, 749 ) 750 751 @property 752 def valid(self): 753 """Checks the validity of the credentials. 754 755 These credentials are always valid because it generates tokens on 756 demand. 757 """ 758 return True 759 760 def _make_jwt_for_audience(self, audience): 761 """Make a new JWT for the given audience. 762 763 Args: 764 audience (str): The intended audience. 765 766 Returns: 767 Tuple[bytes, datetime]: The encoded JWT and the expiration. 768 """ 769 now = _helpers.utcnow() 770 lifetime = datetime.timedelta(seconds=self._token_lifetime) 771 expiry = now + lifetime 772 773 payload = { 774 "iss": self._issuer, 775 "sub": self._subject, 776 "iat": _helpers.datetime_to_secs(now), 777 "exp": _helpers.datetime_to_secs(expiry), 778 "aud": audience, 779 } 780 781 payload.update(self._additional_claims) 782 783 jwt = encode(self._signer, payload) 784 785 return jwt, expiry 786 787 def _get_jwt_for_audience(self, audience): 788 """Get a JWT For a given audience. 789 790 If there is already an existing, non-expired token in the cache for 791 the audience, that token is used. Otherwise, a new token will be 792 created. 793 794 Args: 795 audience (str): The intended audience. 796 797 Returns: 798 bytes: The encoded JWT. 799 """ 800 token, expiry = self._cache.get(audience, (None, None)) 801 802 if token is None or expiry < _helpers.utcnow(): 803 token, expiry = self._make_jwt_for_audience(audience) 804 self._cache[audience] = token, expiry 805 806 return token 807 808 def refresh(self, request): 809 """Raises an exception, these credentials can not be directly 810 refreshed. 811 812 Args: 813 request (Any): Unused. 814 815 Raises: 816 google.auth.RefreshError 817 """ 818 # pylint: disable=unused-argument 819 # (pylint doesn't correctly recognize overridden methods.) 820 raise exceptions.RefreshError( 821 "OnDemandCredentials can not be directly refreshed." 822 ) 823 824 def before_request(self, request, method, url, headers): 825 """Performs credential-specific before request logic. 826 827 Args: 828 request (Any): Unused. JWT credentials do not need to make an 829 HTTP request to refresh. 830 method (str): The request's HTTP method. 831 url (str): The request's URI. This is used as the audience claim 832 when generating the JWT. 833 headers (Mapping): The request's headers. 834 """ 835 # pylint: disable=unused-argument 836 # (pylint doesn't correctly recognize overridden methods.) 837 parts = urllib.parse.urlsplit(url) 838 # Strip query string and fragment 839 audience = urllib.parse.urlunsplit( 840 (parts.scheme, parts.netloc, parts.path, "", "") 841 ) 842 token = self._get_jwt_for_audience(audience) 843 self.apply(headers, token=token) 844 845 @_helpers.copy_docstring(google.auth.credentials.Signing) 846 def sign_bytes(self, message): 847 return self._signer.sign(message) 848 849 @property 850 @_helpers.copy_docstring(google.auth.credentials.Signing) 851 def signer_email(self): 852 return self._issuer 853 854 @property 855 @_helpers.copy_docstring(google.auth.credentials.Signing) 856 def signer(self): 857 return self._signer 858