123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import hashlib
- from ._compat import text_type
- from ._json import json
- from .encoding import want_bytes
- from .exc import BadPayload
- from .exc import BadSignature
- from .signer import Signer
- def is_text_serializer(serializer):
- """Checks whether a serializer generates text or binary."""
- return isinstance(serializer.dumps({}), text_type)
- class Serializer(object):
- """This class provides a serialization interface on top of the
- signer. It provides a similar API to json/pickle and other modules
- but is structured differently internally. If you want to change the
- underlying implementation for parsing and loading you have to
- override the :meth:`load_payload` and :meth:`dump_payload`
- functions.
- This implementation uses simplejson if available for dumping and
- loading and will fall back to the standard library's json module if
- it's not available.
- You do not need to subclass this class in order to switch out or
- customize the :class:`.Signer`. You can instead pass a different
- class to the constructor as well as keyword arguments as a dict that
- should be forwarded.
- .. code-block:: python
- s = Serializer(signer_kwargs={'key_derivation': 'hmac'})
- You may want to upgrade the signing parameters without invalidating
- existing signatures that are in use. Fallback signatures can be
- given that will be tried if unsigning with the current signer fails.
- Fallback signers can be defined by providing a list of
- ``fallback_signers``. Each item can be one of the following: a
- signer class (which is instantiated with ``signer_kwargs``,
- ``salt``, and ``secret_key``), a tuple
- ``(signer_class, signer_kwargs)``, or a dict of ``signer_kwargs``.
- For example, this is a serializer that signs using SHA-512, but will
- unsign using either SHA-512 or SHA1:
- .. code-block:: python
- s = Serializer(
- signer_kwargs={"digest_method": hashlib.sha512},
- fallback_signers=[{"digest_method": hashlib.sha1}]
- )
- .. versionchanged:: 0.14:
- The ``signer`` and ``signer_kwargs`` parameters were added to
- the constructor.
- .. versionchanged:: 1.1.0:
- Added support for ``fallback_signers`` and configured a default
- SHA-512 fallback. This fallback is for users who used the yanked
- 1.0.0 release which defaulted to SHA-512.
- """
- #: If a serializer module or class is not passed to the constructor
- #: this one is picked up. This currently defaults to :mod:`json`.
- default_serializer = json
- #: The default :class:`Signer` class that is being used by this
- #: serializer.
- #:
- #: .. versionadded:: 0.14
- default_signer = Signer
- #: The default fallback signers.
- default_fallback_signers = [{"digest_method": hashlib.sha512}]
- def __init__(
- self,
- secret_key,
- salt=b"itsdangerous",
- serializer=None,
- serializer_kwargs=None,
- signer=None,
- signer_kwargs=None,
- fallback_signers=None,
- ):
- self.secret_key = want_bytes(secret_key)
- self.salt = want_bytes(salt)
- if serializer is None:
- serializer = self.default_serializer
- self.serializer = serializer
- self.is_text_serializer = is_text_serializer(serializer)
- if signer is None:
- signer = self.default_signer
- self.signer = signer
- self.signer_kwargs = signer_kwargs or {}
- if fallback_signers is None:
- fallback_signers = list(self.default_fallback_signers or ())
- self.fallback_signers = fallback_signers
- self.serializer_kwargs = serializer_kwargs or {}
- def load_payload(self, payload, serializer=None):
- """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
- stored on the class. The encoded ``payload`` should always be
- bytes.
- """
- if serializer is None:
- serializer = self.serializer
- is_text = self.is_text_serializer
- else:
- is_text = is_text_serializer(serializer)
- try:
- if is_text:
- payload = payload.decode("utf-8")
- return serializer.loads(payload)
- except Exception as e:
- raise BadPayload(
- "Could not load the payload because an exception"
- " occurred on unserializing the data.",
- original_error=e,
- )
- def dump_payload(self, obj):
- """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=None):
- """Creates a new instance of the signer to be used. The default
- implementation uses the :class:`.Signer` base class.
- """
- if salt is None:
- salt = self.salt
- return self.signer(self.secret_key, salt=salt, **self.signer_kwargs)
- def iter_unsigners(self, salt=None):
- """Iterates over all signers to be tried for unsigning. Starts
- with the configured signer, then constructs each signer
- specified in ``fallback_signers``.
- """
- if salt is None:
- salt = self.salt
- yield self.make_signer(salt)
- for fallback in self.fallback_signers:
- if type(fallback) is dict:
- kwargs = fallback
- fallback = self.signer
- elif type(fallback) is tuple:
- fallback, kwargs = fallback
- else:
- kwargs = self.signer_kwargs
- yield fallback(self.secret_key, salt=salt, **kwargs)
- def dumps(self, obj, salt=None):
- """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.
- """
- payload = want_bytes(self.dump_payload(obj))
- rv = self.make_signer(salt).sign(payload)
- if self.is_text_serializer:
- rv = rv.decode("utf-8")
- return rv
- def dump(self, obj, f, salt=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, salt=None):
- """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
- signature validation fails.
- """
- s = want_bytes(s)
- last_exception = None
- for signer in self.iter_unsigners(salt):
- try:
- return self.load_payload(signer.unsign(s))
- except BadSignature as err:
- last_exception = err
- raise last_exception
- def load(self, f, salt=None):
- """Like :meth:`loads` but loads from a file."""
- return self.loads(f.read(), salt)
- def loads_unsafe(self, s, salt=None):
- """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,
- payload)`` instead of just the payload. The first item will be a
- boolean that indicates if the signature is valid. This function
- never fails.
- Use it for debugging only and if you know that your serializer
- module is not exploitable (for example, do not use it with a
- pickle serializer).
- .. versionadded:: 0.15
- """
- return self._loads_unsafe_impl(s, salt)
- def _loads_unsafe_impl(self, s, salt, load_kwargs=None, load_payload_kwargs=None):
- """Low level helper function to implement :meth:`loads_unsafe`
- in serializer subclasses.
- """
- try:
- return True, self.loads(s, salt=salt, **(load_kwargs or {}))
- except BadSignature as e:
- if e.payload is None:
- return False, None
- try:
- return (
- False,
- self.load_payload(e.payload, **(load_payload_kwargs or {})),
- )
- except BadPayload:
- return False, None
- def load_unsafe(self, f, *args, **kwargs):
- """Like :meth:`loads_unsafe` but loads from a file.
- .. versionadded:: 0.15
- """
- return self.loads_unsafe(f.read(), *args, **kwargs)
|