Format: text/x-python
⬇ Download File (15.2 KB)
   1  from __future__ import annotations
   2  
   3  import collections.abc as cabc
   4  import json
   5  import typing as t
   6  
   7  from .encoding import want_bytes
   8  from .exc import BadPayload
   9  from .exc import BadSignature
  10  from .signer import _make_keys_list
  11  from .signer import Signer
  12  
  13  if t.TYPE_CHECKING:
  14      import typing_extensions as te
  15  
  16      # This should be either be str or bytes. To avoid having to specify the
  17      # bound type, it falls back to a union if structural matching fails.
  18      _TSerialized = te.TypeVar(
  19          "_TSerialized", bound=t.Union[str, bytes], default=t.Union[str, bytes]
  20      )
  21  else:
  22      # Still available at runtime on Python < 3.13, but without the default.
  23      _TSerialized = t.TypeVar("_TSerialized", bound=t.Union[str, bytes])
  24  
  25  
  26  class _PDataSerializer(t.Protocol[_TSerialized]):
  27      def loads(self, payload: _TSerialized, /) -> t.Any: ...
  28      # A signature with additional arguments is not handled correctly by type
  29      # checkers right now, so an overload is used below for serializers that
  30      # don't match this strict protocol.
  31      def dumps(self, obj: t.Any, /) -> _TSerialized: ...
  32  
  33  
  34  # Use TypeIs once it's available in typing_extensions or 3.13.
  35  def is_text_serializer(
  36      serializer: _PDataSerializer[t.Any],
  37  ) -> te.TypeGuard[_PDataSerializer[str]]:
  38      """Checks whether a serializer generates text or binary."""
  39      return isinstance(serializer.dumps({}), str)
  40  
  41  
  42  class Serializer(t.Generic[_TSerialized]):
  43      """A serializer wraps a :class:`~itsdangerous.signer.Signer` to
  44      enable serializing and securely signing data other than bytes. It
  45      can unsign to verify that the data hasn't been changed.
  46  
  47      The serializer provides :meth:`dumps` and :meth:`loads`, similar to
  48      :mod:`json`, and by default uses :mod:`json` internally to serialize
  49      the data to bytes.
  50  
  51      The secret key should be a random string of ``bytes`` and should not
  52      be saved to code or version control. Different salts should be used
  53      to distinguish signing in different contexts. See :doc:`/concepts`
  54      for information about the security of the secret key and salt.
  55  
  56      :param secret_key: The secret key to sign and verify with. Can be a
  57          list of keys, oldest to newest, to support key rotation.
  58      :param salt: Extra key to combine with ``secret_key`` to distinguish
  59          signatures in different contexts.
  60      :param serializer: An object that provides ``dumps`` and ``loads``
  61          methods for serializing data to a string. Defaults to
  62          :attr:`default_serializer`, which defaults to :mod:`json`.
  63      :param serializer_kwargs: Keyword arguments to pass when calling
  64          ``serializer.dumps``.
  65      :param signer: A ``Signer`` class to instantiate when signing data.
  66          Defaults to :attr:`default_signer`, which defaults to
  67          :class:`~itsdangerous.signer.Signer`.
  68      :param signer_kwargs: Keyword arguments to pass when instantiating
  69          the ``Signer`` class.
  70      :param fallback_signers: List of signer parameters to try when
  71          unsigning with the default signer fails. Each item can be a dict
  72          of ``signer_kwargs``, a ``Signer`` class, or a tuple of
  73          ``(signer, signer_kwargs)``. Defaults to
  74          :attr:`default_fallback_signers`.
  75  
  76      .. versionchanged:: 2.0
  77          Added support for key rotation by passing a list to
  78          ``secret_key``.
  79  
  80      .. versionchanged:: 2.0
  81          Removed the default SHA-512 fallback signer from
  82          ``default_fallback_signers``.
  83  
  84      .. versionchanged:: 1.1
  85          Added support for ``fallback_signers`` and configured a default
  86          SHA-512 fallback. This fallback is for users who used the yanked
  87          1.0.0 release which defaulted to SHA-512.
  88  
  89      .. versionchanged:: 0.14
  90          The ``signer`` and ``signer_kwargs`` parameters were added to
  91          the constructor.
  92      """
  93  
  94      #: The default serialization module to use to serialize data to a
  95      #: string internally. The default is :mod:`json`, but can be changed
  96      #: to any object that provides ``dumps`` and ``loads`` methods.
  97      default_serializer: _PDataSerializer[t.Any] = json
  98  
  99      #: The default ``Signer`` class to instantiate when signing data.
 100      #: The default is :class:`itsdangerous.signer.Signer`.
 101      default_signer: type[Signer] = Signer
 102  
 103      #: The default fallback signers to try when unsigning fails.
 104      default_fallback_signers: list[
 105          dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 106      ] = []
 107  
 108      # Serializer[str] if no data serializer is provided, or if it returns str.
 109      @t.overload
 110      def __init__(
 111          self: Serializer[str],
 112          secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
 113          salt: str | bytes | None = b"itsdangerous",
 114          serializer: None | _PDataSerializer[str] = None,
 115          serializer_kwargs: dict[str, t.Any] | None = None,
 116          signer: type[Signer] | None = None,
 117          signer_kwargs: dict[str, t.Any] | None = None,
 118          fallback_signers: list[
 119              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 120          ]
 121          | None = None,
 122      ): ...
 123  
 124      # Serializer[bytes] with a bytes data serializer positional argument.
 125      @t.overload
 126      def __init__(
 127          self: Serializer[bytes],
 128          secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
 129          salt: str | bytes | None,
 130          serializer: _PDataSerializer[bytes],
 131          serializer_kwargs: dict[str, t.Any] | None = None,
 132          signer: type[Signer] | None = None,
 133          signer_kwargs: dict[str, t.Any] | None = None,
 134          fallback_signers: list[
 135              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 136          ]
 137          | None = None,
 138      ): ...
 139  
 140      # Serializer[bytes] with a bytes data serializer keyword argument.
 141      @t.overload
 142      def __init__(
 143          self: Serializer[bytes],
 144          secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
 145          salt: str | bytes | None = b"itsdangerous",
 146          *,
 147          serializer: _PDataSerializer[bytes],
 148          serializer_kwargs: dict[str, t.Any] | None = None,
 149          signer: type[Signer] | None = None,
 150          signer_kwargs: dict[str, t.Any] | None = None,
 151          fallback_signers: list[
 152              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 153          ]
 154          | None = None,
 155      ): ...
 156  
 157      # Fall back with a positional argument. If the strict signature of
 158      # _PDataSerializer doesn't match, fall back to a union, requiring the user
 159      # to specify the type.
 160      @t.overload
 161      def __init__(
 162          self,
 163          secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
 164          salt: str | bytes | None,
 165          serializer: t.Any,
 166          serializer_kwargs: dict[str, t.Any] | None = None,
 167          signer: type[Signer] | None = None,
 168          signer_kwargs: dict[str, t.Any] | None = None,
 169          fallback_signers: list[
 170              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 171          ]
 172          | None = None,
 173      ): ...
 174  
 175      # Fall back with a keyword argument.
 176      @t.overload
 177      def __init__(
 178          self,
 179          secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
 180          salt: str | bytes | None = b"itsdangerous",
 181          *,
 182          serializer: t.Any,
 183          serializer_kwargs: dict[str, t.Any] | None = None,
 184          signer: type[Signer] | None = None,
 185          signer_kwargs: dict[str, t.Any] | None = None,
 186          fallback_signers: list[
 187              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 188          ]
 189          | None = None,
 190      ): ...
 191  
 192      def __init__(
 193          self,
 194          secret_key: str | bytes | cabc.Iterable[str] | cabc.Iterable[bytes],
 195          salt: str | bytes | None = b"itsdangerous",
 196          serializer: t.Any | None = None,
 197          serializer_kwargs: dict[str, t.Any] | None = None,
 198          signer: type[Signer] | None = None,
 199          signer_kwargs: dict[str, t.Any] | None = None,
 200          fallback_signers: list[
 201              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 202          ]
 203          | None = None,
 204      ):
 205          #: The list of secret keys to try for verifying signatures, from
 206          #: oldest to newest. The newest (last) key is used for signing.
 207          #:
 208          #: This allows a key rotation system to keep a list of allowed
 209          #: keys and remove expired ones.
 210          self.secret_keys: list[bytes] = _make_keys_list(secret_key)
 211  
 212          if salt is not None:
 213              salt = want_bytes(salt)
 214              # if salt is None then the signer's default is used
 215  
 216          self.salt = salt
 217  
 218          if serializer is None:
 219              serializer = self.default_serializer
 220  
 221          self.serializer: _PDataSerializer[_TSerialized] = serializer
 222          self.is_text_serializer: bool = is_text_serializer(serializer)
 223  
 224          if signer is None:
 225              signer = self.default_signer
 226  
 227          self.signer: type[Signer] = signer
 228          self.signer_kwargs: dict[str, t.Any] = signer_kwargs or {}
 229  
 230          if fallback_signers is None:
 231              fallback_signers = list(self.default_fallback_signers)
 232  
 233          self.fallback_signers: list[
 234              dict[str, t.Any] | tuple[type[Signer], dict[str, t.Any]] | type[Signer]
 235          ] = fallback_signers
 236          self.serializer_kwargs: dict[str, t.Any] = serializer_kwargs or {}
 237  
 238      @property
 239      def secret_key(self) -> bytes:
 240          """The newest (last) entry in the :attr:`secret_keys` list. This
 241          is for compatibility from before key rotation support was added.
 242          """
 243          return self.secret_keys[-1]
 244  
 245      def load_payload(
 246          self, payload: bytes, serializer: _PDataSerializer[t.Any] | None = None
 247      ) -> t.Any:
 248          """Loads the encoded object. This function raises
 249          :class:`.BadPayload` if the payload is not valid. The
 250          ``serializer`` parameter can be used to override the serializer
 251          stored on the class. The encoded ``payload`` should always be
 252          bytes.
 253          """
 254          if serializer is None:
 255              use_serializer = self.serializer
 256              is_text = self.is_text_serializer
 257          else:
 258              use_serializer = serializer
 259              is_text = is_text_serializer(serializer)
 260  
 261          try:
 262              if is_text:
 263                  return use_serializer.loads(payload.decode("utf-8"))  # type: ignore[arg-type]
 264  
 265              return use_serializer.loads(payload)  # type: ignore[arg-type]
 266          except Exception as e:
 267              raise BadPayload(
 268                  "Could not load the payload because an exception"
 269                  " occurred on unserializing the data.",
 270                  original_error=e,
 271              ) from e
 272  
 273      def dump_payload(self, obj: t.Any) -> bytes:
 274          """Dumps the encoded object. The return value is always bytes.
 275          If the internal serializer returns text, the value will be
 276          encoded as UTF-8.
 277          """
 278          return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
 279  
 280      def make_signer(self, salt: str | bytes | None = None) -> Signer:
 281          """Creates a new instance of the signer to be used. The default
 282          implementation uses the :class:`.Signer` base class.
 283          """
 284          if salt is None:
 285              salt = self.salt
 286  
 287          return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs)
 288  
 289      def iter_unsigners(self, salt: str | bytes | None = None) -> cabc.Iterator[Signer]:
 290          """Iterates over all signers to be tried for unsigning. Starts
 291          with the configured signer, then constructs each signer
 292          specified in ``fallback_signers``.
 293          """
 294          if salt is None:
 295              salt = self.salt
 296  
 297          yield self.make_signer(salt)
 298  
 299          for fallback in self.fallback_signers:
 300              if isinstance(fallback, dict):
 301                  kwargs = fallback
 302                  fallback = self.signer
 303              elif isinstance(fallback, tuple):
 304                  fallback, kwargs = fallback
 305              else:
 306                  kwargs = self.signer_kwargs
 307  
 308              for secret_key in self.secret_keys:
 309                  yield fallback(secret_key, salt=salt, **kwargs)
 310  
 311      def dumps(self, obj: t.Any, salt: str | bytes | None = None) -> _TSerialized:
 312          """Returns a signed string serialized with the internal
 313          serializer. The return value can be either a byte or unicode
 314          string depending on the format of the internal serializer.
 315          """
 316          payload = want_bytes(self.dump_payload(obj))
 317          rv = self.make_signer(salt).sign(payload)
 318  
 319          if self.is_text_serializer:
 320              return rv.decode("utf-8")  # type: ignore[return-value]
 321  
 322          return rv  # type: ignore[return-value]
 323  
 324      def dump(self, obj: t.Any, f: t.IO[t.Any], salt: str | bytes | None = None) -> None:
 325          """Like :meth:`dumps` but dumps into a file. The file handle has
 326          to be compatible with what the internal serializer expects.
 327          """
 328          f.write(self.dumps(obj, salt))
 329  
 330      def loads(
 331          self, s: str | bytes, salt: str | bytes | None = None, **kwargs: t.Any
 332      ) -> t.Any:
 333          """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
 334          signature validation fails.
 335          """
 336          s = want_bytes(s)
 337          last_exception = None
 338  
 339          for signer in self.iter_unsigners(salt):
 340              try:
 341                  return self.load_payload(signer.unsign(s))
 342              except BadSignature as err:
 343                  last_exception = err
 344  
 345          raise t.cast(BadSignature, last_exception)
 346  
 347      def load(self, f: t.IO[t.Any], salt: str | bytes | None = None) -> t.Any:
 348          """Like :meth:`loads` but loads from a file."""
 349          return self.loads(f.read(), salt)
 350  
 351      def loads_unsafe(
 352          self, s: str | bytes, salt: str | bytes | None = None
 353      ) -> tuple[bool, t.Any]:
 354          """Like :meth:`loads` but without verifying the signature. This
 355          is potentially very dangerous to use depending on how your
 356          serializer works. The return value is ``(signature_valid,
 357          payload)`` instead of just the payload. The first item will be a
 358          boolean that indicates if the signature is valid. This function
 359          never fails.
 360  
 361          Use it for debugging only and if you know that your serializer
 362          module is not exploitable (for example, do not use it with a
 363          pickle serializer).
 364  
 365          .. versionadded:: 0.15
 366          """
 367          return self._loads_unsafe_impl(s, salt)
 368  
 369      def _loads_unsafe_impl(
 370          self,
 371          s: str | bytes,
 372          salt: str | bytes | None,
 373          load_kwargs: dict[str, t.Any] | None = None,
 374          load_payload_kwargs: dict[str, t.Any] | None = None,
 375      ) -> tuple[bool, t.Any]:
 376          """Low level helper function to implement :meth:`loads_unsafe`
 377          in serializer subclasses.
 378          """
 379          if load_kwargs is None:
 380              load_kwargs = {}
 381  
 382          try:
 383              return True, self.loads(s, salt=salt, **load_kwargs)
 384          except BadSignature as e:
 385              if e.payload is None:
 386                  return False, None
 387  
 388              if load_payload_kwargs is None:
 389                  load_payload_kwargs = {}
 390  
 391              try:
 392                  return (
 393                      False,
 394                      self.load_payload(e.payload, **load_payload_kwargs),
 395                  )
 396              except BadPayload:
 397                  return False, None
 398  
 399      def load_unsafe(
 400          self, f: t.IO[t.Any], salt: str | bytes | None = None
 401      ) -> tuple[bool, t.Any]:
 402          """Like :meth:`loads_unsafe` but loads from a file.
 403  
 404          .. versionadded:: 0.15
 405          """
 406          return self.loads_unsafe(f.read(), salt=salt)
 407