From 9bd66e83ee87d7608cf0d1f5cb884a4bb8b4e950 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 --- Doc/library/urllib.request.rst | 10 ++++++++++ Lib/test/test_urllib2.py | 15 +++++++++++++++ Lib/urllib/request.py | 15 +++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/Doc/library/urllib.request.rst b/Doc/library/urllib.request.rst index b588dad..56c9d3a 100644 --- a/Doc/library/urllib.request.rst +++ b/Doc/library/urllib.request.rst @@ -301,6 +301,16 @@ The following classes are provided: presented with a wrong Authentication scheme. +.. class:: HTTPBasicPriorAuthHandler(password_mgr=None) + + A variant of :class:`HTTPBasicAuthHandler` which sends authorization + credentials with the first request and doesn’t react to the failed + access attempt. Although it is not strictly according to the HTTPS + standards, many websites in the wild actually don't return proper + error status as they should. Aside from this difference, this behaves + exactly as HTTPBasicAuthHandler. + + .. class:: ProxyBasicAuthHandler(password_mgr=None) Handle authentication with the proxy. *password_mgr*, if given, should be diff --git a/Lib/test/test_urllib2.py b/Lib/test/test_urllib2.py index 9ea39a4..823890e 100644 --- a/Lib/test/test_urllib2.py +++ b/Lib/test/test_urllib2.py @@ -1422,6 +1422,21 @@ class HandlerTests(unittest.TestCase): handler.do_open(conn, req) self.assertTrue(conn.fakesock.closed, "Connection not closed") + def test_auth_prior_handler(self): + pwd_manager = MockPasswordManager() + pwd_manager.add_password(None, 'https://example.com', + 'somebody', 'verysecret') + auth_prior_handler = urllib.request.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.assertNotIn('Authorization', http_hand.httpconn.req_headers) class MiscTests(unittest.TestCase): diff --git a/Lib/urllib/request.py b/Lib/urllib/request.py index 67c7566..4754f66 100644 --- a/Lib/urllib/request.py +++ b/Lib/urllib/request.py @@ -908,6 +908,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 + + # Return n random bytes. _randombytes = os.urandom -- 1.8.3.1