Skip to content

Commit e99b301

Browse files
orsenthilclaude
andcommitted
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 always failed with an unhandled 407. Match ProxyBasicAuthHandler/HTTPBasicAuthHandler: use the proxy's authority (req.host) for the proxy case, and keep using the full URL for ordinary (non-proxy) digest auth so HTTPPasswordMgr's sub-URI realm matching keeps working. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 2507a7f commit e99b301

3 files changed

Lines changed: 71 additions & 1 deletion

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/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)