123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- from __future__ import annotations
- import base64
- import binascii
- import typing as t
- from ..http import dump_header
- from ..http import parse_dict_header
- from ..http import quote_header_value
- from .structures import CallbackDict
- if t.TYPE_CHECKING:
- import typing_extensions as te
- class Authorization:
- """Represents the parts of an ``Authorization`` request header.
- :attr:`.Request.authorization` returns an instance if the header is set.
- An instance can be used with the test :class:`.Client` request methods' ``auth``
- parameter to send the header in test requests.
- Depending on the auth scheme, either :attr:`parameters` or :attr:`token` will be
- set. The ``Basic`` scheme's token is decoded into the ``username`` and ``password``
- parameters.
- For convenience, ``auth["key"]`` and ``auth.key`` both access the key in the
- :attr:`parameters` dict, along with ``auth.get("key")`` and ``"key" in auth``.
- .. versionchanged:: 2.3
- The ``token`` parameter and attribute was added to support auth schemes that use
- a token instead of parameters, such as ``Bearer``.
- .. versionchanged:: 2.3
- The object is no longer a ``dict``.
- .. versionchanged:: 0.5
- The object is an immutable dict.
- """
- def __init__(
- self,
- auth_type: str,
- data: dict[str, str | None] | None = None,
- token: str | None = None,
- ) -> None:
- self.type = auth_type
- """The authorization scheme, like ``basic``, ``digest``, or ``bearer``."""
- if data is None:
- data = {}
- self.parameters = data
- """A dict of parameters parsed from the header. Either this or :attr:`token`
- will have a value for a given scheme.
- """
- self.token = token
- """A token parsed from the header. Either this or :attr:`parameters` will have a
- value for a given scheme.
- .. versionadded:: 2.3
- """
- def __getattr__(self, name: str) -> str | None:
- return self.parameters.get(name)
- def __getitem__(self, name: str) -> str | None:
- return self.parameters.get(name)
- def get(self, key: str, default: str | None = None) -> str | None:
- return self.parameters.get(key, default)
- def __contains__(self, key: str) -> bool:
- return key in self.parameters
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, Authorization):
- return NotImplemented
- return (
- other.type == self.type
- and other.token == self.token
- and other.parameters == self.parameters
- )
- @classmethod
- def from_header(cls, value: str | None) -> te.Self | None:
- """Parse an ``Authorization`` header value and return an instance, or ``None``
- if the value is empty.
- :param value: The header value to parse.
- .. versionadded:: 2.3
- """
- if not value:
- return None
- scheme, _, rest = value.partition(" ")
- scheme = scheme.lower()
- rest = rest.strip()
- if scheme == "basic":
- try:
- username, _, password = base64.b64decode(rest).decode().partition(":")
- except (binascii.Error, UnicodeError):
- return None
- return cls(scheme, {"username": username, "password": password})
- if "=" in rest.rstrip("="):
- # = that is not trailing, this is parameters.
- return cls(scheme, parse_dict_header(rest), None)
- # No = or only trailing =, this is a token.
- return cls(scheme, None, rest)
- def to_header(self) -> str:
- """Produce an ``Authorization`` header value representing this data.
- .. versionadded:: 2.0
- """
- if self.type == "basic":
- value = base64.b64encode(
- f"{self.username}:{self.password}".encode()
- ).decode("utf8")
- return f"Basic {value}"
- if self.token is not None:
- return f"{self.type.title()} {self.token}"
- return f"{self.type.title()} {dump_header(self.parameters)}"
- def __str__(self) -> str:
- return self.to_header()
- def __repr__(self) -> str:
- return f"<{type(self).__name__} {self.to_header()}>"
- class WWWAuthenticate:
- """Represents the parts of a ``WWW-Authenticate`` response header.
- Set :attr:`.Response.www_authenticate` to an instance of list of instances to set
- values for this header in the response. Modifying this instance will modify the
- header value.
- Depending on the auth scheme, either :attr:`parameters` or :attr:`token` should be
- set. The ``Basic`` scheme will encode ``username`` and ``password`` parameters to a
- token.
- For convenience, ``auth["key"]`` and ``auth.key`` both act on the :attr:`parameters`
- dict, and can be used to get, set, or delete parameters. ``auth.get("key")`` and
- ``"key" in auth`` are also provided.
- .. versionchanged:: 2.3
- The ``token`` parameter and attribute was added to support auth schemes that use
- a token instead of parameters, such as ``Bearer``.
- .. versionchanged:: 2.3
- The object is no longer a ``dict``.
- .. versionchanged:: 2.3
- The ``on_update`` parameter was removed.
- """
- def __init__(
- self,
- auth_type: str,
- values: dict[str, str | None] | None = None,
- token: str | None = None,
- ):
- self._type = auth_type.lower()
- self._parameters: dict[str, str | None] = CallbackDict( # type: ignore[misc]
- values, lambda _: self._trigger_on_update()
- )
- self._token = token
- self._on_update: t.Callable[[WWWAuthenticate], None] | None = None
- def _trigger_on_update(self) -> None:
- if self._on_update is not None:
- self._on_update(self)
- @property
- def type(self) -> str:
- """The authorization scheme, like ``basic``, ``digest``, or ``bearer``."""
- return self._type
- @type.setter
- def type(self, value: str) -> None:
- self._type = value
- self._trigger_on_update()
- @property
- def parameters(self) -> dict[str, str | None]:
- """A dict of parameters for the header. Only one of this or :attr:`token` should
- have a value for a given scheme.
- """
- return self._parameters
- @parameters.setter
- def parameters(self, value: dict[str, str]) -> None:
- self._parameters = CallbackDict( # type: ignore[misc]
- value, lambda _: self._trigger_on_update()
- )
- self._trigger_on_update()
- @property
- def token(self) -> str | None:
- """A dict of parameters for the header. Only one of this or :attr:`token` should
- have a value for a given scheme.
- """
- return self._token
- @token.setter
- def token(self, value: str | None) -> None:
- """A token for the header. Only one of this or :attr:`parameters` should have a
- value for a given scheme.
- .. versionadded:: 2.3
- """
- self._token = value
- self._trigger_on_update()
- def __getitem__(self, key: str) -> str | None:
- return self.parameters.get(key)
- def __setitem__(self, key: str, value: str | None) -> None:
- if value is None:
- if key in self.parameters:
- del self.parameters[key]
- else:
- self.parameters[key] = value
- self._trigger_on_update()
- def __delitem__(self, key: str) -> None:
- if key in self.parameters:
- del self.parameters[key]
- self._trigger_on_update()
- def __getattr__(self, name: str) -> str | None:
- return self[name]
- def __setattr__(self, name: str, value: str | None) -> None:
- if name in {"_type", "_parameters", "_token", "_on_update"}:
- super().__setattr__(name, value)
- else:
- self[name] = value
- def __delattr__(self, name: str) -> None:
- del self[name]
- def __contains__(self, key: str) -> bool:
- return key in self.parameters
- def __eq__(self, other: object) -> bool:
- if not isinstance(other, WWWAuthenticate):
- return NotImplemented
- return (
- other.type == self.type
- and other.token == self.token
- and other.parameters == self.parameters
- )
- def get(self, key: str, default: str | None = None) -> str | None:
- return self.parameters.get(key, default)
- @classmethod
- def from_header(cls, value: str | None) -> te.Self | None:
- """Parse a ``WWW-Authenticate`` header value and return an instance, or ``None``
- if the value is empty.
- :param value: The header value to parse.
- .. versionadded:: 2.3
- """
- if not value:
- return None
- scheme, _, rest = value.partition(" ")
- scheme = scheme.lower()
- rest = rest.strip()
- if "=" in rest.rstrip("="):
- # = that is not trailing, this is parameters.
- return cls(scheme, parse_dict_header(rest), None)
- # No = or only trailing =, this is a token.
- return cls(scheme, None, rest)
- def to_header(self) -> str:
- """Produce a ``WWW-Authenticate`` header value representing this data."""
- if self.token is not None:
- return f"{self.type.title()} {self.token}"
- if self.type == "digest":
- items = []
- for key, value in self.parameters.items():
- if key in {"realm", "domain", "nonce", "opaque", "qop"}:
- value = quote_header_value(value, allow_token=False)
- else:
- value = quote_header_value(value)
- items.append(f"{key}={value}")
- return f"Digest {', '.join(items)}"
- return f"{self.type.title()} {dump_header(self.parameters)}"
- def __str__(self) -> str:
- return self.to_header()
- def __repr__(self) -> str:
- return f"<{type(self).__name__} {self.to_header()}>"
|