From be3904ef449272b907680990148e03b9b1c15237 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Cepl?= Date: Mon, 11 Aug 2014 12:07:56 +0200 Subject: [PATCH] Alternative handler adding Authorization header even in the first request. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new HTTPBasicPriorAuthHandler which adds the Authorization header with the authentication code even to the first HTTP request not only as the reaction to the failed attempt to connect as the default HTTPBasicAuthHandler does. Allows working with websites like https://api.github.com which do not follow the strict interpretation of RFC, but more the dicta in the end of section 2 of RFC 2617: > A client MAY preemptively send the corresponding Authorization > header with requests for resources in that space without receipt > of another challenge from the server. Similarly, when a client > sends a request to a proxy, it may reuse a userid and password in > the Proxy-Authorization header field without receiving another > challenge from the proxy server. See section 4 for security > considerations associated with Basic authentication. Fixes issue 19494. Signed-off-by: Matěj Cepl --- Lib/test/test_urllib2.py | 15 +++++++++++++++ Lib/urllib2.py | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 39a0132..018d48f 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1231,6 +1231,21 @@ class HandlerTests(unittest.TestCase): self.assertEqual(len(http_handler.requests), 1) self.assertFalse(http_handler.requests[0].has_header(auth_header)) + def test_auth_prior_handler(self): + pwd_manager = MockPasswordManager() + pwd_manager.add_password(None, 'https://example.com', + 'somebody', 'verysecret') + auth_prior_handler = urllib2.HTTPBasicPriorAuthHandler(pwd_manager) + http_hand = MockHTTPSHandler() + + opener = OpenerDirector() + opener.add_handler(http_hand) + opener.add_handler(auth_prior_handler) + + req = Request("https://example.com") + opener.open(req) + self.assertFalse('Authorization' in http_hand.httpconn.req_headers) + class MiscTests(unittest.TestCase): def test_build_opener(self): diff --git a/Lib/urllib2.py b/Lib/urllib2.py index aadeb73..8859477 100644 --- a/Lib/urllib2.py +++ b/Lib/urllib2.py @@ -915,6 +915,21 @@ class ProxyBasicAuthHandler(AbstractBasicAuthHandler, BaseHandler): return response +class HTTPBasicPriorAuthHandler(HTTPBasicAuthHandler): + handler_order = 400 + + def http_request(self, req): + if not req.has_header('Authorization'): + user, passwd = self.passwd.find_user_password(None, req.host) + credentials = '{0}:{1}'.format(user, passwd).encode() + auth_str = base64.standard_b64encode(credentials).decode() + req.add_unredirected_header('Authorization', + 'Basic {}'.format(auth_str.strip())) + return req + + https_request = http_request + + def randombytes(n): """Return n random bytes.""" # Use /dev/urandom if it is available. Fall back to random module -- 1.8.3.1