Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/datastructures/auth.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

146 statements  

1from __future__ import annotations 

2 

3import base64 

4import binascii 

5import collections.abc as cabc 

6import typing as t 

7 

8from ..http import dump_header 

9from ..http import parse_dict_header 

10from ..http import quote_header_value 

11from .structures import CallbackDict 

12 

13if t.TYPE_CHECKING: 

14 import typing_extensions as te 

15 

16 

17class Authorization: 

18 """Represents the parts of an ``Authorization`` request header. 

19 

20 :attr:`.Request.authorization` returns an instance if the header is set. 

21 

22 An instance can be used with the test :class:`.Client` request methods' ``auth`` 

23 parameter to send the header in test requests. 

24 

25 Depending on the auth scheme, either :attr:`parameters` or :attr:`token` will be 

26 set. The ``Basic`` scheme's token is decoded into the ``username`` and ``password`` 

27 parameters. 

28 

29 For convenience, ``auth["key"]`` and ``auth.key`` both access the key in the 

30 :attr:`parameters` dict, along with ``auth.get("key")`` and ``"key" in auth``. 

31 

32 .. versionchanged:: 2.3 

33 The ``token`` parameter and attribute was added to support auth schemes that use 

34 a token instead of parameters, such as ``Bearer``. 

35 

36 .. versionchanged:: 2.3 

37 The object is no longer a ``dict``. 

38 

39 .. versionchanged:: 0.5 

40 The object is an immutable dict. 

41 """ 

42 

43 def __init__( 

44 self, 

45 auth_type: str, 

46 data: dict[str, str | None] | None = None, 

47 token: str | None = None, 

48 ) -> None: 

49 self.type = auth_type 

50 """The authorization scheme, like ``basic``, ``digest``, or ``bearer``.""" 

51 

52 if data is None: 

53 data = {} 

54 

55 self.parameters = data 

56 """A dict of parameters parsed from the header. Either this or :attr:`token` 

57 will have a value for a given scheme. 

58 """ 

59 

60 self.token = token 

61 """A token parsed from the header. Either this or :attr:`parameters` will have a 

62 value for a given scheme. 

63 

64 .. versionadded:: 2.3 

65 """ 

66 

67 def __getattr__(self, name: str) -> str | None: 

68 return self.parameters.get(name) 

69 

70 def __getitem__(self, name: str) -> str | None: 

71 return self.parameters.get(name) 

72 

73 def get(self, key: str, default: str | None = None) -> str | None: 

74 return self.parameters.get(key, default) 

75 

76 def __contains__(self, key: str) -> bool: 

77 return key in self.parameters 

78 

79 def __eq__(self, other: object) -> bool: 

80 if not isinstance(other, Authorization): 

81 return NotImplemented 

82 

83 return ( 

84 other.type == self.type 

85 and other.token == self.token 

86 and other.parameters == self.parameters 

87 ) 

88 

89 @classmethod 

90 def from_header(cls, value: str | None) -> te.Self | None: 

91 """Parse an ``Authorization`` header value and return an instance, or ``None`` 

92 if the value is empty. 

93 

94 :param value: The header value to parse. 

95 

96 .. versionadded:: 2.3 

97 """ 

98 if not value: 

99 return None 

100 

101 scheme, _, rest = value.partition(" ") 

102 scheme = scheme.lower() 

103 rest = rest.strip(" \t") 

104 

105 if scheme == "basic": 

106 try: 

107 username, _, password = ( 

108 base64.b64decode(rest, validate=True).decode().partition(":") 

109 ) 

110 except (binascii.Error, UnicodeError): 

111 return None 

112 

113 return cls(scheme, {"username": username, "password": password}) 

114 

115 if "=" in rest.rstrip("="): 

116 # = that is not trailing, this is parameters. 

117 return cls(scheme, parse_dict_header(rest), None) 

118 

119 # No = or only trailing =, this is a token. 

120 return cls(scheme, None, rest) 

121 

122 def to_header(self) -> str: 

123 """Produce an ``Authorization`` header value representing this data. 

124 

125 .. versionadded:: 2.0 

126 """ 

127 if self.type == "basic": 

128 value = base64.b64encode( 

129 f"{self.username}:{self.password}".encode() 

130 ).decode("ascii") 

131 return f"Basic {value}" 

132 

133 if self.token is not None: 

134 return f"{self.type.title()} {self.token}" 

135 

136 return f"{self.type.title()} {dump_header(self.parameters)}" 

137 

138 def __str__(self) -> str: 

139 return self.to_header() 

140 

141 def __repr__(self) -> str: 

142 return f"<{type(self).__name__} {self.to_header()}>" 

143 

144 

145class WWWAuthenticate: 

146 """Represents the parts of a ``WWW-Authenticate`` response header. 

147 

148 Set :attr:`.Response.www_authenticate` to an instance of list of instances to set 

149 values for this header in the response. Modifying this instance will modify the 

150 header value. 

151 

152 Depending on the auth scheme, either :attr:`parameters` or :attr:`token` should be 

153 set. The ``Basic`` scheme will encode ``username`` and ``password`` parameters to a 

154 token. 

155 

156 For convenience, ``auth["key"]`` and ``auth.key`` both act on the :attr:`parameters` 

157 dict, and can be used to get, set, or delete parameters. ``auth.get("key")`` and 

158 ``"key" in auth`` are also provided. 

159 

160 .. versionchanged:: 2.3 

161 The ``token`` parameter and attribute was added to support auth schemes that use 

162 a token instead of parameters, such as ``Bearer``. 

163 

164 .. versionchanged:: 2.3 

165 The object is no longer a ``dict``. 

166 

167 .. versionchanged:: 2.3 

168 The ``on_update`` parameter was removed. 

169 """ 

