Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-PyJWT for openSUSE:Factory checked in at 2021-05-20 19:23:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-PyJWT (Old) and /work/SRC/openSUSE:Factory/.python-PyJWT.new.2988 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-PyJWT" Thu May 20 19:23:28 2021 rev:22 rq:894171 version:2.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-PyJWT/python-PyJWT.changes 2021-02-04 20:23:57.710815999 +0100 +++ /work/SRC/openSUSE:Factory/.python-PyJWT.new.2988/python-PyJWT.changes 2021-05-20 19:23:53.862232039 +0200 @@ -1,0 +2,16 @@ +Tue May 18 22:19:50 UTC 2021 - Dirk M??ller <dmuel...@suse.com> + +- update to 2.1.0: + - Allow claims validation without making JWT signature validation mandatory. ` + - Remove padding from JWK test data. ` + - Make `kty` mandatory in JWK to be compliant with RFC7517. ` + - Allow JWK without `alg` to be compliant with RFC7517. ` + - Allow to verify with private key on ECAlgorithm, as well as on Ed25519Algorithm. ` + - Add caching by default to PyJWKClient ` + - Add missing exceptions.InvalidKeyError to jwt module __init__ imports ` + - Add support for ES256K algorithm ` + - Add `from_jwk()` to Ed25519Algorithm ` + - Add `to_jwk()` to Ed25519Algorithm ` + - Export `PyJWK` and `PyJWKSet` + +------------------------------------------------------------------- Old: ---- PyJWT-2.0.1.tar.gz New: ---- PyJWT-2.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-PyJWT.spec ++++++ --- /var/tmp/diff_new_pack.RzRDVB/_old 2021-05-20 19:23:54.254230380 +0200 +++ /var/tmp/diff_new_pack.RzRDVB/_new 2021-05-20 19:23:54.258230364 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %global skip_python2 1 Name: python-PyJWT -Version: 2.0.1 +Version: 2.1.0 Release: 0 Summary: JSON Web Token implementation in Python License: MIT @@ -34,7 +34,7 @@ Requires: python-cryptography >= 3.3.1 Requires: python-setuptools Requires(post): update-alternatives -Requires(postun): update-alternatives +Requires(postun):update-alternatives BuildArch: noarch %python_subpackages ++++++ PyJWT-2.0.1.tar.gz -> PyJWT-2.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/.pre-commit-config.yaml new/PyJWT-2.1.0/.pre-commit-config.yaml --- old/PyJWT-2.0.1/.pre-commit-config.yaml 2021-01-06 03:19:08.000000000 +0100 +++ new/PyJWT-2.1.0/.pre-commit-config.yaml 2021-04-28 13:47:41.000000000 +0200 @@ -1,24 +1,24 @@ repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 21.4b0 hooks: - id: black args: ["--target-version=py36"] - repo: https://github.com/asottile/blacken-docs - rev: v1.9.1 + rev: v1.10.0 hooks: - id: blacken-docs args: ["--target-version=py36"] - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + - repo: https://github.com/PyCQA/flake8 + rev: 3.9.1 hooks: - id: flake8 language_version: python3.8 - repo: https://github.com/PyCQA/isort - rev: 5.7.0 + rev: 5.8.0 hooks: - id: isort diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/CHANGELOG.rst new/PyJWT-2.1.0/CHANGELOG.rst --- old/PyJWT-2.0.1/CHANGELOG.rst 2021-01-17 18:56:23.000000000 +0100 +++ new/PyJWT-2.1.0/CHANGELOG.rst 2021-04-28 13:59:20.000000000 +0200 @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. This project adheres to `Semantic Versioning <https://semver.org/>`__. -`Unreleased <https://github.com/jpadilla/pyjwt/compare/2.0.0...HEAD>`__ +`Unreleased <https://github.com/jpadilla/pyjwt/compare/2.1.0...HEAD>`__ ----------------------------------------------------------------------- Changed @@ -16,6 +16,32 @@ Added ~~~~~ +`v2.1.0 <https://github.com/jpadilla/pyjwt/compare/2.0.1...2.1.0>`__ +-------------------------------------------------------------------- + +Changed +~~~~~~~ + +- Allow claims validation without making JWT signature validation mandatory. `#608 <https://github.com/jpadilla/pyjwt/pull/608>`__ + +Fixed +~~~~~ + +- Remove padding from JWK test data. `#628 <https://github.com/jpadilla/pyjwt/pull/628>`__ +- Make `kty` mandatory in JWK to be compliant with RFC7517. `#624 <https://github.com/jpadilla/pyjwt/pull/624>`__ +- Allow JWK without `alg` to be compliant with RFC7517. `#624 <https://github.com/jpadilla/pyjwt/pull/624>`__ +- Allow to verify with private key on ECAlgorithm, as well as on Ed25519Algorithm. `#645 <https://github.com/jpadilla/pyjwt/pull/645>`__ + +Added +~~~~~ + +- Add caching by default to PyJWKClient `#611 <https://github.com/jpadilla/pyjwt/pull/611>`__ +- Add missing exceptions.InvalidKeyError to jwt module __init__ imports `#620 <https://github.com/jpadilla/pyjwt/pull/620>`__ +- Add support for ES256K algorithm `#629 <https://github.com/jpadilla/pyjwt/pull/629>`__ +- Add `from_jwk()` to Ed25519Algorithm `#621 <https://github.com/jpadilla/pyjwt/pull/621>`__ +- Add `to_jwk()` to Ed25519Algorithm `#643 <https://github.com/jpadilla/pyjwt/pull/643>`__ +- Export `PyJWK` and `PyJWKSet` `#652 <https://github.com/jpadilla/pyjwt/pull/652>`__ + `v2.0.1 <https://github.com/jpadilla/pyjwt/compare/2.0.0...2.0.1>`__ -------------------------------------------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/PKG-INFO new/PyJWT-2.1.0/PKG-INFO --- old/PyJWT-2.0.1/PKG-INFO 2021-01-17 18:56:59.065547200 +0100 +++ new/PyJWT-2.1.0/PKG-INFO 2021-04-28 14:01:11.608931300 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: PyJWT -Version: 2.0.1 +Version: 2.1.0 Summary: JSON Web Token implementation in Python Home-page: https://github.com/jpadilla/pyjwt Author: Jose Padilla @@ -86,6 +86,6 @@ Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: crypto -Provides-Extra: tests Provides-Extra: dev +Provides-Extra: tests Provides-Extra: docs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/PyJWT.egg-info/PKG-INFO new/PyJWT-2.1.0/PyJWT.egg-info/PKG-INFO --- old/PyJWT-2.0.1/PyJWT.egg-info/PKG-INFO 2021-01-17 18:56:58.000000000 +0100 +++ new/PyJWT-2.1.0/PyJWT.egg-info/PKG-INFO 2021-04-28 14:01:11.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: PyJWT -Version: 2.0.1 +Version: 2.1.0 Summary: JSON Web Token implementation in Python Home-page: https://github.com/jpadilla/pyjwt Author: Jose Padilla @@ -86,6 +86,6 @@ Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: crypto -Provides-Extra: tests Provides-Extra: dev +Provides-Extra: tests Provides-Extra: docs diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/PyJWT.egg-info/SOURCES.txt new/PyJWT-2.1.0/PyJWT.egg-info/SOURCES.txt --- old/PyJWT-2.0.1/PyJWT.egg-info/SOURCES.txt 2021-01-17 18:56:58.000000000 +0100 +++ new/PyJWT-2.1.0/PyJWT.egg-info/SOURCES.txt 2021-04-28 14:01:11.000000000 +0200 @@ -5,7 +5,6 @@ LICENSE MANIFEST.in README.rst -pyproject.toml setup.cfg setup.py tox.ini @@ -50,10 +49,14 @@ tests/keys/jwk_ec_key_P-256.json tests/keys/jwk_ec_key_P-384.json tests/keys/jwk_ec_key_P-521.json +tests/keys/jwk_ec_key_secp256k1.json tests/keys/jwk_ec_pub_P-256.json tests/keys/jwk_ec_pub_P-384.json tests/keys/jwk_ec_pub_P-521.json +tests/keys/jwk_ec_pub_secp256k1.json tests/keys/jwk_hmac.json +tests/keys/jwk_okp_key_Ed25519.json +tests/keys/jwk_okp_pub_Ed25519.json tests/keys/jwk_rsa_key.json tests/keys/jwk_rsa_pub.json tests/keys/testkey2_rsa.pub.pem diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/docs/algorithms.rst new/PyJWT-2.1.0/docs/algorithms.rst --- old/PyJWT-2.0.1/docs/algorithms.rst 2020-12-20 01:01:37.000000000 +0100 +++ new/PyJWT-2.1.0/docs/algorithms.rst 2021-04-28 13:43:46.000000000 +0200 @@ -8,6 +8,7 @@ * HS384 - HMAC using SHA-384 hash algorithm * HS512 - HMAC using SHA-512 hash algorithm * ES256 - ECDSA signature algorithm using SHA-256 hash algorithm +* ES256K - ECDSA signature algorithm with secp256k1 curve using SHA-256 hash algorithm * ES384 - ECDSA signature algorithm using SHA-384 hash algorithm * ES512 - ECDSA signature algorithm using SHA-512 hash algorithm * RS256 - RSASSA-PKCS1-v1_5 signature algorithm using SHA-256 hash algorithm @@ -16,7 +17,7 @@ * PS256 - RSASSA-PSS signature using SHA-256 and MGF1 padding with SHA-256 * PS384 - RSASSA-PSS signature using SHA-384 and MGF1 padding with SHA-384 * PS512 - RSASSA-PSS signature using SHA-512 and MGF1 padding with SHA-512 -* EdDSA - Ed25519255 signature using SHA-512. Provides 128-bit security +* EdDSA - Ed25519 signature using SHA-512. Provides 128-bit security Asymmetric (Public-key) Algorithms ---------------------------------- @@ -56,3 +57,15 @@ In the above case, if the JWT has any value for its alg header other than HS512 or HS256, the claim will be rejected with an ``InvalidAlgorithmError``. + +.. warning:: + + Do **not** compute the ``algorithms`` parameter based on the + ``alg`` from the token itself, or on any other data that an + attacker may be able to influence, as that might expose you to + various vulnerabilities (see `RFC 8725 ??2.1 + <https://www.rfc-editor.org/rfc/rfc8725.html#section-2.1>`_). Instead, + either hard-code a fixed value for ``algorithms``, or configure it + in the same place you configure the ``key``. Make sure not to mix + symmetric and asymmetric algorithms that interpret the ``key`` in + different ways (e.g. HS\* and RS\*). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/docs/api.rst new/PyJWT-2.1.0/docs/api.rst --- old/PyJWT-2.0.1/docs/api.rst 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/docs/api.rst 2021-04-28 13:43:48.000000000 +0200 @@ -28,19 +28,33 @@ :param list algorithms: allowed algorithms, e.g. ``["ES256"]`` - .. note:: It is highly recommended to specify the expected ``algorithms``. + .. warning:: - .. note:: It is insecure to mix symmetric and asymmetric algorithms because they require different kinds of keys. + Do **not** compute the ``algorithms`` parameter based on + the ``alg`` from the token itself, or on any other data + that an attacker may be able to influence, as that might + expose you to various vulnerabilities (see `RFC 8725 ??2.1 + <https://www.rfc-editor.org/rfc/rfc8725.html#section-2.1>`_). Instead, + either hard-code a fixed value for ``algorithms``, or + configure it in the same place you configure the + ``key``. Make sure not to mix symmetric and asymmetric + algorithms that interpret the ``key`` in different ways + (e.g. HS\* and RS\*). :param dict options: extended decoding and validation options - * ``require_exp=False`` check that ``exp`` (expiration) claim is present - * ``require_iat=False`` check that ``iat`` (issued at) claim is present - * ``require_nbf=False`` check that ``nbf`` (not before) claim is present - * ``verify_aud=False`` check that ``aud`` (audience) claim matches ``audience`` - * ``verify_iat=False`` check that ``iat`` (issued at) claim value is an integer - * ``verify_exp=False`` check that ``exp`` (expiration) claim value is OK - * ``verify_iss=False`` check that ``iss`` (issuer) claim matches ``issuer`` + * ``require=[]`` list of claims that must be present. E.g. ``require=["exp", "iat", "nbf"]``. + Only verifies that the claims exists. Does NOT verify that the claims are valid. + * ``verify_aud=True`` but will be ignored if ``verify_signature`` is ``False``. + Check that ``aud`` (audience) claim matches ``audience`` + * ``verify_iat=True`` but will be ignored if ``verify_signature`` is ``False``. + Check that ``iat`` (issued at) claim value is an integer + * ``verify_exp=True`` but will be ignored if ``verify_signature`` is ``False``. + Check that ``exp`` (expiration) claim value is OK + * ``verify_iss=True`` but will be ignored if ``verify_signature`` is ``False``. + Check that ``iss`` (issuer) claim matches ``issuer`` + * ``verify_nbf=True`` but will be ignored if ``verify_signature`` is ``False``. + Check that ``nbf`` (not before) is in the past * ``verify_signature=True`` verify the JWT cryptographic signature :param Iterable audience: optional, the value for ``verify_aud`` check diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/docs/usage.rst new/PyJWT-2.1.0/docs/usage.rst --- old/PyJWT-2.0.1/docs/usage.rst 2020-12-22 14:41:00.000000000 +0100 +++ new/PyJWT-2.1.0/docs/usage.rst 2021-04-28 13:23:40.000000000 +0200 @@ -262,7 +262,7 @@ Requiring Presence of Claims ---------------------------- -If you wish to require one or more claims to be present in the claimset, you can set the ``require`` paramenter to include these claims. +If you wish to require one or more claims to be present in the claimset, you can set the ``require`` parameter to include these claims. .. code-block:: pycon diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/jwt/__init__.py new/PyJWT-2.1.0/jwt/__init__.py --- old/PyJWT-2.0.1/jwt/__init__.py 2021-01-17 18:56:23.000000000 +0100 +++ new/PyJWT-2.1.0/jwt/__init__.py 2021-04-28 13:59:20.000000000 +0200 @@ -1,3 +1,4 @@ +from .api_jwk import PyJWK, PyJWKSet from .api_jws import ( PyJWS, get_unverified_header, @@ -13,6 +14,7 @@ InvalidAudienceError, InvalidIssuedAtError, InvalidIssuerError, + InvalidKeyError, InvalidSignatureError, InvalidTokenError, MissingRequiredClaimError, @@ -23,7 +25,7 @@ ) from .jwks_client import PyJWKClient -__version__ = "2.0.1" +__version__ = "2.1.0" __title__ = "PyJWT" __description__ = "JSON Web Token implementation in Python" @@ -42,6 +44,8 @@ "PyJWS", "PyJWT", "PyJWKClient", + "PyJWK", + "PyJWKSet", "decode", "encode", "get_unverified_header", @@ -55,6 +59,7 @@ "InvalidAudienceError", "InvalidIssuedAtError", "InvalidIssuerError", + "InvalidKeyError", "InvalidSignatureError", "InvalidTokenError", "MissingRequiredClaimError", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/jwt/algorithms.py new/PyJWT-2.1.0/jwt/algorithms.py --- old/PyJWT-2.0.1/jwt/algorithms.py 2021-01-12 14:01:47.000000000 +0100 +++ new/PyJWT-2.1.0/jwt/algorithms.py 2021-04-28 13:43:48.000000000 +0200 @@ -37,6 +37,10 @@ rsa_recover_prime_factors, ) from cryptography.hazmat.primitives.serialization import ( + Encoding, + NoEncryption, + PrivateFormat, + PublicFormat, load_pem_private_key, load_pem_public_key, load_ssh_public_key, @@ -51,6 +55,7 @@ "RS384", "RS512", "ES256", + "ES256K", "ES384", "ES521", "ES512", @@ -79,6 +84,7 @@ "RS384": RSAAlgorithm(RSAAlgorithm.SHA384), "RS512": RSAAlgorithm(RSAAlgorithm.SHA512), "ES256": ECAlgorithm(ECAlgorithm.SHA256), + "ES256K": ECAlgorithm(ECAlgorithm.SHA256), "ES384": ECAlgorithm(ECAlgorithm.SHA384), "ES521": ECAlgorithm(ECAlgorithm.SHA512), "ES512": ECAlgorithm( @@ -425,6 +431,8 @@ return False try: + if isinstance(key, EllipticCurvePrivateKey): + key = key.public_key() key.verify(der_sig, msg, ec.ECDSA(self.hash_alg())) return True except InvalidSignature: @@ -467,6 +475,13 @@ curve_obj = ec.SECP521R1() else: raise InvalidKeyError("Coords should be 66 bytes for curve P-521") + elif curve == "secp256k1": + if len(x) == len(y) == 32: + curve_obj = ec.SECP256K1() + else: + raise InvalidKeyError( + "Coords should be 32 bytes for curve secp256k1" + ) else: raise InvalidKeyError(f"Invalid curve: {curve}") @@ -577,3 +592,73 @@ return True # If no exception was raised, the signature is valid. except cryptography.exceptions.InvalidSignature: return False + + @staticmethod + def to_jwk(key): + if isinstance(key, Ed25519PublicKey): + x = key.public_bytes( + encoding=Encoding.Raw, + format=PublicFormat.Raw, + ) + + return json.dumps( + { + "x": base64url_encode(force_bytes(x)).decode(), + "kty": "OKP", + "crv": "Ed25519", + } + ) + + if isinstance(key, Ed25519PrivateKey): + d = key.private_bytes( + encoding=Encoding.Raw, + format=PrivateFormat.Raw, + encryption_algorithm=NoEncryption(), + ) + + x = key.public_key().public_bytes( + encoding=Encoding.Raw, + format=PublicFormat.Raw, + ) + + return json.dumps( + { + "x": base64url_encode(force_bytes(x)).decode(), + "d": base64url_encode(force_bytes(d)).decode(), + "kty": "OKP", + "crv": "Ed25519", + } + ) + + raise InvalidKeyError("Not a public or private key") + + @staticmethod + def from_jwk(jwk): + try: + if isinstance(jwk, str): + obj = json.loads(jwk) + elif isinstance(jwk, dict): + obj = jwk + else: + raise ValueError + except ValueError: + raise InvalidKeyError("Key is not valid JSON") + + if obj.get("kty") != "OKP": + raise InvalidKeyError("Not an Octet Key Pair") + + curve = obj.get("crv") + if curve != "Ed25519": + raise InvalidKeyError(f"Invalid curve: {curve}") + + if "x" not in obj: + raise InvalidKeyError('OKP should have "x" parameter') + x = base64url_decode(obj.get("x")) + + try: + if "d" not in obj: + return Ed25519PublicKey.from_public_bytes(x) + d = base64url_decode(obj.get("d")) + return Ed25519PrivateKey.from_private_bytes(d) + except ValueError as err: + raise InvalidKeyError("Invalid key parameter") from err diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/jwt/api_jwk.py new/PyJWT-2.1.0/jwt/api_jwk.py --- old/PyJWT-2.0.1/jwt/api_jwk.py 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/jwt/api_jwk.py 2021-04-28 13:23:40.000000000 +0200 @@ -1,7 +1,7 @@ import json from .algorithms import get_default_algorithms -from .exceptions import PyJWKError, PyJWKSetError +from .exceptions import InvalidKeyError, PyJWKError, PyJWKSetError class PyJWK: @@ -9,11 +9,40 @@ self._algorithms = get_default_algorithms() self._jwk_data = jwk_data + kty = self._jwk_data.get("kty", None) + if not kty: + raise InvalidKeyError("kty is not found: %s" % self._jwk_data) + if not algorithm and isinstance(self._jwk_data, dict): algorithm = self._jwk_data.get("alg", None) if not algorithm: - raise PyJWKError("Unable to find a algorithm for key: %s" % self._jwk_data) + # Determine alg with kty (and crv). + crv = self._jwk_data.get("crv", None) + if kty == "EC": + if crv == "P-256" or not crv: + algorithm = "ES256" + elif crv == "P-384": + algorithm = "ES384" + elif crv == "P-521": + algorithm = "ES512" + elif crv == "secp256k1": + algorithm = "ES256K" + else: + raise InvalidKeyError("Unsupported crv: %s" % crv) + elif kty == "RSA": + algorithm = "RS256" + elif kty == "oct": + algorithm = "HS256" + elif kty == "OKP": + if not crv: + raise InvalidKeyError("crv is not found: %s" % self._jwk_data) + if crv == "Ed25519": + algorithm = "EdDSA" + else: + raise InvalidKeyError("Unsupported crv: %s" % crv) + else: + raise InvalidKeyError("Unsupported kty: %s" % kty) self.Algorithm = self._algorithms.get(algorithm) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/jwt/api_jwt.py new/PyJWT-2.1.0/jwt/api_jwt.py --- old/PyJWT-2.0.1/jwt/api_jwt.py 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/jwt/api_jwt.py 2021-04-28 13:23:40.000000000 +0200 @@ -75,6 +75,13 @@ else: options.setdefault("verify_signature", True) + if not options["verify_signature"]: + options.setdefault("verify_exp", False) + options.setdefault("verify_nbf", False) + options.setdefault("verify_iat", False) + options.setdefault("verify_aud", False) + options.setdefault("verify_iss", False) + if options["verify_signature"] and not algorithms: raise DecodeError( 'It is required that you pass in a value for the "algorithms" argument when calling decode().' @@ -95,9 +102,8 @@ if not isinstance(payload, dict): raise DecodeError("Invalid payload string: must be a json object") - if options["verify_signature"]: - merged_options = {**self.options, **options} - self._validate_claims(payload, merged_options, **kwargs) + merged_options = {**self.options, **options} + self._validate_claims(payload, merged_options, **kwargs) decoded["payload"] = payload return decoded diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/jwt/jwks_client.py new/PyJWT-2.1.0/jwt/jwks_client.py --- old/PyJWT-2.0.1/jwt/jwks_client.py 2020-12-22 14:41:00.000000000 +0100 +++ new/PyJWT-2.1.0/jwt/jwks_client.py 2021-04-28 13:23:40.000000000 +0200 @@ -1,5 +1,6 @@ import json import urllib.request +from functools import lru_cache from typing import Any, List from .api_jwk import PyJWK, PyJWKSet @@ -8,8 +9,12 @@ class PyJWKClient: - def __init__(self, uri: str): + def __init__(self, uri: str, cache_keys: bool = True, max_cached_keys: int = 16): self.uri = uri + if cache_keys: + # Cache signing keys + # Ignore mypy (https://github.com/python/mypy/issues/2427) + self.get_signing_key = lru_cache(maxsize=max_cached_keys)(self.get_signing_key) # type: ignore def fetch_data(self) -> Any: with urllib.request.urlopen(self.uri) as response: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/pyproject.toml new/PyJWT-2.1.0/pyproject.toml --- old/PyJWT-2.0.1/pyproject.toml 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/pyproject.toml 1970-01-01 01:00:00.000000000 +0100 @@ -1,21 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" - - -[tool.coverage.run] -parallel = true -branch = true -source = ["jwt"] - -[tool.coverage.paths] -source = ["jwt", ".tox/*/site-packages"] - -[tool.coverage.report] -show_missing = true - - -[tool.isort] -profile = "black" -atomic = true -combine_as_imports = true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/keys/jwk_ec_key_P-256.json new/PyJWT-2.1.0/tests/keys/jwk_ec_key_P-256.json --- old/PyJWT-2.0.1/tests/keys/jwk_ec_key_P-256.json 2020-08-24 18:22:55.000000000 +0200 +++ new/PyJWT-2.1.0/tests/keys/jwk_ec_key_P-256.json 2021-04-28 13:23:40.000000000 +0200 @@ -2,7 +2,7 @@ "kty": "EC", "kid": "bilbo.baggins.256@hobbiton.example", "crv": "P-256", - "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4=", - "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU=", - "d": "9GJquUJf57a9sev-u8-PoYlIezIPqI_vGpIaiu4zyZk=" + "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4", + "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU", + "d": "9GJquUJf57a9sev-u8-PoYlIezIPqI_vGpIaiu4zyZk" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/keys/jwk_ec_key_secp256k1.json new/PyJWT-2.1.0/tests/keys/jwk_ec_key_secp256k1.json --- old/PyJWT-2.0.1/tests/keys/jwk_ec_key_secp256k1.json 1970-01-01 01:00:00.000000000 +0100 +++ new/PyJWT-2.1.0/tests/keys/jwk_ec_key_secp256k1.json 2021-04-28 13:23:40.000000000 +0200 @@ -0,0 +1,8 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.256k@hobbiton.example", + "crv": "secp256k1", + "x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs", + "y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI", + "d": "XV7LOlEOANIaSxyil8yE8NPDT5jmVw_HQeCwNDzochQ" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/keys/jwk_ec_pub_P-256.json new/PyJWT-2.1.0/tests/keys/jwk_ec_pub_P-256.json --- old/PyJWT-2.0.1/tests/keys/jwk_ec_pub_P-256.json 2020-08-24 18:22:55.000000000 +0200 +++ new/PyJWT-2.1.0/tests/keys/jwk_ec_pub_P-256.json 2021-04-28 13:23:40.000000000 +0200 @@ -2,6 +2,6 @@ "kty": "EC", "kid": "bilbo.baggins.256@hobbiton.example", "crv": "P-256", - "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4=", - "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU=" + "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4", + "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU" } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/keys/jwk_ec_pub_secp256k1.json new/PyJWT-2.1.0/tests/keys/jwk_ec_pub_secp256k1.json --- old/PyJWT-2.0.1/tests/keys/jwk_ec_pub_secp256k1.json 1970-01-01 01:00:00.000000000 +0100 +++ new/PyJWT-2.1.0/tests/keys/jwk_ec_pub_secp256k1.json 2021-04-28 13:23:40.000000000 +0200 @@ -0,0 +1,7 @@ +{ + "kty": "EC", + "kid": "bilbo.baggins.256k@hobbiton.example", + "crv": "secp256k1", + "x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs", + "y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/keys/jwk_okp_key_Ed25519.json new/PyJWT-2.1.0/tests/keys/jwk_okp_key_Ed25519.json --- old/PyJWT-2.0.1/tests/keys/jwk_okp_key_Ed25519.json 1970-01-01 01:00:00.000000000 +0100 +++ new/PyJWT-2.1.0/tests/keys/jwk_okp_key_Ed25519.json 2021-04-28 13:23:40.000000000 +0200 @@ -0,0 +1,6 @@ +{ + "kty":"OKP", + "crv":"Ed25519", + "d":"nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A", + "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/keys/jwk_okp_pub_Ed25519.json new/PyJWT-2.1.0/tests/keys/jwk_okp_pub_Ed25519.json --- old/PyJWT-2.0.1/tests/keys/jwk_okp_pub_Ed25519.json 1970-01-01 01:00:00.000000000 +0100 +++ new/PyJWT-2.1.0/tests/keys/jwk_okp_pub_Ed25519.json 2021-04-28 13:23:40.000000000 +0200 @@ -0,0 +1,5 @@ +{ + "kty":"OKP", + "crv":"Ed25519", + "x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/test_algorithms.py new/PyJWT-2.1.0/tests/test_algorithms.py --- old/PyJWT-2.0.1/tests/test_algorithms.py 2020-12-22 14:41:00.000000000 +0100 +++ new/PyJWT-2.1.0/tests/test_algorithms.py 2021-04-28 13:43:48.000000000 +0200 @@ -166,6 +166,7 @@ "P-256": ECAlgorithm.SHA256, "P-384": ECAlgorithm.SHA384, "P-521": ECAlgorithm.SHA512, + "secp256k1": ECAlgorithm.SHA256, } for (curve, hash) in tests.items(): algo = ECAlgorithm(hash) @@ -185,8 +186,8 @@ valid_points = { "P-256": { - "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4=", - "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU=", + "x": "PTTjIY84aLtaZCxLTrG_d8I0G6YKCV7lg8M4xkKfwQ4", + "y": "ank6KA34vv24HZLXlChVs85NEGlpg2sbqNmR_BcgyJU", }, "P-384": { "x": "IDC-5s6FERlbC4Nc_4JhKW8sd51AhixtMdNUtPxhRFP323QY6cwWeIA3leyZhz-J", @@ -196,6 +197,10 @@ "x": "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkTKqjqvjyekWF-7ytDyRXYgCF5cj0Kt", "y": "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUdaQkAgDPrwQrJmbnX9cwlGfP-HqHZR1", }, + "secp256k1": { + "x": "MLnVyPDPQpNm0KaaO4iEh0i8JItHXJE0NcIe8GK1SYs", + "y": "7r8d-xF7QAgT5kSRdly6M8xeg4Jz83Gs_CQPQRH65QI", + }, } # Invalid JSON @@ -223,7 +228,7 @@ algo.from_jwk('{"kty": "EC", "x": "dGVzdHRlc3Q=", "y": "dGVzdA=="}') # EC coordinates length invalid - for curve in ("P-256", "P-384", "P-521"): + for curve in ("P-256", "P-384", "P-521", "secp256k1"): with pytest.raises(InvalidKeyError): algo.from_jwk( '{{"kty": "EC", "crv": "{}", "x": "dGVzdA==", ' @@ -653,6 +658,13 @@ result = algo.verify(signing_input, key, signature) assert result + # private key can also be used. + with open(key_path("jwk_ec_key_P-521.json")) as keyfile: + private_key = algo.from_jwk(keyfile.read()) + + result = algo.verify(signing_input, private_key, signature) + assert result + @crypto_required class TestEd25519Algorithms: @@ -728,3 +740,95 @@ jwt_pub_key_second = algo.prepare_key(jwt_pub_key_first) assert jwt_pub_key_first == jwt_pub_key_second + + def test_ed25519_jwk_private_key_should_parse_and_verify(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + key = algo.from_jwk(keyfile.read()) + + signature = algo.sign(b"Hello World!", key) + assert algo.verify(b"Hello World!", key.public_key(), signature) + + def test_ed25519_jwk_public_key_should_parse_and_verify(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + priv_key = algo.from_jwk(keyfile.read()) + + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + pub_key = algo.from_jwk(keyfile.read()) + + signature = algo.sign(b"Hello World!", priv_key) + assert algo.verify(b"Hello World!", pub_key, signature) + + def test_ed25519_jwk_fails_on_invalid_json(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + valid_pub = json.loads(keyfile.read()) + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + valid_key = json.loads(keyfile.read()) + + # Invalid instance type + with pytest.raises(InvalidKeyError): + algo.from_jwk(123) + + # Invalid JSON + with pytest.raises(InvalidKeyError): + algo.from_jwk("<this isn't json>") + + # Invalid kty, not "OKP" + v = valid_pub.copy() + v["kty"] = "oct" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Invalid crv, not "Ed25519" + v = valid_pub.copy() + v["crv"] = "P-256" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Missing x + v = valid_pub.copy() + del v["x"] + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Invalid x + v = valid_pub.copy() + v["x"] = "123" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + # Invalid d + v = valid_key.copy() + v["d"] = "123" + with pytest.raises(InvalidKeyError): + algo.from_jwk(v) + + def test_ed25519_to_jwk_works_with_from_jwk(self): + algo = Ed25519Algorithm() + + with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: + priv_key_1 = algo.from_jwk(keyfile.read()) + + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + pub_key_1 = algo.from_jwk(keyfile.read()) + + pub = algo.to_jwk(pub_key_1) + pub_key_2 = algo.from_jwk(pub) + pri = algo.to_jwk(priv_key_1) + priv_key_2 = algo.from_jwk(pri) + + signature_1 = algo.sign(b"Hello World!", priv_key_1) + signature_2 = algo.sign(b"Hello World!", priv_key_2) + assert algo.verify(b"Hello World!", pub_key_2, signature_1) + assert algo.verify(b"Hello World!", pub_key_2, signature_2) + + def test_ed25519_to_jwk_raises_exception_on_invalid_key(self): + algo = Ed25519Algorithm() + + with pytest.raises(InvalidKeyError): + algo.to_jwk({"not": "a valid key"}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/test_api_jwk.py new/PyJWT-2.1.0/tests/test_api_jwk.py --- old/PyJWT-2.0.1/tests/test_api_jwk.py 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/tests/test_api_jwk.py 2021-04-28 13:23:40.000000000 +0200 @@ -1,12 +1,20 @@ import json +import pytest + from jwt.algorithms import has_crypto from jwt.api_jwk import PyJWK, PyJWKSet +from jwt.exceptions import InvalidKeyError, PyJWKError from .utils import crypto_required, key_path if has_crypto: - from jwt.algorithms import RSAAlgorithm + from jwt.algorithms import ( + ECAlgorithm, + Ed25519Algorithm, + HMACAlgorithm, + RSAAlgorithm, + ) class TestPyJWK: @@ -52,6 +60,158 @@ assert jwk.key_id == "keyid-abc123" assert jwk.public_key_use == "sig" + @crypto_required + def test_should_load_key_without_alg_from_dict(self): + + with open(key_path("jwk_rsa_pub.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "RSA" + assert isinstance(jwk.Algorithm, RSAAlgorithm) + assert jwk.Algorithm.hash_alg == RSAAlgorithm.SHA256 + + @crypto_required + def test_should_load_key_from_dict_with_algorithm(self): + + with open(key_path("jwk_rsa_pub.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data, algorithm="RS256") + + assert jwk.key_type == "RSA" + assert isinstance(jwk.Algorithm, RSAAlgorithm) + assert jwk.Algorithm.hash_alg == RSAAlgorithm.SHA256 + + @crypto_required + def test_should_load_key_ec_p256_from_dict(self): + + with open(key_path("jwk_ec_pub_P-256.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "EC" + assert isinstance(jwk.Algorithm, ECAlgorithm) + assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA256 + + @crypto_required + def test_should_load_key_ec_p384_from_dict(self): + + with open(key_path("jwk_ec_pub_P-384.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "EC" + assert isinstance(jwk.Algorithm, ECAlgorithm) + assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA384 + + @crypto_required + def test_should_load_key_ec_p521_from_dict(self): + + with open(key_path("jwk_ec_pub_P-521.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "EC" + assert isinstance(jwk.Algorithm, ECAlgorithm) + assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA512 + + @crypto_required + def test_should_load_key_ec_secp256k1_from_dict(self): + + with open(key_path("jwk_ec_pub_secp256k1.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "EC" + assert isinstance(jwk.Algorithm, ECAlgorithm) + assert jwk.Algorithm.hash_alg == ECAlgorithm.SHA256 + + @crypto_required + def test_should_load_key_hmac_from_dict(self): + + with open(key_path("jwk_hmac.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "oct" + assert isinstance(jwk.Algorithm, HMACAlgorithm) + assert jwk.Algorithm.hash_alg == HMACAlgorithm.SHA256 + + @crypto_required + def test_should_load_key_hmac_without_alg_from_dict(self): + + with open(key_path("jwk_hmac.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + del key_data["alg"] + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "oct" + assert isinstance(jwk.Algorithm, HMACAlgorithm) + assert jwk.Algorithm.hash_alg == HMACAlgorithm.SHA256 + + @crypto_required + def test_should_load_key_okp_without_alg_from_dict(self): + + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + key_data = json.loads(keyfile.read()) + + jwk = PyJWK.from_dict(key_data) + + assert jwk.key_type == "OKP" + assert isinstance(jwk.Algorithm, Ed25519Algorithm) + + @crypto_required + def test_from_dict_should_throw_exception_if_arg_is_invalid(self): + + with open(key_path("jwk_rsa_pub.json")) as keyfile: + valid_rsa_pub = json.loads(keyfile.read()) + with open(key_path("jwk_ec_pub_P-256.json")) as keyfile: + valid_ec_pub = json.loads(keyfile.read()) + with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: + valid_okp_pub = json.loads(keyfile.read()) + + # Unknown algorithm + with pytest.raises(PyJWKError): + PyJWK.from_dict(valid_rsa_pub, algorithm="unknown") + + # Missing kty + v = valid_rsa_pub.copy() + del v["kty"] + with pytest.raises(InvalidKeyError): + PyJWK.from_dict(v) + + # Unknown kty + v = valid_rsa_pub.copy() + v["kty"] = "unknown" + with pytest.raises(InvalidKeyError): + PyJWK.from_dict(v) + + # Unknown EC crv + v = valid_ec_pub.copy() + v["crv"] = "unknown" + with pytest.raises(InvalidKeyError): + PyJWK.from_dict(v) + + # Unknown OKP crv + v = valid_okp_pub.copy() + v["crv"] = "unknown" + with pytest.raises(InvalidKeyError): + PyJWK.from_dict(v) + + # Missing OKP crv + v = valid_okp_pub.copy() + del v["crv"] + with pytest.raises(InvalidKeyError): + PyJWK.from_dict(v) + class TestPyJWKSet: @crypto_required diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/test_api_jws.py new/PyJWT-2.1.0/tests/test_api_jws.py --- old/PyJWT-2.0.1/tests/test_api_jws.py 2020-12-22 14:41:00.000000000 +0100 +++ new/PyJWT-2.1.0/tests/test_api_jws.py 2021-04-28 13:47:41.000000000 +0200 @@ -32,7 +32,7 @@ @pytest.fixture def payload(): - """ Creates a sample jws claimset for use as a payload during tests """ + """Creates a sample jws claimset for use as a payload during tests""" return b"hello world" @@ -527,6 +527,7 @@ "algo", [ "ES256", + "ES256K", "ES384", "ES512", ], @@ -557,10 +558,12 @@ if has_crypto: assert "ES256" in jws_algorithms + assert "ES256K" in jws_algorithms assert "ES384" in jws_algorithms assert "ES512" in jws_algorithms else: assert "ES256" not in jws_algorithms + assert "ES256K" not in jws_algorithms assert "ES384" not in jws_algorithms assert "ES512" not in jws_algorithms diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/test_api_jwt.py new/PyJWT-2.1.0/tests/test_api_jwt.py --- old/PyJWT-2.0.1/tests/test_api_jwt.py 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/tests/test_api_jwt.py 2021-04-28 13:47:41.000000000 +0200 @@ -27,7 +27,7 @@ @pytest.fixture def payload(): - """ Creates a sample JWT claimset for use as a payload during tests """ + """Creates a sample JWT claimset for use as a payload during tests""" return {"iss": "jeff", "exp": utc_timestamp() + 15, "claim": "insanity"} @@ -579,6 +579,22 @@ options={"verify_exp": True}, ) + def test_decode_with_verify_exp_option_and_signature_off(self, jwt, payload): + payload["exp"] = utc_timestamp() - 1 + secret = "secret" + jwt_message = jwt.encode(payload, secret) + + jwt.decode( + jwt_message, + options={"verify_signature": False}, + ) + + with pytest.raises(ExpiredSignatureError): + jwt.decode( + jwt_message, + options={"verify_signature": False, "verify_exp": True}, + ) + def test_decode_with_optional_algorithms(self, jwt, payload): secret = "secret" jwt_message = jwt.encode(payload, secret) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/PyJWT-2.0.1/tests/test_jwks_client.py new/PyJWT-2.1.0/tests/test_jwks_client.py --- old/PyJWT-2.0.1/tests/test_jwks_client.py 2020-12-21 17:55:46.000000000 +0100 +++ new/PyJWT-2.1.0/tests/test_jwks_client.py 2021-04-28 13:23:40.000000000 +0200 @@ -37,7 +37,7 @@ response.__exit__ = mock.Mock() response.read.side_effect = [json.dumps(data)] urlopen_mock.return_value = response - yield + yield urlopen_mock @crypto_required @@ -88,6 +88,38 @@ assert signing_key.key_id == kid assert signing_key.public_key_use == "sig" + def test_get_signing_key_caches_result(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" + + jwks_client = PyJWKClient(url) + + with mocked_response(RESPONSE_DATA): + jwks_client.get_signing_key(kid) + + # mocked_response does not allow urllib.request.urlopen to be called twice + # so a second mock is needed + with mocked_response(RESPONSE_DATA) as repeated_call: + jwks_client.get_signing_key(kid) + + assert repeated_call.call_count == 0 + + def test_get_signing_key_does_not_cache_opt_out(self): + url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json" + kid = "NEE1QURBOTM4MzI5RkFDNTYxOTU1MDg2ODgwQ0UzMTk1QjYyRkRFQw" + + jwks_client = PyJWKClient(url, cache_keys=False) + + with mocked_response(RESPONSE_DATA): + jwks_client.get_signing_key(kid) + + # mocked_response does not allow urllib.request.urlopen to be called twice + # so a second mock is needed + with mocked_response(RESPONSE_DATA) as repeated_call: + jwks_client.get_signing_key(kid) + + assert repeated_call.call_count == 1 + def test_get_signing_key_from_jwt(self): token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Ik5FRTFRVVJCT1RNNE16STVSa0ZETlRZeE9UVTFNRGcyT0Rnd1EwVXpNVGsxUWpZeVJrUkZRdyJ9.eyJpc3MiOiJodHRwczovL2Rldi04N2V2eDlydS5hdXRoMC5jb20vIiwic3ViIjoiYVc0Q2NhNzl4UmVMV1V6MGFFMkg2a0QwTzNjWEJWdENAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vZXhwZW5zZXMtYXBpIiwiaWF0IjoxNTcyMDA2OTU0LCJleHAiOjE1NzIwMDY5NjQsImF6cCI6ImFXNENjYTc5eFJlTFdVejBhRTJINmtEME8zY1hCVnRDIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIn0.PUxE7xn52aTCohGiWoSdMBZGiYAHwE5FYie0Y1qUT68IHSTXwXVd6hn02HTah6epvHHVKA2FqcFZ4GGv5VTHEvYpeggiiZMgbxFrmTEY0csL6VNkX1eaJGcuehwQCRBKRLL3zKmA5IKGy5GeUnIbpPHLHDxr-GXvgFzsdsyWlVQvPX2xjeaQ217r2PtxDeqjlf66UYl6oY6AqNS8DH3iryCvIfCcybRZkc_hdy-6ZMoKT6Piijvk_aXdm7-QQqKJFHLuEqrVSOuBqqiNfVrG27QzAPuPOxvfXTVLXL2jek5meH6n-VWgrBdoMFH93QEszEDowDAEhQPHVs0xj7SIzA" url = "https://dev-87evx9ru.auth0.com/.well-known/jwks.json"