Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/itsdangerous/serializer.py: 31%

108 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:35 +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(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)