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

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

104 

105 if scheme == "basic": 

106 try: 

107 username, _, password = base64.b64decode(rest).decode().partition(":") 

108 except (binascii.Error, UnicodeError): 

109 return None 

110 

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

112 

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

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

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

116 

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

118 return cls(scheme, None, rest) 

119 

120 def to_header(self) -> str: 

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

122 

123 .. versionadded:: 2.0 

124 """ 

125 if self.type == "basic": 

126 value = base64.b64encode( 

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

128 ).decode("ascii") 

129 return f"Basic {value}" 

130 

131 if self.token is not None: 

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

133 

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

135 

136 def __str__(self) -> str: 

137 return self.to_header() 

138 

139 def __repr__(self) -> str: 

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

141 

142 

143class WWWAuthenticate: 

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

145 

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

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

148 header value. 

149 

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

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

152 token. 

153 

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

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

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

157 

158 .. versionchanged:: 2.3 

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

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

161 

162 .. versionchanged:: 2.3 

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

164 

165 .. versionchanged:: 2.3 

166 The ``on_update`` parameter was removed. 

167 """ 

168 

169 def __init__( 

170 self, 

171 auth_type: str, 

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

173 token: str | None = None, 

174 ): 

175 self._type = auth_type.lower() 

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

177 values, lambda _: self._trigger_on_update() 

178 ) 

179 self._token = token 

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

181 

182 def _trigger_on_update(self) -> None: 

183 if self._on_update is not None: 

184 self._on_update(self) 

185 

186 @property 

187 def type(self) -> str: 

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

189 return self._type 

190 

191 @type.setter 

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

193 self._type = value 

194 self._trigger_on_update() 

195 

196 @property 

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

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

199 have a value for a given scheme. 

200 """ 

201 return self._parameters 

202 

203 @parameters.setter 

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

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

206 self._trigger_on_update() 

207 

208 @property 

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

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

211 have a value for a given scheme. 

212 """ 

213 return self._token 

214 

215 @token.setter 

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

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

218 value for a given scheme. 

219 

220 .. versionadded:: 2.3 

221 """ 

222 self._token = value 

223 self._trigger_on_update() 

224 

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

226 return self.parameters.get(key) 

227 

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

229 if value is None: 

230 if key in self.parameters: 

231 del self.parameters[key] 

232 else: 

233 self.parameters[key] = value 

234 

235 self._trigger_on_update() 

236 

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

238 if key in self.parameters: 

239 del self.parameters[key] 

240 self._trigger_on_update() 

241 

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

243 return self[name] 

244 

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

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

247 super().__setattr__(name, value) 

248 else: 

249 self[name] = value 

250 

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

252 del self[name] 

253 

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

255 return key in self.parameters 

256 

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

258 if not isinstance(other, WWWAuthenticate): 

259 return NotImplemented 

260 

261 return ( 

262 other.type == self.type 

263 and other.token == self.token 

264 and other.parameters == self.parameters 

265 ) 

266 

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

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

269 

270 @classmethod 

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

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

273 if the value is empty. 

274 

275 :param value: The header value to parse. 

276 

277 .. versionadded:: 2.3 

278 """ 

279 if not value: 

280 return None 

281 

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

283 scheme = scheme.lower() 

284 rest = rest.strip() 

285 

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

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

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

289 

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

291 return cls(scheme, None, rest) 

292 

293 def to_header(self) -> str: 

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

295 if self.token is not None: 

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

297 

298 if self.type == "digest": 

299 items = [] 

300 

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

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

303 value = quote_header_value(value, allow_token=False) 

304 else: 

305 value = quote_header_value(value) 

306 

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

308 

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

310 

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

312 

313 def __str__(self) -> str: 

314 return self.to_header() 

315 

316 def __repr__(self) -> str: 

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