Digest authentication support!
authorKenneth Reitz <me@kennethreitz.com>
Sun, 23 Oct 2011 18:44:20 +0000 (14:44 -0400)
committerKenneth Reitz <me@kennethreitz.com>
Sun, 23 Oct 2011 18:44:20 +0000 (14:44 -0400)
Ripped from urllib2

requests/auth.py

index d59a68740dcd62822197527480aa45283590c61d..e9dbce8b9249cdd973b3dc3cf95da9b0c08ecd86 100644 (file)
@@ -7,7 +7,14 @@ requests.auth
 This module contains the authentication handlers for Requests.
 """
 
+import time
+import hashlib
+
 from base64 import b64encode
+from urlparse import urlparse
+
+from .utils import randombytes, parse_dict_header
+
 
 def http_basic(r, username, password):
     """Attaches HTTP Basic Authentication to the given Request object.
@@ -28,7 +35,92 @@ def http_digest(r, username, password):
     Arguments should be considered non-positional.
     """
 
-    r.headers
+    def handle_401(r):
+        """Takes the given response and tries digest-auth, if needed."""
+
+        s_auth = r.headers.get('www-authenticate', '')
+
+        if 'digest' in s_auth.lower():
+
+            last_nonce = ''
+            nonce_count = 0
+
+            chal = parse_dict_header(s_auth.replace('Digest ', ''))
+
+            realm = chal['realm']
+            nonce = chal['nonce']
+            qop = chal.get('qop')
+            algorithm = chal.get('algorithm', 'MD5')
+            opaque = chal.get('opaque', None)
+
+            algorithm = algorithm.upper()
+            # lambdas assume digest modules are imported at the top level
+            if algorithm == 'MD5':
+                H = lambda x: hashlib.md5(x).hexdigest()
+            elif algorithm == 'SHA':
+                H = lambda x: hashlib.sha1(x).hexdigest()
+            # XXX MD5-sess
+            KD = lambda s, d: H("%s:%s" % (s, d))
+
+            if H is None:
+                return None
+
+            # XXX not implemented yet
+            entdig = None
+            path = urlparse(r.request.url).path
+
+            A1 = "%s:%s:%s" % (username, realm, password)
+            A2 = "%s:%s" % (r.request.method, path)
+
+            if qop == 'auth':
+                if nonce == last_nonce:
+                    nonce_count += 1
+                else:
+                    nonce_count = 1
+                    last_nonce = nonce
+
+                ncvalue = '%08x' % nonce_count
+                cnonce = (hashlib.sha1("%s:%s:%s:%s" % (
+                    nonce_count, nonce, time.ctime(), randombytes(8)))
+                    .hexdigest()[:16]
+                )
+                noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, H(A2))
+                respdig = KD(H(A1), noncebit)
+            elif qop is None:
+                respdig = KD(H(A1), "%s:%s" % (nonce, H(A2)))
+            else:
+                # XXX handle auth-int.
+                return None
+
+            # XXX should the partial digests be encoded too?
+            base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
+                   'response="%s"' % (username, realm, nonce, path, respdig)
+            if opaque:
+                base += ', opaque="%s"' % opaque
+            if entdig:
+                base += ', digest="%s"' % entdig
+            base += ', algorithm="%s"' % algorithm
+            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
+            _r.history.append(r)
+            print _r.status_code
+
+            # r.request.response
+
+            print locals()
+
+            print _r.headers
+            return _r
+
+        return r
+
+    r.hooks['response'] = handle_401
+    return r
 
 
 def dispatch(t):