blob: d931bf7b9f75a96ee55adc2a42059548c3979bb7 [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:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700193 raise ValueError("Token used too early, {} < {}".format(now, iat))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700194
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700195 # Make sure the token wasn't issued in the past.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700196 exp = payload["exp"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700197 # Err on the side of accepting a token that is slightly out of date
198 # to account for clow skew.
199 latest = exp + _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700200 if latest < now:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700201 raise ValueError("Token expired, {} < {}".format(latest, now))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700202
203
204def decode(token, certs=None, verify=True, audience=None):
205 """Decode and verify a JWT.
206
207 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700208 token (str): The encoded JWT.
209 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
Tianzi Cai2c6ad782019-03-29 13:49:06 -0700210 certificate used to validate the JWT signature. If bytes or string,
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700211 it must the the public key certificate in PEM format. If a mapping,
212 it must be a mapping of key IDs to public key certificates in PEM
213 format. The mapping must contain the same key ID that's specified
214 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700215 verify (bool): Whether to perform signature and claim validation.
216 Verification is done by default.
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400217 audience (str or list): The audience claim, 'aud', that this JWT should
218 contain. Or a list of audience claims. If None then the JWT's 'aud'
219 parameter is not verified.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700220
221 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700222 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700223
224 Raises:
225 ValueError: if any verification checks failed.
226 """
227 header, payload, signed_section, signature = _unverified_decode(token)
228
229 if not verify:
230 return payload
231
Thea Flowerse290a3d2020-04-01 10:11:42 -0700232 # Pluck the key id and algorithm from the header and make sure we have
233 # a verifier that can support it.
234 key_alg = header.get("alg")
235 key_id = header.get("kid")
236
237 try:
238 verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg]
Tres Seaver560cf1e2021-08-03 16:35:54 -0400239 except KeyError as caught_exc:
Thea Flowerse290a3d2020-04-01 10:11:42 -0700240 if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
Tres Seaver560cf1e2021-08-03 16:35:54 -0400241 msg = (
242 "The key algorithm {} requires the cryptography package "
243 "to be installed."
Thea Flowerse290a3d2020-04-01 10:11:42 -0700244 )
245 else:
Tres Seaver560cf1e2021-08-03 16:35:54 -0400246 msg = "Unsupported signature algorithm {}"
247 new_exc = ValueError(msg.format(key_alg))
248 raise new_exc from caught_exc
Thea Flowerse290a3d2020-04-01 10:11:42 -0700249
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700250 # If certs is specified as a dictionary of key IDs to certificates, then
251 # use the certificate identified by the key ID in the token header.
Jay Leec5a33952020-01-17 11:18:47 -0800252 if isinstance(certs, Mapping):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700253 if key_id:
254 if key_id not in certs:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700255 raise ValueError("Certificate for key id {} not found.".format(key_id))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700256 certs_to_check = [certs[key_id]]
257 # If there's no key id in the header, check against all of the certs.
258 else:
259 certs_to_check = certs.values()
260 else:
261 certs_to_check = certs
262
263 # Verify that the signature matches the message.
Thea Flowerse290a3d2020-04-01 10:11:42 -0700264 if not crypt.verify_signature(
265 signed_section, signature, certs_to_check, verifier_cls
266 ):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700267 raise ValueError("Could not verify token signature.")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700268
269 # Verify the issued at and created times in the payload.
270 _verify_iat_and_exp(payload)
271
272 # Check audience.
273 if audience is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700274 claim_audience = payload.get("aud")
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400275 if isinstance(audience, str):
276 audience = [audience]
277 if claim_audience not in audience:
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700278 raise ValueError(
Jonathan Beaulieu56c39462021-04-15 04:28:04 -0400279 "Token has wrong audience {}, expected one of {}".format(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700280 claim_audience, audience
281 )
282 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700283
284 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700285
286
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600287class Credentials(
288 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
289):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700290 """Credentials that use a JWT as the bearer token.
291
292 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800293 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700294
295 The constructor arguments determine the claims for the JWT that is
296 sent with requests. Usually, you'll construct these credentials with
297 one of the helper constructors as shown in the next section.
298
299 To create JWT credentials using a Google service account private key
300 JSON file::
301
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800302 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700303 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800304 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800305 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700306
307 If you already have the service account file loaded and parsed::
308
309 service_account_info = json.load(open('service_account.json'))
310 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800311 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800312 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700313
314 Both helper methods pass on arguments to the constructor, so you can
315 specify the JWT claims::
316
317 credentials = jwt.Credentials.from_service_account_file(
318 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800319 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700320 additional_claims={'meta': 'data'})
321
322 You can also construct the credentials directly if you have a
323 :class:`~google.auth.crypt.Signer` instance::
324
325 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800326 signer,
327 issuer='your-issuer',
328 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800329 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700330
331 The claims are considered immutable. If you want to modify the claims,
332 you can easily create another instance using :meth:`with_claims`::
333
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800334 new_audience = (
335 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
336 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700337 """
338
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700339 def __init__(
340 self,
341 signer,
342 issuer,
343 subject,
344 audience,
345 additional_claims=None,
346 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700347 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700348 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700349 """
350 Args:
351 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
352 issuer (str): The `iss` claim.
353 subject (str): The `sub` claim.
354 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800355 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700356 additional_claims (Mapping[str, str]): Any additional claims for
357 the JWT payload.
358 token_lifetime (int): The amount of time in seconds for
359 which the token is valid. Defaults to 1 hour.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700360 quota_project_id (Optional[str]): The project ID used for quota
361 and billing.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700362 """
363 super(Credentials, self).__init__()
364 self._signer = signer
365 self._issuer = issuer
366 self._subject = subject
367 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700368 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700369 self._quota_project_id = quota_project_id
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700370
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700371 if additional_claims is None:
372 additional_claims = {}
373
374 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700375
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700376 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700377 def _from_signer_and_info(cls, signer, info, **kwargs):
378 """Creates a Credentials instance from a signer and service account
379 info.
380
381 Args:
382 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
383 info (Mapping[str, str]): The service account info.
384 kwargs: Additional arguments to pass to the constructor.
385
386 Returns:
387 google.auth.jwt.Credentials: The constructed credentials.
388
389 Raises:
390 ValueError: If the info is not in the expected format.
391 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700392 kwargs.setdefault("subject", info["client_email"])
393 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800394 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700395
396 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700397 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700398 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700399
400 Args:
401 info (Mapping[str, str]): The service account info in Google
402 format.
403 kwargs: Additional arguments to pass to the constructor.
404
405 Returns:
406 google.auth.jwt.Credentials: The constructed credentials.
407
408 Raises:
409 ValueError: If the info is not in the expected format.
410 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700411 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700412 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700413
414 @classmethod
415 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700416 """Creates a Credentials instance from a service account .json file
417 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700418
419 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700420 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700421 kwargs: Additional arguments to pass to the constructor.
422
423 Returns:
424 google.auth.jwt.Credentials: The constructed credentials.
425 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700426 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700427 filename, require=["client_email"]
428 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700429 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700430
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800431 @classmethod
432 def from_signing_credentials(cls, credentials, audience, **kwargs):
433 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
434 existing :class:`google.auth.credentials.Signing` instance.
435
436 The new instance will use the same signer as the existing instance and
437 will use the existing instance's signer email as the issuer and
438 subject by default.
439
440 Example::
441
442 svc_creds = service_account.Credentials.from_service_account_file(
443 'service_account.json')
444 audience = (
445 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
446 jwt_creds = jwt.Credentials.from_signing_credentials(
447 svc_creds, audience=audience)
448
449 Args:
450 credentials (google.auth.credentials.Signing): The credentials to
451 use to construct the new credentials.
452 audience (str): the `aud` claim. The intended audience for the
453 credentials.
454 kwargs: Additional arguments to pass to the constructor.
455
456 Returns:
457 google.auth.jwt.Credentials: A new Credentials instance.
458 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700459 kwargs.setdefault("issuer", credentials.signer_email)
460 kwargs.setdefault("subject", credentials.signer_email)
461 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800462
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700463 def with_claims(
464 self, issuer=None, subject=None, audience=None, additional_claims=None
465 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700466 """Returns a copy of these credentials with modified claims.
467
468 Args:
469 issuer (str): The `iss` claim. If unspecified the current issuer
470 claim will be used.
471 subject (str): The `sub` claim. If unspecified the current subject
472 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800473 audience (str): the `aud` claim. If unspecified the current
474 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700475 additional_claims (Mapping[str, str]): Any additional claims for
476 the JWT payload. This will be merged with the current
477 additional claims.
478
479 Returns:
480 google.auth.jwt.Credentials: A new credentials instance.
481 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700482 new_additional_claims = copy.deepcopy(self._additional_claims)
483 new_additional_claims.update(additional_claims or {})
484
Christophe Tatonb649b432018-02-08 14:12:23 -0800485 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700486 self._signer,
487 issuer=issuer if issuer is not None else self._issuer,
488 subject=subject if subject is not None else self._subject,
489 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700490 additional_claims=new_additional_claims,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700491 quota_project_id=self._quota_project_id,
492 )
493
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600494 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700495 def with_quota_project(self, quota_project_id):
496 return self.__class__(
497 self._signer,
498 issuer=self._issuer,
499 subject=self._subject,
500 audience=self._audience,
501 additional_claims=self._additional_claims,
502 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700503 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700504
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800505 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700506 """Make a signed JWT.
507
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700508 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800509 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700510 """
511 now = _helpers.utcnow()
512 lifetime = datetime.timedelta(seconds=self._token_lifetime)
513 expiry = now + lifetime
514
515 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700516 "iss": self._issuer,
517 "sub": self._subject,
518 "iat": _helpers.datetime_to_secs(now),
519 "exp": _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700520 }
arithmetic17282cfe6552021-06-16 15:30:36 -0700521 if self._audience:
522 payload["aud"] = self._audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700523
524 payload.update(self._additional_claims)
525
526 jwt = encode(self._signer, payload)
527
528 return jwt, expiry
529
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700530 def refresh(self, request):
531 """Refreshes the access token.
532
533 Args:
534 request (Any): Unused.
535 """
536 # pylint: disable=unused-argument
537 # (pylint doesn't correctly recognize overridden methods.)
538 self.token, self.expiry = self._make_jwt()
539
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800540 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700541 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700542 return self._signer.sign(message)
543
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800544 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800545 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800546 def signer_email(self):
547 return self._issuer
548
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800549 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800550 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800551 def signer(self):
552 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700553
554
555class OnDemandCredentials(
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600556 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700557):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700558 """On-demand JWT credentials.
559
560 Like :class:`Credentials`, this class uses a JWT as the bearer token for
561 authentication. However, this class does not require the audience at
562 construction time. Instead, it will generate a new token on-demand for
563 each request using the request URI as the audience. It caches tokens
564 so that multiple requests to the same URI do not incur the overhead
565 of generating a new token every time.
566
567 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
568 have multiple audience and gRPC clients may not know all of the audiences
569 required for accessing a particular service. With these credentials,
570 no knowledge of the audiences is required ahead of time.
571
572 .. _grpc: http://www.grpc.io/
573 """
574
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700575 def __init__(
576 self,
577 signer,
578 issuer,
579 subject,
580 additional_claims=None,
581 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
582 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700583 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700584 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700585 """
586 Args:
587 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
588 issuer (str): The `iss` claim.
589 subject (str): The `sub` claim.
590 additional_claims (Mapping[str, str]): Any additional claims for
591 the JWT payload.
592 token_lifetime (int): The amount of time in seconds for
593 which the token is valid. Defaults to 1 hour.
594 max_cache_size (int): The maximum number of JWT tokens to keep in
595 cache. Tokens are cached using :class:`cachetools.LRUCache`.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700596 quota_project_id (Optional[str]): The project ID used for quota
597 and billing.
598
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700599 """
600 super(OnDemandCredentials, self).__init__()
601 self._signer = signer
602 self._issuer = issuer
603 self._subject = subject
604 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700605 self._quota_project_id = quota_project_id
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700606
607 if additional_claims is None:
608 additional_claims = {}
609
610 self._additional_claims = additional_claims
611 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
612
613 @classmethod
614 def _from_signer_and_info(cls, signer, info, **kwargs):
615 """Creates an OnDemandCredentials instance from a signer and service
616 account info.
617
618 Args:
619 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
620 info (Mapping[str, str]): The service account info.
621 kwargs: Additional arguments to pass to the constructor.
622
623 Returns:
624 google.auth.jwt.OnDemandCredentials: The constructed credentials.
625
626 Raises:
627 ValueError: If the info is not in the expected format.
628 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700629 kwargs.setdefault("subject", info["client_email"])
630 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700631 return cls(signer, **kwargs)
632
633 @classmethod
634 def from_service_account_info(cls, info, **kwargs):
635 """Creates an OnDemandCredentials instance from a dictionary.
636
637 Args:
638 info (Mapping[str, str]): The service account info in Google
639 format.
640 kwargs: Additional arguments to pass to the constructor.
641
642 Returns:
643 google.auth.jwt.OnDemandCredentials: The constructed credentials.
644
645 Raises:
646 ValueError: If the info is not in the expected format.
647 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700648 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700649 return cls._from_signer_and_info(signer, info, **kwargs)
650
651 @classmethod
652 def from_service_account_file(cls, filename, **kwargs):
653 """Creates an OnDemandCredentials instance from a service account .json
654 file in Google format.
655
656 Args:
657 filename (str): The path to the service account .json file.
658 kwargs: Additional arguments to pass to the constructor.
659
660 Returns:
661 google.auth.jwt.OnDemandCredentials: The constructed credentials.
662 """
663 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700664 filename, require=["client_email"]
665 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700666 return cls._from_signer_and_info(signer, info, **kwargs)
667
668 @classmethod
669 def from_signing_credentials(cls, credentials, **kwargs):
670 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
671 from an existing :class:`google.auth.credentials.Signing` instance.
672
673 The new instance will use the same signer as the existing instance and
674 will use the existing instance's signer email as the issuer and
675 subject by default.
676
677 Example::
678
679 svc_creds = service_account.Credentials.from_service_account_file(
680 'service_account.json')
681 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
682 svc_creds)
683
684 Args:
685 credentials (google.auth.credentials.Signing): The credentials to
686 use to construct the new credentials.
687 kwargs: Additional arguments to pass to the constructor.
688
689 Returns:
690 google.auth.jwt.Credentials: A new Credentials instance.
691 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700692 kwargs.setdefault("issuer", credentials.signer_email)
693 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700694 return cls(credentials.signer, **kwargs)
695
696 def with_claims(self, issuer=None, subject=None, additional_claims=None):
697 """Returns a copy of these credentials with modified claims.
698
699 Args:
700 issuer (str): The `iss` claim. If unspecified the current issuer
701 claim will be used.
702 subject (str): The `sub` claim. If unspecified the current subject
703 claim will be used.
704 additional_claims (Mapping[str, str]): Any additional claims for
705 the JWT payload. This will be merged with the current
706 additional claims.
707
708 Returns:
709 google.auth.jwt.OnDemandCredentials: A new credentials instance.
710 """
711 new_additional_claims = copy.deepcopy(self._additional_claims)
712 new_additional_claims.update(additional_claims or {})
713
Christophe Tatonb649b432018-02-08 14:12:23 -0800714 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700715 self._signer,
716 issuer=issuer if issuer is not None else self._issuer,
717 subject=subject if subject is not None else self._subject,
718 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700719 max_cache_size=self._cache.maxsize,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700720 quota_project_id=self._quota_project_id,
721 )
722
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600723 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700724 def with_quota_project(self, quota_project_id):
725
726 return self.__class__(
727 self._signer,
728 issuer=self._issuer,
729 subject=self._subject,
730 additional_claims=self._additional_claims,
731 max_cache_size=self._cache.maxsize,
732 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700733 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700734
735 @property
736 def valid(self):
737 """Checks the validity of the credentials.
738
739 These credentials are always valid because it generates tokens on
740 demand.
741 """
742 return True
743
744 def _make_jwt_for_audience(self, audience):
745 """Make a new JWT for the given audience.
746
747 Args:
748 audience (str): The intended audience.
749
750 Returns:
751 Tuple[bytes, datetime]: The encoded JWT and the expiration.
752 """
753 now = _helpers.utcnow()
754 lifetime = datetime.timedelta(seconds=self._token_lifetime)
755 expiry = now + lifetime
756
757 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700758 "iss": self._issuer,
759 "sub": self._subject,
760 "iat": _helpers.datetime_to_secs(now),
761 "exp": _helpers.datetime_to_secs(expiry),
762 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700763 }
764
765 payload.update(self._additional_claims)
766
767 jwt = encode(self._signer, payload)
768
769 return jwt, expiry
770
771 def _get_jwt_for_audience(self, audience):
772 """Get a JWT For a given audience.
773
774 If there is already an existing, non-expired token in the cache for
775 the audience, that token is used. Otherwise, a new token will be
776 created.
777
778 Args:
779 audience (str): The intended audience.
780
781 Returns:
782 bytes: The encoded JWT.
783 """
784 token, expiry = self._cache.get(audience, (None, None))
785
786 if token is None or expiry < _helpers.utcnow():
787 token, expiry = self._make_jwt_for_audience(audience)
788 self._cache[audience] = token, expiry
789
790 return token
791
792 def refresh(self, request):
793 """Raises an exception, these credentials can not be directly
794 refreshed.
795
796 Args:
797 request (Any): Unused.
798
799 Raises:
800 google.auth.RefreshError
801 """
802 # pylint: disable=unused-argument
803 # (pylint doesn't correctly recognize overridden methods.)
804 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700805 "OnDemandCredentials can not be directly refreshed."
806 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700807
808 def before_request(self, request, method, url, headers):
809 """Performs credential-specific before request logic.
810
811 Args:
812 request (Any): Unused. JWT credentials do not need to make an
813 HTTP request to refresh.
814 method (str): The request's HTTP method.
815 url (str): The request's URI. This is used as the audience claim
816 when generating the JWT.
817 headers (Mapping): The request's headers.
818 """
819 # pylint: disable=unused-argument
820 # (pylint doesn't correctly recognize overridden methods.)
821 parts = urllib.parse.urlsplit(url)
822 # Strip query string and fragment
823 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700824 (parts.scheme, parts.netloc, parts.path, "", "")
825 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700826 token = self._get_jwt_for_audience(audience)
827 self.apply(headers, token=token)
828
829 @_helpers.copy_docstring(google.auth.credentials.Signing)
830 def sign_bytes(self, message):
831 return self._signer.sign(message)
832
833 @property
834 @_helpers.copy_docstring(google.auth.credentials.Signing)
835 def signer_email(self):
836 return self._issuer
837
838 @property
839 @_helpers.copy_docstring(google.auth.credentials.Signing)
840 def signer(self):
841 return self._signer