]> git.ipfire.org Git - thirdparty/httpx.git/commitdiff
Handle multiple auth headers correctly (#1240)
authorTom Christie <tom@tomchristie.com>
Tue, 1 Sep 2020 13:08:10 +0000 (14:08 +0100)
committerGitHub <noreply@github.com>
Tue, 1 Sep 2020 13:08:10 +0000 (14:08 +0100)
Handle multiple auth headers correctly

httpx/_auth.py
tests/client/test_auth.py

index 571584593bd861395b4b515860229ff30cd425c4..eb110dea3ae02e2affd7e348c571156a7a89bc34 100644 (file)
@@ -112,28 +112,34 @@ class DigestAuth(Auth):
         response = yield request
 
         if response.status_code != 401 or "www-authenticate" not in response.headers:
-            # If the response is not a 401 WWW-Authenticate, then we don't
+            # If the response is not a 401 then we don't
             # need to build an authenticated request.
             return
 
-        challenge = self._parse_challenge(request, response)
+        for auth_header in response.headers.get_list("www-authenticate"):
+            if auth_header.lower().startswith("digest "):
+                break
+        else:
+            # If the response does not include a 'WWW-Authenticate: Digest ...'
+            # header, then we don't need to build an authenticated request.
+            return
+
+        challenge = self._parse_challenge(request, response, auth_header)
         request.headers["Authorization"] = self._build_auth_header(request, challenge)
         yield request
 
     def _parse_challenge(
-        self, request: Request, response: Response
+        self, request: Request, response: Response, auth_header: str
     ) -> "_DigestAuthChallenge":
         """
         Returns a challenge from a Digest WWW-Authenticate header.
         These take the form of:
         `Digest realm="realm@host.com",qop="auth,auth-int",nonce="abc",opaque="xyz"`
         """
-        header = response.headers["www-authenticate"]
+        scheme, _, fields = auth_header.partition(" ")
 
-        scheme, _, fields = header.partition(" ")
-        if scheme.lower() != "digest":
-            message = "Header does not start with 'Digest'"
-            raise ProtocolError(message, request=request)
+        # This method should only ever have been called with a Digest auth header.
+        assert scheme.lower() == "digest"
 
         header_dict: typing.Dict[str, str] = {}
         for field in parse_http_list(fields):
index 2162e122490d8bcf1b3413f8017131728f53d997..a08c3292fdb60feeeab1a63aa974abc605dbf749 100644 (file)
@@ -356,6 +356,21 @@ async def test_digest_auth_returns_no_auth_if_no_digest_header_in_response() ->
     assert len(response.history) == 0
 
 
+def test_digest_auth_returns_no_auth_if_alternate_auth_scheme() -> None:
+    url = "https://example.org/"
+    auth = DigestAuth(username="tomchristie", password="password123")
+    auth_header = b"Token ..."
+
+    client = httpx.Client(
+        transport=SyncMockTransport(auth_header=auth_header, status_code=401)
+    )
+    response = client.get(url, auth=auth)
+
+    assert response.status_code == 401
+    assert response.json() == {"auth": None}
+    assert len(response.history) == 0
+
+
 @pytest.mark.asyncio
 async def test_digest_auth_200_response_including_digest_auth_header() -> None:
     url = "https://example.org/"
@@ -519,9 +534,6 @@ async def test_digest_auth_incorrect_credentials() -> None:
     "auth_header",
     [
         b'Digest realm="httpx@example.org", qop="auth"',  # missing fields
-        b'realm="httpx@example.org", qop="auth"',  # not starting with Digest
-        b'DigestZ realm="httpx@example.org", qop="auth"'
-        b'qop="auth,auth-int",nonce="abc",opaque="xyz"',
         b'Digest realm="httpx@example.org", qop="auth,au',  # malformed fields list
     ],
 )
@@ -542,9 +554,6 @@ async def test_async_digest_auth_raises_protocol_error_on_malformed_header(
     "auth_header",
     [
         b'Digest realm="httpx@example.org", qop="auth"',  # missing fields
-        b'realm="httpx@example.org", qop="auth"',  # not starting with Digest
-        b'DigestZ realm="httpx@example.org", qop="auth"'
-        b'qop="auth,auth-int",nonce="abc",opaque="xyz"',
         b'Digest realm="httpx@example.org", qop="auth,au',  # malformed fields list
     ],
 )