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

1import json 

2import typing as _t 

3 

4from .encoding import want_bytes 

5from .exc import BadPayload 

6from .exc import BadSignature 

7from .signer import _make_keys_list 

8from .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 

20def is_text_serializer(serializer: _t.Any) -> bool: 

21 """Checks whether a serializer generates text or binary.""" 

22 return isinstance(serializer.dumps({}), str) 

23 

24 

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. 

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( 

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

222 

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 

231 

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 

237 

238 raise _t.cast(BadSignature, last_exception) 

239 

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) 

243 

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. 

253 

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

257 

258 .. versionadded:: 0.15 

259 """ 

260 return self._loads_unsafe_impl(s, salt) 

261 

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 = {} 

274 

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 

280 

281 if load_payload_kwargs is None: 

282 load_payload_kwargs = {} 

283 

284 try: 

285 return ( 

286 False, 

287 self.load_payload(e.payload, **load_payload_kwargs), 

288 ) 

289 except BadPayload: 

290 return False, None 

291 

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. 

296 

297 .. versionadded:: 0.15 

298 """ 

299 return self.loads_unsafe(f.read(), salt=salt)