diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-05-07 08:48:39 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-05-07 08:58:26 +0300 |
commit | 492446437a68ea9f463a02485d5e87fd21586047 (patch) | |
tree | 38fc646b572738d5e602ea4b0d0f36aa663e6bd9 | |
parent | e81a331668b8aeb6178e2ab028aefe6cdaa9ba26 (diff) | |
download | ydb-492446437a68ea9f463a02485d5e87fd21586047.tar.gz |
Intermediate changes
-rw-r--r-- | contrib/python/itsdangerous/py3/.dist-info/METADATA | 87 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/LICENSE.txt (renamed from contrib/python/itsdangerous/py3/LICENSE.rst) | 0 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/README.md | 40 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/README.rst | 68 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/__init__.py | 21 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/_json.py | 8 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/encoding.py | 14 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/exc.py | 29 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/serializer.py | 215 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/signer.py | 63 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/timed.py | 84 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/itsdangerous/url_safe.py | 23 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/tests/test_itsdangerous/test_serializer.py | 9 | ||||
-rw-r--r-- | contrib/python/itsdangerous/py3/ya.make | 2 |
14 files changed, 367 insertions, 296 deletions
diff --git a/contrib/python/itsdangerous/py3/.dist-info/METADATA b/contrib/python/itsdangerous/py3/.dist-info/METADATA index 1d935ed3de..ddf5464849 100644 --- a/contrib/python/itsdangerous/py3/.dist-info/METADATA +++ b/contrib/python/itsdangerous/py3/.dist-info/METADATA @@ -1,32 +1,23 @@ Metadata-Version: 2.1 Name: itsdangerous -Version: 2.1.2 +Version: 2.2.0 Summary: Safely pass data to untrusted environments and back. -Home-page: https://palletsprojects.com/p/itsdangerous/ -Author: Armin Ronacher -Author-email: armin.ronacher@active-4.com -Maintainer: Pallets -Maintainer-email: contact@palletsprojects.com -License: BSD-3-Clause -Project-URL: Donate, https://palletsprojects.com/donate -Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ -Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/ -Project-URL: Source Code, https://github.com/pallets/itsdangerous/ -Project-URL: Issue Tracker, https://github.com/pallets/itsdangerous/issues/ -Project-URL: Twitter, https://twitter.com/PalletsTeam -Project-URL: Chat, https://discord.gg/pallets -Platform: UNKNOWN +Maintainer-email: Pallets <contact@palletsprojects.com> +Requires-Python: >=3.8 +Description-Content-Type: text/markdown Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst -License-File: LICENSE.rst +Classifier: Typing :: Typed +Project-URL: Changes, https://itsdangerous.palletsprojects.com/changes/ +Project-URL: Chat, https://discord.gg/pallets +Project-URL: Documentation, https://itsdangerous.palletsprojects.com/ +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Source, https://github.com/pallets/itsdangerous/ -ItsDangerous -============ +# ItsDangerous ... so better sign this @@ -39,59 +30,31 @@ needed. A timestamp can be added and verified automatically while loading a token. -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - pip install -U itsdangerous - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -A Simple Example ----------------- +## A Simple Example Here's how you could generate a token for transmitting a user's id and name between web requests. -.. code-block:: python - - from itsdangerous import URLSafeSerializer - auth_s = URLSafeSerializer("secret key", "auth") - token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) +```python +from itsdangerous import URLSafeSerializer +auth_s = URLSafeSerializer("secret key", "auth") +token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) - print(token) - # eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg +print(token) +# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg - data = auth_s.loads(token) - print(data["name"]) - # itsdangerous +data = auth_s.loads(token) +print(data["name"]) +# itsdangerous +``` -Donate ------- +## Donate The Pallets organization develops and supports ItsDangerous and other popular packages. In order to grow the community of contributors and users, and allow the maintainers to devote more time to the projects, -`please donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://itsdangerous.palletsprojects.com/ -- Changes: https://itsdangerous.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/ItsDangerous/ -- Source Code: https://github.com/pallets/itsdangerous/ -- Issue Tracker: https://github.com/pallets/itsdangerous/issues/ -- Website: https://palletsprojects.com/p/itsdangerous/ -- Twitter: https://twitter.com/PalletsTeam -- Chat: https://discord.gg/pallets +[please donate today][]. +[please donate today]: https://palletsprojects.com/donate diff --git a/contrib/python/itsdangerous/py3/LICENSE.rst b/contrib/python/itsdangerous/py3/LICENSE.txt index 7b190ca671..7b190ca671 100644 --- a/contrib/python/itsdangerous/py3/LICENSE.rst +++ b/contrib/python/itsdangerous/py3/LICENSE.txt diff --git a/contrib/python/itsdangerous/py3/README.md b/contrib/python/itsdangerous/py3/README.md new file mode 100644 index 0000000000..59b9a5b9b7 --- /dev/null +++ b/contrib/python/itsdangerous/py3/README.md @@ -0,0 +1,40 @@ +# ItsDangerous + +... so better sign this + +Various helpers to pass data to untrusted environments and to get it +back safe and sound. Data is cryptographically signed to ensure that a +token has not been tampered with. + +It's possible to customize how data is serialized. Data is compressed as +needed. A timestamp can be added and verified automatically while +loading a token. + + +## A Simple Example + +Here's how you could generate a token for transmitting a user's id and +name between web requests. + +```python +from itsdangerous import URLSafeSerializer +auth_s = URLSafeSerializer("secret key", "auth") +token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) + +print(token) +# eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg + +data = auth_s.loads(token) +print(data["name"]) +# itsdangerous +``` + + +## Donate + +The Pallets organization develops and supports ItsDangerous and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +[please donate today][]. + +[please donate today]: https://palletsprojects.com/donate diff --git a/contrib/python/itsdangerous/py3/README.rst b/contrib/python/itsdangerous/py3/README.rst deleted file mode 100644 index f3dca9e307..0000000000 --- a/contrib/python/itsdangerous/py3/README.rst +++ /dev/null @@ -1,68 +0,0 @@ -ItsDangerous -============ - -... so better sign this - -Various helpers to pass data to untrusted environments and to get it -back safe and sound. Data is cryptographically signed to ensure that a -token has not been tampered with. - -It's possible to customize how data is serialized. Data is compressed as -needed. A timestamp can be added and verified automatically while -loading a token. - - -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - pip install -U itsdangerous - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -A Simple Example ----------------- - -Here's how you could generate a token for transmitting a user's id and -name between web requests. - -.. code-block:: python - - from itsdangerous import URLSafeSerializer - auth_s = URLSafeSerializer("secret key", "auth") - token = auth_s.dumps({"id": 5, "name": "itsdangerous"}) - - print(token) - # eyJpZCI6NSwibmFtZSI6Iml0c2Rhbmdlcm91cyJ9.6YP6T0BaO67XP--9UzTrmurXSmg - - data = auth_s.loads(token) - print(data["name"]) - # itsdangerous - - -Donate ------- - -The Pallets organization develops and supports ItsDangerous and other -popular packages. In order to grow the community of contributors and -users, and allow the maintainers to devote more time to the projects, -`please donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://itsdangerous.palletsprojects.com/ -- Changes: https://itsdangerous.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/ItsDangerous/ -- Source Code: https://github.com/pallets/itsdangerous/ -- Issue Tracker: https://github.com/pallets/itsdangerous/issues/ -- Website: https://palletsprojects.com/p/itsdangerous/ -- Twitter: https://twitter.com/PalletsTeam -- Chat: https://discord.gg/pallets diff --git a/contrib/python/itsdangerous/py3/itsdangerous/__init__.py b/contrib/python/itsdangerous/py3/itsdangerous/__init__.py index fdb2dfd00a..ea55256ebd 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/__init__.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/__init__.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +import typing as t + from .encoding import base64_decode as base64_decode from .encoding import base64_encode as base64_encode from .encoding import want_bytes as want_bytes @@ -16,4 +20,19 @@ from .timed import TimestampSigner as TimestampSigner from .url_safe import URLSafeSerializer as URLSafeSerializer from .url_safe import URLSafeTimedSerializer as URLSafeTimedSerializer -__version__ = "2.1.2" + +def __getattr__(name: str) -> t.Any: + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " ItsDangerous 2.3. Use feature detection or" + " 'importlib.metadata.version(\"itsdangerous\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("itsdangerous") + + raise AttributeError(name) diff --git a/contrib/python/itsdangerous/py3/itsdangerous/_json.py b/contrib/python/itsdangerous/py3/itsdangerous/_json.py index c70d37a958..fc23feaaff 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/_json.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/_json.py @@ -1,16 +1,18 @@ +from __future__ import annotations + import json as _json -import typing as _t +import typing as t class _CompactJSON: """Wrapper around json module that strips whitespace.""" @staticmethod - def loads(payload: _t.Union[str, bytes]) -> _t.Any: + def loads(payload: str | bytes) -> t.Any: return _json.loads(payload) @staticmethod - def dumps(obj: _t.Any, **kwargs: _t.Any) -> str: + def dumps(obj: t.Any, **kwargs: t.Any) -> str: kwargs.setdefault("ensure_ascii", False) kwargs.setdefault("separators", (",", ":")) return _json.dumps(obj, **kwargs) diff --git a/contrib/python/itsdangerous/py3/itsdangerous/encoding.py b/contrib/python/itsdangerous/py3/itsdangerous/encoding.py index edb04d1a63..f5ca80f905 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/encoding.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/encoding.py @@ -1,15 +1,15 @@ +from __future__ import annotations + import base64 import string import struct -import typing as _t +import typing as t from .exc import BadData -_t_str_bytes = _t.Union[str, bytes] - def want_bytes( - s: _t_str_bytes, encoding: str = "utf-8", errors: str = "strict" + s: str | bytes, encoding: str = "utf-8", errors: str = "strict" ) -> bytes: if isinstance(s, str): s = s.encode(encoding, errors) @@ -17,7 +17,7 @@ def want_bytes( return s -def base64_encode(string: _t_str_bytes) -> bytes: +def base64_encode(string: str | bytes) -> bytes: """Base64 encode a string of bytes or text. The resulting bytes are safe to use in URLs. """ @@ -25,7 +25,7 @@ def base64_encode(string: _t_str_bytes) -> bytes: return base64.urlsafe_b64encode(string).rstrip(b"=") -def base64_decode(string: _t_str_bytes) -> bytes: +def base64_decode(string: str | bytes) -> bytes: """Base64 decode a URL-safe string of bytes or text. The result is bytes. """ @@ -43,7 +43,7 @@ _base64_alphabet = f"{string.ascii_letters}{string.digits}-_=".encode("ascii") _int64_struct = struct.Struct(">Q") _int_to_bytes = _int64_struct.pack -_bytes_to_int = _t.cast("_t.Callable[[bytes], _t.Tuple[int]]", _int64_struct.unpack) +_bytes_to_int = t.cast("t.Callable[[bytes], tuple[int]]", _int64_struct.unpack) def int_to_bytes(num: int) -> bytes: diff --git a/contrib/python/itsdangerous/py3/itsdangerous/exc.py b/contrib/python/itsdangerous/py3/itsdangerous/exc.py index c38a6af520..a75adcd527 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/exc.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/exc.py @@ -1,8 +1,7 @@ -import typing as _t -from datetime import datetime +from __future__ import annotations -_t_opt_any = _t.Optional[_t.Any] -_t_opt_exc = _t.Optional[Exception] +import typing as t +from datetime import datetime class BadData(Exception): @@ -23,7 +22,7 @@ class BadData(Exception): class BadSignature(BadData): """Raised if a signature does not match.""" - def __init__(self, message: str, payload: _t_opt_any = None): + def __init__(self, message: str, payload: t.Any | None = None): super().__init__(message) #: The payload that failed the signature test. In some @@ -31,7 +30,7 @@ class BadSignature(BadData): #: you know it was tampered with. #: #: .. versionadded:: 0.14 - self.payload: _t_opt_any = payload + self.payload: t.Any | None = payload class BadTimeSignature(BadSignature): @@ -42,8 +41,8 @@ class BadTimeSignature(BadSignature): def __init__( self, message: str, - payload: _t_opt_any = None, - date_signed: _t.Optional[datetime] = None, + payload: t.Any | None = None, + date_signed: datetime | None = None, ): super().__init__(message, payload) @@ -75,19 +74,19 @@ class BadHeader(BadSignature): def __init__( self, message: str, - payload: _t_opt_any = None, - header: _t_opt_any = None, - original_error: _t_opt_exc = None, + payload: t.Any | None = None, + header: t.Any | None = None, + original_error: Exception | None = None, ): super().__init__(message, payload) #: If the header is actually available but just malformed it #: might be stored here. - self.header: _t_opt_any = header + self.header: t.Any | None = header #: If available, the error that indicates why the payload was #: not valid. This might be ``None``. - self.original_error: _t_opt_exc = original_error + self.original_error: Exception | None = original_error class BadPayload(BadData): @@ -99,9 +98,9 @@ class BadPayload(BadData): .. versionadded:: 0.15 """ - def __init__(self, message: str, original_error: _t_opt_exc = None): + def __init__(self, message: str, original_error: Exception | None = None): super().__init__(message) #: If available, the error that indicates why the payload was #: not valid. This might be ``None``. - self.original_error: _t_opt_exc = original_error + self.original_error: Exception | None = original_error diff --git a/contrib/python/itsdangerous/py3/itsdangerous/serializer.py b/contrib/python/itsdangerous/py3/itsdangerous/serializer.py index 9f4a84a172..5ddf3871d8 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/serializer.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/serializer.py @@ -1,5 +1,8 @@ +from __future__ import annotations + +import collections.abc as cabc import json -import typing as _t +import typing as t from .encoding import want_bytes from .exc import BadPayload @@ -7,22 +10,36 @@ from .exc import BadSignature from .signer import _make_keys_list from .signer import Signer -_t_str_bytes = _t.Union[str, bytes] -_t_opt_str_bytes = _t.Optional[_t_str_bytes] -_t_kwargs = _t.Dict[str, _t.Any] -_t_opt_kwargs = _t.Optional[_t_kwargs] -_t_signer = _t.Type[Signer] -_t_fallbacks = _t.List[_t.Union[_t_kwargs, _t.Tuple[_t_signer, _t_kwargs], _t_signer]] -_t_load_unsafe = _t.Tuple[bool, _t.Any] -_t_secret_key = _t.Union[_t.Iterable[_t_str_bytes], _t_str_bytes] +if t.TYPE_CHECKING: + import typing_extensions as te + + # This should be either be str or bytes. To avoid having to specify the + # bound type, it falls back to a union if structural matching fails. + _TSerialized = te.TypeVar( + "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes] + ) +else: + # Still available at runtime on Python < 3.13, but without the default. + _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes]) + +class _PDataSerializer(t.Protocol[_TSerialized]): + def loads(self, payload: _TSerialized, /) -> t.Any: ... + # A signature with additional arguments is not handled correctly by type + # checkers right now, so an overload is used below for serializers that + # don't match this strict protocol. + def dumps(self, obj: t.Any, /) -> _TSerialized: ... -def is_text_serializer(serializer: _t.Any) -> bool: + +# Use TypeIs once it's available in typing_extensions or 3.13. +def is_text_serializer( + serializer: _PDataSerializer[t.Any], +) -> te.TypeGuard[_PDataSerializer[str]]: """Checks whether a serializer generates text or binary.""" return isinstance(serializer.dumps({}), str) -class Serializer: +class Serializer(t.Generic[_TSerialized]): """A serializer wraps a :class:`~itsdangerous.signer.Signer` to enable serializing and securely signing data other than bytes. It can unsign to verify that the data hasn't been changed. @@ -77,31 +94,120 @@ class Serializer: #: The default serialization module to use to serialize data to a #: string internally. The default is :mod:`json`, but can be changed #: to any object that provides ``dumps`` and ``loads`` methods. - default_serializer: _t.Any = json + default_serializer: _PDataSerializer[t.Any] = json #: The default ``Signer`` class to instantiate when signing data. #: The default is :class:`itsdangerous.signer.Signer`. - default_signer: _t_signer = Signer + default_signer: type[Signer] = Signer #: The default fallback signers to try when unsigning fails. - default_fallback_signers: _t_fallbacks = [] + default_fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = [] + + # Serializer[str] if no data serializer is provided, or if it returns str. + @t.overload + def __init__( + self: Serializer[str], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: None | _PDataSerializer[str] = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer positional argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Serializer[bytes] with a bytes data serializer keyword argument. + @t.overload + def __init__( + self: Serializer[bytes], + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: _PDataSerializer[bytes], + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a positional argument. If the strict signature of + # _PDataSerializer doesn't match, fall back to a union, requiring the user + # to specify the type. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... + + # Fall back with a keyword argument. + @t.overload + def __init__( + self, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + *, + serializer: t.Any, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, + ): ... def __init__( self, - secret_key: _t_secret_key, - salt: _t_opt_str_bytes = b"itsdangerous", - serializer: _t.Any = None, - serializer_kwargs: _t_opt_kwargs = None, - signer: _t.Optional[_t_signer] = None, - signer_kwargs: _t_opt_kwargs = None, - fallback_signers: _t.Optional[_t_fallbacks] = None, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous", + serializer: t.Any | None = None, + serializer_kwargs: dict[str, t.Any] | None = None, + signer: type[Signer] | None = None, + signer_kwargs: dict[str, t.Any] | None = None, + fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] + | None = None, ): #: The list of secret keys to try for verifying signatures, from #: oldest to newest. The newest (last) key is used for signing. #: #: This allows a key rotation system to keep a list of allowed #: keys and remove expired ones. - self.secret_keys: _t.List[bytes] = _make_keys_list(secret_key) + self.secret_keys: list[bytes] = _make_keys_list(secret_key) if salt is not None: salt = want_bytes(salt) @@ -112,20 +218,22 @@ class Serializer: if serializer is None: serializer = self.default_serializer - self.serializer: _t.Any = serializer + self.serializer: _PDataSerializer[_TSerialized] = serializer self.is_text_serializer: bool = is_text_serializer(serializer) if signer is None: signer = self.default_signer - self.signer: _t_signer = signer - self.signer_kwargs: _t_kwargs = signer_kwargs or {} + self.signer: type[Signer] = signer + self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {} if fallback_signers is None: - fallback_signers = list(self.default_fallback_signers or ()) + fallback_signers = list(self.default_fallback_signers) - self.fallback_signers: _t_fallbacks = fallback_signers - self.serializer_kwargs: _t_kwargs = serializer_kwargs or {} + self.fallback_signers: list[ + dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer] + ] = fallback_signers + self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {} @property def secret_key(self) -> bytes: @@ -135,8 +243,8 @@ class Serializer: return self.secret_keys[-1] def load_payload( - self, payload: bytes, serializer: _t.Optional[_t.Any] = None - ) -> _t.Any: + self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None + ) -> t.Any: """Loads the encoded object. This function raises :class:`.BadPayload` if the payload is not valid. The ``serializer`` parameter can be used to override the serializer @@ -144,16 +252,17 @@ class Serializer: bytes. """ if serializer is None: - serializer = self.serializer + use_serializer = self.serializer is_text = self.is_text_serializer else: + use_serializer = serializer is_text = is_text_serializer(serializer) try: if is_text: - return serializer.loads(payload.decode("utf-8")) + return use_serializer.loads(payload.decode("utf-8")) # type: ignore[arg-type] - return serializer.loads(payload) + return use_serializer.loads(payload) # type: ignore[arg-type] except Exception as e: raise BadPayload( "Could not load the payload because an exception" @@ -161,14 +270,14 @@ class Serializer: original_error=e, ) from e - def dump_payload(self, obj: _t.Any) -> bytes: + def dump_payload(self, obj: t.Any) -> bytes: """Dumps the encoded object. The return value is always bytes. If the internal serializer returns text, the value will be encoded as UTF-8. """ return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs)) - def make_signer(self, salt: _t_opt_str_bytes = None) -> Signer: + def make_signer(self, salt: str | bytes | None = None) -> Signer: """Creates a new instance of the signer to be used. The default implementation uses the :class:`.Signer` base class. """ @@ -177,7 +286,7 @@ class Serializer: return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs) - def iter_unsigners(self, salt: _t_opt_str_bytes = None) -> _t.Iterator[Signer]: + def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]: """Iterates over all signers to be tried for unsigning. Starts with the configured signer, then constructs each signer specified in ``fallback_signers``. @@ -199,7 +308,7 @@ class Serializer: for secret_key in self.secret_keys: yield fallback(secret_key, salt=salt, **kwargs) - def dumps(self, obj: _t.Any, salt: _t_opt_str_bytes = None) -> _t_str_bytes: + def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized: """Returns a signed string serialized with the internal serializer. The return value can be either a byte or unicode string depending on the format of the internal serializer. @@ -208,19 +317,19 @@ class Serializer: rv = self.make_signer(salt).sign(payload) if self.is_text_serializer: - return rv.decode("utf-8") + return rv.decode("utf-8") # type: ignore[return-value] - return rv + return rv # type: ignore[return-value] - def dump(self, obj: _t.Any, f: _t.IO, salt: _t_opt_str_bytes = None) -> None: + def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None: """Like :meth:`dumps` but dumps into a file. The file handle has to be compatible with what the internal serializer expects. """ f.write(self.dumps(obj, salt)) def loads( - self, s: _t_str_bytes, salt: _t_opt_str_bytes = None, **kwargs: _t.Any - ) -> _t.Any: + self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any + ) -> t.Any: """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the signature validation fails. """ @@ -233,15 +342,15 @@ class Serializer: except BadSignature as err: last_exception = err - raise _t.cast(BadSignature, last_exception) + raise t.cast(BadSignature, last_exception) - def load(self, f: _t.IO, salt: _t_opt_str_bytes = None) -> _t.Any: + def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any: """Like :meth:`loads` but loads from a file.""" return self.loads(f.read(), salt) def loads_unsafe( - self, s: _t_str_bytes, salt: _t_opt_str_bytes = None - ) -> _t_load_unsafe: + self, s: str | bytes, salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: """Like :meth:`loads` but without verifying the signature. This is potentially very dangerous to use depending on how your serializer works. The return value is ``(signature_valid, @@ -259,11 +368,11 @@ class Serializer: def _loads_unsafe_impl( self, - s: _t_str_bytes, - salt: _t_opt_str_bytes, - load_kwargs: _t_opt_kwargs = None, - load_payload_kwargs: _t_opt_kwargs = None, - ) -> _t_load_unsafe: + s: str | bytes, + salt: str | bytes | None, + load_kwargs: dict[str, t.Any] | None = None, + load_payload_kwargs: dict[str, t.Any] | None = None, + ) -> tuple[bool, t.Any]: """Low level helper function to implement :meth:`loads_unsafe` in serializer subclasses. """ @@ -287,7 +396,9 @@ class Serializer: except BadPayload: return False, None - def load_unsafe(self, f: _t.IO, salt: _t_opt_str_bytes = None) -> _t_load_unsafe: + def load_unsafe( + self, f: t.IO[t.Any], salt: str | bytes | None = None + ) -> tuple[bool, t.Any]: """Like :meth:`loads_unsafe` but loads from a file. .. versionadded:: 0.15 diff --git a/contrib/python/itsdangerous/py3/itsdangerous/signer.py b/contrib/python/itsdangerous/py3/itsdangerous/signer.py index aa12005e9a..e324dc03da 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/signer.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/signer.py @@ -1,6 +1,9 @@ +from __future__ import annotations + +import collections.abc as cabc import hashlib import hmac -import typing as _t +import typing as t from .encoding import _base64_alphabet from .encoding import base64_decode @@ -8,10 +11,6 @@ from .encoding import base64_encode from .encoding import want_bytes from .exc import BadSignature -_t_str_bytes = _t.Union[str, bytes] -_t_opt_str_bytes = _t.Optional[_t_str_bytes] -_t_secret_key = _t.Union[_t.Iterable[_t_str_bytes], _t_str_bytes] - class SigningAlgorithm: """Subclasses must implement :meth:`get_signature` to provide @@ -38,30 +37,40 @@ class NoneAlgorithm(SigningAlgorithm): return b"" +def _lazy_sha1(string: bytes = b"") -> t.Any: + """Don't access ``hashlib.sha1`` until runtime. FIPS builds may not include + SHA-1, in which case the import and use as a default would fail before the + developer can configure something else. + """ + return hashlib.sha1(string) + + class HMACAlgorithm(SigningAlgorithm): """Provides signature generation using HMACs.""" #: The digest method to use with the MAC algorithm. This defaults to #: SHA1, but can be changed to any other function in the hashlib #: module. - default_digest_method: _t.Any = staticmethod(hashlib.sha1) + default_digest_method: t.Any = staticmethod(_lazy_sha1) - def __init__(self, digest_method: _t.Any = None): + def __init__(self, digest_method: t.Any = None): if digest_method is None: digest_method = self.default_digest_method - self.digest_method: _t.Any = digest_method + self.digest_method: t.Any = digest_method def get_signature(self, key: bytes, value: bytes) -> bytes: mac = hmac.new(key, msg=value, digestmod=self.digest_method) return mac.digest() -def _make_keys_list(secret_key: _t_secret_key) -> _t.List[bytes]: +def _make_keys_list( + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], +) -> list[bytes]: if isinstance(secret_key, (str, bytes)): return [want_bytes(secret_key)] - return [want_bytes(s) for s in secret_key] + return [want_bytes(s) for s in secret_key] # pyright: ignore class Signer: @@ -108,7 +117,7 @@ class Signer: #: doesn't apply when used intermediately in HMAC. #: #: .. versionadded:: 0.14 - default_digest_method: _t.Any = staticmethod(hashlib.sha1) + default_digest_method: t.Any = staticmethod(_lazy_sha1) #: The default scheme to use to derive the signing key from the #: secret key and salt. The default is ``django-concat``. Possible @@ -119,19 +128,19 @@ class Signer: def __init__( self, - secret_key: _t_secret_key, - salt: _t_opt_str_bytes = b"itsdangerous.Signer", - sep: _t_str_bytes = b".", - key_derivation: _t.Optional[str] = None, - digest_method: _t.Optional[_t.Any] = None, - algorithm: _t.Optional[SigningAlgorithm] = None, + secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes], + salt: str | bytes | None = b"itsdangerous.Signer", + sep: str | bytes = b".", + key_derivation: str | None = None, + digest_method: t.Any | None = None, + algorithm: SigningAlgorithm | None = None, ): #: The list of secret keys to try for verifying signatures, from #: oldest to newest. The newest (last) key is used for signing. #: #: This allows a key rotation system to keep a list of allowed #: keys and remove expired ones. - self.secret_keys: _t.List[bytes] = _make_keys_list(secret_key) + self.secret_keys: list[bytes] = _make_keys_list(secret_key) self.sep: bytes = want_bytes(sep) if self.sep in _base64_alphabet: @@ -156,7 +165,7 @@ class Signer: if digest_method is None: digest_method = self.default_digest_method - self.digest_method: _t.Any = digest_method + self.digest_method: t.Any = digest_method if algorithm is None: algorithm = HMACAlgorithm(self.digest_method) @@ -170,7 +179,7 @@ class Signer: """ return self.secret_keys[-1] - def derive_key(self, secret_key: _t_opt_str_bytes = None) -> bytes: + def derive_key(self, secret_key: str | bytes | None = None) -> bytes: """This method is called to derive the key. The default key derivation choices can be overridden here. Key derivation is not intended to be used as a security method to make a complex key @@ -189,9 +198,9 @@ class Signer: secret_key = want_bytes(secret_key) if self.key_derivation == "concat": - return _t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) + return t.cast(bytes, self.digest_method(self.salt + secret_key).digest()) elif self.key_derivation == "django-concat": - return _t.cast( + return t.cast( bytes, self.digest_method(self.salt + b"signer" + secret_key).digest() ) elif self.key_derivation == "hmac": @@ -203,19 +212,19 @@ class Signer: else: raise TypeError("Unknown key derivation method") - def get_signature(self, value: _t_str_bytes) -> bytes: + def get_signature(self, value: str | bytes) -> bytes: """Returns the signature for the given value.""" value = want_bytes(value) key = self.derive_key() sig = self.algorithm.get_signature(key, value) return base64_encode(sig) - def sign(self, value: _t_str_bytes) -> bytes: + def sign(self, value: str | bytes) -> bytes: """Signs the given string.""" value = want_bytes(value) return value + self.sep + self.get_signature(value) - def verify_signature(self, value: _t_str_bytes, sig: _t_str_bytes) -> bool: + def verify_signature(self, value: str | bytes, sig: str | bytes) -> bool: """Verifies the signature for the given value.""" try: sig = base64_decode(sig) @@ -232,7 +241,7 @@ class Signer: return False - def unsign(self, signed_value: _t_str_bytes) -> bytes: + def unsign(self, signed_value: str | bytes) -> bytes: """Unsigns the given string.""" signed_value = want_bytes(signed_value) @@ -246,7 +255,7 @@ class Signer: raise BadSignature(f"Signature {sig!r} does not match", payload=value) - def validate(self, signed_value: _t_str_bytes) -> bool: + def validate(self, signed_value: str | bytes) -> bool: """Only validates the given signed value. Returns ``True`` if the signature exists and is valid. """ diff --git a/contrib/python/itsdangerous/py3/itsdangerous/timed.py b/contrib/python/itsdangerous/py3/itsdangerous/timed.py index cad8da341c..73843755d9 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/timed.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/timed.py @@ -1,6 +1,8 @@ +from __future__ import annotations + +import collections.abc as cabc import time -import typing -import typing as _t +import typing as t from datetime import datetime from datetime import timezone @@ -12,16 +14,10 @@ from .encoding import want_bytes from .exc import BadSignature from .exc import BadTimeSignature from .exc import SignatureExpired +from .serializer import _TSerialized from .serializer import Serializer from .signer import Signer -_t_str_bytes = _t.Union[str, bytes] -_t_opt_str_bytes = _t.Optional[_t_str_bytes] -_t_opt_int = _t.Optional[int] - -if _t.TYPE_CHECKING: - import typing_extensions as _te - class TimestampSigner(Signer): """Works like the regular :class:`.Signer` but also records the time @@ -46,7 +42,7 @@ class TimestampSigner(Signer): """ return datetime.fromtimestamp(ts, tz=timezone.utc) - def sign(self, value: _t_str_bytes) -> bytes: + def sign(self, value: str | bytes) -> bytes: """Signs the given string and also attaches time information.""" value = want_bytes(value) timestamp = base64_encode(int_to_bytes(self.get_timestamp())) @@ -57,30 +53,28 @@ class TimestampSigner(Signer): # Ignore overlapping signatures check, return_timestamp is the only # parameter that affects the return type. - @typing.overload - def unsign( # type: ignore + @t.overload + def unsign( # type: ignore[overload-overlap] self, - signed_value: _t_str_bytes, - max_age: _t_opt_int = None, - return_timestamp: "_te.Literal[False]" = False, - ) -> bytes: - ... + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[False] = False, + ) -> bytes: ... - @typing.overload + @t.overload def unsign( self, - signed_value: _t_str_bytes, - max_age: _t_opt_int = None, - return_timestamp: "_te.Literal[True]" = True, - ) -> _t.Tuple[bytes, datetime]: - ... + signed_value: str | bytes, + max_age: int | None = None, + return_timestamp: t.Literal[True] = True, + ) -> tuple[bytes, datetime]: ... def unsign( self, - signed_value: _t_str_bytes, - max_age: _t_opt_int = None, + signed_value: str | bytes, + max_age: int | None = None, return_timestamp: bool = False, - ) -> _t.Union[_t.Tuple[bytes, datetime], bytes]: + ) -> tuple[bytes, datetime] | bytes: """Works like the regular :meth:`.Signer.unsign` but can also validate the time. See the base docstring of the class for the general behavior. If ``return_timestamp`` is ``True`` the @@ -112,8 +106,8 @@ class TimestampSigner(Signer): raise BadTimeSignature("timestamp missing", payload=result) value, ts_bytes = result.rsplit(sep, 1) - ts_int: _t_opt_int = None - ts_dt: _t.Optional[datetime] = None + ts_int: int | None = None + ts_dt: datetime | None = None try: ts_int = bytes_to_int(base64_decode(ts_bytes)) @@ -163,7 +157,7 @@ class TimestampSigner(Signer): return value - def validate(self, signed_value: _t_str_bytes, max_age: _t_opt_int = None) -> bool: + def validate(self, signed_value: str | bytes, max_age: int | None = None) -> bool: """Only validates the given signed value. Returns ``True`` if the signature exists and is valid.""" try: @@ -173,28 +167,28 @@ class TimestampSigner(Signer): return False -class TimedSerializer(Serializer): +class TimedSerializer(Serializer[_TSerialized]): """Uses :class:`TimestampSigner` instead of the default :class:`.Signer`. """ - default_signer: _t.Type[TimestampSigner] = TimestampSigner + default_signer: type[TimestampSigner] = TimestampSigner def iter_unsigners( - self, salt: _t_opt_str_bytes = None - ) -> _t.Iterator[TimestampSigner]: - return _t.cast("_t.Iterator[TimestampSigner]", super().iter_unsigners(salt)) + self, salt: str | bytes | None = None + ) -> cabc.Iterator[TimestampSigner]: + return t.cast("cabc.Iterator[TimestampSigner]", super().iter_unsigners(salt)) # TODO: Signature is incompatible because parameters were added # before salt. - def loads( # type: ignore + def loads( # type: ignore[override] self, - s: _t_str_bytes, - max_age: _t_opt_int = None, + s: str | bytes, + max_age: int | None = None, return_timestamp: bool = False, - salt: _t_opt_str_bytes = None, - ) -> _t.Any: + salt: str | bytes | None = None, + ) -> t.Any: """Reverse of :meth:`dumps`, raises :exc:`.BadSignature` if the signature validation fails. If a ``max_age`` is provided it will ensure the signature is not older than that time in seconds. In @@ -223,12 +217,12 @@ class TimedSerializer(Serializer): except BadSignature as err: last_exception = err - raise _t.cast(BadSignature, last_exception) + raise t.cast(BadSignature, last_exception) - def loads_unsafe( # type: ignore + def loads_unsafe( # type: ignore[override] self, - s: _t_str_bytes, - max_age: _t_opt_int = None, - salt: _t_opt_str_bytes = None, - ) -> _t.Tuple[bool, _t.Any]: + s: str | bytes, + max_age: int | None = None, + salt: str | bytes | None = None, + ) -> tuple[bool, t.Any]: return self._loads_unsafe_impl(s, salt, load_kwargs={"max_age": max_age}) diff --git a/contrib/python/itsdangerous/py3/itsdangerous/url_safe.py b/contrib/python/itsdangerous/py3/itsdangerous/url_safe.py index d5a9b0c266..56a0793315 100644 --- a/contrib/python/itsdangerous/py3/itsdangerous/url_safe.py +++ b/contrib/python/itsdangerous/py3/itsdangerous/url_safe.py @@ -1,29 +1,32 @@ -import typing as _t +from __future__ import annotations + +import typing as t import zlib from ._json import _CompactJSON from .encoding import base64_decode from .encoding import base64_encode from .exc import BadPayload +from .serializer import _PDataSerializer from .serializer import Serializer from .timed import TimedSerializer -class URLSafeSerializerMixin(Serializer): +class URLSafeSerializerMixin(Serializer[str]): """Mixed in with a regular serializer it will attempt to zlib compress the string to make it shorter if necessary. It will also base64 encode the string so that it can safely be placed in a URL. """ - default_serializer = _CompactJSON + default_serializer: _PDataSerializer[str] = _CompactJSON def load_payload( self, payload: bytes, - *args: _t.Any, - serializer: _t.Optional[_t.Any] = None, - **kwargs: _t.Any, - ) -> _t.Any: + *args: t.Any, + serializer: t.Any | None = None, + **kwargs: t.Any, + ) -> t.Any: decompress = False if payload.startswith(b"."): @@ -49,7 +52,7 @@ class URLSafeSerializerMixin(Serializer): return super().load_payload(json, *args, **kwargs) - def dump_payload(self, obj: _t.Any) -> bytes: + def dump_payload(self, obj: t.Any) -> bytes: json = super().dump_payload(obj) is_compressed = False compressed = zlib.compress(json) @@ -66,14 +69,14 @@ class URLSafeSerializerMixin(Serializer): return base64d -class URLSafeSerializer(URLSafeSerializerMixin, Serializer): +class URLSafeSerializer(URLSafeSerializerMixin, Serializer[str]): """Works like :class:`.Serializer` but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. """ -class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer): +class URLSafeTimedSerializer(URLSafeSerializerMixin, TimedSerializer[str]): """Works like :class:`.TimedSerializer` but dumps and loads into a URL safe string consisting of the upper and lowercase character of the alphabet as well as ``'_'``, ``'-'`` and ``'.'``. diff --git a/contrib/python/itsdangerous/py3/tests/test_itsdangerous/test_serializer.py b/contrib/python/itsdangerous/py3/tests/test_itsdangerous/test_serializer.py index cdc419191c..2c511cd202 100644 --- a/contrib/python/itsdangerous/py3/tests/test_itsdangerous/test_serializer.py +++ b/contrib/python/itsdangerous/py3/tests/test_itsdangerous/test_serializer.py @@ -14,17 +14,16 @@ import pytest from itsdangerous.exc import BadPayload from itsdangerous.exc import BadSignature from itsdangerous.serializer import Serializer +from itsdangerous.signer import _lazy_sha1 from itsdangerous.signer import Signer @overload -def coerce_str(ref: str, s: str) -> str: - ... +def coerce_str(ref: str, s: str) -> str: ... @overload -def coerce_str(ref: bytes, s: str) -> bytes: - ... +def coerce_str(ref: bytes, s: str) -> bytes: ... def coerce_str(ref: Union[str, bytes], s: str) -> Union[str, bytes]: @@ -179,7 +178,7 @@ class TestSerializer: ) unsigners = serializer.iter_unsigners() - assert next(unsigners).digest_method == hashlib.sha1 + assert next(unsigners).digest_method == _lazy_sha1 for signer in unsigners: assert signer.digest_method == hashlib.sha256 diff --git a/contrib/python/itsdangerous/py3/ya.make b/contrib/python/itsdangerous/py3/ya.make index e74a40e020..f1b983d06e 100644 --- a/contrib/python/itsdangerous/py3/ya.make +++ b/contrib/python/itsdangerous/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.1.2) +VERSION(2.2.0) LICENSE(BSD-3-Clause) |