diff options
author | David Lord <davidism@gmail.com> | 2021-11-05 14:27:53 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2021-11-05 14:27:53 -0700 |
commit | 506ca21d8cc1d16313270c37b6990c9e7762f68e (patch) | |
tree | 93cb329c41bdb384c2278a3904c261b03359f62f | |
parent | 7443b9119b9ea29865c41f61e7f6e4e10873f301 (diff) | |
download | itsdangerous-506ca21d8cc1d16313270c37b6990c9e7762f68e.tar.gz |
remove deprecated code
-rw-r--r-- | CHANGES.rst | 8 | ||||
-rw-r--r-- | docs/jws.rst | 51 | ||||
-rw-r--r-- | src/itsdangerous/__init__.py | 3 | ||||
-rw-r--r-- | src/itsdangerous/_json.py | 18 | ||||
-rw-r--r-- | src/itsdangerous/jws.py | 259 | ||||
-rw-r--r-- | tests/test_itsdangerous/test_jws.py | 143 |
6 files changed, 7 insertions, 475 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 78566b6..57da7ec 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,7 +3,13 @@ Version 2.1.0 Unreleased - - Drop support for Python 3.6. :pr:`272` +- Drop support for Python 3.6. :pr:`272` +- Remove previously deprecated code. :pr:`273` + + - JWS functionality: Use a dedicated library such as Authlib + instead. + - ``import itsdangerous.json``: Import ``json`` from the standard + library instead. Version 2.0.2 diff --git a/docs/jws.rst b/docs/jws.rst deleted file mode 100644 index 7d13441..0000000 --- a/docs/jws.rst +++ /dev/null @@ -1,51 +0,0 @@ -:orphan: - -.. module:: itsdangerous.jws - -JSON Web Signature (JWS) -======================== - -.. warning:: - .. deprecated:: 2.0 - ItsDangerous will no longer support JWS in version 2.1. Use a - dedicated JWS/JWT library such as `authlib`_. - -.. _authlib: https://authlib.org/ - -JSON Web Signatures (JWS) work similarly to the existing URL safe -serializer but will emit headers according to `draft-ietf-jose-json-web --signature <http://self-issued.info/docs/draft-ietf-jose-json-web --signature.html>`_. - -.. code-block:: python - - from itsdangerous import JSONWebSignatureSerializer - s = JSONWebSignatureSerializer("secret-key") - s.dumps({"x": 42}) - 'eyJhbGciOiJIUzI1NiJ9.eyJ4Ijo0Mn0.ZdTn1YyGz9Yx5B5wNpWRL221G1WpVE5fPCPKNuc6UAo' - -When loading the value back the header will not be returned by default -like with the other serializers. However it is possible to also ask for -the header by passing ``return_header=True``. Custom header fields can -be provided upon serialization: - -.. code-block:: python - - s.dumps(0, header_fields={"v": 1}) - 'eyJhbGciOiJIUzI1NiIsInYiOjF9.MA.wT-RZI9YU06R919VBdAfTLn82_iIQD70J_j-3F4z_aM' - s.loads( - "eyJhbGciOiJIUzI1NiIsInYiOjF9" - ".MA.wT-RZI9YU06R919VBdAfTLn82_iIQD70J_j-3F4z_aM" - ) - (0, {'alg': 'HS256', 'v': 1}) - -ItsDangerous only provides HMAC SHA derivatives and the none algorithm -at the moment and does not support the ECC based ones. The algorithm in -the header is checked against the one of the serializer and on a -mismatch a :exc:`~itsdangerous.exc.BadSignature` exception is raised. - -.. autoclass:: JSONWebSignatureSerializer - :members: - -.. autoclass:: TimedJSONWebSignatureSerializer - :members: diff --git a/src/itsdangerous/__init__.py b/src/itsdangerous/__init__.py index fc4c342..6c28c42 100644 --- a/src/itsdangerous/__init__.py +++ b/src/itsdangerous/__init__.py @@ -1,4 +1,3 @@ -from ._json import json from .encoding import base64_decode as base64_decode from .encoding import base64_encode as base64_encode from .encoding import want_bytes as want_bytes @@ -8,8 +7,6 @@ from .exc import BadPayload as BadPayload from .exc import BadSignature as BadSignature from .exc import BadTimeSignature as BadTimeSignature from .exc import SignatureExpired as SignatureExpired -from .jws import JSONWebSignatureSerializer -from .jws import TimedJSONWebSignatureSerializer from .serializer import Serializer as Serializer from .signer import HMACAlgorithm as HMACAlgorithm from .signer import NoneAlgorithm as NoneAlgorithm diff --git a/src/itsdangerous/_json.py b/src/itsdangerous/_json.py index 9368da2..c70d37a 100644 --- a/src/itsdangerous/_json.py +++ b/src/itsdangerous/_json.py @@ -1,6 +1,5 @@ import json as _json import typing as _t -from types import ModuleType class _CompactJSON: @@ -15,20 +14,3 @@ class _CompactJSON: kwargs.setdefault("ensure_ascii", False) kwargs.setdefault("separators", (",", ":")) return _json.dumps(obj, **kwargs) - - -class DeprecatedJSON(ModuleType): - def __getattribute__(self, item: str) -> _t.Any: - import warnings - - warnings.warn( - "Importing 'itsdangerous.json' is deprecated and will be" - " removed in ItsDangerous 2.1. Use Python's 'json' module" - " instead.", - DeprecationWarning, - stacklevel=2, - ) - return getattr(_json, item) - - -json = DeprecatedJSON("json") diff --git a/src/itsdangerous/jws.py b/src/itsdangerous/jws.py deleted file mode 100644 index 3a6695f..0000000 --- a/src/itsdangerous/jws.py +++ /dev/null @@ -1,259 +0,0 @@ -import hashlib -import time -import warnings -from datetime import datetime -from datetime import timezone -from decimal import Decimal -from numbers import Real - -from ._json import _CompactJSON -from .encoding import base64_decode -from .encoding import base64_encode -from .encoding import want_bytes -from .exc import BadData -from .exc import BadHeader -from .exc import BadPayload -from .exc import BadSignature -from .exc import SignatureExpired -from .serializer import Serializer -from .signer import HMACAlgorithm -from .signer import NoneAlgorithm - - -class JSONWebSignatureSerializer(Serializer): - """This serializer implements JSON Web Signature (JWS) support. Only - supports the JWS Compact Serialization. - - .. deprecated:: 2.0 - Will be removed in ItsDangerous 2.1. Use a dedicated library - such as authlib. - """ - - jws_algorithms = { - "HS256": HMACAlgorithm(hashlib.sha256), - "HS384": HMACAlgorithm(hashlib.sha384), - "HS512": HMACAlgorithm(hashlib.sha512), - "none": NoneAlgorithm(), - } - - #: The default algorithm to use for signature generation - default_algorithm = "HS512" - - default_serializer = _CompactJSON - - def __init__( - self, - secret_key, - salt=None, - serializer=None, - serializer_kwargs=None, - signer=None, - signer_kwargs=None, - algorithm_name=None, - ): - warnings.warn( - "JWS support is deprecated and will be removed in" - " ItsDangerous 2.1. Use a dedicated JWS/JWT library such as" - " authlib.", - DeprecationWarning, - stacklevel=2, - ) - super().__init__( - secret_key, - salt=salt, - serializer=serializer, - serializer_kwargs=serializer_kwargs, - signer=signer, - signer_kwargs=signer_kwargs, - ) - - if algorithm_name is None: - algorithm_name = self.default_algorithm - - self.algorithm_name = algorithm_name - self.algorithm = self.make_algorithm(algorithm_name) - - def load_payload(self, payload, serializer=None, return_header=False): - payload = want_bytes(payload) - - if b"." not in payload: - raise BadPayload('No "." found in value') - - base64d_header, base64d_payload = payload.split(b".", 1) - - try: - json_header = base64_decode(base64d_header) - except Exception as e: - raise BadHeader( - "Could not base64 decode the header because of an exception", - original_error=e, - ) from e - - try: - json_payload = base64_decode(base64d_payload) - except Exception as e: - raise BadPayload( - "Could not base64 decode the payload because of an exception", - original_error=e, - ) from e - - try: - header = super().load_payload(json_header, serializer=_CompactJSON) - except BadData as e: - raise BadHeader( - "Could not unserialize header because it was malformed", - original_error=e, - ) from e - - if not isinstance(header, dict): - raise BadHeader("Header payload is not a JSON object", header=header) - - payload = super().load_payload(json_payload, serializer=serializer) - - if return_header: - return payload, header - - return payload - - def dump_payload(self, header, obj): - base64d_header = base64_encode( - self.serializer.dumps(header, **self.serializer_kwargs) - ) - base64d_payload = base64_encode( - self.serializer.dumps(obj, **self.serializer_kwargs) - ) - return base64d_header + b"." + base64d_payload - - def make_algorithm(self, algorithm_name): - try: - return self.jws_algorithms[algorithm_name] - except KeyError: - raise NotImplementedError("Algorithm not supported") from None - - def make_signer(self, salt=None, algorithm=None): - if salt is None: - salt = self.salt - - key_derivation = "none" if salt is None else None - - if algorithm is None: - algorithm = self.algorithm - - return self.signer( - self.secret_keys, - salt=salt, - sep=".", - key_derivation=key_derivation, - algorithm=algorithm, - ) - - def make_header(self, header_fields): - header = header_fields.copy() if header_fields else {} - header["alg"] = self.algorithm_name - return header - - def dumps(self, obj, salt=None, header_fields=None): - """Like :meth:`.Serializer.dumps` but creates a JSON Web - Signature. It also allows for specifying additional fields to be - included in the JWS header. - """ - header = self.make_header(header_fields) - signer = self.make_signer(salt, self.algorithm) - return signer.sign(self.dump_payload(header, obj)) - - def loads(self, s, salt=None, return_header=False): - """Reverse of :meth:`dumps`. If requested via ``return_header`` - it will return a tuple of payload and header. - """ - payload, header = self.load_payload( - self.make_signer(salt, self.algorithm).unsign(want_bytes(s)), - return_header=True, - ) - - if header.get("alg") != self.algorithm_name: - raise BadHeader("Algorithm mismatch", header=header, payload=payload) - - if return_header: - return payload, header - - return payload - - def loads_unsafe(self, s, salt=None, return_header=False): - kwargs = {"return_header": return_header} - return self._loads_unsafe_impl(s, salt, kwargs, kwargs) - - -class TimedJSONWebSignatureSerializer(JSONWebSignatureSerializer): - """Works like the regular :class:`JSONWebSignatureSerializer` but - also records the time of the signing and can be used to expire - signatures. - - JWS currently does not specify this behavior but it mentions a - possible extension like this in the spec. Expiry date is encoded - into the header similar to what's specified in `draft-ietf-oauth - -json-web-token <http://self-issued.info/docs/draft-ietf-oauth-json - -web-token.html#expDef>`_. - """ - - DEFAULT_EXPIRES_IN = 3600 - - def __init__(self, secret_key, expires_in=None, **kwargs): - super().__init__(secret_key, **kwargs) - - if expires_in is None: - expires_in = self.DEFAULT_EXPIRES_IN - - self.expires_in = expires_in - - def make_header(self, header_fields): - header = super().make_header(header_fields) - iat = self.now() - exp = iat + self.expires_in - header["iat"] = iat - header["exp"] = exp - return header - - def loads(self, s, salt=None, return_header=False): - payload, header = super().loads(s, salt, return_header=True) - - if "exp" not in header: - raise BadSignature("Missing expiry date", payload=payload) - - int_date_error = BadHeader("Expiry date is not an IntDate", payload=payload) - - try: - header["exp"] = int(header["exp"]) - except ValueError as e: - raise int_date_error from e - - if header["exp"] < 0: - raise int_date_error - - if header["exp"] < self.now(): - raise SignatureExpired( - "Signature expired", - payload=payload, - date_signed=self.get_issue_date(header), - ) - - if return_header: - return payload, header - - return payload - - def get_issue_date(self, header): - """If the header contains the ``iat`` field, return the date the - signature was issued, as a timezone-aware - :class:`datetime.datetime` in UTC. - - .. versionchanged:: 2.0 - The timestamp is returned as a timezone-aware ``datetime`` - in UTC rather than a naive ``datetime`` assumed to be UTC. - """ - rv = header.get("iat") - - if isinstance(rv, (Real, Decimal)): - return datetime.fromtimestamp(int(rv), tz=timezone.utc) - - def now(self): - return int(time.time()) diff --git a/tests/test_itsdangerous/test_jws.py b/tests/test_itsdangerous/test_jws.py deleted file mode 100644 index 6ee8df8..0000000 --- a/tests/test_itsdangerous/test_jws.py +++ /dev/null @@ -1,143 +0,0 @@ -from datetime import timedelta - -import pytest - -from itsdangerous.exc import BadData -from itsdangerous.exc import BadHeader -from itsdangerous.exc import BadPayload -from itsdangerous.exc import BadSignature -from itsdangerous.exc import SignatureExpired -from itsdangerous.jws import JSONWebSignatureSerializer -from itsdangerous.jws import TimedJSONWebSignatureSerializer -from test_itsdangerous.test_serializer import TestSerializer -from test_itsdangerous.test_timed import TestTimedSerializer - - -class TestJWSSerializer(TestSerializer): - @pytest.fixture() - def serializer_factory(self): - def factory(secret_key="secret-key", **kwargs): - with pytest.deprecated_call(): - return JSONWebSignatureSerializer(secret_key=secret_key, **kwargs) - - return factory - - test_signer_cls = None # type: ignore - test_signer_kwargs = None # type: ignore - test_fallback_signers = None # type: ignore - test_iter_unsigners = None # type: ignore - - @pytest.mark.parametrize("algorithm_name", ("HS256", "HS384", "HS512", "none")) - def test_algorithm(self, serializer_factory, algorithm_name): - serializer = serializer_factory(algorithm_name=algorithm_name) - assert serializer.loads(serializer.dumps("value")) == "value" - - def test_invalid_algorithm(self, serializer_factory): - with pytest.raises(NotImplementedError) as exc_info: - serializer_factory(algorithm_name="invalid") - - assert "not supported" in str(exc_info.value) - - def test_algorithm_mismatch(self, serializer_factory, serializer): - other = serializer_factory(algorithm_name="HS256") - other.algorithm = serializer.algorithm - signed = other.dumps("value") - - with pytest.raises(BadHeader) as exc_info: - serializer.loads(signed) - - assert "mismatch" in str(exc_info.value) - - @pytest.mark.parametrize( - ("value", "exc_cls", "match"), - ( - ("ab", BadPayload, '"."'), - ("a.b", BadHeader, "base64 decode"), - ("ew.b", BadPayload, "base64 decode"), - ("ew.ab", BadData, "malformed"), - ("W10.ab", BadHeader, "JSON object"), - ), - ) - def test_load_payload_exceptions(self, serializer, value, exc_cls, match): - signer = serializer.make_signer() - signed = signer.sign(value) - - with pytest.raises(exc_cls) as exc_info: - serializer.loads(signed) - - assert match in str(exc_info.value) - - def test_secret_keys(self): - with pytest.deprecated_call(): - serializer = JSONWebSignatureSerializer("a") - - dumped = serializer.dumps("value") - - with pytest.deprecated_call(): - serializer = JSONWebSignatureSerializer(["a", "b"]) - - assert serializer.loads(dumped) == "value" - - -class TestTimedJWSSerializer(TestJWSSerializer, TestTimedSerializer): - @pytest.fixture() - def serializer_factory(self): - def factory(secret_key="secret-key", expires_in=10, **kwargs): - with pytest.deprecated_call(): - return TimedJSONWebSignatureSerializer( - secret_key=secret_key, expires_in=expires_in, **kwargs - ) - - return factory - - def test_default_expires_in(self, serializer_factory): - serializer = serializer_factory(expires_in=None) - assert serializer.expires_in == serializer.DEFAULT_EXPIRES_IN - - test_max_age = None - - def test_exp(self, serializer, value, ts, freeze): - signed = serializer.dumps(value) - freeze.tick() - assert serializer.loads(signed) == value - freeze.tick(timedelta(seconds=10)) - - with pytest.raises(SignatureExpired) as exc_info: - serializer.loads(signed) - - assert exc_info.value.date_signed == ts - assert exc_info.value.payload == value - - test_return_payload = None - - def test_return_header(self, serializer, value, ts): - signed = serializer.dumps(value) - payload, header = serializer.loads(signed, return_header=True) - date_signed = serializer.get_issue_date(header) - assert (payload, date_signed) == (value, ts) - - def test_missing_exp(self, serializer): - header = serializer.make_header(None) - del header["exp"] - signer = serializer.make_signer() - signed = signer.sign(serializer.dump_payload(header, "value")) - - with pytest.raises(BadSignature): - serializer.loads(signed) - - @pytest.mark.parametrize("exp", ("invalid", -1)) - def test_invalid_exp(self, serializer, exp): - header = serializer.make_header(None) - header["exp"] = exp - signer = serializer.make_signer() - signed = signer.sign(serializer.dump_payload(header, "value")) - - with pytest.raises(BadHeader) as exc_info: - serializer.loads(signed) - - assert "IntDate" in str(exc_info.value) - - def test_invalid_iat(self, serializer): - header = serializer.make_header(None) - header["iat"] = "invalid" - assert serializer.get_issue_date(header) is None |