aboutsummaryrefslogtreecommitdiff
path: root/requests/auth.py
diff options
context:
space:
mode:
Diffstat (limited to 'requests/auth.py')
-rw-r--r--requests/auth.py118
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