| 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) |