diff options
Diffstat (limited to 'requests/auth.py')
-rw-r--r-- | requests/auth.py | 118 |
1 files changed, 61 insertions, 57 deletions
diff --git a/requests/auth.py b/requests/auth.py index aabeb86..183731b 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -7,35 +7,55 @@ requests.auth This module contains the authentication handlers for Requests. """ +from __future__ import unicode_literals + import time import hashlib from base64 import b64encode -from urlparse import urlparse - +from .compat import urlparse, str, bytes from .utils import randombytes, parse_dict_header -def http_basic(r, username, password): - """Attaches HTTP Basic Authentication to the given Request object. - Arguments should be considered non-positional. - """ - username = str(username) - password = str(password) +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + return 'Basic ' + b64encode(("%s:%s" % (username, password)).encode('utf-8')).strip().decode('utf-8') + + +class AuthBase(object): + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError('Auth hooks must be callable.') - auth_s = b64encode('%s:%s' % (username, password)) - r.headers['Authorization'] = ('Basic %s' % auth_s) - return r +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + def __init__(self, username, password): + self.username = username + self.password = password + + def __call__(self, r): + r.headers['Authorization'] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authenetication to a given Request object.""" + def __call__(self, r): + r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) + return r -def http_digest(r, username, password): - """Attaches HTTP Digest Authentication to the given Request object. - Arguments should be considered non-positional. - """ +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + def __init__(self, username, password): + self.username = username + self.password = password - def handle_401(r): + def handle_401(self, r): """Takes the given response and tries digest-auth, if needed.""" s_auth = r.headers.get('www-authenticate', '') @@ -56,9 +76,17 @@ def http_digest(r, username, password): algorithm = algorithm.upper() # lambdas assume digest modules are imported at the top level if algorithm == 'MD5': - H = lambda x: hashlib.md5(x).hexdigest() + def h(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.md5(x).hexdigest() + H = h elif algorithm == 'SHA': - H = lambda x: hashlib.sha1(x).hexdigest() + def h(x): + if isinstance(x, str): + x = x.encode('utf-8') + return hashlib.sha1(x).hexdigest() + H = h # XXX MD5-sess KD = lambda s, d: H("%s:%s" % (s, d)) @@ -68,10 +96,12 @@ def http_digest(r, username, password): # XXX not implemented yet entdig = None p_parsed = urlparse(r.request.url) - path = p_parsed.path + p_parsed.query + path = p_parsed.path + if p_parsed.query: + path += '?' + p_parsed.query - A1 = "%s:%s:%s" % (username, realm, password) - A2 = "%s:%s" % (r.request.method, path) + A1 = '%s:%s:%s' % (self.username, realm, self.password) + A2 = '%s:%s' % (r.request.method, path) if qop == 'auth': if nonce == last_nonce: @@ -81,10 +111,12 @@ def http_digest(r, username, password): last_nonce = nonce ncvalue = '%08x' % nonce_count - cnonce = (hashlib.sha1("%s:%s:%s:%s" % ( - nonce_count, nonce, time.ctime(), randombytes(8))) - .hexdigest()[:16] - ) + s = str(nonce_count).encode('utf-8') + s += nonce.encode('utf-8') + s += time.ctime().encode('utf-8') + s += randombytes(8) + + cnonce = (hashlib.sha1(s).hexdigest()[:16]) noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2)) respdig = KD(H(A1), noncebit) elif qop is None: @@ -95,7 +127,7 @@ def http_digest(r, username, password): # XXX should the partial digests be encoded too? base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ - 'response="%s"' % (username, realm, nonce, path, respdig) + 'response="%s"' % (self.username, realm, nonce, path, respdig) if opaque: base += ', opaque="%s"' % opaque if entdig: @@ -104,7 +136,6 @@ def http_digest(r, username, password): if qop: base += ', qop=auth, nc=%s, cnonce="%s"' % (ncvalue, cnonce) - r.request.headers['Authorization'] = 'Digest %s' % (base) r.request.send(anyway=True) _r = r.request.response @@ -114,33 +145,6 @@ def http_digest(r, username, password): return r - r.hooks['response'] = handle_401 - return r - - -def dispatch(t): - """Given an auth tuple, return an expanded version.""" - - if not t: - return t - else: - t = list(t) - - # Make sure they're passing in something. - assert len(t) >= 2 - - # If only two items are passed in, assume HTTPBasic. - if (len(t) == 2): - t.insert(0, 'basic') - - # Allow built-in string referenced auths. - if isinstance(t[0], basestring): - if t[0] in ('basic', 'forced_basic'): - t[0] = http_basic - elif t[0] in ('digest',): - t[0] = http_digest - - # Return a custom callable. - return (t[0], tuple(t[1:])) - - + def __call__(self, r): + r.register_hook('response', self.handle_401) + return r |