blob: bb9ffae83dc6a8a270e067cffdb28f26067c6aa5 [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
arithmetic1728738611b2021-09-09 17:09:54 -0700170def _verify_iat_and_exp(payload, clock_skew_in_seconds=0):
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.
arithmetic1728738611b2021-09-09 17:09:54 -0700176 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
177 validation.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700178
179 Raises:
180 ValueError: if any checks failed.
181 """
182 now = _helpers.datetime_to_secs(_helpers.utcnow())
183
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700184 # Make sure the iat and exp claims are present.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700185 for key in ("iat", "exp"):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700186 if key not in payload:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700187 raise ValueError("Token does not contain required claim {}".format(key))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700188
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700189 # Make sure the token wasn't issued in the future.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700190 iat = payload["iat"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700191 # Err on the side of accepting a token that is slightly early to account
192 # for clock skew.
arithmetic1728738611b2021-09-09 17:09:54 -0700193 earliest = iat - clock_skew_in_seconds
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700194 if now < earliest:
Liron Newman45c44912021-09-07 23:07:07 +0100195 raise ValueError(
196 "Token used too early, {} < {}. Check that your computer's clock is set correctly.".format(
197 now, iat
198 )
199 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700200
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700201 # Make sure the token wasn't issued in the past.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700202 exp = payload["exp"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700203 # Err on the side of accepting a token that is slightly out of date
204 # to account for clow skew.
arithmetic1728738611b2021-09-09 17:09:54 -0700205 latest = exp + clock_skew_in_seconds
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700206 if latest < now:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700207 raise ValueError("Token expired, {} < {}".format(latest, now))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700208
209
arithmetic1728738611b2021-09-09 17:09:54 -0700210def decode(token, certs=None, verify=True, audience=None, clock_skew_in_seconds=0):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700211 """Decode and verify a JWT.
212
213 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700214 token (str): The encoded JWT.
215 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
Tianzi Cai2c6ad782019-03-29 13:49:06 -0700216 certificate used to validate the JWT signature. If bytes or string,
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700217 it must the the public key certificate in PEM format. If a mapping,
218 it must be a mapping of key IDs to public key certificates in PEM
219 format. The mapping must contain the same key ID that's specified
220 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700221 verify (bool): Whether to perform signature and claim validation.
222 Verification is done by default.
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400223 audience (str or list): The audience claim, 'aud', that this JWT should
224 contain. Or a list of audience claims. If None then the JWT's 'aud'
225 parameter is not verified.
arithmetic1728738611b2021-09-09 17:09:54 -0700226 clock_skew_in_seconds (int): The clock skew used for `iat` and `exp`
227 validation.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700228
229 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700230 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700231
232 Raises:
233 ValueError: if any verification checks failed.
234 """
235 header, payload, signed_section, signature = _unverified_decode(token)
236
237 if not verify:
238 return payload
239
Thea Flowerse290a3d2020-04-01 10:11:42 -0700240 # Pluck the key id and algorithm from the header and make sure we have
241 # a verifier that can support it.
242 key_alg = header.get("alg")
243 key_id = header.get("kid")
244
245 try:
246 verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg]
Tres Seaver560cf1e2021-08-03 16:35:54 -0400247 except KeyError as caught_exc:
Thea Flowerse290a3d2020-04-01 10:11:42 -0700248 if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
Tres Seaver560cf1e2021-08-03 16:35:54 -0400249 msg = (
250 "The key algorithm {} requires the cryptography package "
251 "to be installed."
Thea Flowerse290a3d2020-04-01 10:11:42 -0700252 )
253 else:
Tres Seaver560cf1e2021-08-03 16:35:54 -0400254 msg = "Unsupported signature algorithm {}"
255 new_exc = ValueError(msg.format(key_alg))
256 raise new_exc from caught_exc
Thea Flowerse290a3d2020-04-01 10:11:42 -0700257
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700258 # If certs is specified as a dictionary of key IDs to certificates, then
259 # use the certificate identified by the key ID in the token header.
Jay Leec5a33952020-01-17 11:18:47 -0800260 if isinstance(certs, Mapping):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700261 if key_id:
262 if key_id not in certs:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700263 raise ValueError("Certificate for key id {} not found.".format(key_id))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700264 certs_to_check = [certs[key_id]]
265 # If there's no key id in the header, check against all of the certs.
266 else:
267 certs_to_check = certs.values()
268 else:
269 certs_to_check = certs
270
271 # Verify that the signature matches the message.
Thea Flowerse290a3d2020-04-01 10:11:42 -0700272 if not crypt.verify_signature(
273 signed_section, signature, certs_to_check, verifier_cls
274 ):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700275 raise ValueError("Could not verify token signature.")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700276
277 # Verify the issued at and created times in the payload.
arithmetic1728738611b2021-09-09 17:09:54 -0700278 _verify_iat_and_exp(payload, clock_skew_in_seconds)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700279
280 # Check audience.
281 if audience is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700282 claim_audience = payload.get("aud")
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400283 if isinstance(audience, str):
284 audience = [audience]
285 if claim_audience not in audience:
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700286 raise ValueError(
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400287 "Token has wrong audience {}, expected one of {}".format(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700288 claim_audience, audience
289 )
290 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700291
292 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700293
294
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600295class Credentials(
296 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
297):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700298 """Credentials that use a JWT as the bearer token.
299
300 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800301 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700302
303 The constructor arguments determine the claims for the JWT that is
304 sent with requests. Usually, you'll construct these credentials with
305 one of the helper constructors as shown in the next section.
306
307 To create JWT credentials using a Google service account private key
308 JSON file::
309
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800310 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700311 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800312 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800313 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700314
315 If you already have the service account file loaded and parsed::
316
317 service_account_info = json.load(open('service_account.json'))
318 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800319 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800320 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700321
322 Both helper methods pass on arguments to the constructor, so you can
323 specify the JWT claims::
324
325 credentials = jwt.Credentials.from_service_account_file(
326 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800327 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700328 additional_claims={'meta': 'data'})
329
330 You can also construct the credentials directly if you have a
331 :class:`~google.auth.crypt.Signer` instance::
332
333 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800334 signer,
335 issuer='your-issuer',
336 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800337 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700338
339 The claims are considered immutable. If you want to modify the claims,
340 you can easily create another instance using :meth:`with_claims`::
341
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800342 new_audience = (
343 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
344 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700345 """
346
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700347 def __init__(
348 self,
349 signer,
350 issuer,
351 subject,
352 audience,
353 additional_claims=None,
354 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700355 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700356 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700357 """
358 Args:
359 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
360 issuer (str): The `iss` claim.
361 subject (str): The `sub` claim.
362 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800363 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700364 additional_claims (Mapping[str, str]): Any additional claims for
365 the JWT payload.
366 token_lifetime (int): The amount of time in seconds for
367 which the token is valid. Defaults to 1 hour.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700368 quota_project_id (Optional[str]): The project ID used for quota
369 and billing.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700370 """
371 super(Credentials, self).__init__()
372 self._signer = signer
373 self._issuer = issuer
374 self._subject = subject
375 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700376 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700377 self._quota_project_id = quota_project_id
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700378
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700379 if additional_claims is None:
380 additional_claims = {}
381
382 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700383
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700384 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700385 def _from_signer_and_info(cls, signer, info, **kwargs):
386 """Creates a Credentials instance from a signer and service account
387 info.
388
389 Args:
390 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
391 info (Mapping[str, str]): The service account info.
392 kwargs: Additional arguments to pass to the constructor.
393
394 Returns:
395 google.auth.jwt.Credentials: The constructed credentials.
396
397 Raises:
398 ValueError: If the info is not in the expected format.
399 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700400 kwargs.setdefault("subject", info["client_email"])
401 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800402 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700403
404 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700405 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700406 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700407
408 Args:
409 info (Mapping[str, str]): The service account info in Google
410 format.
411 kwargs: Additional arguments to pass to the constructor.
412
413 Returns:
414 google.auth.jwt.Credentials: The constructed credentials.
415
416 Raises:
417 ValueError: If the info is not in the expected format.
418 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700419 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700420 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700421
422 @classmethod
423 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700424 """Creates a Credentials instance from a service account .json file
425 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700426
427 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700428 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700429 kwargs: Additional arguments to pass to the constructor.
430
431 Returns:
432 google.auth.jwt.Credentials: The constructed credentials.
433 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700434 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700435 filename, require=["client_email"]
436 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700437 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700438
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800439 @classmethod
440 def from_signing_credentials(cls, credentials, audience, **kwargs):
441 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
442 existing :class:`google.auth.credentials.Signing` instance.
443
444 The new instance will use the same signer as the existing instance and
445 will use the existing instance's signer email as the issuer and
446 subject by default.
447
448 Example::
449
450 svc_creds = service_account.Credentials.from_service_account_file(
451 'service_account.json')
452 audience = (
453 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
454 jwt_creds = jwt.Credentials.from_signing_credentials(
455 svc_creds, audience=audience)
456
457 Args:
458 credentials (google.auth.credentials.Signing): The credentials to
459 use to construct the new credentials.
460 audience (str): the `aud` claim. The intended audience for the
461 credentials.
462 kwargs: Additional arguments to pass to the constructor.
463
464 Returns:
465 google.auth.jwt.Credentials: A new Credentials instance.
466 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700467 kwargs.setdefault("issuer", credentials.signer_email)
468 kwargs.setdefault("subject", credentials.signer_email)
469 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800470
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700471 def with_claims(
472 self, issuer=None, subject=None, audience=None, additional_claims=None
473 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700474 """Returns a copy of these credentials with modified claims.
475
476 Args:
477 issuer (str): The `iss` claim. If unspecified the current issuer
478 claim will be used.
479 subject (str): The `sub` claim. If unspecified the current subject
480 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800481 audience (str): the `aud` claim. If unspecified the current
482 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700483 additional_claims (Mapping[str, str]): Any additional claims for
484 the JWT payload. This will be merged with the current
485 additional claims.
486
487 Returns:
488 google.auth.jwt.Credentials: A new credentials instance.
489 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700490 new_additional_claims = copy.deepcopy(self._additional_claims)
491 new_additional_claims.update(additional_claims or {})
492
Christophe Tatonb649b432018-02-08 14:12:23 -0800493 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700494 self._signer,
495 issuer=issuer if issuer is not None else self._issuer,
496 subject=subject if subject is not None else self._subject,
497 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700498 additional_claims=new_additional_claims,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700499 quota_project_id=self._quota_project_id,
500 )
501
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600502 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700503 def with_quota_project(self, quota_project_id):
504 return self.__class__(
505 self._signer,
506 issuer=self._issuer,
507 subject=self._subject,
508 audience=self._audience,
509 additional_claims=self._additional_claims,
510 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700511 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700512
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800513 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700514 """Make a signed JWT.
515
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700516 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800517 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700518 """
519 now = _helpers.utcnow()
520 lifetime = datetime.timedelta(seconds=self._token_lifetime)
521 expiry = now + lifetime
522
523 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700524 "iss": self._issuer,
525 "sub": self._subject,
526 "iat": _helpers.datetime_to_secs(now),
527 "exp": _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700528 }
arithmetic17282cfe6552021-06-16 15:30:36 -0700529 if self._audience:
530 payload["aud"] = self._audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700531
532 payload.update(self._additional_claims)
533
534 jwt = encode(self._signer, payload)
535
536 return jwt, expiry
537
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700538 def refresh(self, request):
539 """Refreshes the access token.
540
541 Args:
542 request (Any): Unused.
543 """
544 # pylint: disable=unused-argument
545 # (pylint doesn't correctly recognize overridden methods.)
546 self.token, self.expiry = self._make_jwt()
547
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800548 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700549 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700550 return self._signer.sign(message)
551
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800552 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800553 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800554 def signer_email(self):
555 return self._issuer
556
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800557 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800558 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800559 def signer(self):
560 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700561
562
563class OnDemandCredentials(
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600564 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700565):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700566 """On-demand JWT credentials.
567
568 Like :class:`Credentials`, this class uses a JWT as the bearer token for
569 authentication. However, this class does not require the audience at
570 construction time. Instead, it will generate a new token on-demand for
571 each request using the request URI as the audience. It caches tokens
572 so that multiple requests to the same URI do not incur the overhead
573 of generating a new token every time.
574
575 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
576 have multiple audience and gRPC clients may not know all of the audiences
577 required for accessing a particular service. With these credentials,
578 no knowledge of the audiences is required ahead of time.
579
580 .. _grpc: http://www.grpc.io/
581 """
582
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700583 def __init__(
584 self,
585 signer,
586 issuer,
587 subject,
588 additional_claims=None,
589 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
590 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700591 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700592 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700593 """
594 Args:
595 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
596 issuer (str): The `iss` claim.
597 subject (str): The `sub` claim.
598 additional_claims (Mapping[str, str]): Any additional claims for
599 the JWT payload.
600 token_lifetime (int): The amount of time in seconds for
601 which the token is valid. Defaults to 1 hour.
602 max_cache_size (int): The maximum number of JWT tokens to keep in
603 cache. Tokens are cached using :class:`cachetools.LRUCache`.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700604 quota_project_id (Optional[str]): The project ID used for quota
605 and billing.
606
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700607 """
608 super(OnDemandCredentials, self).__init__()
609 self._signer = signer
610 self._issuer = issuer
611 self._subject = subject
612 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700613 self._quota_project_id = quota_project_id
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700614
615 if additional_claims is None:
616 additional_claims = {}
617
618 self._additional_claims = additional_claims
619 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
620
621 @classmethod
622 def _from_signer_and_info(cls, signer, info, **kwargs):
623 """Creates an OnDemandCredentials instance from a signer and service
624 account info.
625
626 Args:
627 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
628 info (Mapping[str, str]): The service account info.
629 kwargs: Additional arguments to pass to the constructor.
630
631 Returns:
632 google.auth.jwt.OnDemandCredentials: The constructed credentials.
633
634 Raises:
635 ValueError: If the info is not in the expected format.
636 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700637 kwargs.setdefault("subject", info["client_email"])
638 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700639 return cls(signer, **kwargs)
640
641 @classmethod
642 def from_service_account_info(cls, info, **kwargs):
643 """Creates an OnDemandCredentials instance from a dictionary.
644
645 Args:
646 info (Mapping[str, str]): The service account info in Google
647 format.
648 kwargs: Additional arguments to pass to the constructor.
649
650 Returns:
651 google.auth.jwt.OnDemandCredentials: The constructed credentials.
652
653 Raises:
654 ValueError: If the info is not in the expected format.
655 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700656 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700657 return cls._from_signer_and_info(signer, info, **kwargs)
658
659 @classmethod
660 def from_service_account_file(cls, filename, **kwargs):
661 """Creates an OnDemandCredentials instance from a service account .json
662 file in Google format.
663
664 Args:
665 filename (str): The path to the service account .json file.
666 kwargs: Additional arguments to pass to the constructor.
667
668 Returns:
669 google.auth.jwt.OnDemandCredentials: The constructed credentials.
670 """
671 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700672 filename, require=["client_email"]
673 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700674 return cls._from_signer_and_info(signer, info, **kwargs)
675
676 @classmethod
677 def from_signing_credentials(cls, credentials, **kwargs):
678 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
679 from an existing :class:`google.auth.credentials.Signing` instance.
680
681 The new instance will use the same signer as the existing instance and
682 will use the existing instance's signer email as the issuer and
683 subject by default.
684
685 Example::
686
687 svc_creds = service_account.Credentials.from_service_account_file(
688 'service_account.json')
689 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
690 svc_creds)
691
692 Args:
693 credentials (google.auth.credentials.Signing): The credentials to
694 use to construct the new credentials.
695 kwargs: Additional arguments to pass to the constructor.
696
697 Returns:
698 google.auth.jwt.Credentials: A new Credentials instance.
699 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700700 kwargs.setdefault("issuer", credentials.signer_email)
701 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700702 return cls(credentials.signer, **kwargs)
703
704 def with_claims(self, issuer=None, subject=None, additional_claims=None):
705 """Returns a copy of these credentials with modified claims.
706
707 Args:
708 issuer (str): The `iss` claim. If unspecified the current issuer
709 claim will be used.
710 subject (str): The `sub` claim. If unspecified the current subject
711 claim will be used.
712 additional_claims (Mapping[str, str]): Any additional claims for
713 the JWT payload. This will be merged with the current
714 additional claims.
715
716 Returns:
717 google.auth.jwt.OnDemandCredentials: A new credentials instance.
718 """
719 new_additional_claims = copy.deepcopy(self._additional_claims)
720 new_additional_claims.update(additional_claims or {})
721
Christophe Tatonb649b432018-02-08 14:12:23 -0800722 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700723 self._signer,
724 issuer=issuer if issuer is not None else self._issuer,
725 subject=subject if subject is not None else self._subject,
726 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700727 max_cache_size=self._cache.maxsize,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700728 quota_project_id=self._quota_project_id,
729 )
730
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600731 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700732 def with_quota_project(self, quota_project_id):
733
734 return self.__class__(
735 self._signer,
736 issuer=self._issuer,
737 subject=self._subject,
738 additional_claims=self._additional_claims,
739 max_cache_size=self._cache.maxsize,
740 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700741 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700742
743 @property
744 def valid(self):
745 """Checks the validity of the credentials.
746
747 These credentials are always valid because it generates tokens on
748 demand.
749 """
750 return True
751
752 def _make_jwt_for_audience(self, audience):
753 """Make a new JWT for the given audience.
754
755 Args:
756 audience (str): The intended audience.
757
758 Returns:
759 Tuple[bytes, datetime]: The encoded JWT and the expiration.
760 """
761 now = _helpers.utcnow()
762 lifetime = datetime.timedelta(seconds=self._token_lifetime)
763 expiry = now + lifetime
764
765 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700766 "iss": self._issuer,
767 "sub": self._subject,
768 "iat": _helpers.datetime_to_secs(now),
769 "exp": _helpers.datetime_to_secs(expiry),
770 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700771 }
772
773 payload.update(self._additional_claims)
774
775 jwt = encode(self._signer, payload)
776
777 return jwt, expiry
778
779 def _get_jwt_for_audience(self, audience):
780 """Get a JWT For a given audience.
781
782 If there is already an existing, non-expired token in the cache for
783 the audience, that token is used. Otherwise, a new token will be
784 created.
785
786 Args:
787 audience (str): The intended audience.
788
789 Returns:
790 bytes: The encoded JWT.
791 """
792 token, expiry = self._cache.get(audience, (None, None))
793
794 if token is None or expiry < _helpers.utcnow():
795 token, expiry = self._make_jwt_for_audience(audience)
796 self._cache[audience] = token, expiry
797
798 return token
799
800 def refresh(self, request):
801 """Raises an exception, these credentials can not be directly
802 refreshed.
803
804 Args:
805 request (Any): Unused.
806
807 Raises:
808 google.auth.RefreshError
809 """
810 # pylint: disable=unused-argument
811 # (pylint doesn't correctly recognize overridden methods.)
812 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700813 "OnDemandCredentials can not be directly refreshed."
814 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700815
816 def before_request(self, request, method, url, headers):
817 """Performs credential-specific before request logic.
818
819 Args:
820 request (Any): Unused. JWT credentials do not need to make an
821 HTTP request to refresh.
822 method (str): The request's HTTP method.
823 url (str): The request's URI. This is used as the audience claim
824 when generating the JWT.
825 headers (Mapping): The request's headers.
826 """
827 # pylint: disable=unused-argument
828 # (pylint doesn't correctly recognize overridden methods.)
829 parts = urllib.parse.urlsplit(url)
830 # Strip query string and fragment
831 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700832 (parts.scheme, parts.netloc, parts.path, "", "")
833 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700834 token = self._get_jwt_for_audience(audience)
835 self.apply(headers, token=token)
836
837 @_helpers.copy_docstring(google.auth.credentials.Signing)
838 def sign_bytes(self, message):
839 return self._signer.sign(message)
840
841 @property
842 @_helpers.copy_docstring(google.auth.credentials.Signing)
843 def signer_email(self):
844 return self._issuer
845
846 @property
847 @_helpers.copy_docstring(google.auth.credentials.Signing)
848 def signer(self):
849 return self._signer