blob: 1bc7e5e7188a6cf86b7fedce59399f0c4f195449 [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
Tres Seaver560cf1e2021-08-03 16:35:54 -040043from collections.abc import Mapping
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -070044import copy
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -070045import datetime
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070046import json
Tres Seaver560cf1e2021-08-03 16:35:54 -040047import urllib
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070048
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070049import cachetools
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070050
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070051from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070052from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070053from google.auth import crypt
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070054from google.auth import exceptions
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080055import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070056
Thea Flowerse290a3d2020-04-01 10:11:42 -070057try:
58 from google.auth.crypt import es256
59except ImportError: # pragma: NO COVER
60 es256 = None
61
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070062_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070063_DEFAULT_MAX_CACHE_SIZE = 10
Thea Flowerse290a3d2020-04-01 10:11:42 -070064_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier}
arithmetic1728866d9262020-05-05 12:53:37 -070065_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"])
Thea Flowerse290a3d2020-04-01 10:11:42 -070066
67if es256 is not None: # pragma: NO COVER
68 _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070069
70
71def encode(signer, payload, header=None, key_id=None):
72 """Make a signed JWT.
73
74 Args:
75 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070076 payload (Mapping[str, str]): The JWT payload.
77 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070078 key_id (str): The key id to add to the JWT header. If the
79 signer has a key id it will be used as the default. If this is
80 specified it will override the signer's key id.
81
82 Returns:
83 bytes: The encoded JWT.
84 """
85 if header is None:
86 header = {}
87
88 if key_id is None:
89 key_id = signer.key_id
90
Thea Flowerse290a3d2020-04-01 10:11:42 -070091 header.update({"typ": "JWT"})
92
arithmetic17280a837062021-04-08 10:58:38 -070093 if "alg" not in header:
94 if es256 is not None and isinstance(signer, es256.ES256Signer):
95 header.update({"alg": "ES256"})
96 else:
97 header.update({"alg": "RS256"})
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070098
99 if key_id is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700100 header["kid"] = key_id
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700101
102 segments = [
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700103 _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")),
104 _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")),
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700105 ]
106
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700107 signing_input = b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700108 signature = signer.sign(signing_input)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700109 segments.append(_helpers.unpadded_urlsafe_b64encode(signature))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700110
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700111 return b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700112
113
114def _decode_jwt_segment(encoded_section):
115 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800116 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700117 try:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700118 return json.loads(section_bytes.decode("utf-8"))
Danny Hermes895e3692017-11-09 11:35:57 -0800119 except ValueError as caught_exc:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700120 new_exc = ValueError("Can't parse segment: {0}".format(section_bytes))
Tres Seaver560cf1e2021-08-03 16:35:54 -0400121 raise new_exc from caught_exc
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700122
123
124def _unverified_decode(token):
125 """Decodes a token and does no verification.
126
127 Args:
128 token (Union[str, bytes]): The encoded JWT.
129
130 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800131 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700132 signature.
133
134 Raises:
135 ValueError: if there are an incorrect amount of segments in the token.
136 """
137 token = _helpers.to_bytes(token)
138
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700139 if token.count(b".") != 2:
140 raise ValueError("Wrong number of segments in token: {0}".format(token))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700141
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700142 encoded_header, encoded_payload, signature = token.split(b".")
143 signed_section = encoded_header + b"." + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800144 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700145
146 # Parse segments
147 header = _decode_jwt_segment(encoded_header)
148 payload = _decode_jwt_segment(encoded_payload)
149
150 return header, payload, signed_section, signature
151
152
153def decode_header(token):
154 """Return the decoded header of a token.
155
156 No verification is done. This is useful to extract the key id from
157 the header in order to acquire the appropriate certificate to verify
158 the token.
159
160 Args:
161 token (Union[str, bytes]): the encoded JWT.
162
163 Returns:
164 Mapping: The decoded JWT header.
165 """
166 header, _, _, _ = _unverified_decode(token)
167 return header
168
169
170def _verify_iat_and_exp(payload):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700171 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700172 payload.
173
174 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700175 payload (Mapping[str, str]): The JWT payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700176
177 Raises:
178 ValueError: if any checks failed.
179 """
180 now = _helpers.datetime_to_secs(_helpers.utcnow())
181
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700182 # Make sure the iat and exp claims are present.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700183 for key in ("iat", "exp"):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700184 if key not in payload:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700185 raise ValueError("Token does not contain required claim {}".format(key))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700186
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700187 # Make sure the token wasn't issued in the future.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700188 iat = payload["iat"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700189 # Err on the side of accepting a token that is slightly early to account
190 # for clock skew.
191 earliest = iat - _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700192 if now < earliest:
Liron Newman45c44912021-09-07 23:07:07 +0100193 raise ValueError(
194 "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format(
195 now, iat
196 )
197 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700198
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700199 # Make sure the token wasn't issued in the past.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700200 exp = payload["exp"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700201 # Err on the side of accepting a token that is slightly out of date
202 # to account for clow skew.
203 latest = exp + _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700204 if latest < now:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700205 raise ValueError("Token expired, {} < {}".format(latest, now))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700206
207
208def decode(token, certs=None, verify=True, audience=None):
209 """Decode and verify a JWT.
210
211 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700212 token (str): The encoded JWT.
213 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
Tianzi Cai2c6ad782019-03-29 13:49:06 -0700214 certificate used to validate the JWT signature. If bytes or string,
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700215 it must the the public key certificate in PEM format. If a mapping,
216 it must be a mapping of key IDs to public key certificates in PEM
217 format. The mapping must contain the same key ID that's specified
218 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700219 verify (bool): Whether to perform signature and claim validation.
220 Verification is done by default.
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400221 audience (str or list): The audience claim, 'aud', that this JWT should
222 contain. Or a list of audience claims. If None then the JWT's 'aud'
223 parameter is not verified.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700224
225 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700226 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700227
228 Raises:
229 ValueError: if any verification checks failed.
230 """
231 header, payload, signed_section, signature = _unverified_decode(token)
232
233 if not verify:
234 return payload
235
Thea Flowerse290a3d2020-04-01 10:11:42 -0700236 # Pluck the key id and algorithm from the header and make sure we have
237 # a verifier that can support it.
238 key_alg = header.get("alg")
239 key_id = header.get("kid")
240
241 try:
242 verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg]
Tres Seaver560cf1e2021-08-03 16:35:54 -0400243 except KeyError as caught_exc:
Thea Flowerse290a3d2020-04-01 10:11:42 -0700244 if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
Tres Seaver560cf1e2021-08-03 16:35:54 -0400245 msg = (
246 "The key algorithm {} requires the cryptography package "
247 "to be installed."
Thea Flowerse290a3d2020-04-01 10:11:42 -0700248 )
249 else:
Tres Seaver560cf1e2021-08-03 16:35:54 -0400250 msg = "Unsupported signature algorithm {}"
251 new_exc = ValueError(msg.format(key_alg))
252 raise new_exc from caught_exc
Thea Flowerse290a3d2020-04-01 10:11:42 -0700253
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700254 # If certs is specified as a dictionary of key IDs to certificates, then
255 # use the certificate identified by the key ID in the token header.
Jay Leec5a33952020-01-17 11:18:47 -0800256 if isinstance(certs, Mapping):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700257 if key_id:
258 if key_id not in certs:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700259 raise ValueError("Certificate for key id {} not found.".format(key_id))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700260 certs_to_check = [certs[key_id]]
261 # If there's no key id in the header, check against all of the certs.
262 else:
263 certs_to_check = certs.values()
264 else:
265 certs_to_check = certs
266
267 # Verify that the signature matches the message.
Thea Flowerse290a3d2020-04-01 10:11:42 -0700268 if not crypt.verify_signature(
269 signed_section, signature, certs_to_check, verifier_cls
270 ):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700271 raise ValueError("Could not verify token signature.")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700272
273 # Verify the issued at and created times in the payload.
274 _verify_iat_and_exp(payload)
275
276 # Check audience.
277 if audience is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700278 claim_audience = payload.get("aud")
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400279 if isinstance(audience, str):
280 audience = [audience]
281 if claim_audience not in audience:
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700282 raise ValueError(
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400283 "Token has wrong audience {}, expected one of {}".format(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700284 claim_audience, audience
285 )
286 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700287
288 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700289
290
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600291class Credentials(
292 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
293):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700294 """Credentials that use a JWT as the bearer token.
295
296 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800297 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700298
299 The constructor arguments determine the claims for the JWT that is
300 sent with requests. Usually, you'll construct these credentials with
301 one of the helper constructors as shown in the next section.
302
303 To create JWT credentials using a Google service account private key
304 JSON file::
305
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800306 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700307 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800308 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800309 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700310
311 If you already have the service account file loaded and parsed::
312
313 service_account_info = json.load(open('service_account.json'))
314 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800315 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800316 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700317
318 Both helper methods pass on arguments to the constructor, so you can
319 specify the JWT claims::
320
321 credentials = jwt.Credentials.from_service_account_file(
322 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800323 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700324 additional_claims={'meta': 'data'})
325
326 You can also construct the credentials directly if you have a
327 :class:`~google.auth.crypt.Signer` instance::
328
329 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800330 signer,
331 issuer='your-issuer',
332 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800333 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700334
335 The claims are considered immutable. If you want to modify the claims,
336 you can easily create another instance using :meth:`with_claims`::
337
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800338 new_audience = (
339 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
340 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700341 """
342
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700343 def __init__(
344 self,
345 signer,
346 issuer,
347 subject,
348 audience,
349 additional_claims=None,
350 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700351 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700352 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700353 """
354 Args:
355 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
356 issuer (str): The `iss` claim.
357 subject (str): The `sub` claim.
358 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800359 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700360 additional_claims (Mapping[str, str]): Any additional claims for
361 the JWT payload.
362 token_lifetime (int): The amount of time in seconds for
363 which the token is valid. Defaults to 1 hour.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700364 quota_project_id (Optional[str]): The project ID used for quota
365 and billing.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700366 """
367 super(Credentials, self).__init__()
368 self._signer = signer
369 self._issuer = issuer
370 self._subject = subject
371 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700372 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700373 self._quota_project_id = quota_project_id
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700374
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700375 if additional_claims is None:
376 additional_claims = {}
377
378 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700379
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700380 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700381 def _from_signer_and_info(cls, signer, info, **kwargs):
382 """Creates a Credentials instance from a signer and service account
383 info.
384
385 Args:
386 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
387 info (Mapping[str, str]): The service account info.
388 kwargs: Additional arguments to pass to the constructor.
389
390 Returns:
391 google.auth.jwt.Credentials: The constructed credentials.
392
393 Raises:
394 ValueError: If the info is not in the expected format.
395 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700396 kwargs.setdefault("subject", info["client_email"])
397 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800398 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700399
400 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700401 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700402 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700403
404 Args:
405 info (Mapping[str, str]): The service account info in Google
406 format.
407 kwargs: Additional arguments to pass to the constructor.
408
409 Returns:
410 google.auth.jwt.Credentials: The constructed credentials.
411
412 Raises:
413 ValueError: If the info is not in the expected format.
414 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700415 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700416 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700417
418 @classmethod
419 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700420 """Creates a Credentials instance from a service account .json file
421 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700422
423 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700424 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700425 kwargs: Additional arguments to pass to the constructor.
426
427 Returns:
428 google.auth.jwt.Credentials: The constructed credentials.
429 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700430 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700431 filename, require=["client_email"]
432 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700433 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700434
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800435 @classmethod
436 def from_signing_credentials(cls, credentials, audience, **kwargs):
437 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
438 existing :class:`google.auth.credentials.Signing` instance.
439
440 The new instance will use the same signer as the existing instance and
441 will use the existing instance's signer email as the issuer and
442 subject by default.
443
444 Example::
445
446 svc_creds = service_account.Credentials.from_service_account_file(
447 'service_account.json')
448 audience = (
449 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
450 jwt_creds = jwt.Credentials.from_signing_credentials(
451 svc_creds, audience=audience)
452
453 Args:
454 credentials (google.auth.credentials.Signing): The credentials to
455 use to construct the new credentials.
456 audience (str): the `aud` claim. The intended audience for the
457 credentials.
458 kwargs: Additional arguments to pass to the constructor.
459
460 Returns:
461 google.auth.jwt.Credentials: A new Credentials instance.
462 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700463 kwargs.setdefault("issuer", credentials.signer_email)
464 kwargs.setdefault("subject", credentials.signer_email)
465 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800466
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700467 def with_claims(
468 self, issuer=None, subject=None, audience=None, additional_claims=None
469 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700470 """Returns a copy of these credentials with modified claims.
471
472 Args:
473 issuer (str): The `iss` claim. If unspecified the current issuer
474 claim will be used.
475 subject (str): The `sub` claim. If unspecified the current subject
476 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800477 audience (str): the `aud` claim. If unspecified the current
478 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700479 additional_claims (Mapping[str, str]): Any additional claims for
480 the JWT payload. This will be merged with the current
481 additional claims.
482
483 Returns:
484 google.auth.jwt.Credentials: A new credentials instance.
485 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700486 new_additional_claims = copy.deepcopy(self._additional_claims)
487 new_additional_claims.update(additional_claims or {})
488
Christophe Tatonb649b432018-02-08 14:12:23 -0800489 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700490 self._signer,
491 issuer=issuer if issuer is not None else self._issuer,
492 subject=subject if subject is not None else self._subject,
493 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700494 additional_claims=new_additional_claims,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700495 quota_project_id=self._quota_project_id,
496 )
497
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600498 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700499 def with_quota_project(self, quota_project_id):
500 return self.__class__(
501 self._signer,
502 issuer=self._issuer,
503 subject=self._subject,
504 audience=self._audience,
505 additional_claims=self._additional_claims,
506 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700507 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700508
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800509 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700510 """Make a signed JWT.
511
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700512 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800513 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700514 """
515 now = _helpers.utcnow()
516 lifetime = datetime.timedelta(seconds=self._token_lifetime)
517 expiry = now + lifetime
518
519 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700520 "iss": self._issuer,
521 "sub": self._subject,
522 "iat": _helpers.datetime_to_secs(now),
523 "exp": _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700524 }
arithmetic17282cfe6552021-06-16 15:30:36 -0700525 if self._audience:
526 payload["aud"] = self._audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700527
528 payload.update(self._additional_claims)
529
530 jwt = encode(self._signer, payload)
531
532 return jwt, expiry
533
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700534 def refresh(self, request):
535 """Refreshes the access token.
536
537 Args:
538 request (Any): Unused.
539 """
540 # pylint: disable=unused-argument
541 # (pylint doesn't correctly recognize overridden methods.)
542 self.token, self.expiry = self._make_jwt()
543
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800544 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700545 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700546 return self._signer.sign(message)
547
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800548 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800549 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800550 def signer_email(self):
551 return self._issuer
552
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800553 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800554 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800555 def signer(self):
556 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700557
558
559class OnDemandCredentials(
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600560 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700561):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700562 """On-demand JWT credentials.
563
564 Like :class:`Credentials`, this class uses a JWT as the bearer token for
565 authentication. However, this class does not require the audience at
566 construction time. Instead, it will generate a new token on-demand for
567 each request using the request URI as the audience. It caches tokens
568 so that multiple requests to the same URI do not incur the overhead
569 of generating a new token every time.
570
571 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
572 have multiple audience and gRPC clients may not know all of the audiences
573 required for accessing a particular service. With these credentials,
574 no knowledge of the audiences is required ahead of time.
575
576 .. _grpc: http://www.grpc.io/
577 """
578
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700579 def __init__(
580 self,
581 signer,
582 issuer,
583 subject,
584 additional_claims=None,
585 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
586 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700587 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700588 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700589 """
590 Args:
591 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
592 issuer (str): The `iss` claim.
593 subject (str): The `sub` claim.
594 additional_claims (Mapping[str, str]): Any additional claims for
595 the JWT payload.
596 token_lifetime (int): The amount of time in seconds for
597 which the token is valid. Defaults to 1 hour.
598 max_cache_size (int): The maximum number of JWT tokens to keep in
599 cache. Tokens are cached using :class:`cachetools.LRUCache`.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700600 quota_project_id (Optional[str]): The project ID used for quota
601 and billing.
602
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700603 """
604 super(OnDemandCredentials, self).__init__()
605 self._signer = signer
606 self._issuer = issuer
607 self._subject = subject
608 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700609 self._quota_project_id = quota_project_id
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700610
611 if additional_claims is None:
612 additional_claims = {}
613
614 self._additional_claims = additional_claims
615 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
616
617 @classmethod
618 def _from_signer_and_info(cls, signer, info, **kwargs):
619 """Creates an OnDemandCredentials instance from a signer and service
620 account info.
621
622 Args:
623 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
624 info (Mapping[str, str]): The service account info.
625 kwargs: Additional arguments to pass to the constructor.
626
627 Returns:
628 google.auth.jwt.OnDemandCredentials: The constructed credentials.
629
630 Raises:
631 ValueError: If the info is not in the expected format.
632 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700633 kwargs.setdefault("subject", info["client_email"])
634 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700635 return cls(signer, **kwargs)
636
637 @classmethod
638 def from_service_account_info(cls, info, **kwargs):
639 """Creates an OnDemandCredentials instance from a dictionary.
640
641 Args:
642 info (Mapping[str, str]): The service account info in Google
643 format.
644 kwargs: Additional arguments to pass to the constructor.
645
646 Returns:
647 google.auth.jwt.OnDemandCredentials: The constructed credentials.
648
649 Raises:
650 ValueError: If the info is not in the expected format.
651 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700652 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700653 return cls._from_signer_and_info(signer, info, **kwargs)
654
655 @classmethod
656 def from_service_account_file(cls, filename, **kwargs):
657 """Creates an OnDemandCredentials instance from a service account .json
658 file in Google format.
659
660 Args:
661 filename (str): The path to the service account .json file.
662 kwargs: Additional arguments to pass to the constructor.
663
664 Returns:
665 google.auth.jwt.OnDemandCredentials: The constructed credentials.
666 """
667 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700668 filename, require=["client_email"]
669 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700670 return cls._from_signer_and_info(signer, info, **kwargs)
671
672 @classmethod
673 def from_signing_credentials(cls, credentials, **kwargs):
674 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
675 from an existing :class:`google.auth.credentials.Signing` instance.
676
677 The new instance will use the same signer as the existing instance and
678 will use the existing instance's signer email as the issuer and
679 subject by default.
680
681 Example::
682
683 svc_creds = service_account.Credentials.from_service_account_file(
684 'service_account.json')
685 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
686 svc_creds)
687
688 Args:
689 credentials (google.auth.credentials.Signing): The credentials to
690 use to construct the new credentials.
691 kwargs: Additional arguments to pass to the constructor.
692
693 Returns:
694 google.auth.jwt.Credentials: A new Credentials instance.
695 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700696 kwargs.setdefault("issuer", credentials.signer_email)
697 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700698 return cls(credentials.signer, **kwargs)
699
700 def with_claims(self, issuer=None, subject=None, additional_claims=None):
701 """Returns a copy of these credentials with modified claims.
702
703 Args:
704 issuer (str): The `iss` claim. If unspecified the current issuer
705 claim will be used.
706 subject (str): The `sub` claim. If unspecified the current subject
707 claim will be used.
708 additional_claims (Mapping[str, str]): Any additional claims for
709 the JWT payload. This will be merged with the current
710 additional claims.
711
712 Returns:
713 google.auth.jwt.OnDemandCredentials: A new credentials instance.
714 """
715 new_additional_claims = copy.deepcopy(self._additional_claims)
716 new_additional_claims.update(additional_claims or {})
717
Christophe Tatonb649b432018-02-08 14:12:23 -0800718 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700719 self._signer,
720 issuer=issuer if issuer is not None else self._issuer,
721 subject=subject if subject is not None else self._subject,
722 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700723 max_cache_size=self._cache.maxsize,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700724 quota_project_id=self._quota_project_id,
725 )
726
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600727 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700728 def with_quota_project(self, quota_project_id):
729
730 return self.__class__(
731 self._signer,
732 issuer=self._issuer,
733 subject=self._subject,
734 additional_claims=self._additional_claims,
735 max_cache_size=self._cache.maxsize,
736 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700737 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700738
739 @property
740 def valid(self):
741 """Checks the validity of the credentials.
742
743 These credentials are always valid because it generates tokens on
744 demand.
745 """
746 return True
747
748 def _make_jwt_for_audience(self, audience):
749 """Make a new JWT for the given audience.
750
751 Args:
752 audience (str): The intended audience.
753
754 Returns:
755 Tuple[bytes, datetime]: The encoded JWT and the expiration.
756 """
757 now = _helpers.utcnow()
758 lifetime = datetime.timedelta(seconds=self._token_lifetime)
759 expiry = now + lifetime
760
761 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700762 "iss": self._issuer,
763 "sub": self._subject,
764 "iat": _helpers.datetime_to_secs(now),
765 "exp": _helpers.datetime_to_secs(expiry),
766 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700767 }
768
769 payload.update(self._additional_claims)
770
771 jwt = encode(self._signer, payload)
772
773 return jwt, expiry
774
775 def _get_jwt_for_audience(self, audience):
776 """Get a JWT For a given audience.
777
778 If there is already an existing, non-expired token in the cache for
779 the audience, that token is used. Otherwise, a new token will be
780 created.
781
782 Args:
783 audience (str): The intended audience.
784
785 Returns:
786 bytes: The encoded JWT.
787 """
788 token, expiry = self._cache.get(audience, (None, None))
789
790 if token is None or expiry < _helpers.utcnow():
791 token, expiry = self._make_jwt_for_audience(audience)
792 self._cache[audience] = token, expiry
793
794 return token
795
796 def refresh(self, request):
797 """Raises an exception, these credentials can not be directly
798 refreshed.
799
800 Args:
801 request (Any): Unused.
802
803 Raises:
804 google.auth.RefreshError
805 """
806 # pylint: disable=unused-argument
807 # (pylint doesn't correctly recognize overridden methods.)
808 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700809 "OnDemandCredentials can not be directly refreshed."
810 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700811
812 def before_request(self, request, method, url, headers):
813 """Performs credential-specific before request logic.
814
815 Args:
816 request (Any): Unused. JWT credentials do not need to make an
817 HTTP request to refresh.
818 method (str): The request's HTTP method.
819 url (str): The request's URI. This is used as the audience claim
820 when generating the JWT.
821 headers (Mapping): The request's headers.
822 """
823 # pylint: disable=unused-argument
824 # (pylint doesn't correctly recognize overridden methods.)
825 parts = urllib.parse.urlsplit(url)
826 # Strip query string and fragment
827 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700828 (parts.scheme, parts.netloc, parts.path, "", "")
829 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700830 token = self._get_jwt_for_audience(audience)
831 self.apply(headers, token=token)
832
833 @_helpers.copy_docstring(google.auth.credentials.Signing)
834 def sign_bytes(self, message):
835 return self._signer.sign(message)
836
837 @property
838 @_helpers.copy_docstring(google.auth.credentials.Signing)
839 def signer_email(self):
840 return self._issuer
841
842 @property
843 @_helpers.copy_docstring(google.auth.credentials.Signing)
844 def signer(self):
845 return self._signer