Close #19494: add urrlib.request.HTTPBasicPriorAuthHandler

This auth handler adds the Authorization header to the first
HTTP request rather than waiting for a HTTP 401 Unauthorized
response from the server as the default HTTPBasicAuthHandler
does.

This 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.

Patch by Matej Cepl.
This commit is contained in:
Nick Coghlan 2014-11-12 23:33:50 +10:00
parent ab14088141
commit c216c48699
5 changed files with 53 additions and 0 deletions

View file

@ -304,6 +304,17 @@ The following classes are provided:
presented with a wrong Authentication scheme.
.. class:: HTTPBasicPriorAuthHandler(password_mgr=None)
A variant of :class:`HTTPBasicAuthHandler` which automatically sends
authorization credentials with the first request, rather than waiting to
first receive a HTTP 401 "Unauthorised" error response. This allows
authentication to sites that don't provide a 401 response when receiving
a request without an Authorization header. Aside from this difference,
this behaves exactly as :class:`HTTPBasicAuthHandler`.
.. versionadded:: 3.5
.. class:: ProxyBasicAuthHandler(password_mgr=None)
Handle authentication with the proxy. *password_mgr*, if given, should be

View file

@ -297,6 +297,15 @@ time
* The :func:`time.monotonic` function is now always available. (Contributed by
Victor Stinner in :issue:`22043`.)
time
----
* A new :class:`urllib.request.HTTPBasicPriorAuthHandler` allows HTTP Basic
Authentication credentials to be sent unconditionally with the first HTTP
request, rather than waiting for a HTTP 401 Unauthorized response from the
server.
(Contributed by Matej Cepl in :issue:`19494`.)
wsgiref
-------

View file

@ -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):

View file

@ -916,6 +916,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

View file

@ -183,6 +183,9 @@ Core and Builtins
Library
-------
- Issue #19494: Added urllib.request.HTTPBasicPriorAuthHandler. Patch by
Matej Cepl.
- Issue #22578: Added attributes to the re.error class.
- Issue #12728: Different Unicode characters having the same uppercase but