blob: 35ae034325c7df43e264cf9a5398c9ae27d7128e [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 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,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700349 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700350 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700351 """
352 Args:
353 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
354 issuer (str): The `iss` claim.
355 subject (str): The `sub` claim.
356 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800357 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700358 additional_claims (Mapping[str, str]): Any additional claims for
359 the JWT payload.
360 token_lifetime (int): The amount of time in seconds for
361 which the token is valid. Defaults to 1 hour.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700362 quota_project_id (Optional[str]): The project ID used for quota
363 and billing.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700364 """
365 super(Credentials, self).__init__()
366 self._signer = signer
367 self._issuer = issuer
368 self._subject = subject
369 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700370 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700371 self._quota_project_id = quota_project_id
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700372
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700373 if additional_claims is None:
374 additional_claims = {}
375
376 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700377
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700378 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700379 def _from_signer_and_info(cls, signer, info, **kwargs):
380 """Creates a Credentials instance from a signer and service account
381 info.
382
383 Args:
384 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
385 info (Mapping[str, str]): The service account info.
386 kwargs: Additional arguments to pass to the constructor.
387
388 Returns:
389 google.auth.jwt.Credentials: The constructed credentials.
390
391 Raises:
392 ValueError: If the info is not in the expected format.
393 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700394 kwargs.setdefault("subject", info["client_email"])
395 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800396 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700397
398 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700399 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700400 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700401
402 Args:
403 info (Mapping[str, str]): The service account info in Google
404 format.
405 kwargs: Additional arguments to pass to the constructor.
406
407 Returns:
408 google.auth.jwt.Credentials: The constructed credentials.
409
410 Raises:
411 ValueError: If the info is not in the expected format.
412 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700413 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700414 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700415
416 @classmethod
417 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700418 """Creates a Credentials instance from a service account .json file
419 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700420
421 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700422 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700423 kwargs: Additional arguments to pass to the constructor.
424
425 Returns:
426 google.auth.jwt.Credentials: The constructed credentials.
427 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700428 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700429 filename, require=["client_email"]
430 )
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700431 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700432
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800433 @classmethod
434 def from_signing_credentials(cls, credentials, audience, **kwargs):
435 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
436 existing :class:`google.auth.credentials.Signing` instance.
437
438 The new instance will use the same signer as the existing instance and
439 will use the existing instance's signer email as the issuer and
440 subject by default.
441
442 Example::
443
444 svc_creds = service_account.Credentials.from_service_account_file(
445 'service_account.json')
446 audience = (
447 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
448 jwt_creds = jwt.Credentials.from_signing_credentials(
449 svc_creds, audience=audience)
450
451 Args:
452 credentials (google.auth.credentials.Signing): The credentials to
453 use to construct the new credentials.
454 audience (str): the `aud` claim. The intended audience for the
455 credentials.
456 kwargs: Additional arguments to pass to the constructor.
457
458 Returns:
459 google.auth.jwt.Credentials: A new Credentials instance.
460 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700461 kwargs.setdefault("issuer", credentials.signer_email)
462 kwargs.setdefault("subject", credentials.signer_email)
463 return cls(credentials.signer, audience=audience, **kwargs)
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800464
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700465 def with_claims(
466 self, issuer=None, subject=None, audience=None, additional_claims=None
467 ):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700468 """Returns a copy of these credentials with modified claims.
469
470 Args:
471 issuer (str): The `iss` claim. If unspecified the current issuer
472 claim will be used.
473 subject (str): The `sub` claim. If unspecified the current subject
474 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800475 audience (str): the `aud` claim. If unspecified the current
476 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700477 additional_claims (Mapping[str, str]): Any additional claims for
478 the JWT payload. This will be merged with the current
479 additional claims.
480
481 Returns:
482 google.auth.jwt.Credentials: A new credentials instance.
483 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700484 new_additional_claims = copy.deepcopy(self._additional_claims)
485 new_additional_claims.update(additional_claims or {})
486
Christophe Tatonb649b432018-02-08 14:12:23 -0800487 return self.__class__(
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700488 self._signer,
489 issuer=issuer if issuer is not None else self._issuer,
490 subject=subject if subject is not None else self._subject,
491 audience=audience if audience is not None else self._audience,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700492 additional_claims=new_additional_claims,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700493 quota_project_id=self._quota_project_id,
494 )
495
496 @_helpers.copy_docstring(google.auth.credentials.Credentials)
497 def with_quota_project(self, quota_project_id):
498 return self.__class__(
499 self._signer,
500 issuer=self._issuer,
501 subject=self._subject,
502 audience=self._audience,
503 additional_claims=self._additional_claims,
504 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700505 )
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700506
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800507 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700508 """Make a signed JWT.
509
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700510 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800511 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700512 """
513 now = _helpers.utcnow()
514 lifetime = datetime.timedelta(seconds=self._token_lifetime)
515 expiry = now + lifetime
516
517 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700518 "iss": self._issuer,
519 "sub": self._subject,
520 "iat": _helpers.datetime_to_secs(now),
521 "exp": _helpers.datetime_to_secs(expiry),
522 "aud": self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700523 }
524
525 payload.update(self._additional_claims)
526
527 jwt = encode(self._signer, payload)
528
529 return jwt, expiry
530
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700531 def refresh(self, request):
532 """Refreshes the access token.
533
534 Args:
535 request (Any): Unused.
536 """
537 # pylint: disable=unused-argument
538 # (pylint doesn't correctly recognize overridden methods.)
539 self.token, self.expiry = self._make_jwt()
540
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800541 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700542 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700543 return self._signer.sign(message)
544
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800545 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800546 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800547 def signer_email(self):
548 return self._issuer
549
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800550 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800551 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800552 def signer(self):
553 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700554
555
556class OnDemandCredentials(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700557 google.auth.credentials.Signing, google.auth.credentials.Credentials
558):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700559 """On-demand JWT credentials.
560
561 Like :class:`Credentials`, this class uses a JWT as the bearer token for
562 authentication. However, this class does not require the audience at
563 construction time. Instead, it will generate a new token on-demand for
564 each request using the request URI as the audience. It caches tokens
565 so that multiple requests to the same URI do not incur the overhead
566 of generating a new token every time.
567
568 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
569 have multiple audience and gRPC clients may not know all of the audiences
570 required for accessing a particular service. With these credentials,
571 no knowledge of the audiences is required ahead of time.
572
573 .. _grpc: http://www.grpc.io/
574 """
575
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700576 def __init__(
577 self,
578 signer,
579 issuer,
580 subject,
581 additional_claims=None,
582 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
583 max_cache_size=_DEFAULT_MAX_CACHE_SIZE,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700584 quota_project_id=None,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700585 ):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700586 """
587 Args:
588 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
589 issuer (str): The `iss` claim.
590 subject (str): The `sub` claim.
591 additional_claims (Mapping[str, str]): Any additional claims for
592 the JWT payload.
593 token_lifetime (int): The amount of time in seconds for
594 which the token is valid. Defaults to 1 hour.
595 max_cache_size (int): The maximum number of JWT tokens to keep in
596 cache. Tokens are cached using :class:`cachetools.LRUCache`.
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700597 quota_project_id (Optional[str]): The project ID used for quota
598 and billing.
599
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700600 """
601 super(OnDemandCredentials, self).__init__()
602 self._signer = signer
603 self._issuer = issuer
604 self._subject = subject
605 self._token_lifetime = token_lifetime
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700606 self._quota_project_id = quota_project_id
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700607
608 if additional_claims is None:
609 additional_claims = {}
610
611 self._additional_claims = additional_claims
612 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
613
614 @classmethod
615 def _from_signer_and_info(cls, signer, info, **kwargs):
616 """Creates an OnDemandCredentials instance from a signer and service
617 account info.
618
619 Args:
620 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
621 info (Mapping[str, str]): The service account info.
622 kwargs: Additional arguments to pass to the constructor.
623
624 Returns:
625 google.auth.jwt.OnDemandCredentials: The constructed credentials.
626
627 Raises:
628 ValueError: If the info is not in the expected format.
629 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700630 kwargs.setdefault("subject", info["client_email"])
631 kwargs.setdefault("issuer", info["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700632 return cls(signer, **kwargs)
633
634 @classmethod
635 def from_service_account_info(cls, info, **kwargs):
636 """Creates an OnDemandCredentials instance from a dictionary.
637
638 Args:
639 info (Mapping[str, str]): The service account info in Google
640 format.
641 kwargs: Additional arguments to pass to the constructor.
642
643 Returns:
644 google.auth.jwt.OnDemandCredentials: The constructed credentials.
645
646 Raises:
647 ValueError: If the info is not in the expected format.
648 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700649 signer = _service_account_info.from_dict(info, require=["client_email"])
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700650 return cls._from_signer_and_info(signer, info, **kwargs)
651
652 @classmethod
653 def from_service_account_file(cls, filename, **kwargs):
654 """Creates an OnDemandCredentials instance from a service account .json
655 file in Google format.
656
657 Args:
658 filename (str): The path to the service account .json file.
659 kwargs: Additional arguments to pass to the constructor.
660
661 Returns:
662 google.auth.jwt.OnDemandCredentials: The constructed credentials.
663 """
664 info, signer = _service_account_info.from_filename(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700665 filename, require=["client_email"]
666 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700667 return cls._from_signer_and_info(signer, info, **kwargs)
668
669 @classmethod
670 def from_signing_credentials(cls, credentials, **kwargs):
671 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
672 from an existing :class:`google.auth.credentials.Signing` instance.
673
674 The new instance will use the same signer as the existing instance and
675 will use the existing instance's signer email as the issuer and
676 subject by default.
677
678 Example::
679
680 svc_creds = service_account.Credentials.from_service_account_file(
681 'service_account.json')
682 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
683 svc_creds)
684
685 Args:
686 credentials (google.auth.credentials.Signing): The credentials to
687 use to construct the new credentials.
688 kwargs: Additional arguments to pass to the constructor.
689
690 Returns:
691 google.auth.jwt.Credentials: A new Credentials instance.
692 """
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700693 kwargs.setdefault("issuer", credentials.signer_email)
694 kwargs.setdefault("subject", credentials.signer_email)
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700695 return cls(credentials.signer, **kwargs)
696
697 def with_claims(self, issuer=None, subject=None, additional_claims=None):
698 """Returns a copy of these credentials with modified claims.
699
700 Args:
701 issuer (str): The `iss` claim. If unspecified the current issuer
702 claim will be used.
703 subject (str): The `sub` claim. If unspecified the current subject
704 claim will be used.
705 additional_claims (Mapping[str, str]): Any additional claims for
706 the JWT payload. This will be merged with the current
707 additional claims.
708
709 Returns:
710 google.auth.jwt.OnDemandCredentials: A new credentials instance.
711 """
712 new_additional_claims = copy.deepcopy(self._additional_claims)
713 new_additional_claims.update(additional_claims or {})
714
Christophe Tatonb649b432018-02-08 14:12:23 -0800715 return self.__class__(
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700716 self._signer,
717 issuer=issuer if issuer is not None else self._issuer,
718 subject=subject if subject is not None else self._subject,
719 additional_claims=new_additional_claims,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700720 max_cache_size=self._cache.maxsize,
Bu Sun Kim3dda7b22020-07-09 10:39:39 -0700721 quota_project_id=self._quota_project_id,
722 )
723
724 @_helpers.copy_docstring(google.auth.credentials.Credentials)
725 def with_quota_project(self, quota_project_id):
726
727 return self.__class__(
728 self._signer,
729 issuer=self._issuer,
730 subject=self._subject,
731 additional_claims=self._additional_claims,
732 max_cache_size=self._cache.maxsize,
733 quota_project_id=quota_project_id,
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700734 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700735
736 @property
737 def valid(self):
738 """Checks the validity of the credentials.
739
740 These credentials are always valid because it generates tokens on
741 demand.
742 """
743 return True
744
745 def _make_jwt_for_audience(self, audience):
746 """Make a new JWT for the given audience.
747
748 Args:
749 audience (str): The intended audience.
750
751 Returns:
752 Tuple[bytes, datetime]: The encoded JWT and the expiration.
753 """
754 now = _helpers.utcnow()
755 lifetime = datetime.timedelta(seconds=self._token_lifetime)
756 expiry = now + lifetime
757
758 payload = {
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700759 "iss": self._issuer,
760 "sub": self._subject,
761 "iat": _helpers.datetime_to_secs(now),
762 "exp": _helpers.datetime_to_secs(expiry),
763 "aud": audience,
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700764 }
765
766 payload.update(self._additional_claims)
767
768 jwt = encode(self._signer, payload)
769
770 return jwt, expiry
771
772 def _get_jwt_for_audience(self, audience):
773 """Get a JWT For a given audience.
774
775 If there is already an existing, non-expired token in the cache for
776 the audience, that token is used. Otherwise, a new token will be
777 created.
778
779 Args:
780 audience (str): The intended audience.
781
782 Returns:
783 bytes: The encoded JWT.
784 """
785 token, expiry = self._cache.get(audience, (None, None))
786
787 if token is None or expiry < _helpers.utcnow():
788 token, expiry = self._make_jwt_for_audience(audience)
789 self._cache[audience] = token, expiry
790
791 return token
792
793 def refresh(self, request):
794 """Raises an exception, these credentials can not be directly
795 refreshed.
796
797 Args:
798 request (Any): Unused.
799
800 Raises:
801 google.auth.RefreshError
802 """
803 # pylint: disable=unused-argument
804 # (pylint doesn't correctly recognize overridden methods.)
805 raise exceptions.RefreshError(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700806 "OnDemandCredentials can not be directly refreshed."
807 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700808
809 def before_request(self, request, method, url, headers):
810 """Performs credential-specific before request logic.
811
812 Args:
813 request (Any): Unused. JWT credentials do not need to make an
814 HTTP request to refresh.
815 method (str): The request's HTTP method.
816 url (str): The request's URI. This is used as the audience claim
817 when generating the JWT.
818 headers (Mapping): The request's headers.
819 """
820 # pylint: disable=unused-argument
821 # (pylint doesn't correctly recognize overridden methods.)
822 parts = urllib.parse.urlsplit(url)
823 # Strip query string and fragment
824 audience = urllib.parse.urlunsplit(
Bu Sun Kim9eec0912019-10-21 17:04:21 -0700825 (parts.scheme, parts.netloc, parts.path, "", "")
826 )
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700827 token = self._get_jwt_for_audience(audience)
828 self.apply(headers, token=token)
829
830 @_helpers.copy_docstring(google.auth.credentials.Signing)
831 def sign_bytes(self, message):
832 return self._signer.sign(message)
833
834 @property
835 @_helpers.copy_docstring(google.auth.credentials.Signing)
836 def signer_email(self):
837 return self._issuer
838
839 @property
840 @_helpers.copy_docstring(google.auth.credentials.Signing)
841 def signer(self):
842 return self._signer