Skip to content

Commit 194789e

Browse files
committed
gh-60299: Fix ProxyDigestAuthHandler credential lookup to use proxy authority
AbstractDigestAuthHandler.get_authorization() always looked up credentials using the target request URL (req.full_url), even when invoked via ProxyDigestAuthHandler, whose credentials are registered against the proxy's host:port. The lookup never matched, no Proxy-Authorization header was produced, and proxy digest auth failed with an unhandled 407. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Fix urllib2_localnet test cases.
1 parent 5ca490b commit 194789e

4 files changed

Lines changed: 76 additions & 4 deletions

File tree

‎Lib/test/test_urllib2.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,66 @@ def test_proxy_basic_auth(self):
17351735
"proxy.example.com:3128",
17361736
)
17371737

1738+
def test_proxy_digest_auth(self):
1739+
# gh-60299: ProxyDigestAuthHandler must look up credentials using
1740+
# the proxy's authority
1741+
opener = OpenerDirector()
1742+
ph = urllib.request.ProxyHandler(dict(http="proxy.example.com:3128"))
1743+
opener.add_handler(ph)
1744+
password_manager = MockPasswordManager()
1745+
digest_handler = urllib.request.ProxyDigestAuthHandler(password_manager)
1746+
realm = "ACME Networks"
1747+
http_handler = MockHTTPHandlerRedirect(
1748+
407,
1749+
'Proxy-Authenticate: Digest realm="%s", nonce="abcdef0123456789", '
1750+
'qop="auth"\r\n\r\n' % realm)
1751+
opener.add_handler(digest_handler)
1752+
opener.add_handler(http_handler)
1753+
1754+
user, password = "wile", "coyote"
1755+
request_url = "http://acme.example.com/protected"
1756+
proxy_authority = "proxy.example.com:3128"
1757+
1758+
digest_handler.add_password(realm, proxy_authority, user, password)
1759+
self.assertEqual(realm, password_manager.realm)
1760+
self.assertEqual(proxy_authority, password_manager.url)
1761+
1762+
opener.open(request_url)
1763+
1764+
self.assertEqual(password_manager.target_realm, realm)
1765+
self.assertEqual(password_manager.target_url, proxy_authority)
1766+
1767+
self.assertEqual(len(http_handler.requests), 2)
1768+
self.assertFalse(
1769+
http_handler.requests[0].has_header("Proxy-authorization"))
1770+
self.assertTrue(
1771+
http_handler.requests[1].has_header("Proxy-authorization"))
1772+
self.assertTrue(
1773+
http_handler.requests[1]
1774+
.get_header("Proxy-authorization").startswith("Digest "))
1775+
1776+
def test_digest_auth_uses_full_url(self):
1777+
# The non-proxy case must keep using the full request URL (not
1778+
# just the host) for the password lookup, so that sub-URI realm
1779+
# matching (HTTPPasswordMgr.is_suburi) keeps working
1780+
opener = OpenerDirector()
1781+
password_manager = MockPasswordManager()
1782+
digest_handler = urllib.request.HTTPDigestAuthHandler(password_manager)
1783+
realm = "ACME Networks"
1784+
request_url = "http://acme.example.com/protected"
1785+
http_handler = MockHTTPHandlerRedirect(
1786+
401,
1787+
'WWW-Authenticate: Digest realm="%s", nonce="abcdef0123456789", '
1788+
'qop="auth"\r\n\r\n' % realm)
1789+
opener.add_handler(digest_handler)
1790+
opener.add_handler(http_handler)
1791+
1792+
digest_handler.add_password(realm, request_url, "wile", "coyote")
1793+
opener.open(request_url)
1794+
1795+
self.assertEqual(password_manager.target_realm, realm)
1796+
self.assertEqual(password_manager.target_url, request_url)
1797+
17381798
def test_basic_and_digest_auth_handlers(self):
17391799
# HTTPDigestAuthHandler raised an exception if it couldn't handle a 40*
17401800
# response (https://bugs.python.org/issue1479302), where it should instead

‎Lib/test/test_urllib2_localnet.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,8 @@ def create_fake_proxy_handler(*args, **kwargs):
351351
self.server.start()
352352
self.server.ready.wait()
353353
proxy_url = "http://127.0.0.1:%d" % self.server.port
354+
# Credentials must be registered against proxy authority
355+
self.proxy_authority = "127.0.0.1:%d" % self.server.port
354356
handler = urllib.request.ProxyHandler({"http" : proxy_url})
355357
self.proxy_digest_handler = urllib.request.ProxyDigestAuthHandler()
356358
self.opener = urllib.request.build_opener(
@@ -361,7 +363,7 @@ def stop_server(self):
361363
self.server = None
362364

363365
def test_proxy_with_bad_password_raises_httperror(self):
364-
self.proxy_digest_handler.add_password(self.REALM, self.URL,
366+
self.proxy_digest_handler.add_password(self.REALM, self.proxy_authority,
365367
self.USER, self.PASSWD+"bad")
366368
self.digest_auth_handler.set_qop("auth")
367369
with self.assertRaises(urllib.error.HTTPError) as cm:
@@ -375,15 +377,15 @@ def test_proxy_with_no_password_raises_httperror(self):
375377
cm.exception.close()
376378

377379
def test_proxy_qop_auth_works(self):
378-
self.proxy_digest_handler.add_password(self.REALM, self.URL,
380+
self.proxy_digest_handler.add_password(self.REALM, self.proxy_authority,
379381
self.USER, self.PASSWD)
380382
self.digest_auth_handler.set_qop("auth")
381383
with self.opener.open(self.URL) as result:
382384
while result.read():
383385
pass
384386

385387
def test_proxy_qop_auth_int_works_or_throws_urlerror(self):
386-
self.proxy_digest_handler.add_password(self.REALM, self.URL,
388+
self.proxy_digest_handler.add_password(self.REALM, self.proxy_authority,
387389
self.USER, self.PASSWD)
388390
self.digest_auth_handler.set_qop("auth-int")
389391
try:

‎Lib/urllib/request.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1144,7 +1144,13 @@ def get_authorization(self, req, chal):
11441144
if H is None:
11451145
return None
11461146

1147-
user, pw = self.passwd.find_user_password(realm, req.full_url)
1147+
# proxy auth is looked up by the proxy's authority
1148+
# req.host, set by Request.set_proxy()
1149+
if self.auth_header == 'Proxy-Authorization':
1150+
authority = req.host
1151+
else:
1152+
authority = req.full_url
1153+
user, pw = self.passwd.find_user_password(realm, authority)
11481154
if user is None:
11491155
return None
11501156

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :class:`urllib.request.ProxyDigestAuthHandler` failing to authenticate. It
2+
looked up credentials using the target request URL instead of the proxy's
3+
authority, so passwords registered via ``add_password()`` against the proxy
4+
never matched. Patch by Senthil Kumaran.

0 commit comments

Comments
 (0)