From 560ada0abc4dd5fc2e9bd584475aca651e7555e8 Mon Sep 17 00:00:00 2001 From: David Sillman Date: Mon, 15 Dec 2025 09:30:45 -0500 Subject: [PATCH] fix: Restore original "auth" DigestAuth test cases with corrections to match RFC-7616 example more closely, adding dedicated unit tests for "auth-int" scenarios --- tests/test_auth.py | 130 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 8 deletions(-) diff --git a/tests/test_auth.py b/tests/test_auth.py index 24a249cf..be2708e9 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -208,7 +208,7 @@ def test_digest_auth_rfc_7616_md5(monkeypatch): headers = { "WWW-Authenticate": ( 'Digest realm="http-auth@example.org", ' - 'qop="auth, auth-int", ' + "qop=auth, " "algorithm=MD5, " 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' @@ -232,14 +232,13 @@ def test_digest_auth_rfc_7616_md5(monkeypatch): 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' in request.headers["Authorization"] ) - assert "qop=auth" in request.headers["Authorization"] + assert "qop=auth," in request.headers["Authorization"] assert ( 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' in request.headers["Authorization"] ) assert ( - # 'response="8ca523f5e9506fed4657c9700eebdbec"' - 'response="7d2b5599cc59f94b525f726e44474803"' + 'response="8ca523f5e9506fed4657c9700eebdbec"' in request.headers["Authorization"] ) @@ -269,7 +268,7 @@ def test_digest_auth_rfc_7616_sha_256(monkeypatch): headers = { "WWW-Authenticate": ( 'Digest realm="http-auth@example.org", ' - 'qop="auth, auth-int", ' + "qop=auth, " "algorithm=SHA-256, " 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' @@ -293,14 +292,129 @@ def test_digest_auth_rfc_7616_sha_256(monkeypatch): 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' in request.headers["Authorization"] ) - assert "qop=auth-int" in request.headers["Authorization"] + assert "qop=auth," in request.headers["Authorization"] assert ( 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' in request.headers["Authorization"] ) assert ( - # 'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1"' - 'response="a2274700215378a04e1a528e3706c7aab17a3fe7a988900a6c439c9509209acf"' + 'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1"' + in request.headers["Authorization"] + ) + + # No other requests are made. + response = httpx.Response(content=b"Hello, world!", status_code=200) + with pytest.raises(StopIteration): + flow.send(response) + + +def test_digest_auth_int_rfc_7616_md5(monkeypatch): + def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes: + return "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ".encode() + + auth = httpx.DigestAuth(username="Mufasa", password="Circle of Life") + monkeypatch.setattr(auth, "_get_client_nonce", mock_get_client_nonce) + + request = httpx.Request("GET", "https://www.example.com/dir/index.html") + + # The initial request should not include an auth header. + flow = auth.sync_auth_flow(request) + request = next(flow) + assert "Authorization" not in request.headers + + # If a 401 response is returned, then a digest auth request is made. + headers = { + "WWW-Authenticate": ( + 'Digest realm="http-auth@example.org", ' + "qop=auth-int, " + "algorithm=MD5, " + 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' + 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' + ) + } + response = httpx.Response( + content=b"Auth required", status_code=401, headers=headers, request=request + ) + request = flow.send(response) + assert request.headers["Authorization"].startswith("Digest") + assert 'username="Mufasa"' in request.headers["Authorization"] + assert 'realm="http-auth@example.org"' in request.headers["Authorization"] + assert 'uri="/dir/index.html"' in request.headers["Authorization"] + assert "algorithm=MD5" in request.headers["Authorization"] + assert ( + 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"' + in request.headers["Authorization"] + ) + assert "nc=00000001" in request.headers["Authorization"] + assert ( + 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' + in request.headers["Authorization"] + ) + assert "qop=auth-int," in request.headers["Authorization"] + assert ( + 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' + in request.headers["Authorization"] + ) + assert ( + 'response="8804a53d3640a40a4f73cea12c5ba451"' + in request.headers["Authorization"] + ) + + # No other requests are made. + response = httpx.Response(content=b"Hello, world!", status_code=200) + with pytest.raises(StopIteration): + flow.send(response) + + +def test_digest_auth_int_rfc7616_sha256(monkeypatch): + def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes: + return "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ".encode() + + auth = httpx.DigestAuth(username="Mufasa", password="Circle of Life") + monkeypatch.setattr(auth, "_get_client_nonce", mock_get_client_nonce) + + request = httpx.Request("GET", "https://www.example.com/dir/index.html") + + # The initial request should not include an auth header. + flow = auth.sync_auth_flow(request) + request = next(flow) + assert "Authorization" not in request.headers + + # If a 401 response is returned, then a digest auth request is made. + headers = { + "WWW-Authenticate": ( + 'Digest realm="http-auth@example.org", ' + "qop=auth-int, " + "algorithm=SHA-256, " + 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", ' + 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' + ) + } + response = httpx.Response( + content=b"Auth required", status_code=401, headers=headers, request=request + ) + request = flow.send(response) + assert request.headers["Authorization"].startswith("Digest") + assert 'username="Mufasa"' in request.headers["Authorization"] + assert 'realm="http-auth@example.org"' in request.headers["Authorization"] + assert 'uri="/dir/index.html"' in request.headers["Authorization"] + assert "algorithm=SHA-256" in request.headers["Authorization"] + assert ( + 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"' + in request.headers["Authorization"] + ) + assert "nc=00000001" in request.headers["Authorization"] + assert ( + 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"' + in request.headers["Authorization"] + ) + assert "qop=auth-int," in request.headers["Authorization"] + assert ( + 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"' + in request.headers["Authorization"] + ) + assert ( + 'response="8bdf6f15638e260831e905028de5450562816d093c9bfc5c13d3a46adcdde940"' in request.headers["Authorization"] )