Commits

Anonymous committed 2255108

Working hacky decorator for hardcoded auth

Comments (0)

Files changed (6)

 *.db
 .settings
 *~
+out.err

djangohttpdigest/decorators.py

+from django.http import HttpResponseBadRequest
+
 from http import HttpResponseNotAuthorized
-
-from digest import get_digest_challenge
+from digest import get_digest_challenge, parse_authorization_header, check_hardcoded_authentication
 
 def protect_digest(realm, username, password):
     def _innerDecorator(function):
                 # successfull auth
                 if request.META['AUTH_TYPE'].lower() != 'digest':
                     raise NotImplementedError("Only digest supported")
-                #return function(request, *args, **kwargs)
+                try:
+                    parsed_header = parse_authorization_header(request.META['HTTP_AUTHORIZATION'])
+                except ValueError, err:
+                    return HttpResponseBadRequest(err)
+                
+                if check_hardcoded_authentication(parsed_header, request.method, request.path, request.GET.urlencode(), realm, username, password):
+                    return function(request, *args, **kwargs)
+            
+            # nothing received, return challenge
             response = HttpResponseNotAuthorized("Not Authorized")
             response['www-authenticate'] = get_digest_challenge(realm)
             return response

djangohttpdigest/digest.py

 Helper functions and algorithms for computing HTTP digest thingies.
 """
 import time
+import urllib2
 from md5 import md5
-from django.http import HttpResponseBadRequest
 
 def parse_authorization_header(header):
-    #auth_scheme, auth_params  = credentials.split(" ", 1)
-    pass
+    """ Parse requests authorization header into list.
+    Raise ValueError if some problem occurs. """
+    # digest is marked as part of header and causes problem
+    # parsing, so remove its
+    
+    if header.startswith('Digest '):
+        header = header[len('Digest '):]
+    
+    # Convert the auth params to a dict
+    items = urllib2.parse_http_list(header)
+    params = urllib2.parse_keqv_list(items)
+    
+    required = ["username", "realm", "nonce", "uri", "response"]
+
+    for field in required:
+        if not params.has_key(field):
+            raise ValueError("Required field %s not found" % field)
+
+    # check for qop companions
+    # (RFC 2617, sect. 3.2.2)
+    if params.has_key("qop") and not params.has_key("cnonce") and params.has_key("cn"):
+        raise ValueError("qop sent without cnonce and cn")
+
+    return params
 
 def get_digest_challenge(realm):
     """ Return HTTP digest challenge, which has to be placed into www-authenticate header"""
     else:
         return False
     
+def check_hardcoded_authentication(parsed_header, method, path, params, realm, username, password):
+    """ Do information sent in header authenticates against given credentials? """
+    assert parsed_header['qop'] == 'auth'
+    
+    # compute A1 according to RFC 2617, section 3.2.2.2
+    a1 = md5("%s:%s:%s" % (username, realm, password)).hexdigest()
+    # A2, according to section 3.2.2.3
+    a2 = md5("%s:%s" % (method,path)).hexdigest()
+
+    request = "%s:%s:%s:%s:%s" % (
+            parsed_header["nonce"],
+            parsed_header["nc"],
+            parsed_header["cnonce"],
+            parsed_header["qop"],
+            a2
+    )
+    
+
+    result_secret = md5("%s:%s" % (a1, request)).hexdigest()
+    
+    return parsed_header['response'] == result_secret 
     

djangohttpdigest/tests/test_digest.py

 import urllib2
 
 from django.test import TestCase
-from django.http import HttpRequest, HttpResponseBadRequest
+from django.http import HttpRequest
 from django.core.handlers.wsgi import WSGIRequest
 
 from djangohttpdigest.digest import check_credentials, parse_authorization_header
     def test_parse_authorization_header(self):
         """ Authorization header parsing, for various inputs """
         
-        self.assertRaises(HttpResponseBadRequest, lambda:parse_authorization_header(''))
+        self.assertRaises(ValueError, lambda:parse_authorization_header(''))

testproject/config.py

 MANAGERS = ADMINS
 
 DATABASE_ENGINE = "sqlite3"
-DATABASE_NAME = "/tmp/test.db"
+DATABASE_NAME = "/home/almad/tmp/httpdigesttest.db"
 DATABASE_USER = ""
 DATABASE_PASSWORD = ""
 DATABASE_HOST = "localhost"

testproject/testapi/views.py

     view returns 401 on failure or for challenge, 200 with empty body
     on successfull authorization. 
     """
-    raise ValueError()
     return HttpResponse('')