blob: 02533762fac8c479441c47f245384f51691ac218 [file] [log] [blame]
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -07001# Copyright 2016 Google Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""JSON Web Tokens
16
17Provides support for creating (encoding) and verifying (decoding) JWTs,
18especially JWTs generated and consumed by Google infrastructure.
19
20See `rfc7519`_ for more details on JWTs.
21
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070022To encode a JWT use :func:`encode`::
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070023
24 from google.auth import crypto
25 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
43import base64
44import collections
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -070045import copy
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -070046import datetime
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070047import json
48
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070049import cachetools
Danny Hermes895e3692017-11-09 11:35:57 -080050import six
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070051from six.moves import urllib
52
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070053from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070054from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070055from google.auth import crypt
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070056from google.auth import exceptions
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080057import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070058
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070059_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070060_DEFAULT_MAX_CACHE_SIZE = 10
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070061
62
63def encode(signer, payload, header=None, key_id=None):
64 """Make a signed JWT.
65
66 Args:
67 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070068 payload (Mapping[str, str]): The JWT payload.
69 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070070 key_id (str): The key id to add to the JWT header. If the
71 signer has a key id it will be used as the default. If this is
72 specified it will override the signer's key id.
73
74 Returns:
75 bytes: The encoded JWT.
76 """
77 if header is None:
78 header = {}
79
80 if key_id is None:
81 key_id = signer.key_id
82
83 header.update({'typ': 'JWT', 'alg': 'RS256'})
84
85 if key_id is not None:
86 header['kid'] = key_id
87
88 segments = [
89 base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')),
90 base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')),
91 ]
92
93 signing_input = b'.'.join(segments)
94 signature = signer.sign(signing_input)
95 segments.append(base64.urlsafe_b64encode(signature))
96
97 return b'.'.join(segments)
98
99
100def _decode_jwt_segment(encoded_section):
101 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800102 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700103 try:
104 return json.loads(section_bytes.decode('utf-8'))
Danny Hermes895e3692017-11-09 11:35:57 -0800105 except ValueError as caught_exc:
106 new_exc = ValueError('Can\'t parse segment: {0}'.format(section_bytes))
107 six.raise_from(new_exc, caught_exc)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700108
109
110def _unverified_decode(token):
111 """Decodes a token and does no verification.
112
113 Args:
114 token (Union[str, bytes]): The encoded JWT.
115
116 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800117 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700118 signature.
119
120 Raises:
121 ValueError: if there are an incorrect amount of segments in the token.
122 """
123 token = _helpers.to_bytes(token)
124
125 if token.count(b'.') != 2:
126 raise ValueError(
127 'Wrong number of segments in token: {0}'.format(token))
128
129 encoded_header, encoded_payload, signature = token.split(b'.')
130 signed_section = encoded_header + b'.' + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800131 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700132
133 # Parse segments
134 header = _decode_jwt_segment(encoded_header)
135 payload = _decode_jwt_segment(encoded_payload)
136
137 return header, payload, signed_section, signature
138
139
140def decode_header(token):
141 """Return the decoded header of a token.
142
143 No verification is done. This is useful to extract the key id from
144 the header in order to acquire the appropriate certificate to verify
145 the token.
146
147 Args:
148 token (Union[str, bytes]): the encoded JWT.
149
150 Returns:
151 Mapping: The decoded JWT header.
152 """
153 header, _, _, _ = _unverified_decode(token)
154 return header
155
156
157def _verify_iat_and_exp(payload):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700158 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700159 payload.
160
161 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700162 payload (Mapping[str, str]): The JWT payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700163
164 Raises:
165 ValueError: if any checks failed.
166 """
167 now = _helpers.datetime_to_secs(_helpers.utcnow())
168
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700169 # Make sure the iat and exp claims are present.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700170 for key in ('iat', 'exp'):
171 if key not in payload:
172 raise ValueError(
173 'Token does not contain required claim {}'.format(key))
174
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700175 # Make sure the token wasn't issued in the future.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700176 iat = payload['iat']
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700177 # Err on the side of accepting a token that is slightly early to account
178 # for clock skew.
179 earliest = iat - _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700180 if now < earliest:
181 raise ValueError('Token used too early, {} < {}'.format(now, iat))
182
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700183 # Make sure the token wasn't issued in the past.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700184 exp = payload['exp']
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700185 # Err on the side of accepting a token that is slightly out of date
186 # to account for clow skew.
187 latest = exp + _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700188 if latest < now:
189 raise ValueError('Token expired, {} < {}'.format(latest, now))
190
191
192def decode(token, certs=None, verify=True, audience=None):
193 """Decode and verify a JWT.
194
195 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700196 token (str): The encoded JWT.
197 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
198 certificate used to validate the JWT signatyre. If bytes or string,
199 it must the the public key certificate in PEM format. If a mapping,
200 it must be a mapping of key IDs to public key certificates in PEM
201 format. The mapping must contain the same key ID that's specified
202 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700203 verify (bool): Whether to perform signature and claim validation.
204 Verification is done by default.
205 audience (str): The audience claim, 'aud', that this JWT should
206 contain. If None then the JWT's 'aud' parameter is not verified.
207
208 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700209 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700210
211 Raises:
212 ValueError: if any verification checks failed.
213 """
214 header, payload, signed_section, signature = _unverified_decode(token)
215
216 if not verify:
217 return payload
218
219 # If certs is specified as a dictionary of key IDs to certificates, then
220 # use the certificate identified by the key ID in the token header.
221 if isinstance(certs, collections.Mapping):
222 key_id = header.get('kid')
223 if key_id:
224 if key_id not in certs:
225 raise ValueError(
226 'Certificate for key id {} not found.'.format(key_id))
227 certs_to_check = [certs[key_id]]
228 # If there's no key id in the header, check against all of the certs.
229 else:
230 certs_to_check = certs.values()
231 else:
232 certs_to_check = certs
233
234 # Verify that the signature matches the message.
235 if not crypt.verify_signature(signed_section, signature, certs_to_check):
236 raise ValueError('Could not verify token signature.')
237
238 # Verify the issued at and created times in the payload.
239 _verify_iat_and_exp(payload)
240
241 # Check audience.
242 if audience is not None:
243 claim_audience = payload.get('aud')
244 if audience != claim_audience:
245 raise ValueError(
246 'Token has wrong audience {}, expected {}'.format(
247 claim_audience, audience))
248
249 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700250
251
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800252class Credentials(google.auth.credentials.Signing,
253 google.auth.credentials.Credentials):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700254 """Credentials that use a JWT as the bearer token.
255
256 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800257 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700258
259 The constructor arguments determine the claims for the JWT that is
260 sent with requests. Usually, you'll construct these credentials with
261 one of the helper constructors as shown in the next section.
262
263 To create JWT credentials using a Google service account private key
264 JSON file::
265
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800266 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700267 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800268 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800269 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700270
271 If you already have the service account file loaded and parsed::
272
273 service_account_info = json.load(open('service_account.json'))
274 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800275 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800276 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700277
278 Both helper methods pass on arguments to the constructor, so you can
279 specify the JWT claims::
280
281 credentials = jwt.Credentials.from_service_account_file(
282 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800283 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700284 additional_claims={'meta': 'data'})
285
286 You can also construct the credentials directly if you have a
287 :class:`~google.auth.crypt.Signer` instance::
288
289 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800290 signer,
291 issuer='your-issuer',
292 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800293 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700294
295 The claims are considered immutable. If you want to modify the claims,
296 you can easily create another instance using :meth:`with_claims`::
297
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800298 new_audience = (
299 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
300 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700301 """
302
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800303 def __init__(self, signer, issuer, subject, audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700304 additional_claims=None,
305 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS):
306 """
307 Args:
308 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
309 issuer (str): The `iss` claim.
310 subject (str): The `sub` claim.
311 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800312 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700313 additional_claims (Mapping[str, str]): Any additional claims for
314 the JWT payload.
315 token_lifetime (int): The amount of time in seconds for
316 which the token is valid. Defaults to 1 hour.
317 """
318 super(Credentials, self).__init__()
319 self._signer = signer
320 self._issuer = issuer
321 self._subject = subject
322 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700323 self._token_lifetime = token_lifetime
324
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700325 if additional_claims is None:
326 additional_claims = {}
327
328 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700329
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700330 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700331 def _from_signer_and_info(cls, signer, info, **kwargs):
332 """Creates a Credentials instance from a signer and service account
333 info.
334
335 Args:
336 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
337 info (Mapping[str, str]): The service account info.
338 kwargs: Additional arguments to pass to the constructor.
339
340 Returns:
341 google.auth.jwt.Credentials: The constructed credentials.
342
343 Raises:
344 ValueError: If the info is not in the expected format.
345 """
346 kwargs.setdefault('subject', info['client_email'])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800347 kwargs.setdefault('issuer', info['client_email'])
348 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700349
350 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700351 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700352 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700353
354 Args:
355 info (Mapping[str, str]): The service account info in Google
356 format.
357 kwargs: Additional arguments to pass to the constructor.
358
359 Returns:
360 google.auth.jwt.Credentials: The constructed credentials.
361
362 Raises:
363 ValueError: If the info is not in the expected format.
364 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700365 signer = _service_account_info.from_dict(
366 info, require=['client_email'])
367 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700368
369 @classmethod
370 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700371 """Creates a Credentials instance from a service account .json file
372 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700373
374 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700375 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700376 kwargs: Additional arguments to pass to the constructor.
377
378 Returns:
379 google.auth.jwt.Credentials: The constructed credentials.
380 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700381 info, signer = _service_account_info.from_filename(
382 filename, require=['client_email'])
383 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700384
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800385 @classmethod
386 def from_signing_credentials(cls, credentials, audience, **kwargs):
387 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
388 existing :class:`google.auth.credentials.Signing` instance.
389
390 The new instance will use the same signer as the existing instance and
391 will use the existing instance's signer email as the issuer and
392 subject by default.
393
394 Example::
395
396 svc_creds = service_account.Credentials.from_service_account_file(
397 'service_account.json')
398 audience = (
399 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
400 jwt_creds = jwt.Credentials.from_signing_credentials(
401 svc_creds, audience=audience)
402
403 Args:
404 credentials (google.auth.credentials.Signing): The credentials to
405 use to construct the new credentials.
406 audience (str): the `aud` claim. The intended audience for the
407 credentials.
408 kwargs: Additional arguments to pass to the constructor.
409
410 Returns:
411 google.auth.jwt.Credentials: A new Credentials instance.
412 """
413 kwargs.setdefault('issuer', credentials.signer_email)
414 kwargs.setdefault('subject', credentials.signer_email)
415 return cls(
416 credentials.signer,
417 audience=audience,
418 **kwargs)
419
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700420 def with_claims(self, issuer=None, subject=None, audience=None,
421 additional_claims=None):
422 """Returns a copy of these credentials with modified claims.
423
424 Args:
425 issuer (str): The `iss` claim. If unspecified the current issuer
426 claim will be used.
427 subject (str): The `sub` claim. If unspecified the current subject
428 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800429 audience (str): the `aud` claim. If unspecified the current
430 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700431 additional_claims (Mapping[str, str]): Any additional claims for
432 the JWT payload. This will be merged with the current
433 additional claims.
434
435 Returns:
436 google.auth.jwt.Credentials: A new credentials instance.
437 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700438 new_additional_claims = copy.deepcopy(self._additional_claims)
439 new_additional_claims.update(additional_claims or {})
440
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700441 return Credentials(
442 self._signer,
443 issuer=issuer if issuer is not None else self._issuer,
444 subject=subject if subject is not None else self._subject,
445 audience=audience if audience is not None else self._audience,
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700446 additional_claims=new_additional_claims)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700447
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800448 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700449 """Make a signed JWT.
450
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700451 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800452 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700453 """
454 now = _helpers.utcnow()
455 lifetime = datetime.timedelta(seconds=self._token_lifetime)
456 expiry = now + lifetime
457
458 payload = {
459 'iss': self._issuer,
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800460 'sub': self._subject,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700461 'iat': _helpers.datetime_to_secs(now),
462 'exp': _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800463 'aud': self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700464 }
465
466 payload.update(self._additional_claims)
467
468 jwt = encode(self._signer, payload)
469
470 return jwt, expiry
471
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700472 def refresh(self, request):
473 """Refreshes the access token.
474
475 Args:
476 request (Any): Unused.
477 """
478 # pylint: disable=unused-argument
479 # (pylint doesn't correctly recognize overridden methods.)
480 self.token, self.expiry = self._make_jwt()
481
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800482 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700483 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700484 return self._signer.sign(message)
485
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800486 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800487 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800488 def signer_email(self):
489 return self._issuer
490
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800491 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800492 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800493 def signer(self):
494 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700495
496
497class OnDemandCredentials(
498 google.auth.credentials.Signing,
499 google.auth.credentials.Credentials):
500 """On-demand JWT credentials.
501
502 Like :class:`Credentials`, this class uses a JWT as the bearer token for
503 authentication. However, this class does not require the audience at
504 construction time. Instead, it will generate a new token on-demand for
505 each request using the request URI as the audience. It caches tokens
506 so that multiple requests to the same URI do not incur the overhead
507 of generating a new token every time.
508
509 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
510 have multiple audience and gRPC clients may not know all of the audiences
511 required for accessing a particular service. With these credentials,
512 no knowledge of the audiences is required ahead of time.
513
514 .. _grpc: http://www.grpc.io/
515 """
516
517 def __init__(self, signer, issuer, subject,
518 additional_claims=None,
519 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
520 max_cache_size=_DEFAULT_MAX_CACHE_SIZE):
521 """
522 Args:
523 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
524 issuer (str): The `iss` claim.
525 subject (str): The `sub` claim.
526 additional_claims (Mapping[str, str]): Any additional claims for
527 the JWT payload.
528 token_lifetime (int): The amount of time in seconds for
529 which the token is valid. Defaults to 1 hour.
530 max_cache_size (int): The maximum number of JWT tokens to keep in
531 cache. Tokens are cached using :class:`cachetools.LRUCache`.
532 """
533 super(OnDemandCredentials, self).__init__()
534 self._signer = signer
535 self._issuer = issuer
536 self._subject = subject
537 self._token_lifetime = token_lifetime
538
539 if additional_claims is None:
540 additional_claims = {}
541
542 self._additional_claims = additional_claims
543 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
544
545 @classmethod
546 def _from_signer_and_info(cls, signer, info, **kwargs):
547 """Creates an OnDemandCredentials instance from a signer and service
548 account info.
549
550 Args:
551 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
552 info (Mapping[str, str]): The service account info.
553 kwargs: Additional arguments to pass to the constructor.
554
555 Returns:
556 google.auth.jwt.OnDemandCredentials: The constructed credentials.
557
558 Raises:
559 ValueError: If the info is not in the expected format.
560 """
561 kwargs.setdefault('subject', info['client_email'])
562 kwargs.setdefault('issuer', info['client_email'])
563 return cls(signer, **kwargs)
564
565 @classmethod
566 def from_service_account_info(cls, info, **kwargs):
567 """Creates an OnDemandCredentials instance from a dictionary.
568
569 Args:
570 info (Mapping[str, str]): The service account info in Google
571 format.
572 kwargs: Additional arguments to pass to the constructor.
573
574 Returns:
575 google.auth.jwt.OnDemandCredentials: The constructed credentials.
576
577 Raises:
578 ValueError: If the info is not in the expected format.
579 """
580 signer = _service_account_info.from_dict(
581 info, require=['client_email'])
582 return cls._from_signer_and_info(signer, info, **kwargs)
583
584 @classmethod
585 def from_service_account_file(cls, filename, **kwargs):
586 """Creates an OnDemandCredentials instance from a service account .json
587 file in Google format.
588
589 Args:
590 filename (str): The path to the service account .json file.
591 kwargs: Additional arguments to pass to the constructor.
592
593 Returns:
594 google.auth.jwt.OnDemandCredentials: The constructed credentials.
595 """
596 info, signer = _service_account_info.from_filename(
597 filename, require=['client_email'])
598 return cls._from_signer_and_info(signer, info, **kwargs)
599
600 @classmethod
601 def from_signing_credentials(cls, credentials, **kwargs):
602 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
603 from an existing :class:`google.auth.credentials.Signing` instance.
604
605 The new instance will use the same signer as the existing instance and
606 will use the existing instance's signer email as the issuer and
607 subject by default.
608
609 Example::
610
611 svc_creds = service_account.Credentials.from_service_account_file(
612 'service_account.json')
613 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
614 svc_creds)
615
616 Args:
617 credentials (google.auth.credentials.Signing): The credentials to
618 use to construct the new credentials.
619 kwargs: Additional arguments to pass to the constructor.
620
621 Returns:
622 google.auth.jwt.Credentials: A new Credentials instance.
623 """
624 kwargs.setdefault('issuer', credentials.signer_email)
625 kwargs.setdefault('subject', credentials.signer_email)
626 return cls(credentials.signer, **kwargs)
627
628 def with_claims(self, issuer=None, subject=None, additional_claims=None):
629 """Returns a copy of these credentials with modified claims.
630
631 Args:
632 issuer (str): The `iss` claim. If unspecified the current issuer
633 claim will be used.
634 subject (str): The `sub` claim. If unspecified the current subject
635 claim will be used.
636 additional_claims (Mapping[str, str]): Any additional claims for
637 the JWT payload. This will be merged with the current
638 additional claims.
639
640 Returns:
641 google.auth.jwt.OnDemandCredentials: A new credentials instance.
642 """
643 new_additional_claims = copy.deepcopy(self._additional_claims)
644 new_additional_claims.update(additional_claims or {})
645
646 return OnDemandCredentials(
647 self._signer,
648 issuer=issuer if issuer is not None else self._issuer,
649 subject=subject if subject is not None else self._subject,
650 additional_claims=new_additional_claims,
651 max_cache_size=self._cache.maxsize)
652
653 @property
654 def valid(self):
655 """Checks the validity of the credentials.
656
657 These credentials are always valid because it generates tokens on
658 demand.
659 """
660 return True
661
662 def _make_jwt_for_audience(self, audience):
663 """Make a new JWT for the given audience.
664
665 Args:
666 audience (str): The intended audience.
667
668 Returns:
669 Tuple[bytes, datetime]: The encoded JWT and the expiration.
670 """
671 now = _helpers.utcnow()
672 lifetime = datetime.timedelta(seconds=self._token_lifetime)
673 expiry = now + lifetime
674
675 payload = {
676 'iss': self._issuer,
677 'sub': self._subject,
678 'iat': _helpers.datetime_to_secs(now),
679 'exp': _helpers.datetime_to_secs(expiry),
680 'aud': audience,
681 }
682
683 payload.update(self._additional_claims)
684
685 jwt = encode(self._signer, payload)
686
687 return jwt, expiry
688
689 def _get_jwt_for_audience(self, audience):
690 """Get a JWT For a given audience.
691
692 If there is already an existing, non-expired token in the cache for
693 the audience, that token is used. Otherwise, a new token will be
694 created.
695
696 Args:
697 audience (str): The intended audience.
698
699 Returns:
700 bytes: The encoded JWT.
701 """
702 token, expiry = self._cache.get(audience, (None, None))
703
704 if token is None or expiry < _helpers.utcnow():
705 token, expiry = self._make_jwt_for_audience(audience)
706 self._cache[audience] = token, expiry
707
708 return token
709
710 def refresh(self, request):
711 """Raises an exception, these credentials can not be directly
712 refreshed.
713
714 Args:
715 request (Any): Unused.
716
717 Raises:
718 google.auth.RefreshError
719 """
720 # pylint: disable=unused-argument
721 # (pylint doesn't correctly recognize overridden methods.)
722 raise exceptions.RefreshError(
723 'OnDemandCredentials can not be directly refreshed.')
724
725 def before_request(self, request, method, url, headers):
726 """Performs credential-specific before request logic.
727
728 Args:
729 request (Any): Unused. JWT credentials do not need to make an
730 HTTP request to refresh.
731 method (str): The request's HTTP method.
732 url (str): The request's URI. This is used as the audience claim
733 when generating the JWT.
734 headers (Mapping): The request's headers.
735 """
736 # pylint: disable=unused-argument
737 # (pylint doesn't correctly recognize overridden methods.)
738 parts = urllib.parse.urlsplit(url)
739 # Strip query string and fragment
740 audience = urllib.parse.urlunsplit(
741 (parts.scheme, parts.netloc, parts.path, None, None))
742 token = self._get_jwt_for_audience(audience)
743 self.apply(headers, token=token)
744
745 @_helpers.copy_docstring(google.auth.credentials.Signing)
746 def sign_bytes(self, message):
747 return self._signer.sign(message)
748
749 @property
750 @_helpers.copy_docstring(google.auth.credentials.Signing)
751 def signer_email(self):
752 return self._issuer
753
754 @property
755 @_helpers.copy_docstring(google.auth.credentials.Signing)
756 def signer(self):
757 return self._signer