]> jfr.im git - dlqueue.git/blob - venv/lib/python3.11/site-packages/itsdangerous/serializer.py
init: venv aand flask
[dlqueue.git] / venv / lib / python3.11 / site-packages / itsdangerous / serializer.py
1 import json
2 import typing as _t
3
4 from .encoding import want_bytes
5 from .exc import BadPayload
6 from .exc import BadSignature
7 from .signer import _make_keys_list
8 from .signer import Signer
9
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]
18
19
20 def is_text_serializer(serializer: _t.Any) -> bool:
21 """Checks whether a serializer generates text or binary."""
22 return isinstance(serializer.dumps({}), str)
23
24
25 class 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.
29
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.
33
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.
38
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`.
58
59 .. versionchanged:: 2.0
60 Added support for key rotation by passing a list to
61 ``secret_key``.
62
63 .. versionchanged:: 2.0
64 Removed the default SHA-512 fallback signer from
65 ``default_fallback_signers``.
66
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.
71
72 .. versionchanged:: 0.14
73 The ``signer`` and ``signer_kwargs`` parameters were added to
74 the constructor.
75 """
76
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
81
82 #: The default ``Signer`` class to instantiate when signing data.
83 #: The default is :class:`itsdangerous.signer.Signer`.
84 default_signer: _t_signer = Signer
85
86 #: The default fallback signers to try when unsigning fails.
87 default_fallback_signers: _t_fallbacks = []
88
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)
105
106 if salt is not None:
107 salt = want_bytes(salt)
108 # if salt is None then the signer's default is used
109
110 self.salt = salt
111
112 if serializer is None:
113 serializer = self.default_serializer
114
115 self.serializer: _t.Any = serializer
116 self.is_text_serializer: bool = is_text_serializer(serializer)
117
118 if signer is None:
119 signer = self.default_signer
120
121 self.signer: _t_signer = signer
122 self.signer_kwargs: _t_kwargs = signer_kwargs or {}
123
124 if fallback_signers is None:
125 fallback_signers = list(self.default_fallback_signers or ())
126
127 self.fallback_signers: _t_fallbacks = fallback_signers
128 self.serializer_kwargs: _t_kwargs = serializer_kwargs or {}
129
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]
136
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)
151
152 try:
153 if is_text:
154 return serializer.loads(payload.decode("utf-8"))
155
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
163
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))
170
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
177
178 return self.signer(self.secret_keys, salt=salt, **self.signer_kwargs)
179
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
187
188 yield self.make_signer(salt)
189
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
198
199 for secret_key in self.secret_keys:
200 yield fallback(secret_key, salt=salt, **kwargs)
201
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)
209
210 if self.is_text_serializer:
211 return rv.decode("utf-8")
212
213 return rv
214
215 def dump(self, obj: _t.Any, f: _t.IO, salt: _t_opt_str_bytes = None) -> None:
216 """Like :meth:`dumps` but dumps into a file. The file handle has
217 to be compatible with what the internal serializer expects.
218 """
219 f.write(self.dumps(obj, salt))
220
221 def loads(
222 self, s: _t_str_bytes, salt: _t_opt_str_bytes = None, **kwargs: _t.Any
223 ) -> _t.Any:
224 """Reverse of :meth:`dumps`. Raises :exc:`.BadSignature` if the
225 signature validation fails.
226 """
227 s = want_bytes(s)
228 last_exception = None
229
230 for signer in self.iter_unsigners(salt):
231 try:
232 return self.load_payload(signer.unsign(s))
233 except BadSignature as err:
234 last_exception = err
235
236 raise _t.cast(BadSignature, last_exception)
237
238 def load(self, f: _t.IO, salt: _t_opt_str_bytes = None) -> _t.Any:
239 """Like :meth:`loads` but loads from a file."""
240 return self.loads(f.read(), salt)
241
242 def loads_unsafe(
243 self, s: _t_str_bytes, salt: _t_opt_str_bytes = None
244 ) -> _t_load_unsafe:
245 """Like :meth:`loads` but without verifying the signature. This
246 is potentially very dangerous to use depending on how your
247 serializer works. The return value is ``(signature_valid,
248 payload)`` instead of just the payload. The first item will be a
249 boolean that indicates if the signature is valid. This function
250 never fails.
251
252 Use it for debugging only and if you know that your serializer
253 module is not exploitable (for example, do not use it with a
254 pickle serializer).
255
256 .. versionadded:: 0.15
257 """
258 return self._loads_unsafe_impl(s, salt)
259
260 def _loads_unsafe_impl(
261 self,
262 s: _t_str_bytes,
263 salt: _t_opt_str_bytes,
264 load_kwargs: _t_opt_kwargs = None,
265 load_payload_kwargs: _t_opt_kwargs = None,
266 ) -> _t_load_unsafe:
267 """Low level helper function to implement :meth:`loads_unsafe`
268 in serializer subclasses.
269 """
270 if load_kwargs is None:
271 load_kwargs = {}
272
273 try:
274 return True, self.loads(s, salt=salt, **load_kwargs)
275 except BadSignature as e:
276 if e.payload is None:
277 return False, None
278
279 if load_payload_kwargs is None:
280 load_payload_kwargs = {}
281
282 try:
283 return (
284 False,
285 self.load_payload(e.payload, **load_payload_kwargs),
286 )
287 except BadPayload:
288 return False, None
289
290 def load_unsafe(self, f: _t.IO, salt: _t_opt_str_bytes = None) -> _t_load_unsafe:
291 """Like :meth:`loads_unsafe` but loads from a file.
292
293 .. versionadded:: 0.15
294 """
295 return self.loads_unsafe(f.read(), salt=salt)