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

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

180 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 

13 

14class Authorization: 

15 """A parsed ``Authorization`` header. 

16 

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

18 

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

20 parameter to send the header in test requests. 

21 

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

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

24 parameters. 

25 

26 .. versionchanged:: 3.2 

27 Get/set/delete ``auth["key"]`` and ``auth.key``, along with 

28 ``"key" in auth`` and ``auth.get("key")``, are deprecated and will 

29 be removed in Werkzeug 4.0. Use ``parameters`` directly instead. 

30 

31 .. versionchanged:: 2.3 

32 The ``token`` parameter and attribute was added to support auth schemes 

33 that use a token instead of parameters, such as ``Bearer``. 

34 

35 .. versionchanged:: 2.3 

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

37 

38 .. versionchanged:: 0.5 

39 The object is an immutable dict. 

40 """ 

41 

42 def __init__( 

43 self, 

44 auth_type: str, 

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

46 token: str | None = None, 

47 ) -> None: 

48 self.type = auth_type 

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

50 

51 if data is None: 

52 data = {} 

53 

54 self.parameters = data 

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

56 will have a value for a given scheme. 

57 """ 

58 

59 self.token = token 

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

61 value for a given scheme. 

62 

63 .. versionadded:: 2.3 

64 """ 

65 

66 @property 

67 def username(self) -> str | None: 

68 """The ``username`` key from :attr:`parameters`. A shortcut for 

69 ``Basic`` auth. 

70 """ 

71 return self.parameters.get("username") 

72 

73 @property 

74 def password(self) -> str | None: 

75 """The ``password`` key from :attr:`parameters`. A shortcut for 

76 ``Basic`` auth. 

77 """ 

78 return self.parameters.get("password") 

79 

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

81 import warnings 

82 

83 warnings.warn( 

84 f"'auth.{name}' is deprecated and will be removed in Werkzeug 4.0." 

85 f" Use 'auth.parameters[\"{name}\"]' instead.", 

86 DeprecationWarning, 

87 stacklevel=2, 

88 ) 

89 return self.parameters.get(name) 

90 

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

92 import warnings 

93 

94 warnings.warn( 

95 "'auth[key]' is deprecated and will be removed in Werkzeug 4.0." 

96 " Use 'auth.parameters[key]' instead.", 

97 DeprecationWarning, 

98 stacklevel=2, 

99 ) 

100 return self.parameters.get(name) 

101 

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

103 import warnings 

104 

105 warnings.warn( 

106 "'auth.get(key)' is deprecated and will be removed in Werkzeug 4.0." 

107 " Use 'auth.parameters.get(key)' instead.", 

108 DeprecationWarning, 

109 stacklevel=2, 

110 ) 

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

112 

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

114 import warnings 

115 

116 warnings.warn( 

117 "'key in auth' is deprecated and will be removed in Werkzeug 4.0." 

118 " Use 'key in auth.parameters' instead.", 

119 DeprecationWarning, 

120 stacklevel=2, 

121 ) 

122 return key in self.parameters 

123 

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

125 if not isinstance(other, Authorization): 

126 return NotImplemented 

127 

128 return ( 

129 other.type == self.type 

130 and other.token == self.token 

131 and other.parameters == self.parameters 

132 ) 

133 

134 @classmethod 

135 def from_header(cls, value: str | None) -> t.Self | None: 

136 """Parse an ``Authorization`` header value and create an instance of 

137 this class, or ``None`` if the value is empty. 

138 

139 :param value: The header value to parse. 

140 

141 .. versionadded:: 2.3 

142 """ 

143 if not value: 

144 return None 

145 

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

147 scheme = scheme.lower() 

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

149 

150 if scheme == "basic": 

151 try: 

152 username, _, password = ( 

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

154 ) 

155 except (binascii.Error, UnicodeError): 

156 return None 

157 

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

159 

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

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

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

163 

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

165 return cls(scheme, None, rest) 

166 

167 def to_header(self) -> str: 

168 """Convert to an ``Authorization`` header value. 

169 

170 .. versionadded:: 2.0 

171 """ 

172 if self.type == "basic": 

173 value = base64.b64encode( 

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

175 ).decode("ascii") 

176 return f"Basic {value}" 

177 

178 if self.token is not None: 

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

180 

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

182 

183 def __str__(self) -> str: 

184 return self.to_header() 

185 

186 def __repr__(self) -> str: 

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

188 

189 

190class WWWAuthenticate: 

191 """A parsed ``WWW-Authenticate`` header. 

192 

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

194 the header. Modifying the instance will update the header. 

195 

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

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

198 token. 

199 

200 .. versionchanged:: 3.2 

201 Considered ``False`` if ``type`` is the empty string. 

202 

203 .. versionchanged:: 3.2 

204 Get/set/delete ``auth["key"]`` and ``auth.key``, along with 

205 ``"key" in auth`` and ``auth.get("key")``, are deprecated and will 

206 be removed in Werkzeug 4.0. Use ``parameters`` directly instead. 

207 

208 .. versionchanged:: 2.3 

209 The ``token`` parameter and attribute was added to support auth schemes 

210 that use a token instead of parameters, such as ``Bearer``. 

211 

212 .. versionchanged:: 2.3 

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

214 

215 .. versionchanged:: 2.3 

216 The ``on_update`` parameter was removed. 

217 """ 

218 

219 def __init__( 

220 self, 

221 auth_type: str = "", 

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

223 token: str | None = None, 

224 ): 

225 self._type = auth_type.lower() 

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

227 values, lambda _: self._trigger_on_update() 

228 ) 

229 self._token = token 

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

