# HG changeset patch # Parent 843441e674e5c008808ff4b17bea685038a4bdba diff -r 843441e674e5 Doc/library/urllib.request.rst --- a/Doc/library/urllib.request.rst Sat Jun 20 21:39:51 2015 -0700 +++ b/Doc/library/urllib.request.rst Sun Jun 21 07:37:05 2015 +0000 @@ -926,6 +926,12 @@ ``"python.org:80"`` are fine, ``"joe:password@python.org"`` is not). +.. method:: AbstractBasicAuthHandler.add_password(realm, uri, user, passwd) + + Delegates to the :meth:`~HTTPPasswordMgr.add_password` method of + the *password_mgr* object, or an internal :class:`HTTPPasswordMgr` object. + + .. _http-basic-auth-handler: HTTPBasicAuthHandler Objects @@ -962,6 +968,12 @@ error headers. +.. method:: AbstractDigestAuthHandler.add_password(realm, uri, user, passwd) + + Delegates to the :meth:`~HTTPPasswordMgr.add_password` method of + the *password_mgr* object, or an internal :class:`HTTPPasswordMgr` object. + + .. _http-digest-auth-handler: HTTPDigestAuthHandler Objects diff -r 843441e674e5 Lib/test/test_urllib2.py --- a/Lib/test/test_urllib2.py Sat Jun 20 21:39:51 2015 -0700 +++ b/Lib/test/test_urllib2.py Sun Jun 21 07:37:05 2015 +0000 @@ -1432,11 +1432,53 @@ request_url, protected_url): import base64 user, password = "wile", "coyote" + userpass = bytes('%s:%s' % (user, password), "ascii") + auth_hdr_value = ('Basic ' + + base64.encodebytes(userpass).strip().decode()) + self._test_auth( + opener, auth_handler, auth_header, realm, http_handler, + password_manager, request_url, protected_url, + user, password, auth_hdr_value, + ) + def test_proxy_digest_auth(self): + # Auth details copied from RFC 2617's example + # Digest URI and therefore digest response parameters are different + proxy = "http://proxy.example.com:3128" + ph = urllib.request.ProxyHandler({"http": proxy}) + passwords = MockPasswordManager() + auth_handler = urllib.request.ProxyDigestAuthHandler(passwords) + auth_handler.get_cnonce = lambda nonce: "0a4f113b" + http_handler = MockHTTPHandler(407, "Proxy-Authenticate: Digest " + 'realm="testrealm@host.com",qop="auth",' + 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",' + 'opaque="5ccc069c403ebaf9f0171e9517f40e41"\r\n\r\n') + opener = OpenerDirector() + opener.add_handler(ph) + opener.add_handler(auth_handler) + opener.add_handler(http_handler) + expected = ('Digest username="Mufasa", realm="testrealm@host.com", ' + 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ' + 'uri="http://www.nowhere.org/dir/index.html", ' + 'response="731ce59ce1621682c84c3f4af20d812c", ' + 'opaque="5ccc069c403ebaf9f0171e9517f40e41", ' + 'algorithm="MD5", qop=auth, nc=00000001, cnonce="0a4f113b"') + self._test_auth( + opener, auth_handler, "Proxy-authorization", + "testrealm@host.com", http_handler, passwords, + "http://www.nowhere.org/dir/index.html", proxy, + "Mufasa", "Circle Of Life", expected, + ) + + def _test_auth( + self, opener, auth_handler, auth_header, realm, http_handler, + password_manager, request_url, protected_url, + user, password, auth_hdr_value, + ): # .add_password() fed through to password manager - auth_handler.add_password(realm, request_url, user, password) - self.assertEqual(realm, password_manager.realm) - self.assertEqual(request_url, password_manager.url) + auth_handler.add_password("dummy realm", "dummy URI", user, password) + self.assertEqual("dummy realm", password_manager.realm) + self.assertEqual("dummy URI", password_manager.url) self.assertEqual(user, password_manager.user) self.assertEqual(password, password_manager.password) @@ -1449,9 +1491,6 @@ # expect one request without authorization, then one with self.assertEqual(len(http_handler.requests), 2) self.assertFalse(http_handler.requests[0].has_header(auth_header)) - userpass = bytes('%s:%s' % (user, password), "ascii") - auth_hdr_value = ('Basic ' + - base64.encodebytes(userpass).strip().decode()) self.assertEqual(http_handler.requests[1].get_header(auth_header), auth_hdr_value) self.assertEqual(http_handler.requests[1].unredirected_hdrs[auth_header], diff -r 843441e674e5 Lib/test/test_urllib2_localnet.py --- a/Lib/test/test_urllib2_localnet.py Sat Jun 20 21:39:51 2015 -0700 +++ b/Lib/test/test_urllib2_localnet.py Sun Jun 21 07:37:05 2015 +0000 @@ -333,8 +333,8 @@ self.server = LoopbackHttpServerThread(create_fake_proxy_handler) self.server.start() self.server.ready.wait() - proxy_url = "http://127.0.0.1:%d" % self.server.port - handler = urllib.request.ProxyHandler({"http" : proxy_url}) + self.proxy_url = "http://127.0.0.1:%d" % self.server.port + handler = urllib.request.ProxyHandler({"http" : self.proxy_url}) self.proxy_digest_handler = urllib.request.ProxyDigestAuthHandler() self.opener = urllib.request.build_opener( handler, self.proxy_digest_handler) @@ -344,10 +344,10 @@ super(ProxyAuthTests, self).tearDown() def test_proxy_with_bad_password_raises_httperror(self): - self.proxy_digest_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.proxy_url, self.USER, self.PASSWD+"bad") self.digest_auth_handler.set_qop("auth") - self.assertRaises(urllib.error.HTTPError, + self.assertRaisesRegex(urllib.error.HTTPError, "digest auth failed", self.opener.open, self.URL) @@ -358,7 +358,7 @@ self.URL) def test_proxy_qop_auth_works(self): - self.proxy_digest_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.proxy_url, self.USER, self.PASSWD) self.digest_auth_handler.set_qop("auth") result = self.opener.open(self.URL) @@ -367,15 +367,16 @@ result.close() def test_proxy_qop_auth_int_works_or_throws_urlerror(self): - self.proxy_digest_handler.add_password(self.REALM, self.URL, + self.proxy_digest_handler.add_password(self.REALM, self.proxy_url, self.USER, self.PASSWD) self.digest_auth_handler.set_qop("auth-int") try: result = self.opener.open(self.URL) - except urllib.error.URLError: + except urllib.error.URLError as err: # It's okay if we don't support auth-int, but we certainly - # shouldn't receive any kind of exception here other than - # a URLError. + # shouldn't receive any other error here. + if "qop 'auth-int' is not supported" not in format(err): + raise result = None if result: while result.read(): diff -r 843441e674e5 Lib/urllib/request.py --- a/Lib/urllib/request.py Sat Jun 20 21:39:51 2015 -0700 +++ b/Lib/urllib/request.py Sun Jun 21 07:37:05 2015 +0000 @@ -91,6 +91,7 @@ import posixpath import re import socket +import string import sys import time import collections @@ -1015,15 +1016,15 @@ if authreq: scheme = authreq.split()[0] if scheme.lower() == 'digest': - return self.retry_http_digest_auth(req, authreq) + return self.retry_http_digest_auth(host, req, authreq) elif scheme.lower() != 'basic': raise ValueError("AbstractDigestAuthHandler does not support" " the following scheme: '%s'" % scheme) - def retry_http_digest_auth(self, req, auth): + def retry_http_digest_auth(self, authuri, req, auth): token, challenge = auth.split(' ', 1) chal = parse_keqv_list(filter(None, parse_http_list(challenge))) - auth = self.get_authorization(req, chal) + auth = self.get_authorization(authuri, req, chal) if auth: auth_val = 'Digest %s' % auth if req.headers.get(self.auth_header, None) == auth_val: @@ -1043,7 +1044,7 @@ dig = hashlib.sha1(b).hexdigest() return dig[:16] - def get_authorization(self, req, chal): + def get_authorization(self, authuri, req, chal): try: realm = chal['realm'] nonce = chal['nonce'] @@ -1059,7 +1060,7 @@ if H is None: return None - user, pw = self.passwd.find_user_password(realm, req.full_url) + user, pw = self.passwd.find_user_password(realm, authuri) if user is None: return None @@ -1129,9 +1130,8 @@ handler_order = 490 # before Basic auth def http_error_401(self, req, fp, code, msg, headers): - host = urlparse(req.full_url)[1] retry = self.http_error_auth_reqed('www-authenticate', - host, req, headers) + req.full_url, req, headers) self.reset_retry_count() return retry @@ -1142,9 +1142,12 @@ handler_order = 490 # before Basic auth def http_error_407(self, req, fp, code, msg, headers): - host = req.host + # Reconstruct the proxy's URL + delete_unsafe = dict.fromkeys(map(ord, "%/?#"), None) + safe = string.punctuation.translate(delete_unsafe) + authuri = req.type + "://" + quote(req.host, safe) retry = self.http_error_auth_reqed('proxy-authenticate', - host, req, headers) + authuri, req, headers) self.reset_retry_count() return retry