1# 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
22To encode a JWT use :func:`encode`::
23
24    from google.auth import crypt
25    from google.auth import jwt
26
27    signer = crypt.Signer(private_key)
28    payload = {'some': 'payload'}
29    encoded = jwt.encode(signer, payload)
30
31To decode a JWT and verify claims use :func:`decode`::
32
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 collections
44import copy
45import datetime
46import json
47
48import cachetools
49import six
50from six.moves import urllib
51
52from google.auth import _helpers
53from google.auth import _service_account_info
54from google.auth import crypt
55from google.auth import exceptions
56import google.auth.credentials
57
58_DEFAULT_TOKEN_LIFETIME_SECS = 3600  # 1 hour in seconds
59_DEFAULT_MAX_CACHE_SIZE = 10
60
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.
67        payload (Mapping[str, str]): The JWT payload.
68        header (Mapping[str, str]): Additional JWT header payload.
69        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        _helpers.unpadded_urlsafe_b64encode(
89            json.dumps(header).encode('utf-8')
90        ),
91        _helpers.unpadded_urlsafe_b64encode(
92            json.dumps(payload).encode('utf-8')
93        ),
94    ]
95
96    signing_input = b'.'.join(segments)
97    signature = signer.sign(signing_input)
98    segments.append(
99        _helpers.unpadded_urlsafe_b64encode(signature)
100    )
101
102    return b'.'.join(segments)
103
104
105def _decode_jwt_segment(encoded_section):
106    """Decodes a single JWT segment."""
107    section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
108    try:
109        return json.loads(section_bytes.decode('utf-8'))
110    except ValueError as caught_exc:
111        new_exc = ValueError('Can\'t parse segment: {0}'.format(section_bytes))
112        six.raise_from(new_exc, caught_exc)
113
114
115def _unverified_decode(token):
116    """Decodes a token and does no verification.
117
118    Args:
119        token (Union[str, bytes]): The encoded JWT.
120
121    Returns:
122        Tuple[str, str, str, str]: header, payload, signed_section, and
123            signature.
124
125    Raises:
126        ValueError: if there are an incorrect amount of segments in the token.
127    """
128    token = _helpers.to_bytes(token)
129
130    if token.count(b'.') != 2:
131        raise ValueError(
132            'Wrong number of segments in token: {0}'.format(token))
133
134    encoded_header, encoded_payload, signature = token.split(b'.')
135    signed_section = encoded_header + b'.' + encoded_payload
136    signature = _helpers.padded_urlsafe_b64decode(signature)
137
138    # Parse segments
139    header = _decode_jwt_segment(encoded_header)
140    payload = _decode_jwt_segment(encoded_payload)
141
142    return header, payload, signed_section, signature
143
144
145def decode_header(token):
146    """Return the decoded header of a token.
147
148    No verification is done. This is useful to extract the key id from
149    the header in order to acquire the appropriate certificate to verify
150    the token.
151
152    Args:
153        token (Union[str, bytes]): the encoded JWT.
154
155    Returns:
156        Mapping: The decoded JWT header.
157    """
158    header, _, _, _ = _unverified_decode(token)
159    return header
160
161
162def _verify_iat_and_exp(payload):
163    """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
164    payload.
165
166    Args:
167        payload (Mapping[str, str]): The JWT payload.
168
169    Raises:
170        ValueError: if any checks failed.
171    """
172    now = _helpers.datetime_to_secs(_helpers.utcnow())
173
174    # Make sure the iat and exp claims are present.
175    for key in ('iat', 'exp'):
176        if key not in payload:
177            raise ValueError(
178                'Token does not contain required claim {}'.format(key))
179
180    # Make sure the token wasn't issued in the future.
181    iat = payload['iat']
182    # Err on the side of accepting a token that is slightly early to account
183    # for clock skew.
184    earliest = iat - _helpers.CLOCK_SKEW_SECS
185    if now < earliest:
186        raise ValueError('Token used too early, {} < {}'.format(now, iat))
187
188    # Make sure the token wasn't issued in the past.
189    exp = payload['exp']
190    # Err on the side of accepting a token that is slightly out of date
191    # to account for clow skew.
192    latest = exp + _helpers.CLOCK_SKEW_SECS
193    if latest < now:
194        raise ValueError('Token expired, {} < {}'.format(latest, now))
195
196
197def decode(token, certs=None, verify=True, audience=None):
198    """Decode and verify a JWT.
199
200    Args:
201        token (str): The encoded JWT.
202        certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
203            certificate used to validate the JWT signatyre. If bytes or string,
204            it must the the public key certificate in PEM format. If a mapping,
205            it must be a mapping of key IDs to public key certificates in PEM
206            format. The mapping must contain the same key ID that's specified
207            in the token's header.
208        verify (bool): Whether to perform signature and claim validation.
209            Verification is done by default.
210        audience (str): The audience claim, 'aud', that this JWT should
211            contain. If None then the JWT's 'aud' parameter is not verified.
212
213    Returns:
214        Mapping[str, str]: The deserialized JSON payload in the JWT.
215
216    Raises:
217        ValueError: if any verification checks failed.
218    """
219    header, payload, signed_section, signature = _unverified_decode(token)
220
221    if not verify:
222        return payload
223
224    # If certs is specified as a dictionary of key IDs to certificates, then
225    # use the certificate identified by the key ID in the token header.
226    if isinstance(certs, collections.Mapping):
227        key_id = header.get('kid')
228        if key_id:
229            if key_id not in certs:
230                raise ValueError(
231                    'Certificate for key id {} not found.'.format(key_id))
232            certs_to_check = [certs[key_id]]
233        # If there's no key id in the header, check against all of the certs.
234        else:
235            certs_to_check = certs.values()
236    else:
237        certs_to_check = certs
238
239    # Verify that the signature matches the message.
240    if not crypt.verify_signature(signed_section, signature, certs_to_check):
241        raise ValueError('Could not verify token signature.')
242
243    # Verify the issued at and created times in the payload.
244    _verify_iat_and_exp(payload)
245
246    # Check audience.
247    if audience is not None:
248        claim_audience = payload.get('aud')
249        if audience != claim_audience:
250            raise ValueError(
251                'Token has wrong audience {}, expected {}'.format(
252                    claim_audience, audience))
253
254    return payload
255
256
257class Credentials(google.auth.credentials.Signing,
258                  google.auth.credentials.Credentials):
259    """Credentials that use a JWT as the bearer token.
260
261    These credentials require an "audience" claim. This claim identifies the
262    intended recipient of the bearer token.
263
264    The constructor arguments determine the claims for the JWT that is
265    sent with requests. Usually, you'll construct these credentials with
266    one of the helper constructors as shown in the next section.
267
268    To create JWT credentials using a Google service account private key
269    JSON file::
270
271        audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
272        credentials = jwt.Credentials.from_service_account_file(
273            'service-account.json',
274            audience=audience)
275
276    If you already have the service account file loaded and parsed::
277
278        service_account_info = json.load(open('service_account.json'))
279        credentials = jwt.Credentials.from_service_account_info(
280            service_account_info,
281            audience=audience)
282
283    Both helper methods pass on arguments to the constructor, so you can
284    specify the JWT claims::
285
286        credentials = jwt.Credentials.from_service_account_file(
287            'service-account.json',
288            audience=audience,
289            additional_claims={'meta': 'data'})
290
291    You can also construct the credentials directly if you have a
292    :class:`~google.auth.crypt.Signer` instance::
293
294        credentials = jwt.Credentials(
295            signer,
296            issuer='your-issuer',
297            subject='your-subject',
298            audience=audience)
299
300    The claims are considered immutable. If you want to modify the claims,
301    you can easily create another instance using :meth:`with_claims`::
302
303        new_audience = (
304            'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
305        new_credentials = credentials.with_claims(audience=new_audience)
306    """
307
308    def __init__(self, signer, issuer, subject, audience,
309                 additional_claims=None,
310                 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS):
311        """
312        Args:
313            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
314            issuer (str): The `iss` claim.
315            subject (str): The `sub` claim.
316            audience (str): the `aud` claim. The intended audience for the
317                credentials.
318            additional_claims (Mapping[str, str]): Any additional claims for
319                the JWT payload.
320            token_lifetime (int): The amount of time in seconds for
321                which the token is valid. Defaults to 1 hour.
322        """
323        super(Credentials, self).__init__()
324        self._signer = signer
325        self._issuer = issuer
326        self._subject = subject
327        self._audience = audience
328        self._token_lifetime = token_lifetime
329
330        if additional_claims is None:
331            additional_claims = {}
332
333        self._additional_claims = additional_claims
334
335    @classmethod
336    def _from_signer_and_info(cls, signer, info, **kwargs):
337        """Creates a Credentials instance from a signer and service account
338        info.
339
340        Args:
341            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
342            info (Mapping[str, str]): The service account info.
343            kwargs: Additional arguments to pass to the constructor.
344
345        Returns:
346            google.auth.jwt.Credentials: The constructed credentials.
347
348        Raises:
349            ValueError: If the info is not in the expected format.
350        """
351        kwargs.setdefault('subject', info['client_email'])
352        kwargs.setdefault('issuer', info['client_email'])
353        return cls(signer, **kwargs)
354
355    @classmethod
356    def from_service_account_info(cls, info, **kwargs):
357        """Creates an Credentials instance from a dictionary.
358
359        Args:
360            info (Mapping[str, str]): The service account info in Google
361                format.
362            kwargs: Additional arguments to pass to the constructor.
363
364        Returns:
365            google.auth.jwt.Credentials: The constructed credentials.
366
367        Raises:
368            ValueError: If the info is not in the expected format.
369        """
370        signer = _service_account_info.from_dict(
371            info, require=['client_email'])
372        return cls._from_signer_and_info(signer, info, **kwargs)
373
374    @classmethod
375    def from_service_account_file(cls, filename, **kwargs):
376        """Creates a Credentials instance from a service account .json file
377        in Google format.
378
379        Args:
380            filename (str): The path to the service account .json file.
381            kwargs: Additional arguments to pass to the constructor.
382
383        Returns:
384            google.auth.jwt.Credentials: The constructed credentials.
385        """
386        info, signer = _service_account_info.from_filename(
387            filename, require=['client_email'])
388        return cls._from_signer_and_info(signer, info, **kwargs)
389
390    @classmethod
391    def from_signing_credentials(cls, credentials, audience, **kwargs):
392        """Creates a new :class:`google.auth.jwt.Credentials` instance from an
393        existing :class:`google.auth.credentials.Signing` instance.
394
395        The new instance will use the same signer as the existing instance and
396        will use the existing instance's signer email as the issuer and
397        subject by default.
398
399        Example::
400
401            svc_creds = service_account.Credentials.from_service_account_file(
402                'service_account.json')
403            audience = (
404                'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
405            jwt_creds = jwt.Credentials.from_signing_credentials(
406                svc_creds, audience=audience)
407
408        Args:
409            credentials (google.auth.credentials.Signing): The credentials to
410                use to construct the new credentials.
411            audience (str): the `aud` claim. The intended audience for the
412                credentials.
413            kwargs: Additional arguments to pass to the constructor.
414
415        Returns:
416            google.auth.jwt.Credentials: A new Credentials instance.
417        """
418        kwargs.setdefault('issuer', credentials.signer_email)
419        kwargs.setdefault('subject', credentials.signer_email)
420        return cls(
421            credentials.signer,
422            audience=audience,
423            **kwargs)
424
425    def with_claims(self, issuer=None, subject=None, audience=None,
426                    additional_claims=None):
427        """Returns a copy of these credentials with modified claims.
428
429        Args:
430            issuer (str): The `iss` claim. If unspecified the current issuer
431                claim will be used.
432            subject (str): The `sub` claim. If unspecified the current subject
433                claim will be used.
434            audience (str): the `aud` claim. If unspecified the current
435                audience claim will be used.
436            additional_claims (Mapping[str, str]): Any additional claims for
437                the JWT payload. This will be merged with the current
438                additional claims.
439
440        Returns:
441            google.auth.jwt.Credentials: A new credentials instance.
442        """
443        new_additional_claims = copy.deepcopy(self._additional_claims)
444        new_additional_claims.update(additional_claims or {})
445
446        return self.__class__(
447            self._signer,
448            issuer=issuer if issuer is not None else self._issuer,
449            subject=subject if subject is not None else self._subject,
450            audience=audience if audience is not None else self._audience,
451            additional_claims=new_additional_claims)
452
453    def _make_jwt(self):
454        """Make a signed JWT.
455
456        Returns:
457            Tuple[bytes, datetime]: The encoded JWT and the expiration.
458        """
459        now = _helpers.utcnow()
460        lifetime = datetime.timedelta(seconds=self._token_lifetime)
461        expiry = now + lifetime
462
463        payload = {
464            'iss': self._issuer,
465            'sub': self._subject,
466            'iat': _helpers.datetime_to_secs(now),
467            'exp': _helpers.datetime_to_secs(expiry),
468            'aud': self._audience,
469        }
470
471        payload.update(self._additional_claims)
472
473        jwt = encode(self._signer, payload)
474
475        return jwt, expiry
476
477    def refresh(self, request):
478        """Refreshes the access token.
479
480        Args:
481            request (Any): Unused.
482        """
483        # pylint: disable=unused-argument
484        # (pylint doesn't correctly recognize overridden methods.)
485        self.token, self.expiry = self._make_jwt()
486
487    @_helpers.copy_docstring(google.auth.credentials.Signing)
488    def sign_bytes(self, message):
489        return self._signer.sign(message)
490
491    @property
492    @_helpers.copy_docstring(google.auth.credentials.Signing)
493    def signer_email(self):
494        return self._issuer
495
496    @property
497    @_helpers.copy_docstring(google.auth.credentials.Signing)
498    def signer(self):
499        return self._signer
500
501
502class OnDemandCredentials(
503        google.auth.credentials.Signing,
504        google.auth.credentials.Credentials):
505    """On-demand JWT credentials.
506
507    Like :class:`Credentials`, this class uses a JWT as the bearer token for
508    authentication. However, this class does not require the audience at
509    construction time. Instead, it will generate a new token on-demand for
510    each request using the request URI as the audience. It caches tokens
511    so that multiple requests to the same URI do not incur the overhead
512    of generating a new token every time.
513
514    This behavior is especially useful for `gRPC`_ clients. A gRPC service may
515    have multiple audience and gRPC clients may not know all of the audiences
516    required for accessing a particular service. With these credentials,
517    no knowledge of the audiences is required ahead of time.
518
519    .. _grpc: http://www.grpc.io/
520    """
521
522    def __init__(self, signer, issuer, subject,
523                 additional_claims=None,
524                 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS,
525                 max_cache_size=_DEFAULT_MAX_CACHE_SIZE):
526        """
527        Args:
528            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
529            issuer (str): The `iss` claim.
530            subject (str): The `sub` claim.
531            additional_claims (Mapping[str, str]): Any additional claims for
532                the JWT payload.
533            token_lifetime (int): The amount of time in seconds for
534                which the token is valid. Defaults to 1 hour.
535            max_cache_size (int): The maximum number of JWT tokens to keep in
536                cache. Tokens are cached using :class:`cachetools.LRUCache`.
537        """
538        super(OnDemandCredentials, self).__init__()
539        self._signer = signer
540        self._issuer = issuer
541        self._subject = subject
542        self._token_lifetime = token_lifetime
543
544        if additional_claims is None:
545            additional_claims = {}
546
547        self._additional_claims = additional_claims
548        self._cache = cachetools.LRUCache(maxsize=max_cache_size)
549
550    @classmethod
551    def _from_signer_and_info(cls, signer, info, **kwargs):
552        """Creates an OnDemandCredentials instance from a signer and service
553        account info.
554
555        Args:
556            signer (google.auth.crypt.Signer): The signer used to sign JWTs.
557            info (Mapping[str, str]): The service account info.
558            kwargs: Additional arguments to pass to the constructor.
559
560        Returns:
561            google.auth.jwt.OnDemandCredentials: The constructed credentials.
562
563        Raises:
564            ValueError: If the info is not in the expected format.
565        """
566        kwargs.setdefault('subject', info['client_email'])
567        kwargs.setdefault('issuer', info['client_email'])
568        return cls(signer, **kwargs)
569
570    @classmethod
571    def from_service_account_info(cls, info, **kwargs):
572        """Creates an OnDemandCredentials instance from a dictionary.
573
574        Args:
575            info (Mapping[str, str]): The service account info in Google
576                format.
577            kwargs: Additional arguments to pass to the constructor.
578
579        Returns:
580            google.auth.jwt.OnDemandCredentials: The constructed credentials.
581
582        Raises:
583            ValueError: If the info is not in the expected format.
584        """
585        signer = _service_account_info.from_dict(
586            info, require=['client_email'])
587        return cls._from_signer_and_info(signer, info, **kwargs)
588
589    @classmethod
590    def from_service_account_file(cls, filename, **kwargs):
591        """Creates an OnDemandCredentials instance from a service account .json
592        file in Google format.
593
594        Args:
595            filename (str): The path to the service account .json file.
596            kwargs: Additional arguments to pass to the constructor.
597
598        Returns:
599            google.auth.jwt.OnDemandCredentials: The constructed credentials.
600        """
601        info, signer = _service_account_info.from_filename(
602            filename, require=['client_email'])
603        return cls._from_signer_and_info(signer, info, **kwargs)
604
605    @classmethod
606    def from_signing_credentials(cls, credentials, **kwargs):
607        """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance
608        from an existing :class:`google.auth.credentials.Signing` instance.
609
610        The new instance will use the same signer as the existing instance and
611        will use the existing instance's signer email as the issuer and
612        subject by default.
613
614        Example::
615
616            svc_creds = service_account.Credentials.from_service_account_file(
617                'service_account.json')
618            jwt_creds = jwt.OnDemandCredentials.from_signing_credentials(
619                svc_creds)
620
621        Args:
622            credentials (google.auth.credentials.Signing): The credentials to
623                use to construct the new credentials.
624            kwargs: Additional arguments to pass to the constructor.
625
626        Returns:
627            google.auth.jwt.Credentials: A new Credentials instance.
628        """
629        kwargs.setdefault('issuer', credentials.signer_email)
630        kwargs.setdefault('subject', credentials.signer_email)
631        return cls(credentials.signer, **kwargs)
632
633    def with_claims(self, issuer=None, subject=None, additional_claims=None):
634        """Returns a copy of these credentials with modified claims.
635
636        Args:
637            issuer (str): The `iss` claim. If unspecified the current issuer
638                claim will be used.
639            subject (str): The `sub` claim. If unspecified the current subject
640                claim will be used.
641            additional_claims (Mapping[str, str]): Any additional claims for
642                the JWT payload. This will be merged with the current
643                additional claims.
644
645        Returns:
646            google.auth.jwt.OnDemandCredentials: A new credentials instance.
647        """
648        new_additional_claims = copy.deepcopy(self._additional_claims)
649        new_additional_claims.update(additional_claims or {})
650
651        return self.__class__(
652            self._signer,
653            issuer=issuer if issuer is not None else self._issuer,
654            subject=subject if subject is not None else self._subject,
655            additional_claims=new_additional_claims,
656            max_cache_size=self._cache.maxsize)
657
658    @property
659    def valid(self):
660        """Checks the validity of the credentials.
661
662        These credentials are always valid because it generates tokens on
663        demand.
664        """
665        return True
666
667    def _make_jwt_for_audience(self, audience):
668        """Make a new JWT for the given audience.
669
670        Args:
671            audience (str): The intended audience.
672
673        Returns:
674            Tuple[bytes, datetime]: The encoded JWT and the expiration.
675        """
676        now = _helpers.utcnow()
677        lifetime = datetime.timedelta(seconds=self._token_lifetime)
678        expiry = now + lifetime
679
680        payload = {
681            'iss': self._issuer,
682            'sub': self._subject,
683            'iat': _helpers.datetime_to_secs(now),
684            'exp': _helpers.datetime_to_secs(expiry),
685            'aud': audience,
686        }
687
688        payload.update(self._additional_claims)
689
690        jwt = encode(self._signer, payload)
691
692        return jwt, expiry
693
694    def _get_jwt_for_audience(self, audience):
695        """Get a JWT For a given audience.
696
697        If there is already an existing, non-expired token in the cache for
698        the audience, that token is used. Otherwise, a new token will be
699        created.
700
701        Args:
702            audience (str): The intended audience.
703
704        Returns:
705            bytes: The encoded JWT.
706        """
707        token, expiry = self._cache.get(audience, (None, None))
708
709        if token is None or expiry < _helpers.utcnow():
710            token, expiry = self._make_jwt_for_audience(audience)
711            self._cache[audience] = token, expiry
712
713        return token
714
715    def refresh(self, request):
716        """Raises an exception, these credentials can not be directly
717        refreshed.
718
719        Args:
720            request (Any): Unused.
721
722        Raises:
723            google.auth.RefreshError
724        """
725        # pylint: disable=unused-argument
726        # (pylint doesn't correctly recognize overridden methods.)
727        raise exceptions.RefreshError(
728            'OnDemandCredentials can not be directly refreshed.')
729
730    def before_request(self, request, method, url, headers):
731        """Performs credential-specific before request logic.
732
733        Args:
734            request (Any): Unused. JWT credentials do not need to make an
735                HTTP request to refresh.
736            method (str): The request's HTTP method.
737            url (str): The request's URI. This is used as the audience claim
738                when generating the JWT.
739            headers (Mapping): The request's headers.
740        """
741        # pylint: disable=unused-argument
742        # (pylint doesn't correctly recognize overridden methods.)
743        parts = urllib.parse.urlsplit(url)
744        # Strip query string and fragment
745        audience = urllib.parse.urlunsplit(
746            (parts.scheme, parts.netloc, parts.path, "", ""))
747        token = self._get_jwt_for_audience(audience)
748        self.apply(headers, token=token)
749
750    @_helpers.copy_docstring(google.auth.credentials.Signing)
751    def sign_bytes(self, message):
752        return self._signer.sign(message)
753
754    @property
755    @_helpers.copy_docstring(google.auth.credentials.Signing)
756    def signer_email(self):
757        return self._issuer
758
759    @property
760    @_helpers.copy_docstring(google.auth.credentials.Signing)
761    def signer(self):
762        return self._signer
763