231 

232 def _trigger_on_update(self) -> None: 

233 if self._on_update is not None: 

234 self._on_update(self) 

235 

236 @property 

237 def type(self) -> str: 

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

239 return self._type 

240 

241 @type.setter 

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

243 self._type = value 

244 self._trigger_on_update() 

245 

246 @property 

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

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

249 have a value for a given scheme. 

250 """ 

251 return self._parameters 

252 

253 @parameters.setter 

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

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

256 self._trigger_on_update() 

257 

258 @property 

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

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

261 have a value for a given scheme. 

262 """ 

263 return self._token 

264 

265 @token.setter 

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

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

268 value for a given scheme. 

269 

270 .. versionadded:: 2.3 

271 """ 

272 self._token = value 

273 self._trigger_on_update() 

274 

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

276 import warnings 

277 

278 warnings.warn( 

279 "'auth[key]' is deprecated and will be removed in Werkzeug 4.0." 

280 " Use 'auth.parameters[key]' instead.", 

281 DeprecationWarning, 

282 stacklevel=2, 

283 ) 

284 return self.parameters.get(key) 

285 

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

287 import warnings 

288 

289 warnings.warn( 

290 "'auth[key] = value' is deprecated and will be removed in" 

291 " Werkzeug 4.0. Use 'auth.parameters[key] = value' instead.", 

292 DeprecationWarning, 

293 stacklevel=2, 

294 ) 

295 if value is None: 

296 if key in self.parameters: 

297 del self.parameters[key] 

298 else: 

299 self.parameters[key] = value 

300 

301 self._trigger_on_update() 

302 

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

304 import warnings 

305 

306 warnings.warn( 

307 "'del auth[key]' is deprecated and will be removed in Werkzeug 4.0." 

308 " Use 'del auth.parameters[key]' instead.", 

309 DeprecationWarning, 

310 stacklevel=2, 

311 ) 

312 

313 if key in self.parameters: 

314 del self.parameters[key] 

315 self._trigger_on_update() 

316 

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

318 import warnings 

319 

320 warnings.warn( 

321 f"'auth.{name}' is deprecated and will be removed in Werkzeug 4.0." 

322 f" Use 'auth.parameters[\"{name}\"]' instead.", 

323 DeprecationWarning, 

324 stacklevel=2, 

325 ) 

326 return self[name] 

327 

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

329 if name in { 

330 "type", 

331 "_type", 

332 "parameters", 

333 "_parameters", 

334 "token", 

335 "_token", 

336 "_on_update", 

337 }: 

338 super().__setattr__(name, value) 

339 else: 

340 import warnings 

341 

342 warnings.warn( 

343 f"'auth.{name} = value' is deprecated and will be removed in" 

344 f" Werkzeug 4.0. Use 'auth.parameters[\"{name}\"] = value' instead.", 

345 DeprecationWarning, 

346 stacklevel=2, 

347 ) 

348 self[name] = value 

349 

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

351 import warnings 

352 

353 warnings.warn( 

354 f"'del auth.{name}' is deprecated and will be removed in Werkzeug 4.0." 

355 f" Use 'del auth.parameters[\"{name}\"]' instead.", 

356 DeprecationWarning, 

357 stacklevel=2, 

358 ) 

359 del self[name] 

360 

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

362 import warnings 

363 

364 warnings.warn( 

365 "'key in auth' is deprecated and will be removed in Werkzeug 4.0." 

366 " Use 'key in auth.parameters' instead.", 

367 DeprecationWarning, 

368 stacklevel=2, 

369 ) 

370 return key in self.parameters 

371 

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

373 if not isinstance(other, WWWAuthenticate): 

374 return NotImplemented 

375 

376 return ( 

377 other.type == self.type 

378 and other.token == self.token 

379 and other.parameters == self.parameters 

380 ) 

381 

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

383 import warnings 

384 

385 warnings.warn( 

386 "'auth.get(key)' is deprecated and will be removed in Werkzeug 4.0." 

387 " Use 'auth.parameters.get(key)' instead.", 

388 DeprecationWarning, 

389 stacklevel=2, 

390 ) 

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

392 

393 @classmethod 

394 def from_header(cls, value: str | None) -> t.Self: 

395 """Parse a ``WWW-Authenticate`` header value and create an instance of 

396 this class. 

397 

398 :param value: The header value to parse. 

399 

400 .. versionchanged:: 3.2 

401 Return an empty instance instead of ``None`` if the value is empty. 

402 

403 .. versionadded:: 2.3 

404 """ 

405 if not value: 

406 return cls() 

407 

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

409 scheme = scheme.lower() 

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

411 

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

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

414 return cls(scheme, parse_dict_header(rest)) 

415 

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

417 if rest: 

418 return cls(scheme, token=rest) 

419 

420 return cls(scheme) 

421 

422 def to_header(self) -> str: 

423 """Convert to a ``WWW-Authenticate`` header value. 

424 

425 .. versionchanged:: 3.2 

426 Return the empty string if the instance is empty. 

427 """ 

428 if not self: 

429 return "" 

430 

431 if self.token is not None: 

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

433 

434 if not self.parameters: 

435 return self.type.title() 

436 

437 if self.type == "digest": 

438 items = [] 

439 

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

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

442 value = quote_header_value(value, allow_token=False) 

443 else: 

444 value = quote_header_value(value) 

445 

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

447 

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

449 

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

451 

452 def __bool__(self) -> bool: 

453 return self._type != "" 

454 

455 def __str__(self) -> str: 

456 return self.to_header() 

457 

458 def __repr__(self) -> str: 

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