blob: d56559510791b47dbb02225ea198acecb6c4c1dd [file] [log] [blame]
C.J. Collier37141e42020-02-13 13:49:49 -08001# Copyright 2016 Google LLC
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -07002#
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
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070022To encode a JWT use :func:`encode`::
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070023
Marco Rougeth7e1270b2018-02-28 20:13:56 -030024 from google.auth import crypt
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070025 from google.auth import jwt
26
27 signer = crypt.Signer(private_key)
28 payload = {'some': 'payload'}
29 encoded = jwt.encode(signer, payload)
30
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070031To decode a JWT and verify claims use :func:`decode`::
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070032
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
arithmetic17285bd5ccf2021-10-21 15:25:46 -070043try:
44 from collections.abc import Mapping
45# Python 2.7 compatibility
46except ImportError: # pragma: NO COVER
47 from collections import Mapping
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -070048import copy
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -070049import datetime
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070050import json
51
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070052import cachetools
arithmetic17285bd5ccf2021-10-21 15:25:46 -070053import six
54from six.moves import urllib
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070055
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070056from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070057from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070058from google.auth import crypt
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070059from google.auth import exceptions
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080060import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070061
Thea Flowerse290a3d2020-04-01 10:11:42 -070062try:
63 from google.auth.crypt import es256
64except ImportError: # pragma: NO COVER
65 es256 = None
66
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070067_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070068_DEFAULT_MAX_CACHE_SIZE = 10
Thea Flowerse290a3d2020-04-01 10:11:42 -070069_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier}
arithmetic1728866d9262020-05-05 12:53:37 -070070_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"])
Thea Flowerse290a3d2020-04-01 10:11:42 -070071
72if es256 is not None: # pragma: NO COVER
73 _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070074
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.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070081 payload (Mapping[str, str]): The JWT payload.
82 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070083 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
Thea Flowerse290a3d2020-04-01 10:11:42 -070096 header.update({"typ": "JWT"})
97
arithmetic17280a837062021-04-08 10:58:38 -070098 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"})
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700103
104 if key_id is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700105 header["kid"] = key_id
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700106
107 segments = [
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700108 _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")),
109 _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")),
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700110 ]
111
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700112 signing_input = b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700113 signature = signer.sign(signing_input)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700114 segments.append(_helpers.unpadded_urlsafe_b64encode(signature))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700115
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700116 return b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700117
118
119def _decode_jwt_segment(encoded_section):
120 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800121 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700122 try:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700123 return json.loads(section_bytes.decode("utf-8"))
Danny Hermes895e3692017-11-09 11:35:57 -0800124 except ValueError as caught_exc:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700125 new_exc = ValueError("Can't parse segment: {0}".format(section_bytes))
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700126 six.raise_from(new_exc, caught_exc)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700127
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:
Danny Hermes48c85f72016-11-08 09:30:44 -0800136 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700137 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
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700144 if token.count(b".") != 2:
145 raise ValueError("Wrong number of segments in token: {0}".format(token))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700146
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700147 encoded_header, encoded_payload, signature = token.split(b".")
148 signed_section = encoded_header + b"." + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800149 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700150
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
arithmetic1728738611b2021-09-09 17:09:54 -0700175def _verify_iat_and_exp(payload, clock_skew_in_seconds=0):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700176 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700177 payload.
178
179 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700180 payload (Mapping[str, str]): The JWT payload.
arithmetic1728738611b2021-09-09 17:09:54 -0700181 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
182 validation.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700183
184 Raises:
185 ValueError: if any checks failed.
186 """
187 now = _helpers.datetime_to_secs(_helpers.utcnow())
188
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700189 # Make sure the iat and exp claims are present.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700190 for key in ("iat", "exp"):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700191 if key not in payload:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700192 raise ValueError("Token does not contain required claim {}".format(key))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700193
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700194 # Make sure the token wasn't issued in the future.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700195 iat = payload["iat"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700196 # Err on the side of accepting a token that is slightly early to account
197 # for clock skew.
arithmetic1728738611b2021-09-09 17:09:54 -0700198 earliest = iat - clock_skew_in_seconds
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700199 if now < earliest:
Liron Newman45c44912021-09-07 23:07:07 +0100200 raise ValueError(
201 "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format(
202 now, iat
203 )
204 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700205
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700206 # Make sure the token wasn't issued in the past.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700207 exp = payload["exp"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700208 # Err on the side of accepting a token that is slightly out of date
209 # to account for clow skew.
arithmetic1728738611b2021-09-09 17:09:54 -0700210 latest = exp + clock_skew_in_seconds
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700211 if latest < now:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700212 raise ValueError("Token expired, {} < {}".format(latest, now))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700213
214
arithmetic1728738611b2021-09-09 17:09:54 -0700215def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700216 """Decode and verify a JWT.
217
218 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700219 token (str): The encoded JWT.
220 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
Tianzi Cai2c6ad782019-03-29 13:49:06 -0700221 certificate used to validate the JWT signature. If bytes or string,
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700222 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.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700226 verify (bool): Whether to perform signature and claim validation.
227 Verification is done by default.
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400228 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.
arithmetic1728738611b2021-09-09 17:09:54 -0700231 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
232 validation.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700233
234 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700235 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700236
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
Thea Flowerse290a3d2020-04-01 10:11:42 -0700245 # 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]
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700252 except KeyError as exc:
Thea Flowerse290a3d2020-04-01 10:11:42 -0700253 if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700254 six.raise_from(
255 ValueError(
256 "The key algorithm {} requires the cryptography package "
257 "to be installed.".format(key_alg)
258 ),
259 exc,
Thea Flowerse290a3d2020-04-01 10:11:42 -0700260 )
261 else:
arithmetic17285bd5ccf2021-10-21 15:25:46 -0700262 six.raise_from(
263 ValueError("Unsupported signature algorithm {}".format(key_alg)), exc
264 )
Thea Flowerse290a3d2020-04-01 10:11:42 -0700265
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700266 # 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.
Jay Leec5a33952020-01-17 11:18:47 -0800268 if isinstance(certs, Mapping):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700269 if key_id:
270 if key_id not in certs:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700271 raise ValueError("Certificate for key id {} not found.".format(key_id))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700272 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.
Thea Flowerse290a3d2020-04-01 10:11:42 -0700280 if not crypt.verify_signature(
281 signed_section, signature, certs_to_check, verifier_cls
282 ):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700283 raise ValueError("Could not verify token signature.")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700284
285 # Verify the issued at and created times in the payload.
arithmetic1728738611b2021-09-09 17:09:54 -0700286 _verify_iat_and_exp(payload, clock_skew_in_seconds)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700287
288 # Check audience.
289 if audience is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700290 claim_audience = payload.get("aud")
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400291 if isinstance(audience, str):
292 audience = [audience]
293 if claim_audience not in audience:
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700294 raise ValueError(
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400295 "Token has wrong audience {}, expected one of {}".format(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700296 claim_audience, audience
297 )
298 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700299
300 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700301
302
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600303class Credentials(
304 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
305):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700306 """Credentials that use a JWT as the bearer token.
307
308 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800309 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700310
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
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800318 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700319 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800320 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800321 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700322
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(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800327 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800328 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700329
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',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800335 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700336 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(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800342 signer,
343 issuer='your-issuer',
344 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800345 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700346
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
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800350 new_audience = (
351 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
352 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700353 """
354
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700355 def __init__(
356 self,
357 signer,
358 issuer,
359 subject,
360 audience,
361 additional_claims=None,
362 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700363 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700364 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700365 """
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
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800371 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700372 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.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700376 quota_project_id (Optional[str]): The project ID used for quota
377 and billing.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700378 """
379 super(Credentials, self).__init__()
380 self._signer = signer
381 self._issuer = issuer
382 self._subject = subject
383 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700384 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700385 self._quota_project_id = quota_project_id
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700386
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700387 if additional_claims is None:
388 additional_claims = {}
389
390 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700391
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700392 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700393 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 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700408 kwargs.setdefault("subject", info["client_email"])
409 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800410 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700411
412 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700413 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700414 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700415
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 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700427 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700428 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700429
430 @classmethod
431 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700432 """Creates a Credentials instance from a service account .json file
433 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700434
435 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700436 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700437 kwargs: Additional arguments to pass to the constructor.
438
439 Returns:
440 google.auth.jwt.Credentials: The constructed credentials.
441 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700442 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700443 filename, require=["client_email"]
444 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700445 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700446
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800447 @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 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700475 kwargs.setdefault("issuer", credentials.signer_email)
476 kwargs.setdefault("subject", credentials.signer_email)
477 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800478
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700479 def with_claims(
480 self, issuer=None, subject=None, audience=None, additional_claims=None
481 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700482 """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.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800489 audience (str): the `aud` claim. If unspecified the current
490 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700491 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 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700498 new_additional_claims = copy.deepcopy(self._additional_claims)
499 new_additional_claims.update(additional_claims or {})
500
Christophe Tatonb649b432018-02-08 14:12:23 -0800501 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700502 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,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700506 additional_claims=new_additional_claims,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700507 quota_project_id=self._quota_project_id,
508 )
509
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600510 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700511 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,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700519 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700520
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800521 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700522 """Make a signed JWT.
523
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700524 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800525 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700526 """
527 now = _helpers.utcnow()
528 lifetime = datetime.timedelta(seconds=self._token_lifetime)
529 expiry = now + lifetime
530
531 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700532 "iss": self._issuer,
533 "sub": self._subject,
534 "iat": _helpers.datetime_to_secs(now),
535 "exp": _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700536 }
arithmetic17282cfe6552021-06-16 15:30:36 -0700537 if self._audience:
538 payload["aud"] = self._audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700539
540 payload.update(self._additional_claims)
541
542 jwt = encode(self._signer, payload)
543
544 return jwt, expiry
545
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700546 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
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800556 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700557 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700558 return self._signer.sign(message)
559
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800560 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800561 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800562 def signer_email(self):
563 return self._issuer
564
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800565 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800566 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800567 def signer(self):
568 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700569
570
571class OnDemandCredentials(
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600572 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700573):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700574 """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
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700591 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,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700599 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700600 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700601 """
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`.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700612 quota_project_id (Optional[str]): The project ID used for quota
613 and billing.
614
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700615 """
616 super(OnDemandCredentials, self).__init__()
617 self._signer = signer
618 self._issuer = issuer
619 self._subject = subject
620 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700621 self._quota_project_id = quota_project_id
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700622
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 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700645 kwargs.setdefault("subject", info["client_email"])
646 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700647 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 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700664 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700665 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(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700680 filename, require=["client_email"]
681 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700682 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 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700708 kwargs.setdefault("issuer", credentials.signer_email)
709 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700710 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
Christophe Tatonb649b432018-02-08 14:12:23 -0800730 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700731 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,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700735 max_cache_size=self._cache.maxsize,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700736 quota_project_id=self._quota_project_id,
737 )
738
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600739 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700740 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,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700749 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700750
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 = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700774 "iss": self._issuer,
775 "sub": self._subject,
776 "iat": _helpers.datetime_to_secs(now),
777 "exp": _helpers.datetime_to_secs(expiry),
778 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700779 }
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(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700821 "OnDemandCredentials can not be directly refreshed."
822 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700823
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(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700840 (parts.scheme, parts.netloc, parts.path, "", "")
841 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700842 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