blob: a30c575b2c3a5dc6ec6450f6615ddc7ecb969115 [file] [log] [blame]
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -07001# Copyright 2016 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""JSON Web Tokens
16
17Provides support for creating (encoding) and verifying (decoding) JWTs,
18especially JWTs generated and consumed by Google infrastructure.
19
20See `rfc7519`_ for more details on JWTs.
21
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
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070043import collections
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
47
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070048import cachetools
Danny Hermes895e3692017-11-09 11:35:57 -080049import six
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070050from six.moves import urllib
51
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070052from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070053from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070054from google.auth import crypt
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070055from google.auth import exceptions
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080056import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070057
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070058_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070059_DEFAULT_MAX_CACHE_SIZE = 10
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070060
61
62def encode(signer, payload, header=None, key_id=None):
63 """Make a signed JWT.
64
65 Args:
66 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070067 payload (Mapping[str, str]): The JWT payload.
68 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070069 key_id (str): The key id to add to the JWT header. If the
70 signer has a key id it will be used as the default. If this is
71 specified it will override the signer's key id.
72
73 Returns:
74 bytes: The encoded JWT.
75 """
76 if header is None:
77 header = {}
78
79 if key_id is None:
80 key_id = signer.key_id
81
Bu Sun Kim9eec0912019-10-21 17:04:21 -070082 header.update({"typ": "JWT", "alg": "RS256"})
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070083
84 if key_id is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -070085 header["kid"] = key_id
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070086
87 segments = [
Bu Sun Kim9eec0912019-10-21 17:04:21 -070088 _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")),
89 _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")),
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070090 ]
91
Bu Sun Kim9eec0912019-10-21 17:04:21 -070092 signing_input = b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070093 signature = signer.sign(signing_input)
Bu Sun Kim9eec0912019-10-21 17:04:21 -070094 segments.append(_helpers.unpadded_urlsafe_b64encode(signature))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070095
Bu Sun Kim9eec0912019-10-21 17:04:21 -070096 return b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070097
98
99def _decode_jwt_segment(encoded_section):
100 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800101 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700102 try:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700103 return json.loads(section_bytes.decode("utf-8"))
Danny Hermes895e3692017-11-09 11:35:57 -0800104 except ValueError as caught_exc:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700105 new_exc = ValueError("Can't parse segment: {0}".format(section_bytes))
Danny Hermes895e3692017-11-09 11:35:57 -0800106 six.raise_from(new_exc, caught_exc)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700107
108
109def _unverified_decode(token):
110 """Decodes a token and does no verification.
111
112 Args:
113 token (Union[str, bytes]): The encoded JWT.
114
115 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800116 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700117 signature.
118
119 Raises:
120 ValueError: if there are an incorrect amount of segments in the token.
121 """
122 token = _helpers.to_bytes(token)
123
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700124 if token.count(b".") != 2:
125 raise ValueError("Wrong number of segments in token: {0}".format(token))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700126
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700127 encoded_header, encoded_payload, signature = token.split(b".")
128 signed_section = encoded_header + b"." + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800129 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700130
131 # Parse segments
132 header = _decode_jwt_segment(encoded_header)
133 payload = _decode_jwt_segment(encoded_payload)
134
135 return header, payload, signed_section, signature
136
137
138def decode_header(token):
139 """Return the decoded header of a token.
140
141 No verification is done. This is useful to extract the key id from
142 the header in order to acquire the appropriate certificate to verify
143 the token.
144
145 Args:
146 token (Union[str, bytes]): the encoded JWT.
147
148 Returns:
149 Mapping: The decoded JWT header.
150 """
151 header, _, _, _ = _unverified_decode(token)
152 return header
153
154
155def _verify_iat_and_exp(payload):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700156 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700157 payload.
158
159 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700160 payload (Mapping[str, str]): The JWT payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700161
162 Raises:
163 ValueError: if any checks failed.
164 """
165 now = _helpers.datetime_to_secs(_helpers.utcnow())
166
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700167 # Make sure the iat and exp claims are present.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700168 for key in ("iat", "exp"):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700169 if key not in payload:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700170 raise ValueError("Token does not contain required claim {}".format(key))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700171
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700172 # Make sure the token wasn't issued in the future.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700173 iat = payload["iat"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700174 # Err on the side of accepting a token that is slightly early to account
175 # for clock skew.
176 earliest = iat - _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700177 if now < earliest:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700178 raise ValueError("Token used too early, {} < {}".format(now, iat))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700179
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700180 # Make sure the token wasn't issued in the past.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700181 exp = payload["exp"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700182 # Err on the side of accepting a token that is slightly out of date
183 # to account for clow skew.
184 latest = exp + _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700185 if latest < now:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700186 raise ValueError("Token expired, {} < {}".format(latest, now))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700187
188
189def decode(token, certs=None, verify=True, audience=None):
190 """Decode and verify a JWT.
191
192 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700193 token (str): The encoded JWT.
194 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
Tianzi Cai2c6ad782019-03-29 13:49:06 -0700195 certificate used to validate the JWT signature. If bytes or string,
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700196 it must the the public key certificate in PEM format. If a mapping,
197 it must be a mapping of key IDs to public key certificates in PEM
198 format. The mapping must contain the same key ID that's specified
199 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700200 verify (bool): Whether to perform signature and claim validation.
201 Verification is done by default.
202 audience (str): The audience claim, 'aud', that this JWT should
203 contain. If None then the JWT's 'aud' parameter is not verified.
204
205 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700206 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700207
208 Raises:
209 ValueError: if any verification checks failed.
210 """
211 header, payload, signed_section, signature = _unverified_decode(token)
212
213 if not verify:
214 return payload
215
216 # If certs is specified as a dictionary of key IDs to certificates, then
217 # use the certificate identified by the key ID in the token header.
218 if isinstance(certs, collections.Mapping):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700219 key_id = header.get("kid")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700220 if key_id:
221 if key_id not in certs:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700222 raise ValueError("Certificate for key id {} not found.".format(key_id))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700223 certs_to_check = [certs[key_id]]
224 # If there's no key id in the header, check against all of the certs.
225 else:
226 certs_to_check = certs.values()
227 else:
228 certs_to_check = certs
229
230 # Verify that the signature matches the message.
231 if not crypt.verify_signature(signed_section, signature, certs_to_check):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700232 raise ValueError("Could not verify token signature.")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700233
234 # Verify the issued at and created times in the payload.
235 _verify_iat_and_exp(payload)
236
237 # Check audience.
238 if audience is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700239 claim_audience = payload.get("aud")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700240 if audience != claim_audience:
241 raise ValueError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700242 "Token has wrong audience {}, expected {}".format(
243 claim_audience, audience
244 )
245 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700246
247 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700248
249
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700250class Credentials(google.auth.credentials.Signing, google.auth.credentials.Credentials):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700251 """Credentials that use a JWT as the bearer token.
252
253 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800254 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700255
256 The constructor arguments determine the claims for the JWT that is
257 sent with requests. Usually, you'll construct these credentials with
258 one of the helper constructors as shown in the next section.
259
260 To create JWT credentials using a Google service account private key
261 JSON file::
262
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800263 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700264 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800265 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800266 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700267
268 If you already have the service account file loaded and parsed::
269
270 service_account_info = json.load(open('service_account.json'))
271 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800272 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800273 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700274
275 Both helper methods pass on arguments to the constructor, so you can
276 specify the JWT claims::
277
278 credentials = jwt.Credentials.from_service_account_file(
279 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800280 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700281 additional_claims={'meta': 'data'})
282
283 You can also construct the credentials directly if you have a
284 :class:`~google.auth.crypt.Signer` instance::
285
286 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800287 signer,
288 issuer='your-issuer',
289 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800290 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700291
292 The claims are considered immutable. If you want to modify the claims,
293 you can easily create another instance using :meth:`with_claims`::
294
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800295 new_audience = (
296 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
297 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700298 """
299
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700300 def __init__(
301 self,
302 signer,
303 issuer,
304 subject,
305 audience,
306 additional_claims=None,
307 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
308 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700309 """
310 Args:
311 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
312 issuer (str): The `iss` claim.
313 subject (str): The `sub` claim.
314 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800315 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700316 additional_claims (Mapping[str, str]): Any additional claims for
317 the JWT payload.
318 token_lifetime (int): The amount of time in seconds for
319 which the token is valid. Defaults to 1 hour.
320 """
321 super(Credentials, self).__init__()
322 self._signer = signer
323 self._issuer = issuer
324 self._subject = subject
325 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700326 self._token_lifetime = token_lifetime
327
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700328 if additional_claims is None:
329 additional_claims = {}
330
331 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700332
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700333 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700334 def _from_signer_and_info(cls, signer, info, **kwargs):
335 """Creates a Credentials instance from a signer and service account
336 info.
337
338 Args:
339 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
340 info (Mapping[str, str]): The service account info.
341 kwargs: Additional arguments to pass to the constructor.
342
343 Returns:
344 google.auth.jwt.Credentials: The constructed credentials.
345
346 Raises:
347 ValueError: If the info is not in the expected format.
348 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700349 kwargs.setdefault("subject", info["client_email"])
350 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800351 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700352
353 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700354 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700355 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700356
357 Args:
358 info (Mapping[str, str]): The service account info in Google
359 format.
360 kwargs: Additional arguments to pass to the constructor.
361
362 Returns:
363 google.auth.jwt.Credentials: The constructed credentials.
364
365 Raises:
366 ValueError: If the info is not in the expected format.
367 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700368 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700369 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700370
371 @classmethod
372 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700373 """Creates a Credentials instance from a service account .json file
374 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700375
376 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700377 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700378 kwargs: Additional arguments to pass to the constructor.
379
380 Returns:
381 google.auth.jwt.Credentials: The constructed credentials.
382 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700383 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700384 filename, require=["client_email"]
385 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700386 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700387
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800388 @classmethod
389 def from_signing_credentials(cls, credentials, audience, **kwargs):
390 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
391 existing :class:`google.auth.credentials.Signing` instance.
392
393 The new instance will use the same signer as the existing instance and
394 will use the existing instance's signer email as the issuer and
395 subject by default.
396
397 Example::
398
399 svc_creds = service_account.Credentials.from_service_account_file(
400 'service_account.json')
401 audience = (
402 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
403 jwt_creds = jwt.Credentials.from_signing_credentials(
404 svc_creds, audience=audience)
405
406 Args:
407 credentials (google.auth.credentials.Signing): The credentials to
408 use to construct the new credentials.
409 audience (str): the `aud` claim. The intended audience for the
410 credentials.
411 kwargs: Additional arguments to pass to the constructor.
412
413 Returns:
414 google.auth.jwt.Credentials: A new Credentials instance.
415 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700416 kwargs.setdefault("issuer", credentials.signer_email)
417 kwargs.setdefault("subject", credentials.signer_email)
418 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800419
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700420 def with_claims(
421 self, issuer=None, subject=None, audience=None, additional_claims=None
422 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700423 """Returns a copy of these credentials with modified claims.
424
425 Args:
426 issuer (str): The `iss` claim. If unspecified the current issuer
427 claim will be used.
428 subject (str): The `sub` claim. If unspecified the current subject
429 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800430 audience (str): the `aud` claim. If unspecified the current
431 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700432 additional_claims (Mapping[str, str]): Any additional claims for
433 the JWT payload. This will be merged with the current
434 additional claims.
435
436 Returns:
437 google.auth.jwt.Credentials: A new credentials instance.
438 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700439 new_additional_claims = copy.deepcopy(self._additional_claims)
440 new_additional_claims.update(additional_claims or {})
441
Christophe Tatonb649b432018-02-08 14:12:23 -0800442 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700443 self._signer,
444 issuer=issuer if issuer is not None else self._issuer,
445 subject=subject if subject is not None else self._subject,
446 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700447 additional_claims=new_additional_claims,
448 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700449
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800450 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700451 """Make a signed JWT.
452
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700453 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800454 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700455 """
456 now = _helpers.utcnow()
457 lifetime = datetime.timedelta(seconds=self._token_lifetime)
458 expiry = now + lifetime
459
460 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700461 "iss": self._issuer,
462 "sub": self._subject,
463 "iat": _helpers.datetime_to_secs(now),
464 "exp": _helpers.datetime_to_secs(expiry),
465 "aud": self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700466 }
467
468 payload.update(self._additional_claims)
469
470 jwt = encode(self._signer, payload)
471
472 return jwt, expiry
473
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700474 def refresh(self, request):
475 """Refreshes the access token.
476
477 Args:
478 request (Any): Unused.
479 """
480 # pylint: disable=unused-argument
481 # (pylint doesn't correctly recognize overridden methods.)
482 self.token, self.expiry = self._make_jwt()
483
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800484 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700485 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700486 return self._signer.sign(message)
487
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800488 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800489 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800490 def signer_email(self):
491 return self._issuer
492
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800493 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800494 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800495 def signer(self):
496 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700497
498
499class OnDemandCredentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700500 google.auth.credentials.Signing, google.auth.credentials.Credentials
501):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700502 """On-demand JWT credentials.
503
504 Like :class:`Credentials`, this class uses a JWT as the bearer token for
505 authentication. However, this class does not require the audience at
506 construction time. Instead, it will generate a new token on-demand for
507 each request using the request URI as the audience. It caches tokens
508 so that multiple requests to the same URI do not incur the overhead
509 of generating a new token every time.
510
511 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
512 have multiple audience and gRPC clients may not know all of the audiences
513 required for accessing a particular service. With these credentials,
514 no knowledge of the audiences is required ahead of time.
515
516 .. _grpc: http://www.grpc.io/
517 """
518
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700519 def __init__(
520 self,
521 signer,
522 issuer,
523 subject,
524 additional_claims=None,
525 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
526 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
527 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700528 """
529 Args:
530 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
531 issuer (str): The `iss` claim.
532 subject (str): The `sub` claim.
533 additional_claims (Mapping[str, str]): Any additional claims for
534 the JWT payload.
535 token_lifetime (int): The amount of time in seconds for
536 which the token is valid. Defaults to 1 hour.
537 max_cache_size (int): The maximum number of JWT tokens to keep in
538 cache. Tokens are cached using :class:`cachetools.LRUCache`.
539 """
540 super(OnDemandCredentials, self).__init__()
541 self._signer = signer
542 self._issuer = issuer
543 self._subject = subject
544 self._token_lifetime = token_lifetime
545
546 if additional_claims is None:
547 additional_claims = {}
548
549 self._additional_claims = additional_claims
550 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
551
552 @classmethod
553 def _from_signer_and_info(cls, signer, info, **kwargs):
554 """Creates an OnDemandCredentials instance from a signer and service
555 account info.
556
557 Args:
558 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
559 info (Mapping[str, str]): The service account info.
560 kwargs: Additional arguments to pass to the constructor.
561
562 Returns:
563 google.auth.jwt.OnDemandCredentials: The constructed credentials.
564
565 Raises:
566 ValueError: If the info is not in the expected format.
567 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700568 kwargs.setdefault("subject", info["client_email"])
569 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700570 return cls(signer, **kwargs)
571
572 @classmethod
573 def from_service_account_info(cls, info, **kwargs):
574 """Creates an OnDemandCredentials instance from a dictionary.
575
576 Args:
577 info (Mapping[str, str]): The service account info in Google
578 format.
579 kwargs: Additional arguments to pass to the constructor.
580
581 Returns:
582 google.auth.jwt.OnDemandCredentials: The constructed credentials.
583
584 Raises:
585 ValueError: If the info is not in the expected format.
586 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700587 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700588 return cls._from_signer_and_info(signer, info, **kwargs)
589
590 @classmethod
591 def from_service_account_file(cls, filename, **kwargs):
592 """Creates an OnDemandCredentials instance from a service account .json
593 file in Google format.
594
595 Args:
596 filename (str): The path to the service account .json file.
597 kwargs: Additional arguments to pass to the constructor.
598
599 Returns:
600 google.auth.jwt.OnDemandCredentials: The constructed credentials.
601 """
602 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700603 filename, require=["client_email"]
604 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700605 return cls._from_signer_and_info(signer, info, **kwargs)
606
607 @classmethod
608 def from_signing_credentials(cls, credentials, **kwargs):
609 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
610 from an existing :class:`google.auth.credentials.Signing` instance.
611
612 The new instance will use the same signer as the existing instance and
613 will use the existing instance's signer email as the issuer and
614 subject by default.
615
616 Example::
617
618 svc_creds = service_account.Credentials.from_service_account_file(
619 'service_account.json')
620 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
621 svc_creds)
622
623 Args:
624 credentials (google.auth.credentials.Signing): The credentials to
625 use to construct the new credentials.
626 kwargs: Additional arguments to pass to the constructor.
627
628 Returns:
629 google.auth.jwt.Credentials: A new Credentials instance.
630 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700631 kwargs.setdefault("issuer", credentials.signer_email)
632 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700633 return cls(credentials.signer, **kwargs)
634
635 def with_claims(self, issuer=None, subject=None, additional_claims=None):
636 """Returns a copy of these credentials with modified claims.
637
638 Args:
639 issuer (str): The `iss` claim. If unspecified the current issuer
640 claim will be used.
641 subject (str): The `sub` claim. If unspecified the current subject
642 claim will be used.
643 additional_claims (Mapping[str, str]): Any additional claims for
644 the JWT payload. This will be merged with the current
645 additional claims.
646
647 Returns:
648 google.auth.jwt.OnDemandCredentials: A new credentials instance.
649 """
650 new_additional_claims = copy.deepcopy(self._additional_claims)
651 new_additional_claims.update(additional_claims or {})
652
Christophe Tatonb649b432018-02-08 14:12:23 -0800653 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700654 self._signer,
655 issuer=issuer if issuer is not None else self._issuer,
656 subject=subject if subject is not None else self._subject,
657 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700658 max_cache_size=self._cache.maxsize,
659 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700660
661 @property
662 def valid(self):
663 """Checks the validity of the credentials.
664
665 These credentials are always valid because it generates tokens on
666 demand.
667 """
668 return True
669
670 def _make_jwt_for_audience(self, audience):
671 """Make a new JWT for the given audience.
672
673 Args:
674 audience (str): The intended audience.
675
676 Returns:
677 Tuple[bytes, datetime]: The encoded JWT and the expiration.
678 """
679 now = _helpers.utcnow()
680 lifetime = datetime.timedelta(seconds=self._token_lifetime)
681 expiry = now + lifetime
682
683 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700684 "iss": self._issuer,
685 "sub": self._subject,
686 "iat": _helpers.datetime_to_secs(now),
687 "exp": _helpers.datetime_to_secs(expiry),
688 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700689 }
690
691 payload.update(self._additional_claims)
692
693 jwt = encode(self._signer, payload)
694
695 return jwt, expiry
696
697 def _get_jwt_for_audience(self, audience):
698 """Get a JWT For a given audience.
699
700 If there is already an existing, non-expired token in the cache for
701 the audience, that token is used. Otherwise, a new token will be
702 created.
703
704 Args:
705 audience (str): The intended audience.
706
707 Returns:
708 bytes: The encoded JWT.
709 """
710 token, expiry = self._cache.get(audience, (None, None))
711
712 if token is None or expiry < _helpers.utcnow():
713 token, expiry = self._make_jwt_for_audience(audience)
714 self._cache[audience] = token, expiry
715
716 return token
717
718 def refresh(self, request):
719 """Raises an exception, these credentials can not be directly
720 refreshed.
721
722 Args:
723 request (Any): Unused.
724
725 Raises:
726 google.auth.RefreshError
727 """
728 # pylint: disable=unused-argument
729 # (pylint doesn't correctly recognize overridden methods.)
730 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700731 "OnDemandCredentials can not be directly refreshed."
732 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700733
734 def before_request(self, request, method, url, headers):
735 """Performs credential-specific before request logic.
736
737 Args:
738 request (Any): Unused. JWT credentials do not need to make an
739 HTTP request to refresh.
740 method (str): The request's HTTP method.
741 url (str): The request's URI. This is used as the audience claim
742 when generating the JWT.
743 headers (Mapping): The request's headers.
744 """
745 # pylint: disable=unused-argument
746 # (pylint doesn't correctly recognize overridden methods.)
747 parts = urllib.parse.urlsplit(url)
748 # Strip query string and fragment
749 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700750 (parts.scheme, parts.netloc, parts.path, "", "")
751 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700752 token = self._get_jwt_for_audience(audience)
753 self.apply(headers, token=token)
754
755 @_helpers.copy_docstring(google.auth.credentials.Signing)
756 def sign_bytes(self, message):
757 return self._signer.sign(message)
758
759 @property
760 @_helpers.copy_docstring(google.auth.credentials.Signing)
761 def signer_email(self):
762 return self._issuer
763
764 @property
765 @_helpers.copy_docstring(google.auth.credentials.Signing)
766 def signer(self):
767 return self._signer