Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/itsdangerous/serializer.py: 66%
108 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:37 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-25 06:37 +0000
1import json
2import typing as _t
4from .encoding import want_bytes
5from .exc import BadPayload
6from .exc import BadSignature
7from .signer import _make_keys_list
8from .signer import Signer
10_t_str_bytes = _t.Union[str, bytes]
11_t_opt_str_bytes = _t.Optional[_t_str_bytes]
12_t_kwargs = _t.Dict[str, _t.Any]
13_t_opt_kwargs = _t.Optional[_t_kwargs]
14_t_signer = _t.Type[Signer]
15_t_fallbacks = _t.List[_t.Union[_t_kwargs, _t.Tuple[_t_signer, _t_kwargs], _t_signer]]
16_t_load_unsafe = _t.Tuple[bool, _t.Any]
17_t_secret_key = _t.Union[_t.Iterable[_t_str_bytes], _t_str_bytes]
20def is_text_serializer(serializer: _t.Any) -> bool:
21 """Checks whether a serializer generates text or binary."""
22 return isinstance(serializer.dumps({}), str)
25class Serializer:
26 """A serializer wraps a :class:`~itsdangerous.signer.Signer` to
27 enable serializing and securely signing data other than bytes. It
28 can unsign to verify that the data hasn't been changed.
30 The serializer provides :meth:`dumps` and :meth:`loads`, similar to
31 :mod:`json`, and by default uses :mod:`json` internally to serialize
32 the data to bytes.
34 The secret key should be a random string of ``bytes`` and should not
35 be saved to code or version control. Different salts should be used
36 to distinguish signing in different contexts. See :doc:`/concepts`
37 for information about the security of the secret key and salt.
39 :param secret_key: The secret key to sign and verify with. Can be a
40 list of keys, oldest to newest, to support key rotation.
41 :param salt: Extra key to combine with ``secret_key`` to distinguish
42 signatures in different contexts.
43 :param serializer: An object that provides ``dumps`` and ``loads``
44 methods for serializing data to a string. Defaults to
45 :attr:`default_serializer`, which defaults to :mod:`json`.
46 :param serializer_kwargs: Keyword arguments to pass when calling
47 ``serializer.dumps``.
48 :param signer: A ``Signer`` class to instantiate when signing data.
49 Defaults to :attr:`default_signer`, which defaults to
50 :class:`~itsdangerous.signer.Signer`.
51 :param signer_kwargs: Keyword arguments to pass when instantiating
52 the ``Signer`` class.
53 :param fallback_signers: List of signer parameters to try when
54 unsigning with the default signer fails. Each item can be a dict
55 of ``signer_kwargs``, a ``Signer`` class, or a tuple of
56 ``(signer, signer_kwargs)``. Defaults to
57 :attr:`default_fallback_signers`.
59 .. versionchanged:: 2.0
60 Added support for key rotation by passing a list to
61 ``secret_key``.
63 .. versionchanged:: 2.0
64 Removed the default SHA-512 fallback signer from
65 ``default_fallback_signers``.
67 .. versionchanged:: 1.1
68 Added support for ``fallback_signers`` and configured a default
69 SHA-512 fallback. This fallback is for users who used the yanked
70 1.0.0 release which defaulted to SHA-512.
72 .. versionchanged:: 0.14
73 The ``signer`` and ``signer_kwargs`` parameters were added to
74 the constructor.
75 """
77 #: The default serialization module to use to serialize data to a
78 #: string internally. The default is :mod:`json`, but can be changed
79 #: to any object that provides ``dumps`` and ``loads`` methods.
80 default_serializer: _t.Any = json
82 #: The default ``Signer`` class to instantiate when signing data.
83 #: The default is :class:`itsdangerous.signer.Signer`.
84 default_signer: _t_signer = Signer
86 #: The default fallback signers to try when unsigning fails.
87 default_fallback_signers: _t_fallbacks = []
89 def __init__(
90 self,
91 secret_key: _t_secret_key,
92 salt: _t_opt_str_bytes = b"itsdangerous",
93 serializer: _t.Any = None,
94 serializer_kwargs: _t_opt_kwargs = None,
95 signer: _t.Optional[_t_signer] = None,
96 signer_kwargs: _t_opt_kwargs = None,
97 fallback_signers: _t.Optional[_t_fallbacks] = None,
98 ):
99 #: The list of secret keys to try for verifying signatures, from
100 #: oldest to newest. The newest (last) key is used for signing.
101 #:
102 #: This allows a key rotation system to keep a list of allowed
103 #: keys and remove expired ones.
104 self.secret_keys: _t.List[bytes] = _make_keys_list(secret_key)
106 if salt is not None:
107 salt = want_bytes(salt)
108 # if salt is None then the signer's default is used
110 self.salt = salt
112 if serializer is None:
113 serializer = self.default_serializer
115 self.serializer: _t.Any = serializer
116 self.is_text_serializer: bool = is_text_serializer(serializer)
118 if signer is None:
119 signer = self.default_signer
121 self.signer: _t_signer = signer
122 self.signer_kwargs: _t_kwargs = signer_kwargs or {}
124 if fallback_signers is None:
125 fallback_signers = list(self.default_fallback_signers or ())
127 self.fallback_signers: _t_fallbacks = fallback_signers
128 self.serializer_kwargs: _t_kwargs = serializer_kwargs or {}
130 @property
131 def secret_key(self) -> bytes:
132 """The newest (last) entry in the :attr:`secret_keys` list. This
133 is for compatibility from before key rotation support was added.
134 """
135 return self.secret_keys[-1]
137 def load_payload(
138 self, payload: bytes, serializer: _t.Optional[_t.Any] = None
139 ) -> _t.Any:
140 """Loads the encoded object. This function raises
141 :class:`.BadPayload` if the payload is not valid. The
142 ``serializer`` parameter can be used to override the serializer
143 stored on the class. The encoded ``payload`` should always be
144 bytes.
145 """
146 if serializer is None:
147 serializer = self.serializer
148 is_text = self.is_text_serializer
149 else:
150 is_text = is_text_serializer(serializer)
152 try:
153 if is_text:
154 return serializer.loads(payload.decode("utf-8"))
156 return serializer.loads(payload)
157 except Exception as e:
158 raise BadPayload(
159 "Could not load the payload because an exception"
160 " occurred on unserializing the data.",
161 original_error=e,
162 ) from e
164 def dump_payload(self, obj: _t.Any) -> bytes:
165 """Dumps the encoded object. The return value is always bytes.
166 If the internal serializer returns text, the value will be
167 encoded as UTF-8.
168 """
169 return want_bytes(self.serializer.dumps(obj, **self.serializer_kwargs))
171 def make_signer(self, salt: _t_opt_str_bytes = None) -> Signer:
172 """Creates a new instance of the signer to be used. The default
173 implementation uses the :class:`.Signer` base class.
174 """
175 if salt is None:
176 salt = self.salt
178 return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs)
180 def iter_unsigners(self, salt: _t_opt_str_bytes = None) -> _t.Iterator[Signer]:
181 """Iterates over all signers to be tried for unsigning. Starts
182 with the configured signer, then constructs each signer
183 specified in ``fallback_signers``.
184 """
185 if salt is None:
186 salt = self.salt
188 yield self.make_signer(salt)
190 for fallback in self.fallback_signers:
191 if isinstance(fallback, dict):
192 kwargs = fallback
193 fallback = self.signer
194 elif isinstance(fallback, tuple):
195 fallback, kwargs = fallback
196 else:
197 kwargs = self.signer_kwargs
199 for secret_key in self.secret_keys:
200 yield fallback(secret_key, salt=salt, **kwargs)
202 def dumps(self, obj: _t.Any, salt: _t_opt_str_bytes = None) -> _t_str_bytes:
203 """Returns a signed string serialized with the internal
204 serializer. The return value can be either a byte or unicode
205 string depending on the format of the internal serializer.
206 """
207 payload = want_bytes(self.dump_payload(obj))
208 rv = self.make_signer(salt).sign(payload)
210 if self.is_text_serializer:
211 return rv.decode("utf-8")
213 return rv
215 def dump(
216 self, obj: _t.Any, f: _t.IO[_t.Any], salt: _t_opt_str_bytes = None
217 ) -> None:
218 """Like :meth:`dumps` but dumps into a file. The file handle has
219 to be compatible with what the internal serializer expects.
220 """
221 f.write(self.dumps(obj, salt))
223 def loads(
224 self, s: _t_str_bytes, salt: _t_opt_str_bytes = None, **kwargs: _t.Any
225 ) -> _t.Any:
226 """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
227 signature validation fails.
228 """
229 s = want_bytes(s)
230 last_exception = None
232 for signer in self.iter_unsigners(salt):
233 try:
234 return self.load_payload(signer.unsign(s))
235 except BadSignature as err:
236 last_exception = err
238 raise _t.cast(BadSignature, last_exception)
240 def load(self, f: _t.IO[_t.Any], salt: _t_opt_str_bytes = None) -> _t.Any:
241 """Like :meth:`loads` but loads from a file."""
242 return self.loads(f.read(), salt)
244 def loads_unsafe(
245 self, s: _t_str_bytes, salt: _t_opt_str_bytes = None
246 ) -> _t_load_unsafe:
247 """Like :meth:`loads` but without verifying the signature. This
248 is potentially very dangerous to use depending on how your
249 serializer works. The return value is ``(signature_valid,
250 payload)`` instead of just the payload. The first item will be a
251 boolean that indicates if the signature is valid. This function
252 never fails.
254 Use it for debugging only and if you know that your serializer
255 module is not exploitable (for example, do not use it with a
256 pickle serializer).
258 .. versionadded:: 0.15
259 """
260 return self._loads_unsafe_impl(s, salt)
262 def _loads_unsafe_impl(
263 self,
264 s: _t_str_bytes,
265 salt: _t_opt_str_bytes,
266 load_kwargs: _t_opt_kwargs = None,
267 load_payload_kwargs: _t_opt_kwargs = None,
268 ) -> _t_load_unsafe:
269 """Low level helper function to implement :meth:`loads_unsafe`
270 in serializer subclasses.
271 """
272 if load_kwargs is None:
273 load_kwargs = {}
275 try:
276 return True, self.loads(s, salt=salt, **load_kwargs)
277 except BadSignature as e:
278 if e.payload is None:
279 return False, None
281 if load_payload_kwargs is None:
282 load_payload_kwargs = {}
284 try:
285 return (
286 False,
287 self.load_payload(e.payload, **load_payload_kwargs),
288 )
289 except BadPayload:
290 return False, None
292 def load_unsafe(
293 self, f: _t.IO[_t.Any], salt: _t_opt_str_bytes = None
294 ) -> _t_load_unsafe:
295 """Like :meth:`loads_unsafe` but loads from a file.
297 .. versionadded:: 0.15
298 """
299 return self.loads_unsafe(f.read(), salt=salt)