@@ -208,7 +208,7 @@ def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes:
208208 headers = {
209209 "WWW-Authenticate" : (
210210 'Digest realm="http-auth@example.org", '
211- ' qop=" auth, auth-int", '
211+ " qop=auth, "
212212 "algorithm=MD5, "
213213 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", '
214214 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
@@ -232,14 +232,13 @@ def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes:
232232 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"'
233233 in request .headers ["Authorization" ]
234234 )
235- assert "qop=auth" in request .headers ["Authorization" ]
235+ assert "qop=auth, " in request .headers ["Authorization" ]
236236 assert (
237237 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
238238 in request .headers ["Authorization" ]
239239 )
240240 assert (
241- # 'response="8ca523f5e9506fed4657c9700eebdbec"'
242- 'response="7d2b5599cc59f94b525f726e44474803"'
241+ 'response="8ca523f5e9506fed4657c9700eebdbec"'
243242 in request .headers ["Authorization" ]
244243 )
245244
@@ -269,7 +268,7 @@ def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes:
269268 headers = {
270269 "WWW-Authenticate" : (
271270 'Digest realm="http-auth@example.org", '
272- ' qop=" auth, auth-int", '
271+ " qop=auth, "
273272 "algorithm=SHA-256, "
274273 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", '
275274 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
@@ -293,14 +292,129 @@ def mock_get_client_nonce(nonce_count: int, nonce: bytes) -> bytes:
293292 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"'
294293 in request .headers ["Authorization" ]
295294 )
296- assert "qop=auth-int " in request .headers ["Authorization" ]
295+ assert "qop=auth, " in request .headers ["Authorization" ]
297296 assert (
298297 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
299298 in request .headers ["Authorization" ]
300299 )
301300 assert (
302- # 'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1"'
303- 'response="a2274700215378a04e1a528e3706c7aab17a3fe7a988900a6c439c9509209acf"'
301+ 'response="753927fa0e85d155564e2e272a28d1802ca10daf4496794697cf8db5856cb6c1"'
302+ in request .headers ["Authorization" ]
303+ )
304+
305+ # No other requests are made.
306+ response = httpx .Response (content = b"Hello, world!" , status_code = 200 )
307+ with pytest .raises (StopIteration ):
308+ flow .send (response )
309+
310+
311+ def test_digest_auth_int_rfc_7616_md5 (monkeypatch ):
312+ def mock_get_client_nonce (nonce_count : int , nonce : bytes ) -> bytes :
313+ return "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ" .encode ()
314+
315+ auth = httpx .DigestAuth (username = "Mufasa" , password = "Circle of Life" )
316+ monkeypatch .setattr (auth , "_get_client_nonce" , mock_get_client_nonce )
317+
318+ request = httpx .Request ("GET" , "https://www.example.com/dir/index.html" )
319+
320+ # The initial request should not include an auth header.
321+ flow = auth .sync_auth_flow (request )
322+ request = next (flow )
323+ assert "Authorization" not in request .headers
324+
325+ # If a 401 response is returned, then a digest auth request is made.
326+ headers = {
327+ "WWW-Authenticate" : (
328+ 'Digest realm="http-auth@example.org", '
329+ "qop=auth-int, "
330+ "algorithm=MD5, "
331+ 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", '
332+ 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
333+ )
334+ }
335+ response = httpx .Response (
336+ content = b"Auth required" , status_code = 401 , headers = headers , request = request
337+ )
338+ request = flow .send (response )
339+ assert request .headers ["Authorization" ].startswith ("Digest" )
340+ assert 'username="Mufasa"' in request .headers ["Authorization" ]
341+ assert 'realm="http-auth@example.org"' in request .headers ["Authorization" ]
342+ assert 'uri="/dir/index.html"' in request .headers ["Authorization" ]
343+ assert "algorithm=MD5" in request .headers ["Authorization" ]
344+ assert (
345+ 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"'
346+ in request .headers ["Authorization" ]
347+ )
348+ assert "nc=00000001" in request .headers ["Authorization" ]
349+ assert (
350+ 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"'
351+ in request .headers ["Authorization" ]
352+ )
353+ assert "qop=auth-int," in request .headers ["Authorization" ]
354+ assert (
355+ 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
356+ in request .headers ["Authorization" ]
357+ )
358+ assert (
359+ 'response="8804a53d3640a40a4f73cea12c5ba451"'
360+ in request .headers ["Authorization" ]
361+ )
362+
363+ # No other requests are made.
364+ response = httpx .Response (content = b"Hello, world!" , status_code = 200 )
365+ with pytest .raises (StopIteration ):
366+ flow .send (response )
367+
368+
369+ def test_digest_auth_int_rfc7616_sha256 (monkeypatch ):
370+ def mock_get_client_nonce (nonce_count : int , nonce : bytes ) -> bytes :
371+ return "f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ" .encode ()
372+
373+ auth = httpx .DigestAuth (username = "Mufasa" , password = "Circle of Life" )
374+ monkeypatch .setattr (auth , "_get_client_nonce" , mock_get_client_nonce )
375+
376+ request = httpx .Request ("GET" , "https://www.example.com/dir/index.html" )
377+
378+ # The initial request should not include an auth header.
379+ flow = auth .sync_auth_flow (request )
380+ request = next (flow )
381+ assert "Authorization" not in request .headers
382+
383+ # If a 401 response is returned, then a digest auth request is made.
384+ headers = {
385+ "WWW-Authenticate" : (
386+ 'Digest realm="http-auth@example.org", '
387+ "qop=auth-int, "
388+ "algorithm=SHA-256, "
389+ 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v", '
390+ 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
391+ )
392+ }
393+ response = httpx .Response (
394+ content = b"Auth required" , status_code = 401 , headers = headers , request = request
395+ )
396+ request = flow .send (response )
397+ assert request .headers ["Authorization" ].startswith ("Digest" )
398+ assert 'username="Mufasa"' in request .headers ["Authorization" ]
399+ assert 'realm="http-auth@example.org"' in request .headers ["Authorization" ]
400+ assert 'uri="/dir/index.html"' in request .headers ["Authorization" ]
401+ assert "algorithm=SHA-256" in request .headers ["Authorization" ]
402+ assert (
403+ 'nonce="7ypf/xlj9XXwfDPEoM4URrv/xwf94BcCAzFZH4GiTo0v"'
404+ in request .headers ["Authorization" ]
405+ )
406+ assert "nc=00000001" in request .headers ["Authorization" ]
407+ assert (
408+ 'cnonce="f2/wE4q74E6zIJEtWaHKaf5wv/H5QzzpXusqGemxURZJ"'
409+ in request .headers ["Authorization" ]
410+ )
411+ assert "qop=auth-int," in request .headers ["Authorization" ]
412+ assert (
413+ 'opaque="FQhe/qaU925kfnzjCev0ciny7QMkPqMAFRtzCUYo5tdS"'
414+ in request .headers ["Authorization" ]
415+ )
416+ assert (
417+ 'response="8bdf6f15638e260831e905028de5450562816d093c9bfc5c13d3a46adcdde940"'
304418 in request .headers ["Authorization" ]
305419 )
306420
0 commit comments