summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Hellegouarch <sh@defuze.org>2006-09-11 19:53:21 +0000
committerSylvain Hellegouarch <sh@defuze.org>2006-09-11 19:53:21 +0000
commit4cd65390c6f0b88694b6e01ed333bf702ad7cee8 (patch)
tree12dbf61d540a5d897d4c13b5b5d76e130f0858cc
parent06d8ffcd7c9e19c220bb22c0957df6df9bf56c1d (diff)
downloadcherrypy-git-4cd65390c6f0b88694b6e01ed333bf702ad7cee8.tar.gz
Digest and basic auth can now take a callable which must return a dict with user credentials so that it can fetch those from a database for instance.
Basic takes also an encrypt parameter which must be a callable that will encrypt the password sent back the user-agent. So that passwords can be stored encrypted on the server.
-rw-r--r--cherrypy/lib/auth.py24
-rw-r--r--cherrypy/lib/httpauth.py10
-rw-r--r--cherrypy/test/test_httpauth.py31
3 files changed, 42 insertions, 23 deletions
diff --git a/cherrypy/lib/auth.py b/cherrypy/lib/auth.py
index fed8820f..4f2eb053 100644
--- a/cherrypy/lib/auth.py
+++ b/cherrypy/lib/auth.py
@@ -3,31 +3,41 @@ import cherrypy
from httpauth import parseAuthorization, checkResponse, basicAuth, digestAuth
-def check_auth(users):
+def check_auth(users, encrypt=None):
"""If an authorization header contains credentials, return True, else False."""
if 'authorization' in cherrypy.request.headers:
# make sure the provided credentials are correctly set
ah = parseAuthorization(cherrypy.request.headers['authorization'])
if ah is None:
raise cherrypy.HTTPError(400, 'Bad Request')
-
+
+ if not encrypt:
+ encrypt = lambda x: x
+
+ if callable(users):
+ users = users() # expect it to return a dictionary
+
+ if not isinstance(users, dict):
+ raise ValueError, "Authentication users must be passed contained in a dictionary"
+
# fetch the user password
password = users.get(ah["username"], None)
# validate the authorization by re-computing it here
# and compare it with what the user-agent provided
- if checkResponse(ah, password, method=cherrypy.request.method):
+ if checkResponse(ah, password, method=cherrypy.request.method, encrypt=encrypt):
return True
return False
-def basic_auth(realm, users):
+def basic_auth(realm, users, encrypt=None):
"""If auth fails, raise 401 with a basic authentication header.
realm: a string containing the authentication realm.
- users: a dict of the form: {username: password}.
+ users: a dict of the form: {username: password} or a callable returning a dict.
+ encrypt: callable used to encrypt the password returned from the user-agent.
"""
- if check_auth(users):
+ if check_auth(users, encrypt):
return
# inform the user-agent this path is protected
@@ -39,7 +49,7 @@ def digest_auth(realm, users):
"""If auth fails, raise 401 with a digest authentication header.
realm: a string containing the authentication realm.
- users: a dict of the form: {username: password}.
+ users: a dict of the form: {username: password} or a callable returning a dict.
"""
if check_auth(users):
return
diff --git a/cherrypy/lib/httpauth.py b/cherrypy/lib/httpauth.py
index dac20813..8aab3cc2 100644
--- a/cherrypy/lib/httpauth.py
+++ b/cherrypy/lib/httpauth.py
@@ -295,7 +295,7 @@ def _computeDigestResponse(auth_map, password, method = "GET", A1 = None,**kwarg
return KD(H_A1, request)
-def _checkDigestResponse(auth_map, password, method = "GET", A1 = None,**kwargs):
+def _checkDigestResponse(auth_map, password, method = "GET", A1 = None, **kwargs):
"""This function is used to verify the response given by the client when
he tries to authenticate.
Optional arguments:
@@ -308,15 +308,15 @@ def _checkDigestResponse(auth_map, password, method = "GET", A1 = None,**kwargs)
return response == auth_map["response"]
-def _checkBasicResponse (auth_map, password, method='GET', **kwargs):
- return auth_map["password"] == password
+def _checkBasicResponse (auth_map, password, method='GET', encrypt=None, **kwargs):
+ return encrypt(auth_map["password"]) == password
AUTH_RESPONSES = {
"basic": _checkBasicResponse,
"digest": _checkDigestResponse,
}
-def checkResponse (auth_map, password, method = "GET", **kwargs):
+def checkResponse (auth_map, password, method = "GET", encrypt=None, **kwargs):
"""'checkResponse' compares the auth_map with the password and optionally
other arguments that each implementation might need.
@@ -335,7 +335,7 @@ def checkResponse (auth_map, password, method = "GET", **kwargs):
"""
global AUTH_RESPONSES
checker = AUTH_RESPONSES[auth_map["auth_scheme"]]
- return checker (auth_map, password, method, **kwargs)
+ return checker (auth_map, password, method=method, encrypt=encrypt, **kwargs)
diff --git a/cherrypy/test/test_httpauth.py b/cherrypy/test/test_httpauth.py
index 0341eed9..83e4e125 100644
--- a/cherrypy/test/test_httpauth.py
+++ b/cherrypy/test/test_httpauth.py
@@ -1,6 +1,8 @@
from cherrypy.test import test
test.prefer_parent_path()
+import md5
+
import cherrypy
from cherrypy.lib import httpauth
@@ -20,12 +22,19 @@ def setup_server():
return "This is protected by Basic auth."
index.exposed = True
+ def md5_encrypt(data):
+ return md5.new(data).hexdigest()
+
+ def fetch_users():
+ return {'test': 'test'}
+
conf = {'/digest': {'tools.digestauth.on': True,
'tools.digestauth.realm': 'localhost',
- 'tools.digestauth.users': {'test': 'test'}},
+ 'tools.digestauth.users': fetch_users},
'/basic': {'tools.basicauth.on': True,
'tools.basicauth.realm': 'localhost',
- 'tools.basicauth.users': {'test': 'test'}}}
+ 'tools.basicauth.users': {'test': md5_encrypt('test')},
+ 'tools.basicauth.encrypt': md5_encrypt}}
root = Root()
root.digest = DigestProtected()
root.basic = BasicProtected()
@@ -96,18 +105,18 @@ class HTTPAuthTest(helper.CPWebCase):
self._handlewebError(bad_value_msg % ('qop', '"auth"', tokens['qop']))
# now let's see if what
- base_auth = 'Digest username="test", realm="localhost", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
+ base_auth = 'Digest username="test", realm="localhost", nonce="%s", uri="/digest/", algorithm=MD5, response="%s", qop=auth, nc=%s, cnonce="1522e61005789929"'
- auth = base_auth % (nonce, '', '00000001')
+ auth = base_auth % (nonce, '', '00000001')
- params = httpauth.parseAuthorization(auth)
- response = httpauth._computeDigestResponse(params, 'test')
+ params = httpauth.parseAuthorization(auth)
+ response = httpauth._computeDigestResponse(params, 'test')
+
+ auth = base_auth % (nonce, response, '00000001')
+ self.getPage('/digest/', [('Authorization', auth)])
+ self.assertStatus('200 OK')
+ self.assertBody('This is protected by Digest auth.')
- auth = base_auth % (nonce, response, '00000001')
- self.getPage('/digest/', [('Authorization', auth)])
- self.assertStatus('200 OK')
- self.assertBody('This is protected by Digest auth.')
-
if __name__ == "__main__":
setup_server()
helper.testmain()