Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/datastructures/auth.py: 35%
141 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 07:17 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 07:17 +0000
1from __future__ import annotations
3import base64
4import binascii
5import typing as t
7from ..http import dump_header
8from ..http import parse_dict_header
9from ..http import quote_header_value
10from .structures import CallbackDict
12if t.TYPE_CHECKING:
13 import typing_extensions as te
16class Authorization:
17 """Represents the parts of an ``Authorization`` request header.
19 :attr:`.Request.authorization` returns an instance if the header is set.
21 An instance can be used with the test :class:`.Client` request methods' ``auth``
22 parameter to send the header in test requests.
24 Depending on the auth scheme, either :attr:`parameters` or :attr:`token` will be
25 set. The ``Basic`` scheme's token is decoded into the ``username`` and ``password``
26 parameters.
28 For convenience, ``auth["key"]`` and ``auth.key`` both access the key in the
29 :attr:`parameters` dict, along with ``auth.get("key")`` and ``"key" in auth``.
31 .. versionchanged:: 2.3
32 The ``token`` parameter and attribute was added to support auth schemes that use
33 a token instead of parameters, such as ``Bearer``.
35 .. versionchanged:: 2.3
36 The object is no longer a ``dict``.
38 .. versionchanged:: 0.5
39 The object is an immutable dict.
40 """
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``."""
51 if data is None:
52 data = {}
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 """
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.
63 .. versionadded:: 2.3
64 """
66 def __getattr__(self, name: str) -> str | None:
67 return self.parameters.get(name)
69 def __getitem__(self, name: str) -> str | None:
70 return self.parameters.get(name)
72 def get(self, key: str, default: str | None = None) -> str | None:
73 return self.parameters.get(key, default)
75 def __contains__(self, key: str) -> bool:
76 return key in self.parameters
78 def __eq__(self, other: object) -> bool:
79 if not isinstance(other, Authorization):
80 return NotImplemented
82 return (
83 other.type == self.type
84 and other.token == self.token
85 and other.parameters == self.parameters
86 )
88 @classmethod
89 def from_header(cls, value: str | None) -> te.Self | None:
90 """Parse an ``Authorization`` header value and return an instance, or ``None``
91 if the value is empty.
93 :param value: The header value to parse.
95 .. versionadded:: 2.3
96 """
97 if not value:
98 return None
100 scheme, _, rest = value.partition(" ")
101 scheme = scheme.lower()
102 rest = rest.strip()
104 if scheme == "basic":
105 try:
106 username, _, password = base64.b64decode(rest).decode().partition(":")
107 except (binascii.Error, UnicodeError):
108 return None
110 return cls(scheme, {"username": username, "password": password})
112 if "=" in rest.rstrip("="):
113 # = that is not trailing, this is parameters.
114 return cls(scheme, parse_dict_header(rest), None)
116 # No = or only trailing =, this is a token.
117 return cls(scheme, None, rest)
119 def to_header(self) -> str:
120 """Produce an ``Authorization`` header value representing this data.
122 .. versionadded:: 2.0
123 """
124 if self.type == "basic":
125 value = base64.b64encode(
126 f"{self.username}:{self.password}".encode()
127 ).decode("utf8")
128 return f"Basic {value}"
130 if self.token is not None:
131 return f"{self.type.title()} {self.token}"
133 return f"{self.type.title()} {dump_header(self.parameters)}"
135 def __str__(self) -> str:
136 return self.to_header()
138 def __repr__(self) -> str:
139 return f"<{type(self).__name__} {self.to_header()}>"
142class WWWAuthenticate:
143 """Represents the parts of a ``WWW-Authenticate`` response header.
145 Set :attr:`.Response.www_authenticate` to an instance of list of instances to set
146 values for this header in the response. Modifying this instance will modify the
147 header value.
149 Depending on the auth scheme, either :attr:`parameters` or :attr:`token` should be
150 set. The ``Basic`` scheme will encode ``username`` and ``password`` parameters to a
151 token.
153 For convenience, ``auth["key"]`` and ``auth.key`` both act on the :attr:`parameters`
154 dict, and can be used to get, set, or delete parameters. ``auth.get("key")`` and
155 ``"key" in auth`` are also provided.
157 .. versionchanged:: 2.3
158 The ``token`` parameter and attribute was added to support auth schemes that use
159 a token instead of parameters, such as ``Bearer``.
161 .. versionchanged:: 2.3
162 The object is no longer a ``dict``.
164 .. versionchanged:: 2.3
165 The ``on_update`` parameter was removed.
166 """
168 def __init__(
169 self,
170 auth_type: str,
171 values: dict[str, str | None] | None = None,
172 token: str | None = None,
173 ):
174 self._type = auth_type.lower()
175 self._parameters: dict[str, str | None] = CallbackDict( # type: ignore[misc]
176 values, lambda _: self._trigger_on_update()
177 )
178 self._token = token
179 self._on_update: t.Callable[[WWWAuthenticate], None] | None = None
181 def _trigger_on_update(self) -> None:
182 if self._on_update is not None:
183 self._on_update(self)
185 @property
186 def type(self) -> str:
187 """The authorization scheme, like ``basic``, ``digest``, or ``bearer``."""
188 return self._type
190 @type.setter
191 def type(self, value: str) -> None:
192 self._type = value
193 self._trigger_on_update()
195 @property
196 def parameters(self) -> dict[str, str | None]:
197 """A dict of parameters for the header. Only one of this or :attr:`token` should
198 have a value for a given scheme.
199 """
200 return self._parameters
202 @parameters.setter
203 def parameters(self, value: dict[str, str]) -> None:
204 self._parameters = CallbackDict( # type: ignore[misc]
205 value, lambda _: self._trigger_on_update()
206 )
207 self._trigger_on_update()
209 @property
210 def token(self) -> str | None:
211 """A dict of parameters for the header. Only one of this or :attr:`token` should
212 have a value for a given scheme.
213 """
214 return self._token
216 @token.setter
217 def token(self, value: str | None) -> None:
218 """A token for the header. Only one of this or :attr:`parameters` should have a
219 value for a given scheme.
221 .. versionadded:: 2.3
222 """
223 self._token = value
224 self._trigger_on_update()
226 def __getitem__(self, key: str) -> str | None:
227 return self.parameters.get(key)
229 def __setitem__(self, key: str, value: str | None) -> None:
230 if value is None:
231 if key in self.parameters:
232 del self.parameters[key]
233 else:
234 self.parameters[key] = value
236 self._trigger_on_update()
238 def __delitem__(self, key: str) -> None:
239 if key in self.parameters:
240 del self.parameters[key]
241 self._trigger_on_update()
243 def __getattr__(self, name: str) -> str | None:
244 return self[name]
246 def __setattr__(self, name: str, value: str | None) -> None:
247 if name in {"_type", "_parameters", "_token", "_on_update"}:
248 super().__setattr__(name, value)
249 else:
250 self[name] = value
252 def __delattr__(self, name: str) -> None:
253 del self[name]
255 def __contains__(self, key: str) -> bool:
256 return key in self.parameters
258 def __eq__(self, other: object) -> bool:
259 if not isinstance(other, WWWAuthenticate):
260 return NotImplemented
262 return (
263 other.type == self.type
264 and other.token == self.token
265 and other.parameters == self.parameters
266 )
268 def get(self, key: str, default: str | None = None) -> str | None:
269 return self.parameters.get(key, default)
271 @classmethod
272 def from_header(cls, value: str | None) -> te.Self | None:
273 """Parse a ``WWW-Authenticate`` header value and return an instance, or ``None``
274 if the value is empty.
276 :param value: The header value to parse.
278 .. versionadded:: 2.3
279 """
280 if not value:
281 return None
283 scheme, _, rest = value.partition(" ")
284 scheme = scheme.lower()
285 rest = rest.strip()
287 if "=" in rest.rstrip("="):
288 # = that is not trailing, this is parameters.
289 return cls(scheme, parse_dict_header(rest), None)
291 # No = or only trailing =, this is a token.
292 return cls(scheme, None, rest)
294 def to_header(self) -> str:
295 """Produce a ``WWW-Authenticate`` header value representing this data."""
296 if self.token is not None:
297 return f"{self.type.title()} {self.token}"
299 if self.type == "digest":
300 items = []
302 for key, value in self.parameters.items():
303 if key in {"realm", "domain", "nonce", "opaque", "qop"}:
304 value = quote_header_value(value, allow_token=False)
305 else:
306 value = quote_header_value(value)
308 items.append(f"{key}={value}")
310 return f"Digest {', '.join(items)}"
312 return f"{self.type.title()} {dump_header(self.parameters)}"
314 def __str__(self) -> str:
315 return self.to_header()
317 def __repr__(self) -> str:
318 return f"<{type(self).__name__} {self.to_header()}>"