From bd11de3d9b783b4e117f5c3460635388cb1cb2d5 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Fri, 14 Apr 2023 15:37:56 +0900 Subject: [PATCH 01/13] Add `as_dict` option to `Algorithm.to_jwt` --- jwt/algorithms.py | 139 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 109 insertions(+), 30 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index bc928fec..24fd82c9 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -2,7 +2,7 @@ import hmac import json from abc import ABC, abstractmethod -from typing import Any, ClassVar, Dict, Type, Union +from typing import Any, ClassVar, Dict, Literal, Type, Union, overload from .exceptions import InvalidKeyError from .types import HashlibHash, JWKDict @@ -170,9 +170,21 @@ def verify(self, msg: bytes, key: Any, sig: bytes) -> bool: for the specified message and key values. """ + @overload @staticmethod @abstractmethod - def to_jwk(key_obj) -> JWKDict: + def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: + ... + + @overload + @staticmethod + @abstractmethod + def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: + ... + + @staticmethod + @abstractmethod + def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: """ Serializes a given RSA key into a JWK """ @@ -206,8 +218,18 @@ def sign(self, msg, key): def verify(self, msg, key, sig): return False + @overload @staticmethod - def to_jwk(key_obj) -> JWKDict: + def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: + ... + + @overload + @staticmethod + def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: + ... + + @staticmethod + def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: raise NotImplementedError() @staticmethod @@ -239,14 +261,27 @@ def prepare_key(self, key): return key + @overload @staticmethod - def to_jwk(key_obj): - return json.dumps( - { - "k": base64url_encode(force_bytes(key_obj)).decode(), - "kty": "oct", - } - ) + def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: + ... + + @overload + @staticmethod + def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: + ... + + @staticmethod + def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: + jwk = { + "k": base64url_encode(force_bytes(key_obj)).decode(), + "kty": "oct", + } + + if as_dict: + return jwk + else: + return json.dumps(jwk) @staticmethod def from_jwk(jwk): @@ -304,8 +339,18 @@ def prepare_key(self, key): except ValueError: return load_pem_public_key(key_bytes) + @overload + @staticmethod + def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: + ... + + @overload + @staticmethod + def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: + ... + @staticmethod - def to_jwk(key_obj): + def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: obj = None if hasattr(key_obj, "private_numbers"): @@ -338,7 +383,10 @@ def to_jwk(key_obj): else: raise InvalidKeyError("Not a public or private key") - return json.dumps(obj) + if as_dict: + return obj + else: + return json.dumps(obj) @staticmethod def from_jwk(jwk): @@ -482,8 +530,18 @@ def verify(self, msg, key, sig): except InvalidSignature: return False + @overload + @staticmethod + def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: + ... + + @overload @staticmethod - def to_jwk(key_obj): + def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: + ... + + @staticmethod + def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: if isinstance(key_obj, EllipticCurvePrivateKey): public_numbers = key_obj.public_key().public_numbers() elif isinstance(key_obj, EllipticCurvePublicKey): @@ -514,7 +572,10 @@ def to_jwk(key_obj): key_obj.private_numbers().private_value ).decode() - return json.dumps(obj) + if as_dict: + return obj + else: + return json.dumps(obj) @staticmethod def from_jwk( @@ -682,21 +743,35 @@ def verify(self, msg, key, sig): except cryptography.exceptions.InvalidSignature: return False + @overload @staticmethod - def to_jwk(key): + def to_jwk(key, as_dict: Literal[True]) -> JWKDict: + ... + + @overload + @staticmethod + def to_jwk(key, as_dict: Literal[False] = False) -> str: + ... + + @staticmethod + def to_jwk(key, as_dict: bool = False) -> JWKDict | str: if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)): x = key.public_bytes( encoding=Encoding.Raw, format=PublicFormat.Raw, ) crv = "Ed25519" if isinstance(key, Ed25519PublicKey) else "Ed448" - return json.dumps( - { - "x": base64url_encode(force_bytes(x)).decode(), - "kty": "OKP", - "crv": crv, - } - ) + + obj = { + "x": base64url_encode(force_bytes(x)).decode(), + "kty": "OKP", + "crv": crv, + } + + if as_dict: + return obj + else: + return json.dumps(obj) if isinstance(key, (Ed25519PrivateKey, Ed448PrivateKey)): d = key.private_bytes( @@ -711,14 +786,18 @@ def to_jwk(key): ) crv = "Ed25519" if isinstance(key, Ed25519PrivateKey) else "Ed448" - return json.dumps( - { - "x": base64url_encode(force_bytes(x)).decode(), - "d": base64url_encode(force_bytes(d)).decode(), - "kty": "OKP", - "crv": crv, - } - ) + obj = { + "x": base64url_encode(force_bytes(x)).decode(), + "d": base64url_encode(force_bytes(d)).decode(), + "kty": "OKP", + "crv": crv, + } + + if as_dict: + return obj + else: + return json.dumps(obj) + raise InvalidKeyError("Not a public or private key") From 7db8f7aa1de604dd1c5be4bb471f29fdcce402da Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Fri, 14 Apr 2023 15:58:00 +0900 Subject: [PATCH 02/13] Update unit tests --- tests/test_algorithms.py | 67 +++++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 8aa9ad72..bbc5efc5 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -73,11 +73,15 @@ def test_hmac_jwk_should_parse_and_verify(self): signature = algo.sign(b"Hello World!", key) assert algo.verify(b"Hello World!", key, signature) - def test_hmac_to_jwk_returns_correct_values(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_hmac_to_jwk_returns_correct_values(self, as_dict): algo = HMACAlgorithm(HMACAlgorithm.SHA256) - key = algo.to_jwk("secret") + key = algo.to_jwk("secret", as_dict=as_dict) - assert json.loads(key) == {"kty": "oct", "k": "c2VjcmV0"} + if not as_dict: + key = json.loads(key) + + assert key == {"kty": "oct", "k": "c2VjcmV0"} def test_hmac_from_jwk_should_raise_exception_if_not_hmac_key(self): algo = HMACAlgorithm(HMACAlgorithm.SHA256) @@ -243,13 +247,17 @@ def test_ec_public_key_to_jwk_works_with_from_jwk(self): assert parsed_key.public_numbers() == orig_key.public_numbers() @crypto_required - def test_ec_to_jwk_returns_correct_values_for_public_key(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_ec_to_jwk_returns_correct_values_for_public_key(self, as_dict): algo = ECAlgorithm(ECAlgorithm.SHA256) with open(key_path("testkey_ec.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(pub_key) + key = algo.to_jwk(pub_key, as_dict=as_dict) + + if not as_dict: + key = json.loads(key) expected = { "kty": "EC", @@ -258,16 +266,20 @@ def test_ec_to_jwk_returns_correct_values_for_public_key(self): "y": "t2G02kbWiOqimYfQAfnARdp2CTycsJPhwA8rn1Cn0SQ", } - assert json.loads(key) == expected + assert key == expected @crypto_required - def test_ec_to_jwk_returns_correct_values_for_private_key(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_ec_to_jwk_returns_correct_values_for_private_key(self, as_dict): algo = ECAlgorithm(ECAlgorithm.SHA256) with open(key_path("testkey_ec.priv")) as keyfile: priv_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(priv_key) + key = algo.to_jwk(priv_key, as_dict=as_dict) + + if not as_dict: + key = json.loads(key) expected = { "kty": "EC", @@ -277,7 +289,7 @@ def test_ec_to_jwk_returns_correct_values_for_private_key(self): "d": "2nninfu2jMHDwAbn9oERUhRADS6duQaJEadybLaa0YQ", } - assert json.loads(key) == expected + assert key == expected @crypto_required def test_ec_to_jwk_raises_exception_on_invalid_key(self): @@ -287,7 +299,8 @@ def test_ec_to_jwk_raises_exception_on_invalid_key(self): algo.to_jwk({"not": "a valid key"}) @crypto_required - def test_ec_to_jwk_with_valid_curves(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_ec_to_jwk_with_valid_curves(self, as_dict): tests = { "P-256": ECAlgorithm.SHA256, "P-384": ECAlgorithm.SHA384, @@ -299,11 +312,21 @@ def test_ec_to_jwk_with_valid_curves(self): with open(key_path(f"jwk_ec_pub_{curve}.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) - assert json.loads(algo.to_jwk(pub_key))["crv"] == curve + jwk = algo.to_jwk(pub_key, as_dict=as_dict) + + if not as_dict: + jwk = json.loads(jwk) + + assert jwk["crv"] == curve with open(key_path(f"jwk_ec_key_{curve}.json")) as keyfile: priv_key = algo.from_jwk(keyfile.read()) - assert json.loads(algo.to_jwk(priv_key))["crv"] == curve + jwk = algo.to_jwk(priv_key, as_dict=as_dict) + + if not as_dict: + jwk = json.loads(jwk) + + assert jwk["crv"] == curve @crypto_required def test_ec_to_jwk_with_invalid_curve(self): @@ -420,13 +443,17 @@ def test_rsa_jwk_raises_exception_if_not_a_valid_key(self): algo.from_jwk('{"kty": "RSA"}') @crypto_required - def test_rsa_to_jwk_returns_correct_values_for_public_key(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_rsa_to_jwk_returns_correct_values_for_public_key(self, as_dict): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(pub_key) + key = algo.to_jwk(pub_key, as_dict=as_dict) + + if not as_dict: + key = json.loads(key) expected = { "e": "AQAB", @@ -441,16 +468,20 @@ def test_rsa_to_jwk_returns_correct_values_for_public_key(self): "sNruF3ogJWNq1Lyn_ijPQnkPLpZHyhvuiycYcI3DiQ" ), } - assert json.loads(key) == expected + assert key == expected @crypto_required - def test_rsa_to_jwk_returns_correct_values_for_private_key(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_rsa_to_jwk_returns_correct_values_for_private_key(self, as_dict): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with open(key_path("testkey_rsa.priv")) as keyfile: priv_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(priv_key) + key = algo.to_jwk(priv_key, as_dict=as_dict) + + if not as_dict: + key = json.loads(key) expected = { "key_ops": ["sign"], @@ -498,7 +529,7 @@ def test_rsa_to_jwk_returns_correct_values_for_private_key(self): "AuKhin-kc4mh9ssDXRQZwlMymZP0QtaxUDw_nlfVrUCZgO7L1_ZsUTk" ), } - assert json.loads(key) == expected + assert key == expected @crypto_required def test_rsa_to_jwk_raises_exception_on_invalid_key(self): From 35d9218f3873c5ae645d3d8b3a3e20f8f9031a0b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 14 Apr 2023 07:26:01 +0000 Subject: [PATCH 03/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jwt/algorithms.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 24fd82c9..8867d167 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -798,7 +798,6 @@ def to_jwk(key, as_dict: bool = False) -> JWKDict | str: else: return json.dumps(obj) - raise InvalidKeyError("Not a public or private key") @staticmethod From d369b0ddea3a9138fb45ecddd09c0e1823824433 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Fri, 14 Apr 2023 16:29:44 +0900 Subject: [PATCH 04/13] fixup! Add `as_dict` option to `Algorithm.to_jwt` --- jwt/algorithms.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 8867d167..ef4894b7 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -184,7 +184,7 @@ def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: @staticmethod @abstractmethod - def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: + def to_jwk(key_obj, as_dict: bool = False) -> Union[JWKDict, str]: """ Serializes a given RSA key into a JWK """ @@ -229,7 +229,7 @@ def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: ... @staticmethod - def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: + def to_jwk(key_obj, as_dict: bool = False) -> Union[JWKDict, str]: raise NotImplementedError() @staticmethod @@ -272,7 +272,7 @@ def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: ... @staticmethod - def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: + def to_jwk(key_obj, as_dict: bool = False) -> Union[JWKDict, str]: jwk = { "k": base64url_encode(force_bytes(key_obj)).decode(), "kty": "oct", @@ -350,7 +350,7 @@ def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: ... @staticmethod - def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: + def to_jwk(key_obj, as_dict: bool = False) -> Union[JWKDict, str]: obj = None if hasattr(key_obj, "private_numbers"): @@ -541,7 +541,7 @@ def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: ... @staticmethod - def to_jwk(key_obj, as_dict: bool = False) -> JWKDict | str: + def to_jwk(key_obj, as_dict: bool = False) -> Union[JWKDict, str]: if isinstance(key_obj, EllipticCurvePrivateKey): public_numbers = key_obj.public_key().public_numbers() elif isinstance(key_obj, EllipticCurvePublicKey): @@ -754,7 +754,7 @@ def to_jwk(key, as_dict: Literal[False] = False) -> str: ... @staticmethod - def to_jwk(key, as_dict: bool = False) -> JWKDict | str: + def to_jwk(key, as_dict: bool = False) -> Union[JWKDict, str]: if isinstance(key, (Ed25519PublicKey, Ed448PublicKey)): x = key.public_bytes( encoding=Encoding.Raw, From 8ce39ac98df94b22d5b608402dda38cfac572644 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Fri, 14 Apr 2023 16:42:34 +0900 Subject: [PATCH 05/13] fixup! Add `as_dict` option to `Algorithm.to_jwt` --- tests/test_algorithms.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index bbc5efc5..4cad9a74 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -1,5 +1,6 @@ import base64 import json +from typing import Any import pytest @@ -76,7 +77,7 @@ def test_hmac_jwk_should_parse_and_verify(self): @pytest.mark.parametrize("as_dict", (False, True)) def test_hmac_to_jwk_returns_correct_values(self, as_dict): algo = HMACAlgorithm(HMACAlgorithm.SHA256) - key = algo.to_jwk("secret", as_dict=as_dict) + key: Any = algo.to_jwk("secret", as_dict=as_dict) if not as_dict: key = json.loads(key) @@ -254,7 +255,7 @@ def test_ec_to_jwk_returns_correct_values_for_public_key(self, as_dict): with open(key_path("testkey_ec.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(pub_key, as_dict=as_dict) + key: Any = algo.to_jwk(pub_key, as_dict=as_dict) if not as_dict: key = json.loads(key) @@ -276,7 +277,7 @@ def test_ec_to_jwk_returns_correct_values_for_private_key(self, as_dict): with open(key_path("testkey_ec.priv")) as keyfile: priv_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(priv_key, as_dict=as_dict) + key: Any = algo.to_jwk(priv_key, as_dict=as_dict) if not as_dict: key = json.loads(key) @@ -312,7 +313,7 @@ def test_ec_to_jwk_with_valid_curves(self, as_dict): with open(key_path(f"jwk_ec_pub_{curve}.json")) as keyfile: pub_key = algo.from_jwk(keyfile.read()) - jwk = algo.to_jwk(pub_key, as_dict=as_dict) + jwk: Any = algo.to_jwk(pub_key, as_dict=as_dict) if not as_dict: jwk = json.loads(jwk) @@ -450,7 +451,7 @@ def test_rsa_to_jwk_returns_correct_values_for_public_key(self, as_dict): with open(key_path("testkey_rsa.pub")) as keyfile: pub_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(pub_key, as_dict=as_dict) + key: Any = algo.to_jwk(pub_key, as_dict=as_dict) if not as_dict: key = json.loads(key) @@ -478,7 +479,7 @@ def test_rsa_to_jwk_returns_correct_values_for_private_key(self, as_dict): with open(key_path("testkey_rsa.priv")) as keyfile: priv_key = algo.prepare_key(keyfile.read()) - key = algo.to_jwk(priv_key, as_dict=as_dict) + key: Any = algo.to_jwk(priv_key, as_dict=as_dict) if not as_dict: key = json.loads(key) From 4e9e2c6ae205d2af737d3b5d3647679790adc595 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Fri, 14 Apr 2023 16:42:45 +0900 Subject: [PATCH 06/13] fixup! Update unit tests --- jwt/algorithms.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index ef4894b7..419e11ba 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -2,7 +2,9 @@ import hmac import json from abc import ABC, abstractmethod -from typing import Any, ClassVar, Dict, Literal, Type, Union, overload +from typing import Any, ClassVar, Dict, Type, Union, overload + +from typing_extensions import Literal from .exceptions import InvalidKeyError from .types import HashlibHash, JWKDict From 17b601ca031afc9324f7918d07d1316f83d533cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Apr 2023 01:54:16 +0000 Subject: [PATCH 07/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- jwt/algorithms.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 9faa0e4d..c9b1d614 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -4,7 +4,8 @@ import hmac import json from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, ClassVar, NoReturn, cast, Union, overload +from typing import TYPE_CHECKING, Any, ClassVar, NoReturn, Union, cast, overload + from typing_extensions import Literal from .exceptions import InvalidKeyError From b186e6b61419b2201dcce4dccf89b10ab4a37536 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Mon, 17 Apr 2023 11:40:16 +0900 Subject: [PATCH 08/13] Fix type errors --- jwt/algorithms.py | 2 +- tests/test_algorithms.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index c9b1d614..74298a51 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -235,7 +235,7 @@ def verify(self, msg: bytes, key: None, sig: bytes) -> bool: return False @staticmethod - def to_jwk(key_obj: Any) -> NoReturn: + def to_jwk(key_obj: Any, as_dict: bool = False) -> NoReturn: raise NotImplementedError() @staticmethod diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 8b0c1dbc..7edd549b 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -314,7 +314,7 @@ def test_ec_to_jwk_raises_exception_on_invalid_key(self): algo = ECAlgorithm(ECAlgorithm.SHA256) with pytest.raises(InvalidKeyError): - algo.to_jwk({"not": "a valid key"}) # type: ignore[arg-type] + algo.to_jwk({"not": "a valid key"}) # type: ignore[call-overload] @crypto_required @pytest.mark.parametrize("as_dict", (False, True)) @@ -556,7 +556,7 @@ def test_rsa_to_jwk_raises_exception_on_invalid_key(self): algo = RSAAlgorithm(RSAAlgorithm.SHA256) with pytest.raises(InvalidKeyError): - algo.to_jwk({"not": "a valid key"}) # type: ignore[arg-type] + algo.to_jwk({"not": "a valid key"}) # type: ignore[call-overload] @crypto_required def test_rsa_from_jwk_raises_exception_on_invalid_key(self): @@ -977,7 +977,7 @@ def test_okp_to_jwk_raises_exception_on_invalid_key(self): algo = OKPAlgorithm() with pytest.raises(InvalidKeyError): - algo.to_jwk({"not": "a valid key"}) # type: ignore[arg-type] + algo.to_jwk({"not": "a valid key"}) # type: ignore[call-overload] def test_okp_ed448_jwk_private_key_should_parse_and_verify(self): algo = OKPAlgorithm() From 9bae5dc34db3ec40624395ebbd0de40ee1c014d5 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Mon, 17 Apr 2023 17:10:51 +0900 Subject: [PATCH 09/13] Fix tox test errors --- jwt/algorithms.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 74298a51..9eff49fb 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -4,9 +4,16 @@ import hmac import json from abc import ABC, abstractmethod -from typing import TYPE_CHECKING, Any, ClassVar, NoReturn, Union, cast, overload - -from typing_extensions import Literal +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Literal, + NoReturn, + Union, + cast, + overload, +) from .exceptions import InvalidKeyError from .types import HashlibHash, JWKDict From 7d8e9bc04a867c6180e6368b8e4c6475ff33f8e0 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Mon, 17 Apr 2023 18:41:08 +0900 Subject: [PATCH 10/13] Fix typing for Python 3.7 --- jwt/algorithms.py | 18 ++++++++---------- setup.cfg | 2 ++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 9eff49fb..4a693f36 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -3,17 +3,9 @@ import hashlib import hmac import json +import sys from abc import ABC, abstractmethod -from typing import ( - TYPE_CHECKING, - Any, - ClassVar, - Literal, - NoReturn, - Union, - cast, - overload, -) +from typing import TYPE_CHECKING, Any, ClassVar, NoReturn, Union, cast, overload from .exceptions import InvalidKeyError from .types import HashlibHash, JWKDict @@ -29,6 +21,12 @@ to_base64url_uint, ) +if sys.version_info >= (3, 8): + from typing import Literal +else: + from typing_extensions import Literal + + try: from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend diff --git a/setup.cfg b/setup.cfg index 08a38b45..44ccf0b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -35,6 +35,8 @@ zip_safe = false include_package_data = true python_requires = >=3.7 packages = find: +install_requires = + typing_extensions; python_version<="3.7" [options.package_data] * = py.typed From f6aa78255b12fb6b220b58f548e611a252f2d178 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Wed, 19 Apr 2023 17:41:29 +0900 Subject: [PATCH 11/13] Add OKP jwk tests --- tests/test_algorithms.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_algorithms.py b/tests/test_algorithms.py index 7edd549b..1a395527 100644 --- a/tests/test_algorithms.py +++ b/tests/test_algorithms.py @@ -954,7 +954,8 @@ def test_okp_ed25519_jwk_fails_on_invalid_json(self): with pytest.raises(InvalidKeyError): algo.from_jwk(v) - def test_okp_ed25519_to_jwk_works_with_from_jwk(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_okp_ed25519_to_jwk_works_with_from_jwk(self, as_dict): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed25519.json")) as keyfile: @@ -963,9 +964,9 @@ def test_okp_ed25519_to_jwk_works_with_from_jwk(self): with open(key_path("jwk_okp_pub_Ed25519.json")) as keyfile: pub_key_1 = cast(Ed25519PublicKey, algo.from_jwk(keyfile.read())) - pub = algo.to_jwk(pub_key_1) + pub = algo.to_jwk(pub_key_1, as_dict=as_dict) pub_key_2 = algo.from_jwk(pub) - pri = algo.to_jwk(priv_key_1) + pri = algo.to_jwk(priv_key_1, as_dict=as_dict) priv_key_2 = cast(Ed25519PrivateKey, algo.from_jwk(pri)) signature_1 = algo.sign(b"Hello World!", priv_key_1) @@ -1063,7 +1064,8 @@ def test_okp_ed448_jwk_fails_on_invalid_json(self): with pytest.raises(InvalidKeyError): algo.from_jwk(v) - def test_okp_ed448_to_jwk_works_with_from_jwk(self): + @pytest.mark.parametrize("as_dict", (False, True)) + def test_okp_ed448_to_jwk_works_with_from_jwk(self, as_dict): algo = OKPAlgorithm() with open(key_path("jwk_okp_key_Ed448.json")) as keyfile: @@ -1072,9 +1074,9 @@ def test_okp_ed448_to_jwk_works_with_from_jwk(self): with open(key_path("jwk_okp_pub_Ed448.json")) as keyfile: pub_key_1 = cast(Ed448PublicKey, algo.from_jwk(keyfile.read())) - pub = algo.to_jwk(pub_key_1) + pub = algo.to_jwk(pub_key_1, as_dict=as_dict) pub_key_2 = algo.from_jwk(pub) - pri = algo.to_jwk(priv_key_1) + pri = algo.to_jwk(priv_key_1, as_dict=as_dict) priv_key_2 = cast(Ed448PrivateKey, algo.from_jwk(pri)) signature_1 = algo.sign(b"Hello World!", priv_key_1) From 89563910d37633b2dabaf8534a8fbd1b08c3d752 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Mon, 1 May 2023 11:17:05 +0900 Subject: [PATCH 12/13] Add `pragma: no cover` to method overloads --- jwt/algorithms.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/jwt/algorithms.py b/jwt/algorithms.py index 4a693f36..ed187152 100644 --- a/jwt/algorithms.py +++ b/jwt/algorithms.py @@ -195,13 +195,13 @@ def verify(self, msg: bytes, key: Any, sig: bytes) -> bool: @staticmethod @abstractmethod def to_jwk(key_obj, as_dict: Literal[True]) -> JWKDict: - ... + ... # pragma: no cover @overload @staticmethod @abstractmethod def to_jwk(key_obj, as_dict: Literal[False] = False) -> str: - ... + ... # pragma: no cover @staticmethod @abstractmethod @@ -275,12 +275,12 @@ def prepare_key(self, key: str | bytes) -> bytes: @overload @staticmethod def to_jwk(key_obj: str | bytes, as_dict: Literal[True]) -> JWKDict: - ... + ... # pragma: no cover @overload @staticmethod def to_jwk(key_obj: str | bytes, as_dict: Literal[False] = False) -> str: - ... + ... # pragma: no cover @staticmethod def to_jwk(key_obj: str | bytes, as_dict: bool = False) -> Union[JWKDict, str]: @@ -355,12 +355,12 @@ def prepare_key(self, key: AllowedRSAKeys | str | bytes) -> AllowedRSAKeys: @overload @staticmethod def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[True]) -> JWKDict: - ... + ... # pragma: no cover @overload @staticmethod def to_jwk(key_obj: AllowedRSAKeys, as_dict: Literal[False] = False) -> str: - ... + ... # pragma: no cover @staticmethod def to_jwk( @@ -553,12 +553,12 @@ def verify(self, msg: bytes, key: "AllowedECKeys", sig: bytes) -> bool: @overload @staticmethod def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[True]) -> JWKDict: - ... + ... # pragma: no cover @overload @staticmethod def to_jwk(key_obj: AllowedECKeys, as_dict: Literal[False] = False) -> str: - ... + ... # pragma: no cover @staticmethod def to_jwk( @@ -772,12 +772,12 @@ def verify( @overload @staticmethod def to_jwk(key: AllowedOKPKeys, as_dict: Literal[True]) -> JWKDict: - ... + ... # pragma: no cover @overload @staticmethod def to_jwk(key: AllowedOKPKeys, as_dict: Literal[False] = False) -> str: - ... + ... # pragma: no cover @staticmethod def to_jwk(key: AllowedOKPKeys, as_dict: bool = False) -> Union[JWKDict, str]: From aa72db596fa8a08aa1df61b17ce2d1e870d17756 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Tue, 9 May 2023 16:55:04 +0900 Subject: [PATCH 13/13] Add pragma: no cover to exclude lines --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 40dfb7bf..613b91dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ source = ["jwt", ".tox/*/site-packages"] [tool.coverage.report] show_missing = true -exclude_lines = ["if TYPE_CHECKING:"] +exclude_lines = ["if TYPE_CHECKING:", "pragma: no cover"] [tool.isort] profile = "black"