Skip to content

Digest auth helper #10725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/10725.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added a digest authentication helper class.
1 change: 1 addition & 0 deletions CHANGES/2213.feature.rst
1 change: 1 addition & 0 deletions CONTRIBUTORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ Jesus Cea
Jian Zeng
Jinkyu Yi
Joel Watts
John Feusi
John Parton
Jon Nabozny
Jonas Krüger Svensson
Expand Down
3 changes: 2 additions & 1 deletion aiohttp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from .connector import AddrInfoType, SocketFactoryType
from .cookiejar import CookieJar, DummyCookieJar
from .formdata import FormData
from .helpers import BasicAuth, ChainMapProxy, ETag
from .helpers import BasicAuth, ChainMapProxy, DigestAuth, ETag
from .http import (
HttpVersion,
HttpVersion10,
Expand Down Expand Up @@ -164,6 +164,7 @@
# helpers
"BasicAuth",
"ChainMapProxy",
"DigestAuth",
"ETag",
# http
"HttpVersion",
Expand Down
23 changes: 15 additions & 8 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
from .helpers import (
_SENTINEL,
EMPTY_BODY_METHODS,
AuthBase,
BasicAuth,
TimeoutHandle,
frozen_dataclass_decorator,
Expand Down Expand Up @@ -174,7 +175,7 @@ class _RequestOptions(TypedDict, total=False):
cookies: Union[LooseCookies, None]
headers: Union[LooseHeaders, None]
skip_auto_headers: Union[Iterable[str], None]
auth: Union[BasicAuth, None]
auth: Union[AuthBase, None]
allow_redirects: bool
max_redirects: int
compress: Union[str, bool]
Expand Down Expand Up @@ -272,7 +273,7 @@ def __init__(
proxy: Optional[StrOrURL] = None,
proxy_auth: Optional[BasicAuth] = None,
skip_auto_headers: Optional[Iterable[str]] = None,
auth: Optional[BasicAuth] = None,
auth: Optional[AuthBase] = None,
json_serialize: JSONEncoder = json.dumps,
request_class: Type[ClientRequest] = ClientRequest,
response_class: Type[ClientResponse] = ClientResponse,
Expand Down Expand Up @@ -429,7 +430,7 @@ async def _request(
cookies: Optional[LooseCookies] = None,
headers: Optional[LooseHeaders] = None,
skip_auto_headers: Optional[Iterable[str]] = None,
auth: Optional[BasicAuth] = None,
auth: Optional[AuthBase] = None,
allow_redirects: bool = True,
max_redirects: int = 10,
compress: Union[str, bool] = False,
Expand Down Expand Up @@ -672,6 +673,13 @@ async def _request(
resp = await req.send(conn)
try:
await resp.start(conn)
# Try performing digest authentication. It returns
# True if we need to resend the request, as
# DigestAuth is a bit of a handshake. This is
# a no-op for BasicAuth. If it
if auth is not None and auth.authenticate(resp):
resp.close()
continue
except BaseException:
resp.close()
raise
Expand Down Expand Up @@ -824,7 +832,7 @@ def ws_connect(
autoclose: bool = True,
autoping: bool = True,
heartbeat: Optional[float] = None,
auth: Optional[BasicAuth] = None,
auth: Optional[AuthBase] = None,
origin: Optional[str] = None,
params: Query = None,
headers: Optional[LooseHeaders] = None,
Expand Down Expand Up @@ -872,7 +880,7 @@ async def _ws_connect(
autoclose: bool = True,
autoping: bool = True,
heartbeat: Optional[float] = None,
auth: Optional[BasicAuth] = None,
auth: Optional[AuthBase] = None,
origin: Optional[str] = None,
params: Query = None,
headers: Optional[LooseHeaders] = None,
Expand Down Expand Up @@ -1247,7 +1255,7 @@ def skip_auto_headers(self) -> FrozenSet[istr]:
return self._skip_auto_headers

@property
def auth(self) -> Optional[BasicAuth]: # type: ignore[misc]
def auth(self) -> Optional[AuthBase]:
"""An object that represents HTTP Basic Authorization"""
return self._default_auth

Expand Down Expand Up @@ -1412,8 +1420,7 @@ def request(
headers - (optional) Dictionary of HTTP Headers to send with
the request
cookies - (optional) Dict object to send with the request
auth - (optional) BasicAuth named tuple represent HTTP Basic Auth
auth - aiohttp.helpers.BasicAuth
auth - (optional) something implementing AuthBase for authentication
allow_redirects - (optional) If set to False, do not follow
redirects
version - Request HTTP version.
Expand Down
22 changes: 13 additions & 9 deletions aiohttp/client_reqrep.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@
from .hdrs import CONTENT_TYPE
from .helpers import (
_SENTINEL,
AuthBase,
BaseTimerContext,
BasicAuth,
DigestAuth,
HeadersMixin,
TimerNoop,
basicauth_from_netrc,
Expand Down Expand Up @@ -230,7 +232,7 @@ def __init__(
skip_auto_headers: Optional[Iterable[str]] = None,
data: Any = None,
cookies: Optional[LooseCookies] = None,
auth: Optional[BasicAuth] = None,
auth: Optional[AuthBase] = None,
version: http.HttpVersion = http.HttpVersion11,
compress: Union[str, bool] = False,
chunked: Optional[bool] = None,
Expand Down Expand Up @@ -287,12 +289,12 @@ def __init__(
self.update_auto_headers(skip_auto_headers)
self.update_cookies(cookies)
self.update_content_encoding(data, compress)
self.update_auth(auth, trust_env)
self.update_proxy(proxy, proxy_auth, proxy_headers)

self.update_body_from_data(data)
if data is not None or self.method not in self.GET_METHODS:
self.update_transfer_encoding()
self.update_auth(auth, trust_env) # Must be after we set the body
self.update_expect_continue(expect100)
self._traces = [] if traces is None else traces

Expand Down Expand Up @@ -322,7 +324,7 @@ def ssl(self) -> Union["SSLContext", bool, Fingerprint]:
return self._ssl

@property
def connection_key(self) -> ConnectionKey: # type: ignore[misc]
def connection_key(self) -> ConnectionKey:
if proxy_headers := self.proxy_headers:
h: Optional[int] = hash(tuple(proxy_headers.items()))
else:
Expand Down Expand Up @@ -370,7 +372,7 @@ def update_host(self, url: URL) -> None:

# basic auth info
if url.raw_user or url.raw_password:
self.auth = helpers.BasicAuth(url.user or "", url.password or "")
self.auth = BasicAuth(url.user or "", url.password or "")

def update_version(self, version: Union[http.HttpVersion, str]) -> None:
"""Convert request version to two elements tuple.
Expand Down Expand Up @@ -494,7 +496,7 @@ def update_transfer_encoding(self) -> None:
if hdrs.CONTENT_LENGTH not in self.headers:
self.headers[hdrs.CONTENT_LENGTH] = str(len(self.body))

def update_auth(self, auth: Optional[BasicAuth], trust_env: bool = False) -> None:
def update_auth(self, auth: Optional[AuthBase], trust_env: bool = False) -> None:
"""Set basic auth."""
if auth is None:
auth = self.auth
Expand All @@ -505,10 +507,12 @@ def update_auth(self, auth: Optional[BasicAuth], trust_env: bool = False) -> Non
if auth is None:
return

if not isinstance(auth, helpers.BasicAuth):
raise TypeError("BasicAuth() tuple is required instead")
if not isinstance(auth, BasicAuth) and not isinstance(auth, DigestAuth):
raise TypeError("BasicAuth() or DigestAuth() is required instead")

self.headers[hdrs.AUTHORIZATION] = auth.encode()
authorization_hdr = auth.encode(self.method, self.url, self.body)
if authorization_hdr:
self.headers[hdrs.AUTHORIZATION] = authorization_hdr

def update_body_from_data(self, body: Any) -> None:
if body is None:
Expand Down Expand Up @@ -570,7 +574,7 @@ def update_proxy(
self.proxy_headers = None
return

if proxy_auth and not isinstance(proxy_auth, helpers.BasicAuth):
if proxy_auth and not isinstance(proxy_auth, BasicAuth):
raise ValueError("proxy_auth must be None or BasicAuth() tuple")
self.proxy_auth = proxy_auth

Expand Down
Loading
Loading