blob: b1eb5fb91e95e393b8246e60eeef2f846a2b7d3a [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
50from six.moves import urllib
51
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070052from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070053from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070054from google.auth import crypt
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070055from google.auth import exceptions
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080056import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070057
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -070058_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -070059_DEFAULT_MAX_CACHE_SIZE = 10
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070060
61
62def encode(signer, payload, header=None, key_id=None):
63 """Make a signed JWT.
64
65 Args:
66 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070067 payload (Mapping[str, str]): The JWT payload.
68 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070069 key_id (str): The key id to add to the JWT header. If the
70 signer has a key id it will be used as the default. If this is
71 specified it will override the signer's key id.
72
73 Returns:
74 bytes: The encoded JWT.
75 """
76 if header is None:
77 header = {}
78
79 if key_id is None:
80 key_id = signer.key_id
81
82 header.update({'typ': 'JWT', 'alg': 'RS256'})
83
84 if key_id is not None:
85 header['kid'] = key_id
86
87 segments = [
88 base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')),
89 base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')),
90 ]
91
92 signing_input = b'.'.join(segments)
93 signature = signer.sign(signing_input)
94 segments.append(base64.urlsafe_b64encode(signature))
95
96 return b'.'.join(segments)
97
98
99def _decode_jwt_segment(encoded_section):
100 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800101 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700102 try:
103 return json.loads(section_bytes.decode('utf-8'))
104 except ValueError:
105 raise ValueError('Can\'t parse segment: {0}'.format(section_bytes))
106
107
108def _unverified_decode(token):
109 """Decodes a token and does no verification.
110
111 Args:
112 token (Union[str, bytes]): The encoded JWT.
113
114 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800115 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700116 signature.
117
118 Raises:
119 ValueError: if there are an incorrect amount of segments in the token.
120 """
121 token = _helpers.to_bytes(token)
122
123 if token.count(b'.') != 2:
124 raise ValueError(
125 'Wrong number of segments in token: {0}'.format(token))
126
127 encoded_header, encoded_payload, signature = token.split(b'.')
128 signed_section = encoded_header + b'.' + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800129 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700130
131 # Parse segments
132 header = _decode_jwt_segment(encoded_header)
133 payload = _decode_jwt_segment(encoded_payload)
134
135 return header, payload, signed_section, signature
136
137
138def decode_header(token):
139 """Return the decoded header of a token.
140
141 No verification is done. This is useful to extract the key id from
142 the header in order to acquire the appropriate certificate to verify
143 the token.
144
145 Args:
146 token (Union[str, bytes]): the encoded JWT.
147
148 Returns:
149 Mapping: The decoded JWT header.
150 """
151 header, _, _, _ = _unverified_decode(token)
152 return header
153
154
155def _verify_iat_and_exp(payload):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700156 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700157 payload.
158
159 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700160 payload (Mapping[str, str]): The JWT payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700161
162 Raises:
163 ValueError: if any checks failed.
164 """
165 now = _helpers.datetime_to_secs(_helpers.utcnow())
166
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700167 # Make sure the iat and exp claims are present.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700168 for key in ('iat', 'exp'):
169 if key not in payload:
170 raise ValueError(
171 'Token does not contain required claim {}'.format(key))
172
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700173 # Make sure the token wasn't issued in the future.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700174 iat = payload['iat']
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700175 # Err on the side of accepting a token that is slightly early to account
176 # for clock skew.
177 earliest = iat - _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700178 if now < earliest:
179 raise ValueError('Token used too early, {} < {}'.format(now, iat))
180
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700181 # Make sure the token wasn't issued in the past.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700182 exp = payload['exp']
Jon Wayne Parrotte60c1242017-03-23 16:00:24 -0700183 # Err on the side of accepting a token that is slightly out of date
184 # to account for clow skew.
185 latest = exp + _helpers.CLOCK_SKEW_SECS
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700186 if latest < now:
187 raise ValueError('Token expired, {} < {}'.format(latest, now))
188
189
190def decode(token, certs=None, verify=True, audience=None):
191 """Decode and verify a JWT.
192
193 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700194 token (str): The encoded JWT.
195 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
196 certificate used to validate the JWT signatyre. If bytes or string,
197 it must the the public key certificate in PEM format. If a mapping,
198 it must be a mapping of key IDs to public key certificates in PEM
199 format. The mapping must contain the same key ID that's specified
200 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700201 verify (bool): Whether to perform signature and claim validation.
202 Verification is done by default.
203 audience (str): The audience claim, 'aud', that this JWT should
204 contain. If None then the JWT's 'aud' parameter is not verified.
205
206 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700207 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700208
209 Raises:
210 ValueError: if any verification checks failed.
211 """
212 header, payload, signed_section, signature = _unverified_decode(token)
213
214 if not verify:
215 return payload
216
217 # If certs is specified as a dictionary of key IDs to certificates, then
218 # use the certificate identified by the key ID in the token header.
219 if isinstance(certs, collections.Mapping):
220 key_id = header.get('kid')
221 if key_id:
222 if key_id not in certs:
223 raise ValueError(
224 'Certificate for key id {} not found.'.format(key_id))
225 certs_to_check = [certs[key_id]]
226 # If there's no key id in the header, check against all of the certs.
227 else:
228 certs_to_check = certs.values()
229 else:
230 certs_to_check = certs
231
232 # Verify that the signature matches the message.
233 if not crypt.verify_signature(signed_section, signature, certs_to_check):
234 raise ValueError('Could not verify token signature.')
235
236 # Verify the issued at and created times in the payload.
237 _verify_iat_and_exp(payload)
238
239 # Check audience.
240 if audience is not None:
241 claim_audience = payload.get('aud')
242 if audience != claim_audience:
243 raise ValueError(
244 'Token has wrong audience {}, expected {}'.format(
245 claim_audience, audience))
246
247 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700248
249
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800250class Credentials(google.auth.credentials.Signing,
251 google.auth.credentials.Credentials):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700252 """Credentials that use a JWT as the bearer token.
253
254 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800255 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700256
257 The constructor arguments determine the claims for the JWT that is
258 sent with requests. Usually, you'll construct these credentials with
259 one of the helper constructors as shown in the next section.
260
261 To create JWT credentials using a Google service account private key
262 JSON file::
263
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800264 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700265 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800266 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800267 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700268
269 If you already have the service account file loaded and parsed::
270
271 service_account_info = json.load(open('service_account.json'))
272 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800273 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800274 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700275
276 Both helper methods pass on arguments to the constructor, so you can
277 specify the JWT claims::
278
279 credentials = jwt.Credentials.from_service_account_file(
280 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800281 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700282 additional_claims={'meta': 'data'})
283
284 You can also construct the credentials directly if you have a
285 :class:`~google.auth.crypt.Signer` instance::
286
287 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800288 signer,
289 issuer='your-issuer',
290 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800291 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700292
293 The claims are considered immutable. If you want to modify the claims,
294 you can easily create another instance using :meth:`with_claims`::
295
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800296 new_audience = (
297 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
298 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700299 """
300
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800301 def __init__(self, signer, issuer, subject, audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700302 additional_claims=None,
303 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS):
304 """
305 Args:
306 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
307 issuer (str): The `iss` claim.
308 subject (str): The `sub` claim.
309 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800310 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700311 additional_claims (Mapping[str, str]): Any additional claims for
312 the JWT payload.
313 token_lifetime (int): The amount of time in seconds for
314 which the token is valid. Defaults to 1 hour.
315 """
316 super(Credentials, self).__init__()
317 self._signer = signer
318 self._issuer = issuer
319 self._subject = subject
320 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700321 self._token_lifetime = token_lifetime
322
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700323 if additional_claims is None:
324 additional_claims = {}
325
326 self._additional_claims = additional_claims
Danny Hermes93d1aa42016-10-17 13:15:07 -0700327
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700328 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700329 def _from_signer_and_info(cls, signer, info, **kwargs):
330 """Creates a Credentials instance from a signer and service account
331 info.
332
333 Args:
334 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
335 info (Mapping[str, str]): The service account info.
336 kwargs: Additional arguments to pass to the constructor.
337
338 Returns:
339 google.auth.jwt.Credentials: The constructed credentials.
340
341 Raises:
342 ValueError: If the info is not in the expected format.
343 """
344 kwargs.setdefault('subject', info['client_email'])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800345 kwargs.setdefault('issuer', info['client_email'])
346 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700347
348 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700349 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700350 """Creates an Credentials instance from a dictionary.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700351
352 Args:
353 info (Mapping[str, str]): The service account info in Google
354 format.
355 kwargs: Additional arguments to pass to the constructor.
356
357 Returns:
358 google.auth.jwt.Credentials: The constructed credentials.
359
360 Raises:
361 ValueError: If the info is not in the expected format.
362 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700363 signer = _service_account_info.from_dict(
364 info, require=['client_email'])
365 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700366
367 @classmethod
368 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700369 """Creates a Credentials instance from a service account .json file
370 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700371
372 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700373 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700374 kwargs: Additional arguments to pass to the constructor.
375
376 Returns:
377 google.auth.jwt.Credentials: The constructed credentials.
378 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700379 info, signer = _service_account_info.from_filename(
380 filename, require=['client_email'])
381 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700382
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800383 @classmethod
384 def from_signing_credentials(cls, credentials, audience, **kwargs):
385 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
386 existing :class:`google.auth.credentials.Signing` instance.
387
388 The new instance will use the same signer as the existing instance and
389 will use the existing instance's signer email as the issuer and
390 subject by default.
391
392 Example::
393
394 svc_creds = service_account.Credentials.from_service_account_file(
395 'service_account.json')
396 audience = (
397 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
398 jwt_creds = jwt.Credentials.from_signing_credentials(
399 svc_creds, audience=audience)
400
401 Args:
402 credentials (google.auth.credentials.Signing): The credentials to
403 use to construct the new credentials.
404 audience (str): the `aud` claim. The intended audience for the
405 credentials.
406 kwargs: Additional arguments to pass to the constructor.
407
408 Returns:
409 google.auth.jwt.Credentials: A new Credentials instance.
410 """
411 kwargs.setdefault('issuer', credentials.signer_email)
412 kwargs.setdefault('subject', credentials.signer_email)
413 return cls(
414 credentials.signer,
415 audience=audience,
416 **kwargs)
417
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700418 def with_claims(self, issuer=None, subject=None, audience=None,
419 additional_claims=None):
420 """Returns a copy of these credentials with modified claims.
421
422 Args:
423 issuer (str): The `iss` claim. If unspecified the current issuer
424 claim will be used.
425 subject (str): The `sub` claim. If unspecified the current subject
426 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800427 audience (str): the `aud` claim. If unspecified the current
428 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700429 additional_claims (Mapping[str, str]): Any additional claims for
430 the JWT payload. This will be merged with the current
431 additional claims.
432
433 Returns:
434 google.auth.jwt.Credentials: A new credentials instance.
435 """
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700436 new_additional_claims = copy.deepcopy(self._additional_claims)
437 new_additional_claims.update(additional_claims or {})
438
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700439 return Credentials(
440 self._signer,
441 issuer=issuer if issuer is not None else self._issuer,
442 subject=subject if subject is not None else self._subject,
443 audience=audience if audience is not None else self._audience,
Jon Wayne Parrott75c78b22017-03-23 13:14:53 -0700444 additional_claims=new_additional_claims)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700445
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800446 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700447 """Make a signed JWT.
448
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700449 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800450 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700451 """
452 now = _helpers.utcnow()
453 lifetime = datetime.timedelta(seconds=self._token_lifetime)
454 expiry = now + lifetime
455
456 payload = {
457 'iss': self._issuer,
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800458 'sub': self._subject,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700459 'iat': _helpers.datetime_to_secs(now),
460 'exp': _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800461 'aud': self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700462 }
463
464 payload.update(self._additional_claims)
465
466 jwt = encode(self._signer, payload)
467
468 return jwt, expiry
469
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700470 def refresh(self, request):
471 """Refreshes the access token.
472
473 Args:
474 request (Any): Unused.
475 """
476 # pylint: disable=unused-argument
477 # (pylint doesn't correctly recognize overridden methods.)
478 self.token, self.expiry = self._make_jwt()
479
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800480 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700481 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700482 return self._signer.sign(message)
483
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800484 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800485 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800486 def signer_email(self):
487 return self._issuer
488
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800489 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800490 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800491 def signer(self):
492 return self._signer
Jon Wayne Parrottcfbfd252017-03-28 13:03:11 -0700493
494
495class OnDemandCredentials(
496 google.auth.credentials.Signing,
497 google.auth.credentials.Credentials):
498 """On-demand JWT credentials.
499
500 Like :class:`Credentials`, this class uses a JWT as the bearer token for
501 authentication. However, this class does not require the audience at
502 construction time. Instead, it will generate a new token on-demand for
503 each request using the request URI as the audience. It caches tokens
504 so that multiple requests to the same URI do not incur the overhead
505 of generating a new token every time.
506
507 This behavior is especially useful for `gRPC`_ clients. A gRPC service may
508 have multiple audience and gRPC clients may not know all of the audiences
509 required for accessing a particular service. With these credentials,
510 no knowledge of the audiences is required ahead of time.
511
512 .. _grpc: http://www.grpc.io/
513 """
514
515 def __init__(self, signer, issuer, subject,
516 additional_claims=None,
517 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
518 max_cache_size=_DEFAULT_MAX_CACHE_SIZE):
519 """
520 Args:
521 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
522 issuer (str): The `iss` claim.
523 subject (str): The `sub` claim.
524 additional_claims (Mapping[str, str]): Any additional claims for
525 the JWT payload.
526 token_lifetime (int): The amount of time in seconds for
527 which the token is valid. Defaults to 1 hour.
528 max_cache_size (int): The maximum number of JWT tokens to keep in
529 cache. Tokens are cached using :class:`cachetools.LRUCache`.
530 """
531 super(OnDemandCredentials, self).__init__()
532 self._signer = signer
533 self._issuer = issuer
534 self._subject = subject
535 self._token_lifetime = token_lifetime
536
537 if additional_claims is None:
538 additional_claims = {}
539
540 self._additional_claims = additional_claims
541 self._cache = cachetools.LRUCache(maxsize=max_cache_size)
542
543 @classmethod
544 def _from_signer_and_info(cls, signer, info, **kwargs):
545 """Creates an OnDemandCredentials instance from a signer and service
546 account info.
547
548 Args:
549 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
550 info (Mapping[str, str]): The service account info.
551 kwargs: Additional arguments to pass to the constructor.
552
553 Returns:
554 google.auth.jwt.OnDemandCredentials: The constructed credentials.
555
556 Raises:
557 ValueError: If the info is not in the expected format.
558 """
559 kwargs.setdefault('subject', info['client_email'])
560 kwargs.setdefault('issuer', info['client_email'])
561 return cls(signer, **kwargs)
562
563 @classmethod
564 def from_service_account_info(cls, info, **kwargs):
565 """Creates an OnDemandCredentials instance from a dictionary.
566
567 Args:
568 info (Mapping[str, str]): The service account info in Google
569 format.
570 kwargs: Additional arguments to pass to the constructor.
571
572 Returns:
573 google.auth.jwt.OnDemandCredentials: The constructed credentials.
574
575 Raises:
576 ValueError: If the info is not in the expected format.
577 """
578 signer = _service_account_info.from_dict(
579 info, require=['client_email'])
580 return cls._from_signer_and_info(signer, info, **kwargs)
581
582 @classmethod
583 def from_service_account_file(cls, filename, **kwargs):
584 """Creates an OnDemandCredentials instance from a service account .json
585 file in Google format.
586
587 Args:
588 filename (str): The path to the service account .json file.
589 kwargs: Additional arguments to pass to the constructor.
590
591 Returns:
592 google.auth.jwt.OnDemandCredentials: The constructed credentials.
593 """
594 info, signer = _service_account_info.from_filename(
595 filename, require=['client_email'])
596 return cls._from_signer_and_info(signer, info, **kwargs)
597
598 @classmethod
599 def from_signing_credentials(cls, credentials, **kwargs):
600 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
601 from an existing :class:`google.auth.credentials.Signing` instance.
602
603 The new instance will use the same signer as the existing instance and
604 will use the existing instance's signer email as the issuer and
605 subject by default.
606
607 Example::
608
609 svc_creds = service_account.Credentials.from_service_account_file(
610 'service_account.json')
611 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
612 svc_creds)
613
614 Args:
615 credentials (google.auth.credentials.Signing): The credentials to
616 use to construct the new credentials.
617 kwargs: Additional arguments to pass to the constructor.
618
619 Returns:
620 google.auth.jwt.Credentials: A new Credentials instance.
621 """
622 kwargs.setdefault('issuer', credentials.signer_email)
623 kwargs.setdefault('subject', credentials.signer_email)
624 return cls(credentials.signer, **kwargs)
625
626 def with_claims(self, issuer=None, subject=None, additional_claims=None):
627 """Returns a copy of these credentials with modified claims.
628
629 Args:
630 issuer (str): The `iss` claim. If unspecified the current issuer
631 claim will be used.
632 subject (str): The `sub` claim. If unspecified the current subject
633 claim will be used.
634 additional_claims (Mapping[str, str]): Any additional claims for
635 the JWT payload. This will be merged with the current
636 additional claims.
637
638 Returns:
639 google.auth.jwt.OnDemandCredentials: A new credentials instance.
640 """
641 new_additional_claims = copy.deepcopy(self._additional_claims)
642 new_additional_claims.update(additional_claims or {})
643
644 return OnDemandCredentials(
645 self._signer,
646 issuer=issuer if issuer is not None else self._issuer,
647 subject=subject if subject is not None else self._subject,
648 additional_claims=new_additional_claims,
649 max_cache_size=self._cache.maxsize)
650
651 @property
652 def valid(self):
653 """Checks the validity of the credentials.
654
655 These credentials are always valid because it generates tokens on
656 demand.
657 """
658 return True
659
660 def _make_jwt_for_audience(self, audience):
661 """Make a new JWT for the given audience.
662
663 Args:
664 audience (str): The intended audience.
665
666 Returns:
667 Tuple[bytes, datetime]: The encoded JWT and the expiration.
668 """
669 now = _helpers.utcnow()
670 lifetime = datetime.timedelta(seconds=self._token_lifetime)
671 expiry = now + lifetime
672
673 payload = {
674 'iss': self._issuer,
675 'sub': self._subject,
676 'iat': _helpers.datetime_to_secs(now),
677 'exp': _helpers.datetime_to_secs(expiry),
678 'aud': audience,
679 }
680
681 payload.update(self._additional_claims)
682
683 jwt = encode(self._signer, payload)
684
685 return jwt, expiry
686
687 def _get_jwt_for_audience(self, audience):
688 """Get a JWT For a given audience.
689
690 If there is already an existing, non-expired token in the cache for
691 the audience, that token is used. Otherwise, a new token will be
692 created.
693
694 Args:
695 audience (str): The intended audience.
696
697 Returns:
698 bytes: The encoded JWT.
699 """
700 token, expiry = self._cache.get(audience, (None, None))
701
702 if token is None or expiry < _helpers.utcnow():
703 token, expiry = self._make_jwt_for_audience(audience)
704 self._cache[audience] = token, expiry
705
706 return token
707
708 def refresh(self, request):
709 """Raises an exception, these credentials can not be directly
710 refreshed.
711
712 Args:
713 request (Any): Unused.
714
715 Raises:
716 google.auth.RefreshError
717 """
718 # pylint: disable=unused-argument
719 # (pylint doesn't correctly recognize overridden methods.)
720 raise exceptions.RefreshError(
721 'OnDemandCredentials can not be directly refreshed.')
722
723 def before_request(self, request, method, url, headers):
724 """Performs credential-specific before request logic.
725
726 Args:
727 request (Any): Unused. JWT credentials do not need to make an
728 HTTP request to refresh.
729 method (str): The request's HTTP method.
730 url (str): The request's URI. This is used as the audience claim
731 when generating the JWT.
732 headers (Mapping): The request's headers.
733 """
734 # pylint: disable=unused-argument
735 # (pylint doesn't correctly recognize overridden methods.)
736 parts = urllib.parse.urlsplit(url)
737 # Strip query string and fragment
738 audience = urllib.parse.urlunsplit(
739 (parts.scheme, parts.netloc, parts.path, None, None))
740 token = self._get_jwt_for_audience(audience)
741 self.apply(headers, token=token)
742
743 @_helpers.copy_docstring(google.auth.credentials.Signing)
744 def sign_bytes(self, message):
745 return self._signer.sign(message)
746
747 @property
748 @_helpers.copy_docstring(google.auth.credentials.Signing)
749 def signer_email(self):
750 return self._issuer
751
752 @property
753 @_helpers.copy_docstring(google.auth.credentials.Signing)
754 def signer(self):
755 return self._signer