170 

171 def __init__( 

172 self, 

173 auth_type: str, 

174 values: dict[str, str | None] | None = None, 

175 token: str | None = None, 

176 ): 

177 self._type = auth_type.lower() 

178 self._parameters: dict[str, str | None] = CallbackDict( 

179 values, lambda _: self._trigger_on_update() 

180 ) 

181 self._token = token 

182 self._on_update: cabc.Callable[[WWWAuthenticate], None] | None = None 

183 

184 def _trigger_on_update(self) -> None: 

185 if self._on_update is not None: 

186 self._on_update(self) 

187 

188 @property 

189 def type(self) -> str: 

190 """The authorization scheme, like ``basic``, ``digest``, or ``bearer``.""" 

191 return self._type 

192 

193 @type.setter 

194 def type(self, value: str) -> None: 

195 self._type = value 

196 self._trigger_on_update() 

197 

198 @property 

199 def parameters(self) -> dict[str, str | None]: 

200 """A dict of parameters for the header. Only one of this or :attr:`token` should 

201 have a value for a given scheme. 

202 """ 

203 return self._parameters 

204 

205 @parameters.setter 

206 def parameters(self, value: dict[str, str]) -> None: 

207 self._parameters = CallbackDict(value, lambda _: self._trigger_on_update()) 

208 self._trigger_on_update() 

209 

210 @property 

211 def token(self) -> str | None: 

212 """A dict of parameters for the header. Only one of this or :attr:`token` should 

213 have a value for a given scheme. 

214 """ 

215 return self._token 

216 

217 @token.setter 

218 def token(self, value: str | None) -> None: 

219 """A token for the header. Only one of this or :attr:`parameters` should have a 

220 value for a given scheme. 

221 

222 .. versionadded:: 2.3 

223 """ 

224 self._token = value 

225 self._trigger_on_update() 

226 

227 def __getitem__(self, key: str) -> str | None: 

228 return self.parameters.get(key) 

229 

230 def __setitem__(self, key: str, value: str | None) -> None: 

231 if value is None: 

232 if key in self.parameters: 

233 del self.parameters[key] 

234 else: 

235 self.parameters[key] = value 

236 

237 self._trigger_on_update() 

238 

239 def __delitem__(self, key: str) -> None: 

240 if key in self.parameters: 

241 del self.parameters[key] 

242 self._trigger_on_update() 

243 

244 def __getattr__(self, name: str) -> str | None: 

245 return self[name] 

246 

247 def __setattr__(self, name: str, value: str | None) -> None: 

248 if name in {"_type", "_parameters", "_token", "_on_update"}: 

249 super().__setattr__(name, value) 

250 else: 

251 self[name] = value 

252 

253 def __delattr__(self, name: str) -> None: 

254 del self[name] 

255 

256 def __contains__(self, key: str) -> bool: 

257 return key in self.parameters 

258 

259 def __eq__(self, other: object) -> bool: 

260 if not isinstance(other, WWWAuthenticate): 

261 return NotImplemented 

262 

263 return ( 

264 other.type == self.type 

265 and other.token == self.token 

266 and other.parameters == self.parameters 

267 ) 

268 

269 def get(self, key: str, default: str | None = None) -> str | None: 

270 return self.parameters.get(key, default) 

271 

272 @classmethod 

273 def from_header(cls, value: str | None) -> te.Self | None: 

274 """Parse a ``WWW-Authenticate`` header value and return an instance, or ``None`` 

275 if the value is empty. 

276 

277 :param value: The header value to parse. 

278 

279 .. versionadded:: 2.3 

280 """ 

281 if not value: 

282 return None 

283 

284 scheme, _, rest = value.partition(" ") 

285 scheme = scheme.lower() 

286 rest = rest.strip(" \t") 

287 

288 if "=" in rest.rstrip("="): 

289 # = that is not trailing, this is parameters. 

290 return cls(scheme, parse_dict_header(rest), None) 

291 

292 # No = or only trailing =, this is a token. 

293 return cls(scheme, None, rest) 

294 

295 def to_header(self) -> str: 

296 """Produce a ``WWW-Authenticate`` header value representing this data.""" 

297 if self.token is not None: 

298 return f"{self.type.title()} {self.token}" 

299 

300 if not self.parameters: 

301 return self.type.title() 

302 

303 if self.type == "digest": 

304 items = [] 

305 

306 for key, value in self.parameters.items(): 

307 if key in {"realm", "domain", "nonce", "opaque", "qop"}: 

308 value = quote_header_value(value, allow_token=False) 

309 else: 

310 value = quote_header_value(value) 

311 

312 items.append(f"{key}={value}") 

313 

314 return f"Digest {', '.join(items)}" 

315 

316 return f"{self.type.title()} {dump_header(self.parameters)}" 

317 

318 def __str__(self) -> str: 

319 return self.to_header() 

320 

321 def __repr__(self) -> str: 

322 return f"<{type(self).__name__} {self.to_header()}>"