blob: a4f04f529e039d74a8e8dd388e0ce9d14fe3a146 [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}
arithmetic1728866d9262020-05-05 12:53:37 -070070_CRYPTOGRAPHY_BASED_ALGORITHMS = frozenset(["ES256"])
Thea Flowerse290a3d2020-04-01 10:11:42 -070071
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 Kim41599ae2020-09-02 12:55:42 -0600291class Credentials(
292 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
293):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700294 """Credentials that use a JWT as the bearer token.
295
296 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800297 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700298
299 The constructor arguments determine the claims for the JWT that is
300 sent with requests. Usually, you'll construct these credentials with
301 one of the helper constructors as shown in the next section.
302
303 To create JWT credentials using a Google service account private key
304 JSON file::
305
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800306 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700307 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800308 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800309 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700310
311 If you already have the service account file loaded and parsed::
312
313 service_account_info = json.load(open('service_account.json'))
314 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800315 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800316 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700317
318 Both helper methods pass on arguments to the constructor, so you can
319 specify the JWT claims::
320
321 credentials = jwt.Credentials.from_service_account_file(
322 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800323 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700324 additional_claims={'meta': 'data'})
325
326 You can also construct the credentials directly if you have a
327 :class:`~google.auth.crypt.Signer` instance::
328
329 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800330 signer,
331 issuer='your-issuer',
332 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800333 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700334
335 The claims are considered immutable. If you want to modify the claims,
336 you can easily create another instance using :meth:`with_claims`::
337
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800338 new_audience = (
339 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
340 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700341 """
342
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700343 def __init__(
344 self,
345 signer,
346 issuer,
347 subject,
348 audience,
349 additional_claims=None,
350 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700351 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700352 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700353 """
354 Args:
355 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
356 issuer (str): The `iss` claim.
357 subject (str): The `sub` claim.
358 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800359 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700360 additional_claims (Mapping[str, str]): Any additional claims for
361 the JWT payload.
362 token_lifetime (int): The amount of time in seconds for
363 which the token is valid. Defaults to 1 hour.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700364 quota_project_id (Optional[str]): The project ID used for quota
365 and billing.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700366 """
367 super(Credentials, self).__init__()
368 self._signer = signer
369 self._issuer = issuer
370 self._subject = subject
371 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700372 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700373 self._quota_project_id = quota_project_id
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700374
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700375 if additional_claims is None:
376 additional_claims = {}
377
378 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700379
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700380 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700381 def _from_signer_and_info(cls, signer, info, **kwargs):
382 """Creates a Credentials instance from a signer and service account
383 info.
384
385 Args:
386 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
387 info (Mapping[str, str]): The service account info.
388 kwargs: Additional arguments to pass to the constructor.
389
390 Returns:
391 google.auth.jwt.Credentials: The constructed credentials.
392
393 Raises:
394 ValueError: If the info is not in the expected format.
395 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700396 kwargs.setdefault("subject", info["client_email"])
397 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800398 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700399
400 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700401 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700402 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700403
404 Args:
405 info (Mapping[str, str]): The service account info in Google
406 format.
407 kwargs: Additional arguments to pass to the constructor.
408
409 Returns:
410 google.auth.jwt.Credentials: The constructed credentials.
411
412 Raises:
413 ValueError: If the info is not in the expected format.
414 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700415 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700416 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700417
418 @classmethod
419 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700420 """Creates a Credentials instance from a service account .json file
421 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700422
423 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700424 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700425 kwargs: Additional arguments to pass to the constructor.
426
427 Returns:
428 google.auth.jwt.Credentials: The constructed credentials.
429 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700430 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700431 filename, require=["client_email"]
432 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700433 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700434
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800435 @classmethod
436 def from_signing_credentials(cls, credentials, audience, **kwargs):
437 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
438 existing :class:`google.auth.credentials.Signing` instance.
439
440 The new instance will use the same signer as the existing instance and
441 will use the existing instance's signer email as the issuer and
442 subject by default.
443
444 Example::
445
446 svc_creds = service_account.Credentials.from_service_account_file(
447 'service_account.json')
448 audience = (
449 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
450 jwt_creds = jwt.Credentials.from_signing_credentials(
451 svc_creds, audience=audience)
452
453 Args:
454 credentials (google.auth.credentials.Signing): The credentials to
455 use to construct the new credentials.
456 audience (str): the `aud` claim. The intended audience for the
457 credentials.
458 kwargs: Additional arguments to pass to the constructor.
459
460 Returns:
461 google.auth.jwt.Credentials: A new Credentials instance.
462 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700463 kwargs.setdefault("issuer", credentials.signer_email)
464 kwargs.setdefault("subject", credentials.signer_email)
465 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800466
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700467 def with_claims(
468 self, issuer=None, subject=None, audience=None, additional_claims=None
469 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700470 """Returns a copy of these credentials with modified claims.
471
472 Args:
473 issuer (str): The `iss` claim. If unspecified the current issuer
474 claim will be used.
475 subject (str): The `sub` claim. If unspecified the current subject
476 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800477 audience (str): the `aud` claim. If unspecified the current
478 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700479 additional_claims (Mapping[str, str]): Any additional claims for
480 the JWT payload. This will be merged with the current
481 additional claims.
482
483 Returns:
484 google.auth.jwt.Credentials: A new credentials instance.
485 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700486 new_additional_claims = copy.deepcopy(self._additional_claims)
487 new_additional_claims.update(additional_claims or {})
488
Christophe Tatonb649b432018-02-08 14:12:23 -0800489 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700490 self._signer,
491 issuer=issuer if issuer is not None else self._issuer,
492 subject=subject if subject is not None else self._subject,
493 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700494 additional_claims=new_additional_claims,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700495 quota_project_id=self._quota_project_id,
496 )
497
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600498 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700499 def with_quota_project(self, quota_project_id):
500 return self.__class__(
501 self._signer,
502 issuer=self._issuer,
503 subject=self._subject,
504 audience=self._audience,
505 additional_claims=self._additional_claims,
506 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700507 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700508
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800509 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700510 """Make a signed JWT.
511
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700512 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800513 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700514 """
515 now = _helpers.utcnow()
516 lifetime = datetime.timedelta(seconds=self._token_lifetime)
517 expiry = now + lifetime
518
519 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700520 "iss": self._issuer,
521 "sub": self._subject,
522 "iat": _helpers.datetime_to_secs(now),
523 "exp": _helpers.datetime_to_secs(expiry),
524 "aud": self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700525 }
526
527 payload.update(self._additional_claims)
528
529 jwt = encode(self._signer, payload)
530
531 return jwt, expiry
532
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700533 def refresh(self, request):
534 """Refreshes the access token.
535
536 Args:
537 request (Any): Unused.
538 """
539 # pylint: disable=unused-argument
540 # (pylint doesn't correctly recognize overridden methods.)
541 self.token, self.expiry = self._make_jwt()
542
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800543 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700544 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700545 return self._signer.sign(message)
546
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800547 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800548 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800549 def signer_email(self):
550 return self._issuer
551
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800552 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800553 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800554 def signer(self):
555 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700556
557
558class OnDemandCredentials(
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600559 google.auth.credentials.Signing, google.auth.credentials.CredentialsWithQuotaProject
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700560):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700561 """On-demand JWT credentials.
562
563 Like :class:`Credentials`, this class uses a JWT as the bearer token for
564 authentication. However, this class does not require the audience at
565 construction time. Instead, it will generate a new token on-demand for
566 each request using the request URI as the audience. It caches tokens
567 so that multiple requests to the same URI do not incur the overhead
568 of generating a new token every time.
569
570 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
571 have multiple audience and gRPC clients may not know all of the audiences
572 required for accessing a particular service. With these credentials,
573 no knowledge of the audiences is required ahead of time.
574
575 .. _grpc: http://www.grpc.io/
576 """
577
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700578 def __init__(
579 self,
580 signer,
581 issuer,
582 subject,
583 additional_claims=None,
584 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
585 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700586 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700587 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700588 """
589 Args:
590 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
591 issuer (str): The `iss` claim.
592 subject (str): The `sub` claim.
593 additional_claims (Mapping[str, str]): Any additional claims for
594 the JWT payload.
595 token_lifetime (int): The amount of time in seconds for
596 which the token is valid. Defaults to 1 hour.
597 max_cache_size (int): The maximum number of JWT tokens to keep in
598 cache. Tokens are cached using :class:`cachetools.LRUCache`.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700599 quota_project_id (Optional[str]): The project ID used for quota
600 and billing.
601
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700602 """
603 super(OnDemandCredentials, self).__init__()
604 self._signer = signer
605 self._issuer = issuer
606 self._subject = subject
607 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700608 self._quota_project_id = quota_project_id
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700609
610 if additional_claims is None:
611 additional_claims = {}
612
613 self._additional_claims = additional_claims
614 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
615
616 @classmethod
617 def _from_signer_and_info(cls, signer, info, **kwargs):
618 """Creates an OnDemandCredentials instance from a signer and service
619 account info.
620
621 Args:
622 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
623 info (Mapping[str, str]): The service account info.
624 kwargs: Additional arguments to pass to the constructor.
625
626 Returns:
627 google.auth.jwt.OnDemandCredentials: The constructed credentials.
628
629 Raises:
630 ValueError: If the info is not in the expected format.
631 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700632 kwargs.setdefault("subject", info["client_email"])
633 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700634 return cls(signer, **kwargs)
635
636 @classmethod
637 def from_service_account_info(cls, info, **kwargs):
638 """Creates an OnDemandCredentials instance from a dictionary.
639
640 Args:
641 info (Mapping[str, str]): The service account info in Google
642 format.
643 kwargs: Additional arguments to pass to the constructor.
644
645 Returns:
646 google.auth.jwt.OnDemandCredentials: The constructed credentials.
647
648 Raises:
649 ValueError: If the info is not in the expected format.
650 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700651 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700652 return cls._from_signer_and_info(signer, info, **kwargs)
653
654 @classmethod
655 def from_service_account_file(cls, filename, **kwargs):
656 """Creates an OnDemandCredentials instance from a service account .json
657 file in Google format.
658
659 Args:
660 filename (str): The path to the service account .json file.
661 kwargs: Additional arguments to pass to the constructor.
662
663 Returns:
664 google.auth.jwt.OnDemandCredentials: The constructed credentials.
665 """
666 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700667 filename, require=["client_email"]
668 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700669 return cls._from_signer_and_info(signer, info, **kwargs)
670
671 @classmethod
672 def from_signing_credentials(cls, credentials, **kwargs):
673 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
674 from an existing :class:`google.auth.credentials.Signing` instance.
675
676 The new instance will use the same signer as the existing instance and
677 will use the existing instance's signer email as the issuer and
678 subject by default.
679
680 Example::
681
682 svc_creds = service_account.Credentials.from_service_account_file(
683 'service_account.json')
684 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
685 svc_creds)
686
687 Args:
688 credentials (google.auth.credentials.Signing): The credentials to
689 use to construct the new credentials.
690 kwargs: Additional arguments to pass to the constructor.
691
692 Returns:
693 google.auth.jwt.Credentials: A new Credentials instance.
694 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700695 kwargs.setdefault("issuer", credentials.signer_email)
696 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700697 return cls(credentials.signer, **kwargs)
698
699 def with_claims(self, issuer=None, subject=None, additional_claims=None):
700 """Returns a copy of these credentials with modified claims.
701
702 Args:
703 issuer (str): The `iss` claim. If unspecified the current issuer
704 claim will be used.
705 subject (str): The `sub` claim. If unspecified the current subject
706 claim will be used.
707 additional_claims (Mapping[str, str]): Any additional claims for
708 the JWT payload. This will be merged with the current
709 additional claims.
710
711 Returns:
712 google.auth.jwt.OnDemandCredentials: A new credentials instance.
713 """
714 new_additional_claims = copy.deepcopy(self._additional_claims)
715 new_additional_claims.update(additional_claims or {})
716
Christophe Tatonb649b432018-02-08 14:12:23 -0800717 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700718 self._signer,
719 issuer=issuer if issuer is not None else self._issuer,
720 subject=subject if subject is not None else self._subject,
721 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700722 max_cache_size=self._cache.maxsize,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700723 quota_project_id=self._quota_project_id,
724 )
725
Bu Sun Kim41599ae2020-09-02 12:55:42 -0600726 @_helpers.copy_docstring(google.auth.credentials.CredentialsWithQuotaProject)
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700727 def with_quota_project(self, quota_project_id):
728
729 return self.__class__(
730 self._signer,
731 issuer=self._issuer,
732 subject=self._subject,
733 additional_claims=self._additional_claims,
734 max_cache_size=self._cache.maxsize,
735 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700736 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700737
738 @property
739 def valid(self):
740 """Checks the validity of the credentials.
741
742 These credentials are always valid because it generates tokens on
743 demand.
744 """
745 return True
746
747 def _make_jwt_for_audience(self, audience):
748 """Make a new JWT for the given audience.
749
750 Args:
751 audience (str): The intended audience.
752
753 Returns:
754 Tuple[bytes, datetime]: The encoded JWT and the expiration.
755 """
756 now = _helpers.utcnow()
757 lifetime = datetime.timedelta(seconds=self._token_lifetime)
758 expiry = now + lifetime
759
760 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700761 "iss": self._issuer,
762 "sub": self._subject,
763 "iat": _helpers.datetime_to_secs(now),
764 "exp": _helpers.datetime_to_secs(expiry),
765 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700766 }
767
768 payload.update(self._additional_claims)
769
770 jwt = encode(self._signer, payload)
771
772 return jwt, expiry
773
774 def _get_jwt_for_audience(self, audience):
775 """Get a JWT For a given audience.
776
777 If there is already an existing, non-expired token in the cache for
778 the audience, that token is used. Otherwise, a new token will be
779 created.
780
781 Args:
782 audience (str): The intended audience.
783
784 Returns:
785 bytes: The encoded JWT.
786 """
787 token, expiry = self._cache.get(audience, (None, None))
788
789 if token is None or expiry < _helpers.utcnow():
790 token, expiry = self._make_jwt_for_audience(audience)
791 self._cache[audience] = token, expiry
792
793 return token
794
795 def refresh(self, request):
796 """Raises an exception, these credentials can not be directly
797 refreshed.
798
799 Args:
800 request (Any): Unused.
801
802 Raises:
803 google.auth.RefreshError
804 """
805 # pylint: disable=unused-argument
806 # (pylint doesn't correctly recognize overridden methods.)
807 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700808 "OnDemandCredentials can not be directly refreshed."
809 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700810
811 def before_request(self, request, method, url, headers):
812 """Performs credential-specific before request logic.
813
814 Args:
815 request (Any): Unused. JWT credentials do not need to make an
816 HTTP request to refresh.
817 method (str): The request's HTTP method.
818 url (str): The request's URI. This is used as the audience claim
819 when generating the JWT.
820 headers (Mapping): The request's headers.
821 """
822 # pylint: disable=unused-argument
823 # (pylint doesn't correctly recognize overridden methods.)
824 parts = urllib.parse.urlsplit(url)
825 # Strip query string and fragment
826 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700827 (parts.scheme, parts.netloc, parts.path, "", "")
828 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700829 token = self._get_jwt_for_audience(audience)
830 self.apply(headers, token=token)
831
832 @_helpers.copy_docstring(google.auth.credentials.Signing)
833 def sign_bytes(self, message):
834 return self._signer.sign(message)
835
836 @property
837 @_helpers.copy_docstring(google.auth.credentials.Signing)
838 def signer_email(self):
839 return self._issuer
840
841 @property
842 @_helpers.copy_docstring(google.auth.credentials.Signing)
843 def signer(self):
844 return self._signer