blob: 9248eb27f13328b06f1cf6df345bd210a7038d59 [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
Jay Leec5a33952020-01-17 11:18:47 -080043try:
44 from collections.abc import Mapping
Bu Sun Kim0ca0ee52020-01-18 00:38:49 -080045# Python 2.7 compatibility
46except ImportError: # pragma: NO COVER
Jay Leec5a33952020-01-17 11:18:47 -080047 from collections import Mapping
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -070048import copy
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -070049import datetime
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070050import json
51
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070052import cachetools
Danny Hermes895e3692017-11-09 11:35:57 -080053import six
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070054from six.moves import urllib
55
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070056from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070057from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070058from google.auth import crypt
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070059from google.auth import exceptions
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080060import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070061
Thea Flowerse290a3d2020-04-01 10:11:42 -070062try:
63 from google.auth.crypt import es256
64except ImportError: # pragma: NO COVER
65 es256 = None
66
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070067_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070068_DEFAULT_MAX_CACHE_SIZE = 10
Thea Flowerse290a3d2020-04-01 10:11:42 -070069_ALGORITHM_TO_VERIFIER_CLASS = {"RS256": crypt.RSAVerifier}
70_CRYPTOGRAPHY_BASED_ALGORITHMS = set(["ES256"])
71
72if es256 is not None: # pragma: NO COVER
73 _ALGORITHM_TO_VERIFIER_CLASS["ES256"] = es256.ES256Verifier
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070074
75
76def encode(signer, payload, header=None, key_id=None):
77 """Make a signed JWT.
78
79 Args:
80 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070081 payload (Mapping[str, str]): The JWT payload.
82 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070083 key_id (str): The key id to add to the JWT header. If the
84 signer has a key id it will be used as the default. If this is
85 specified it will override the signer's key id.
86
87 Returns:
88 bytes: The encoded JWT.
89 """
90 if header is None:
91 header = {}
92
93 if key_id is None:
94 key_id = signer.key_id
95
Thea Flowerse290a3d2020-04-01 10:11:42 -070096 header.update({"typ": "JWT"})
97
98 if es256 is not None and isinstance(signer, es256.ES256Signer):
99 header.update({"alg": "ES256"})
100 else:
101 header.update({"alg": "RS256"})
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700102
103 if key_id is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700104 header["kid"] = key_id
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700105
106 segments = [
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700107 _helpers.unpadded_urlsafe_b64encode(json.dumps(header).encode("utf-8")),
108 _helpers.unpadded_urlsafe_b64encode(json.dumps(payload).encode("utf-8")),
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700109 ]
110
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700111 signing_input = b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700112 signature = signer.sign(signing_input)
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700113 segments.append(_helpers.unpadded_urlsafe_b64encode(signature))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700114
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700115 return b".".join(segments)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700116
117
118def _decode_jwt_segment(encoded_section):
119 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800120 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700121 try:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700122 return json.loads(section_bytes.decode("utf-8"))
Danny Hermes895e3692017-11-09 11:35:57 -0800123 except ValueError as caught_exc:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700124 new_exc = ValueError("Can't parse segment: {0}".format(section_bytes))
Danny Hermes895e3692017-11-09 11:35:57 -0800125 six.raise_from(new_exc, caught_exc)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700126
127
128def _unverified_decode(token):
129 """Decodes a token and does no verification.
130
131 Args:
132 token (Union[str, bytes]): The encoded JWT.
133
134 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800135 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700136 signature.
137
138 Raises:
139 ValueError: if there are an incorrect amount of segments in the token.
140 """
141 token = _helpers.to_bytes(token)
142
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700143 if token.count(b".") != 2:
144 raise ValueError("Wrong number of segments in token: {0}".format(token))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700145
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700146 encoded_header, encoded_payload, signature = token.split(b".")
147 signed_section = encoded_header + b"." + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800148 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700149
150 # Parse segments
151 header = _decode_jwt_segment(encoded_header)
152 payload = _decode_jwt_segment(encoded_payload)
153
154 return header, payload, signed_section, signature
155
156
157def decode_header(token):
158 """Return the decoded header of a token.
159
160 No verification is done. This is useful to extract the key id from
161 the header in order to acquire the appropriate certificate to verify
162 the token.
163
164 Args:
165 token (Union[str, bytes]): the encoded JWT.
166
167 Returns:
168 Mapping: The decoded JWT header.
169 """
170 header, _, _, _ = _unverified_decode(token)
171 return header
172
173
174def _verify_iat_and_exp(payload):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700175 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700176 payload.
177
178 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700179 payload (Mapping[str, str]): The JWT payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700180
181 Raises:
182 ValueError: if any checks failed.
183 """
184 now = _helpers.datetime_to_secs(_helpers.utcnow())
185
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700186 # Make sure the iat and exp claims are present.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700187 for key in ("iat", "exp"):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700188 if key not in payload:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700189 raise ValueError("Token does not contain required claim {}".format(key))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700190
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700191 # Make sure the token wasn't issued in the future.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700192 iat = payload["iat"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700193 # Err on the side of accepting a token that is slightly early to account
194 # for clock skew.
195 earliest = iat - _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700196 if now < earliest:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700197 raise ValueError("Token used too early, {} < {}".format(now, iat))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700198
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700199 # Make sure the token wasn't issued in the past.
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700200 exp = payload["exp"]
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700201 # Err on the side of accepting a token that is slightly out of date
202 # to account for clow skew.
203 latest = exp + _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700204 if latest < now:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700205 raise ValueError("Token expired, {} < {}".format(latest, now))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700206
207
208def decode(token, certs=None, verify=True, audience=None):
209 """Decode and verify a JWT.
210
211 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700212 token (str): The encoded JWT.
213 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
Tianzi Cai2c6ad782019-03-29 13:49:06 -0700214 certificate used to validate the JWT signature. If bytes or string,
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700215 it must the the public key certificate in PEM format. If a mapping,
216 it must be a mapping of key IDs to public key certificates in PEM
217 format. The mapping must contain the same key ID that's specified
218 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700219 verify (bool): Whether to perform signature and claim validation.
220 Verification is done by default.
221 audience (str): The audience claim, 'aud', that this JWT should
222 contain. If None then the JWT's 'aud' parameter is not verified.
223
224 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700225 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700226
227 Raises:
228 ValueError: if any verification checks failed.
229 """
230 header, payload, signed_section, signature = _unverified_decode(token)
231
232 if not verify:
233 return payload
234
Thea Flowerse290a3d2020-04-01 10:11:42 -0700235 # Pluck the key id and algorithm from the header and make sure we have
236 # a verifier that can support it.
237 key_alg = header.get("alg")
238 key_id = header.get("kid")
239
240 try:
241 verifier_cls = _ALGORITHM_TO_VERIFIER_CLASS[key_alg]
242 except KeyError as exc:
243 if key_alg in _CRYPTOGRAPHY_BASED_ALGORITHMS:
244 six.raise_from(
245 ValueError(
246 "The key algorithm {} requires the cryptography package "
247 "to be installed.".format(key_alg)
248 ),
249 exc,
250 )
251 else:
252 six.raise_from(
253 ValueError("Unsupported signature algorithm {}".format(key_alg)), exc
254 )
255
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700256 # If certs is specified as a dictionary of key IDs to certificates, then
257 # use the certificate identified by the key ID in the token header.
Jay Leec5a33952020-01-17 11:18:47 -0800258 if isinstance(certs, Mapping):
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700259 if key_id:
260 if key_id not in certs:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700261 raise ValueError("Certificate for key id {} not found.".format(key_id))
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700262 certs_to_check = [certs[key_id]]
263 # If there's no key id in the header, check against all of the certs.
264 else:
265 certs_to_check = certs.values()
266 else:
267 certs_to_check = certs
268
269 # Verify that the signature matches the message.
Thea Flowerse290a3d2020-04-01 10:11:42 -0700270 if not crypt.verify_signature(
271 signed_section, signature, certs_to_check, verifier_cls
272 ):
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700273 raise ValueError("Could not verify token signature.")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700274
275 # Verify the issued at and created times in the payload.
276 _verify_iat_and_exp(payload)
277
278 # Check audience.
279 if audience is not None:
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700280 claim_audience = payload.get("aud")
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700281 if audience != claim_audience:
282 raise ValueError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700283 "Token has wrong audience {}, expected {}".format(
284 claim_audience, audience
285 )
286 )
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700287
288 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700289
290
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700291class Credentials(google.auth.credentials.Signing, google.auth.credentials.Credentials):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700292 """Credentials that use a JWT as the bearer token.
293
294 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800295 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700296
297 The constructor arguments determine the claims for the JWT that is
298 sent with requests. Usually, you'll construct these credentials with
299 one of the helper constructors as shown in the next section.
300
301 To create JWT credentials using a Google service account private key
302 JSON file::
303
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800304 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700305 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800306 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800307 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700308
309 If you already have the service account file loaded and parsed::
310
311 service_account_info = json.load(open('service_account.json'))
312 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800313 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800314 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700315
316 Both helper methods pass on arguments to the constructor, so you can
317 specify the JWT claims::
318
319 credentials = jwt.Credentials.from_service_account_file(
320 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800321 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700322 additional_claims={'meta': 'data'})
323
324 You can also construct the credentials directly if you have a
325 :class:`~google.auth.crypt.Signer` instance::
326
327 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800328 signer,
329 issuer='your-issuer',
330 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800331 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700332
333 The claims are considered immutable. If you want to modify the claims,
334 you can easily create another instance using :meth:`with_claims`::
335
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800336 new_audience = (
337 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
338 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700339 """
340
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700341 def __init__(
342 self,
343 signer,
344 issuer,
345 subject,
346 audience,
347 additional_claims=None,
348 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
349 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700350 """
351 Args:
352 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
353 issuer (str): The `iss` claim.
354 subject (str): The `sub` claim.
355 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800356 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700357 additional_claims (Mapping[str, str]): Any additional claims for
358 the JWT payload.
359 token_lifetime (int): The amount of time in seconds for
360 which the token is valid. Defaults to 1 hour.
361 """
362 super(Credentials, self).__init__()
363 self._signer = signer
364 self._issuer = issuer
365 self._subject = subject
366 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700367 self._token_lifetime = token_lifetime
368
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700369 if additional_claims is None:
370 additional_claims = {}
371
372 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700373
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700374 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700375 def _from_signer_and_info(cls, signer, info, **kwargs):
376 """Creates a Credentials instance from a signer and service account
377 info.
378
379 Args:
380 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
381 info (Mapping[str, str]): The service account info.
382 kwargs: Additional arguments to pass to the constructor.
383
384 Returns:
385 google.auth.jwt.Credentials: The constructed credentials.
386
387 Raises:
388 ValueError: If the info is not in the expected format.
389 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700390 kwargs.setdefault("subject", info["client_email"])
391 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800392 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700393
394 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700395 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700396 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700397
398 Args:
399 info (Mapping[str, str]): The service account info in Google
400 format.
401 kwargs: Additional arguments to pass to the constructor.
402
403 Returns:
404 google.auth.jwt.Credentials: The constructed credentials.
405
406 Raises:
407 ValueError: If the info is not in the expected format.
408 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700409 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700410 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700411
412 @classmethod
413 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700414 """Creates a Credentials instance from a service account .json file
415 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700416
417 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700418 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700419 kwargs: Additional arguments to pass to the constructor.
420
421 Returns:
422 google.auth.jwt.Credentials: The constructed credentials.
423 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700424 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700425 filename, require=["client_email"]
426 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700427 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700428
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800429 @classmethod
430 def from_signing_credentials(cls, credentials, audience, **kwargs):
431 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
432 existing :class:`google.auth.credentials.Signing` instance.
433
434 The new instance will use the same signer as the existing instance and
435 will use the existing instance's signer email as the issuer and
436 subject by default.
437
438 Example::
439
440 svc_creds = service_account.Credentials.from_service_account_file(
441 'service_account.json')
442 audience = (
443 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
444 jwt_creds = jwt.Credentials.from_signing_credentials(
445 svc_creds, audience=audience)
446
447 Args:
448 credentials (google.auth.credentials.Signing): The credentials to
449 use to construct the new credentials.
450 audience (str): the `aud` claim. The intended audience for the
451 credentials.
452 kwargs: Additional arguments to pass to the constructor.
453
454 Returns:
455 google.auth.jwt.Credentials: A new Credentials instance.
456 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700457 kwargs.setdefault("issuer", credentials.signer_email)
458 kwargs.setdefault("subject", credentials.signer_email)
459 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800460
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700461 def with_claims(
462 self, issuer=None, subject=None, audience=None, additional_claims=None
463 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700464 """Returns a copy of these credentials with modified claims.
465
466 Args:
467 issuer (str): The `iss` claim. If unspecified the current issuer
468 claim will be used.
469 subject (str): The `sub` claim. If unspecified the current subject
470 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800471 audience (str): the `aud` claim. If unspecified the current
472 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700473 additional_claims (Mapping[str, str]): Any additional claims for
474 the JWT payload. This will be merged with the current
475 additional claims.
476
477 Returns:
478 google.auth.jwt.Credentials: A new credentials instance.
479 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700480 new_additional_claims = copy.deepcopy(self._additional_claims)
481 new_additional_claims.update(additional_claims or {})
482
Christophe Tatonb649b432018-02-08 14:12:23 -0800483 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700484 self._signer,
485 issuer=issuer if issuer is not None else self._issuer,
486 subject=subject if subject is not None else self._subject,
487 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700488 additional_claims=new_additional_claims,
489 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700490
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800491 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700492 """Make a signed JWT.
493
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700494 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800495 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700496 """
497 now = _helpers.utcnow()
498 lifetime = datetime.timedelta(seconds=self._token_lifetime)
499 expiry = now + lifetime
500
501 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700502 "iss": self._issuer,
503 "sub": self._subject,
504 "iat": _helpers.datetime_to_secs(now),
505 "exp": _helpers.datetime_to_secs(expiry),
506 "aud": self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700507 }
508
509 payload.update(self._additional_claims)
510
511 jwt = encode(self._signer, payload)
512
513 return jwt, expiry
514
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700515 def refresh(self, request):
516 """Refreshes the access token.
517
518 Args:
519 request (Any): Unused.
520 """
521 # pylint: disable=unused-argument
522 # (pylint doesn't correctly recognize overridden methods.)
523 self.token, self.expiry = self._make_jwt()
524
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800525 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700526 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700527 return self._signer.sign(message)
528
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800529 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800530 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800531 def signer_email(self):
532 return self._issuer
533
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800534 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800535 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800536 def signer(self):
537 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700538
539
540class OnDemandCredentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700541 google.auth.credentials.Signing, google.auth.credentials.Credentials
542):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700543 """On-demand JWT credentials.
544
545 Like :class:`Credentials`, this class uses a JWT as the bearer token for
546 authentication. However, this class does not require the audience at
547 construction time. Instead, it will generate a new token on-demand for
548 each request using the request URI as the audience. It caches tokens
549 so that multiple requests to the same URI do not incur the overhead
550 of generating a new token every time.
551
552 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
553 have multiple audience and gRPC clients may not know all of the audiences
554 required for accessing a particular service. With these credentials,
555 no knowledge of the audiences is required ahead of time.
556
557 .. _grpc: http://www.grpc.io/
558 """
559
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700560 def __init__(
561 self,
562 signer,
563 issuer,
564 subject,
565 additional_claims=None,
566 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
567 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
568 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700569 """
570 Args:
571 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
572 issuer (str): The `iss` claim.
573 subject (str): The `sub` claim.
574 additional_claims (Mapping[str, str]): Any additional claims for
575 the JWT payload.
576 token_lifetime (int): The amount of time in seconds for
577 which the token is valid. Defaults to 1 hour.
578 max_cache_size (int): The maximum number of JWT tokens to keep in
579 cache. Tokens are cached using :class:`cachetools.LRUCache`.
580 """
581 super(OnDemandCredentials, self).__init__()
582 self._signer = signer
583 self._issuer = issuer
584 self._subject = subject
585 self._token_lifetime = token_lifetime
586
587 if additional_claims is None:
588 additional_claims = {}
589
590 self._additional_claims = additional_claims
591 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
592
593 @classmethod
594 def _from_signer_and_info(cls, signer, info, **kwargs):
595 """Creates an OnDemandCredentials instance from a signer and service
596 account info.
597
598 Args:
599 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
600 info (Mapping[str, str]): The service account info.
601 kwargs: Additional arguments to pass to the constructor.
602
603 Returns:
604 google.auth.jwt.OnDemandCredentials: The constructed credentials.
605
606 Raises:
607 ValueError: If the info is not in the expected format.
608 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700609 kwargs.setdefault("subject", info["client_email"])
610 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700611 return cls(signer, **kwargs)
612
613 @classmethod
614 def from_service_account_info(cls, info, **kwargs):
615 """Creates an OnDemandCredentials instance from a dictionary.
616
617 Args:
618 info (Mapping[str, str]): The service account info in Google
619 format.
620 kwargs: Additional arguments to pass to the constructor.
621
622 Returns:
623 google.auth.jwt.OnDemandCredentials: The constructed credentials.
624
625 Raises:
626 ValueError: If the info is not in the expected format.
627 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700628 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700629 return cls._from_signer_and_info(signer, info, **kwargs)
630
631 @classmethod
632 def from_service_account_file(cls, filename, **kwargs):
633 """Creates an OnDemandCredentials instance from a service account .json
634 file in Google format.
635
636 Args:
637 filename (str): The path to the service account .json file.
638 kwargs: Additional arguments to pass to the constructor.
639
640 Returns:
641 google.auth.jwt.OnDemandCredentials: The constructed credentials.
642 """
643 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700644 filename, require=["client_email"]
645 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700646 return cls._from_signer_and_info(signer, info, **kwargs)
647
648 @classmethod
649 def from_signing_credentials(cls, credentials, **kwargs):
650 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
651 from an existing :class:`google.auth.credentials.Signing` instance.
652
653 The new instance will use the same signer as the existing instance and
654 will use the existing instance's signer email as the issuer and
655 subject by default.
656
657 Example::
658
659 svc_creds = service_account.Credentials.from_service_account_file(
660 'service_account.json')
661 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
662 svc_creds)
663
664 Args:
665 credentials (google.auth.credentials.Signing): The credentials to
666 use to construct the new credentials.
667 kwargs: Additional arguments to pass to the constructor.
668
669 Returns:
670 google.auth.jwt.Credentials: A new Credentials instance.
671 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700672 kwargs.setdefault("issuer", credentials.signer_email)
673 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700674 return cls(credentials.signer, **kwargs)
675
676 def with_claims(self, issuer=None, subject=None, additional_claims=None):
677 """Returns a copy of these credentials with modified claims.
678
679 Args:
680 issuer (str): The `iss` claim. If unspecified the current issuer
681 claim will be used.
682 subject (str): The `sub` claim. If unspecified the current subject
683 claim will be used.
684 additional_claims (Mapping[str, str]): Any additional claims for
685 the JWT payload. This will be merged with the current
686 additional claims.
687
688 Returns:
689 google.auth.jwt.OnDemandCredentials: A new credentials instance.
690 """
691 new_additional_claims = copy.deepcopy(self._additional_claims)
692 new_additional_claims.update(additional_claims or {})
693
Christophe Tatonb649b432018-02-08 14:12:23 -0800694 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700695 self._signer,
696 issuer=issuer if issuer is not None else self._issuer,
697 subject=subject if subject is not None else self._subject,
698 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700699 max_cache_size=self._cache.maxsize,
700 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700701
702 @property
703 def valid(self):
704 """Checks the validity of the credentials.
705
706 These credentials are always valid because it generates tokens on
707 demand.
708 """
709 return True
710
711 def _make_jwt_for_audience(self, audience):
712 """Make a new JWT for the given audience.
713
714 Args:
715 audience (str): The intended audience.
716
717 Returns:
718 Tuple[bytes, datetime]: The encoded JWT and the expiration.
719 """
720 now = _helpers.utcnow()
721 lifetime = datetime.timedelta(seconds=self._token_lifetime)
722 expiry = now + lifetime
723
724 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700725 "iss": self._issuer,
726 "sub": self._subject,
727 "iat": _helpers.datetime_to_secs(now),
728 "exp": _helpers.datetime_to_secs(expiry),
729 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700730 }
731
732 payload.update(self._additional_claims)
733
734 jwt = encode(self._signer, payload)
735
736 return jwt, expiry
737
738 def _get_jwt_for_audience(self, audience):
739 """Get a JWT For a given audience.
740
741 If there is already an existing, non-expired token in the cache for
742 the audience, that token is used. Otherwise, a new token will be
743 created.
744
745 Args:
746 audience (str): The intended audience.
747
748 Returns:
749 bytes: The encoded JWT.
750 """
751 token, expiry = self._cache.get(audience, (None, None))
752
753 if token is None or expiry < _helpers.utcnow():
754 token, expiry = self._make_jwt_for_audience(audience)
755 self._cache[audience] = token, expiry
756
757 return token
758
759 def refresh(self, request):
760 """Raises an exception, these credentials can not be directly
761 refreshed.
762
763 Args:
764 request (Any): Unused.
765
766 Raises:
767 google.auth.RefreshError
768 """
769 # pylint: disable=unused-argument
770 # (pylint doesn't correctly recognize overridden methods.)
771 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700772 "OnDemandCredentials can not be directly refreshed."
773 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700774
775 def before_request(self, request, method, url, headers):
776 """Performs credential-specific before request logic.
777
778 Args:
779 request (Any): Unused. JWT credentials do not need to make an
780 HTTP request to refresh.
781 method (str): The request's HTTP method.
782 url (str): The request's URI. This is used as the audience claim
783 when generating the JWT.
784 headers (Mapping): The request's headers.
785 """
786 # pylint: disable=unused-argument
787 # (pylint doesn't correctly recognize overridden methods.)
788 parts = urllib.parse.urlsplit(url)
789 # Strip query string and fragment
790 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700791 (parts.scheme, parts.netloc, parts.path, "", "")
792 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700793 token = self._get_jwt_for_audience(audience)
794 self.apply(headers, token=token)
795
796 @_helpers.copy_docstring(google.auth.credentials.Signing)
797 def sign_bytes(self, message):
798 return self._signer.sign(message)
799
800 @property
801 @_helpers.copy_docstring(google.auth.credentials.Signing)
802 def signer_email(self):
803 return self._issuer
804
805 @property
806 @_helpers.copy_docstring(google.auth.credentials.Signing)
807 def signer(self):
808 return self._signer