serializer.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import hashlib
  2. from ._compat import text_type
  3. from ._json import json
  4. from .encoding import want_bytes
  5. from .exc import BadPayload
  6. from .exc import BadSignature
  7. from .signer import Signer
  8. def is_text_serializer(serializer):
  9. """Checks whether a serializer generates text or binary."""
  10. return isinstance(serializer.dumps({}), text_type)
  11. class Serializer(object):
  12. """This class provides a serialization interface on top of the
  13. signer. It provides a similar API to json/pickle and other modules
  14. but is structured differently internally. If you want to change the
  15. underlying implementation for parsing and loading you have to
  16. override the :meth:`load_payload` and :meth:`dump_payload`
  17. functions.
  18. This implementation uses simplejson if available for dumping and
  19. loading and will fall back to the standard library's json module if
  20. it's not available.
  21. You do not need to subclass this class in order to switch out or
  22. customize the :class:`.Signer`. You can instead pass a different
  23. class to the constructor as well as keyword arguments as a dict that
  24. should be forwarded.
  25. .. code-block:: python
  26. s = Serializer(signer_kwargs={'key_derivation': 'hmac'})
  27. You may want to upgrade the signing parameters without invalidating
  28. existing signatures that are in use. Fallback signatures can be
  29. given that will be tried if unsigning with the current signer fails.
  30. Fallback signers can be defined by providing a list of
  31. ``fallback_signers``. Each item can be one of the following: a
  32. signer class (which is instantiated with ``signer_kwargs``,
  33. ``salt``, and ``secret_key``), a tuple
  34. ``(signer_class, signer_kwargs)``, or a dict of ``signer_kwargs``.
  35. For example, this is a serializer that signs using SHA-512, but will
  36. unsign using either SHA-512 or SHA1:
  37. .. code-block:: python
  38. s = Serializer(
  39. signer_kwargs={"digest_method": hashlib.sha512},
  40. fallback_signers=[{"digest_method": hashlib.sha1}]
  41. )
  42. .. versionchanged:: 0.14:
  43. The ``signer`` and ``signer_kwargs`` parameters were added to
  44. the constructor.
  45. .. versionchanged:: 1.1.0:
  46. Added support for ``fallback_signers`` and configured a default
  47. SHA-512 fallback. This fallback is for users who used the yanked
  48. 1.0.0 release which defaulted to SHA-512.
  49. """
  50. #: If a serializer module or class is not passed to the constructor
  51. #: this one is picked up. This currently defaults to :mod:`json`.
  52. default_serializer = json
  53. #: The default :class:`Signer` class that is being used by this
  54. #: serializer.
  55. #:
  56. #: .. versionadded:: 0.14
  57. default_signer = Signer
  58. #: The default fallback signers.
  59. default_fallback_signers = [{"digest_method": hashlib.sha512}]
  60. def __init__(
  61. self,
  62. secret_key,
  63. salt=b"itsdangerous",
  64. serializer=None,
  65. serializer_kwargs=None,
  66. signer=None,
  67. signer_kwargs=None,
  68. fallback_signers=None,
  69. ):
  70. self.secret_key = want_bytes(secret_key)
  71. self.salt = want_bytes(salt)
  72. if serializer is None:
  73. serializer = self.default_serializer
  74. self.serializer = serializer
  75. self.is_text_serializer = is_text_serializer(serializer)
  76. if signer is None:
  77. signer = self.default_signer
  78. self.signer = signer
  79. self.signer_kwargs = signer_kwargs or {}
  80. if fallback_signers is None:
  81. fallback_signers = list(self.default_fallback_signers or ())
  82. self.fallback_signers = fallback_signers
  83. self.serializer_kwargs = serializer_kwargs or {}
  84. def load_payload(self, payload, serializer=None):
  85. """Loads the encoded object. This function raises
  86. :class:`.BadPayload` if the payload is not valid. The
  87. ``serializer`` parameter can be used to override the serializer
  88. stored on the class. The encoded ``payload`` should always be
  89. bytes.
  90. """
  91. if serializer is None:
  92. serializer = self.serializer
  93. is_text = self.is_text_serializer
  94. else:
  95. is_text = is_text_serializer(serializer)
  96. try:
  97. if is_text:
  98. payload = payload.decode("utf-8")
  99. return serializer.loads(payload)
  100. except Exception as e:
  101. raise BadPayload(
  102. "Could not load the payload because an exception"
  103. " occurred on unserializing the data.",
  104. original_error=e,
  105. )
  106. def dump_payload(self, obj):
  107. """Dumps the encoded object. The return value is always bytes.
  108. If the internal serializer returns text, the value will be
  109. encoded as UTF-8.
  110. """
  111. return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
  112. def make_signer(self, salt=None):
  113. """Creates a new instance of the signer to be used. The default
  114. implementation uses the :class:`.Signer` base class.
  115. """
  116. if salt is None:
  117. salt = self.salt
  118. return self.signer(self.secret_key, salt=salt, **self.signer_kwargs)
  119. def iter_unsigners(self, salt=None):
  120. """Iterates over all signers to be tried for unsigning. Starts
  121. with the configured signer, then constructs each signer
  122. specified in ``fallback_signers``.
  123. """
  124. if salt is None:
  125. salt = self.salt
  126. yield self.make_signer(salt)
  127. for fallback in self.fallback_signers:
  128. if type(fallback) is dict:
  129. kwargs = fallback
  130. fallback = self.signer
  131. elif type(fallback) is tuple:
  132. fallback, kwargs = fallback
  133. else:
  134. kwargs = self.signer_kwargs
  135. yield fallback(self.secret_key, salt=salt, **kwargs)
  136. def dumps(self, obj, salt=None):
  137. """Returns a signed string serialized with the internal
  138. serializer. The return value can be either a byte or unicode
  139. string depending on the format of the internal serializer.
  140. """
  141. payload = want_bytes(self.dump_payload(obj))
  142. rv = self.make_signer(salt).sign(payload)
  143. if self.is_text_serializer:
  144. rv = rv.decode("utf-8")
  145. return rv
  146. def dump(self, obj, f, salt=None):
  147. """Like :meth:`dumps` but dumps into a file. The file handle has
  148. to be compatible with what the internal serializer expects.
  149. """
  150. f.write(self.dumps(obj, salt))
  151. def loads(self, s, salt=None):
  152. """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
  153. signature validation fails.
  154. """
  155. s = want_bytes(s)
  156. last_exception = None
  157. for signer in self.iter_unsigners(salt):
  158. try:
  159. return self.load_payload(signer.unsign(s))
  160. except BadSignature as err:
  161. last_exception = err
  162. raise last_exception
  163. def load(self, f, salt=None):
  164. """Like :meth:`loads` but loads from a file."""
  165. return self.loads(f.read(), salt)
  166. def loads_unsafe(self, s, salt=None):
  167. """Like :meth:`loads` but without verifying the signature. This
  168. is potentially very dangerous to use depending on how your
  169. serializer works. The return value is ``(signature_valid,
  170. payload)`` instead of just the payload. The first item will be a
  171. boolean that indicates if the signature is valid. This function
  172. never fails.
  173. Use it for debugging only and if you know that your serializer
  174. module is not exploitable (for example, do not use it with a
  175. pickle serializer).
  176. .. versionadded:: 0.15
  177. """
  178. return self._loads_unsafe_impl(s, salt)
  179. def _loads_unsafe_impl(self, s, salt, load_kwargs=None, load_payload_kwargs=None):
  180. """Low level helper function to implement :meth:`loads_unsafe`
  181. in serializer subclasses.
  182. """
  183. try:
  184. return True, self.loads(s, salt=salt, **(load_kwargs or {}))
  185. except BadSignature as e:
  186. if e.payload is None:
  187. return False, None
  188. try:
  189. return (
  190. False,
  191. self.load_payload(e.payload, **(load_payload_kwargs or {})),
  192. )
  193. except BadPayload:
  194. return False, None
  195. def load_unsafe(self, f, *args, **kwargs):
  196. """Like :meth:`loads_unsafe` but loads from a file.
  197. .. versionadded:: 0.15
  198. """
  199. return self.loads_unsafe(f.read(), *args, **kwargs)