@@ -1751,6 +1751,66 @@ def test_proxy_basic_auth(self):
17511751 "proxy.example.com:3128" ,
17521752 )
17531753
1754+ def test_proxy_digest_auth (self ):
1755+ # gh-60299: ProxyDigestAuthHandler must look up credentials using
1756+ # the proxy's authority
1757+ opener = OpenerDirector ()
1758+ ph = urllib .request .ProxyHandler (dict (http = "proxy.example.com:3128" ))
1759+ opener .add_handler (ph )
1760+ password_manager = MockPasswordManager ()
1761+ digest_handler = urllib .request .ProxyDigestAuthHandler (password_manager )
1762+ realm = "ACME Networks"
1763+ http_handler = MockHTTPHandlerRedirect (
1764+ 407 ,
1765+ 'Proxy-Authenticate: Digest realm="%s", nonce="abcdef0123456789", '
1766+ 'qop="auth"\r \n \r \n ' % realm )
1767+ opener .add_handler (digest_handler )
1768+ opener .add_handler (http_handler )
1769+
1770+ user , password = "wile" , "coyote"
1771+ request_url = "http://acme.example.com/protected"
1772+ proxy_authority = "proxy.example.com:3128"
1773+
1774+ digest_handler .add_password (realm , proxy_authority , user , password )
1775+ self .assertEqual (realm , password_manager .realm )
1776+ self .assertEqual (proxy_authority , password_manager .url )
1777+
1778+ opener .open (request_url )
1779+
1780+ self .assertEqual (password_manager .target_realm , realm )
1781+ self .assertEqual (password_manager .target_url , proxy_authority )
1782+
1783+ self .assertEqual (len (http_handler .requests ), 2 )
1784+ self .assertFalse (
1785+ http_handler .requests [0 ].has_header ("Proxy-authorization" ))
1786+ self .assertTrue (
1787+ http_handler .requests [1 ].has_header ("Proxy-authorization" ))
1788+ self .assertTrue (
1789+ http_handler .requests [1 ]
1790+ .get_header ("Proxy-authorization" ).startswith ("Digest " ))
1791+
1792+ def test_digest_auth_uses_full_url (self ):
1793+ # The non-proxy case must keep using the full request URL (not
1794+ # just the host) for the password lookup, so that sub-URI realm
1795+ # matching (HTTPPasswordMgr.is_suburi) keeps working
1796+ opener = OpenerDirector ()
1797+ password_manager = MockPasswordManager ()
1798+ digest_handler = urllib .request .HTTPDigestAuthHandler (password_manager )
1799+ realm = "ACME Networks"
1800+ request_url = "http://acme.example.com/protected"
1801+ http_handler = MockHTTPHandlerRedirect (
1802+ 401 ,
1803+ 'WWW-Authenticate: Digest realm="%s", nonce="abcdef0123456789", '
1804+ 'qop="auth"\r \n \r \n ' % realm )
1805+ opener .add_handler (digest_handler )
1806+ opener .add_handler (http_handler )
1807+
1808+ digest_handler .add_password (realm , request_url , "wile" , "coyote" )
1809+ opener .open (request_url )
1810+
1811+ self .assertEqual (password_manager .target_realm , realm )
1812+ self .assertEqual (password_manager .target_url , request_url )
1813+
17541814 def test_basic_and_digest_auth_handlers (self ):
17551815 # HTTPDigestAuthHandler raised an exception if it couldn't handle a 40*
17561816 # response (https://bugs.python.org/issue1479302), where it should instead
@@ -1811,6 +1871,25 @@ def test_unsupported_auth_digest_handler(self):
18111871 opener .add_handler (http_handler )
18121872 self .assertRaises (ValueError , opener .open , "http://www.example.com" )
18131873
1874+ def test_digest_auth_failure_code (self ):
1875+ # gh-53852: a failed proxy digest auth must report 407, not 401.
1876+ headers = http .client .HTTPMessage ()
1877+ for handler_class , auth_header , code in [
1878+ (urllib .request .HTTPDigestAuthHandler ,
1879+ 'www-authenticate' , 401 ),
1880+ (urllib .request .ProxyDigestAuthHandler ,
1881+ 'proxy-authenticate' , 407 )]:
1882+ with self .subTest (handler = handler_class .__name__ ):
1883+ handler = handler_class ()
1884+ handler .retried = 6
1885+ req = Request ("http://www.example.com/" )
1886+ with self .assertRaises (urllib .error .HTTPError ) as cm :
1887+ handler .http_error_auth_reqed (auth_header ,
1888+ "www.example.com" ,
1889+ req , headers )
1890+ self .assertEqual (cm .exception .code , code )
1891+ cm .exception .close ()
1892+
18141893 def test_unsupported_auth_basic_handler (self ):
18151894 # While using BasicAuthHandler
18161895 opener = OpenerDirector ()
0 commit comments