Index: roundup/cgi/actions.py =================================================================== RCS file: /cvsroot/roundup/roundup/roundup/cgi/actions.py,v retrieving revision 1.37 diff -B -u -r1.37 actions.py --- roundup/cgi/actions.py 7 Aug 2004 22:17:11 -0000 1.37 +++ roundup/cgi/actions.py 16 Nov 2004 22:41:46 -0000 @@ -5,7 +5,7 @@ from roundup import hyperdb, token, date, password, rcsv, exceptions from roundup.i18n import _ from roundup.cgi import templating -from roundup.cgi.exceptions import Redirect, Unauthorised, SeriousError +from roundup.cgi.exceptions import Redirect, Unauthorised, SeriousError, LoginError from roundup.mailgw import uidFromAddress __all__ = ['Action', 'ShowAction', 'RetireAction', 'SearchAction', @@ -838,27 +838,11 @@ else: password = '' - # make sure the user exists try: - self.client.userid = self.db.user.lookup(self.client.user) - except KeyError: - name = self.client.user - self.client.error_message.append(self._('Ivalid login')) - self.client.make_user_anonymous() - return - - # verify the password - if not self.verifyPassword(self.client.userid, password): + self.verifyLogin(self.client.user, password) + except LoginError, err: self.client.make_user_anonymous() - self.client.error_message.append(self._('Invalid login')) - return - - # Determine whether the user has permission to log in. - # Base behaviour is to check the user has "Web Access". - if not self.hasPermission("Web Access"): - self.client.make_user_anonymous() - self.client.error_message.append( - self._("You do not have permission to login")) + self.client.error_message.extend(list(err.args)) return # now we're OK, re-open the database for real, using the user @@ -867,6 +851,22 @@ # set the session cookie self.client.set_cookie(self.client.user) + def verifyLogin(self, username, password): + # make sure the user exists + try: + self.client.userid = self.db.user.lookup(username) + except KeyError: + raise LoginError, self._('Invalid login') + + # verify the password + if not self.verifyPassword(self.client.userid, password): + raise LoginError, self._('Invalid login') + + # Determine whether the user has permission to log in. + # Base behaviour is to check the user has "Web Access". + if not self.hasPermission("Web Access"): + raise LoginError, self._("You do not have permission to login") + def verifyPassword(self, userid, password): ''' Verify the password that the user has supplied ''' Index: roundup/cgi/client.py =================================================================== RCS file: /cvsroot/roundup/roundup/roundup/cgi/client.py,v retrieving revision 1.200 diff -B -u -r1.200 client.py --- roundup/cgi/client.py 12 Nov 2004 04:07:05 -0000 1.200 +++ roundup/cgi/client.py 16 Nov 2004 22:41:46 -0000 @@ -5,7 +5,7 @@ __docformat__ = 'restructuredtext' import os, os.path, cgi, StringIO, urlparse, re, traceback, mimetypes, urllib -import binascii, Cookie, time, random, stat, rfc822 +import binascii, Cookie, time, random, stat, rfc822, base64 import codecs @@ -151,6 +151,9 @@ # parse cookies (used in charset and session lookups) self.cookie = Cookie.SimpleCookie(self.env.get('HTTP_COOKIE', '')) + self.user = None + self.userid = None + def setTranslator(self, translator=None): """Replace the translation engine @@ -375,6 +378,26 @@ else: user = 'anonymous' + # try handling Basic Auth ourselves + if user == 'anonymous': + if self.env['HTTP_AUTHORIZATION']: + scheme, challenge = self.env['HTTP_AUTHORIZATION'].split(' ', 1) + if scheme.lower() == 'basic': + try: + decoded = base64.decodestring(challenge) + except TypeError: + # invalid challenge + pass + username, password = decoded.split(':') + try: + LoginAction(self).verifyLogin(username, password) + except LoginError, err: + self.make_user_anonymous() + self.response_code = 403 + raise Unauthorised, err + + user = username + # look up the user session cookie (may override the REMOTE_USER) cookie = self.cookie if (cookie.has_key(self.cookie_name) and Index: roundup/cgi/exceptions.py =================================================================== RCS file: /cvsroot/roundup/roundup/roundup/cgi/exceptions.py,v retrieving revision 1.5 diff -B -u -r1.5 exceptions.py --- roundup/cgi/exceptions.py 11 May 2004 13:03:07 -0000 1.5 +++ roundup/cgi/exceptions.py 16 Nov 2004 22:41:46 -0000 @@ -9,6 +9,9 @@ class HTTPException(Exception): pass +class LoginError(HTTPException): + pass + class Unauthorised(HTTPException): pass Index: test/test_actions.py =================================================================== RCS file: /cvsroot/roundup/roundup/test/test_actions.py,v retrieving revision 1.12 diff -B -u -r1.12 test_actions.py --- test/test_actions.py 2 Jul 2004 05:45:34 -0000 1.12 +++ test/test_actions.py 16 Nov 2004 22:41:47 -0000 @@ -169,6 +169,51 @@ self.failIf(self.action.detectCollision(self.now, self.now - Interval("1d"))) self.failIf(self.action.detectCollision(None, self.now)) +class LoginTestCase(ActionTestCase): + def setUp(self): + ActionTestCase.setUp(self) + self.client.error_message = [] + + # set the db password to 'right' + self.client.db.user.get = lambda a,b: 'right' + + # unless explicitly overridden, we should never get here + self.client.opendb = lambda a: self.fail("Logged in, but we shouldn't be.") + + def assertLoginLeavesMessages(self, messages, username=None, password=None): + if username is not None: + self.form.value.append(MiniFieldStorage('__login_name', username)) + if password is not None: + self.form.value.append(MiniFieldStorage('__login_password', password)) + + LoginAction(self.client).handle() + self.assertEqual(self.client.error_message, messages) + + def testNoUsername(self): + self.assertLoginLeavesMessages(['Username required']) + + def testInvalidUsername(self): + def raiseKeyError(a): + raise KeyError + self.client.db.user.lookup = raiseKeyError + self.assertLoginLeavesMessages(['Invalid login'], 'foo') + + def testInvalidPassword(self): + self.assertLoginLeavesMessages(['Invalid login'], 'foo', 'wrong') + + def testNoWebAccess(self): + self.assertLoginLeavesMessages(['You do not have permission to login'], + 'foo', 'right') + + def testCorrectLogin(self): + self.client.db.security.hasPermission = lambda a,b,c: True + + def opendb(username): + self.assertEqual(username, 'foo') + self.client.opendb = opendb + + self.assertLoginLeavesMessages([], 'foo', 'right') + def test_suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(RetireActionTestCase)) @@ -176,6 +221,7 @@ suite.addTest(unittest.makeSuite(FakeFilterVarsTestCase)) suite.addTest(unittest.makeSuite(ShowActionTestCase)) suite.addTest(unittest.makeSuite(CollisionDetectionTestCase)) + suite.addTest(unittest.makeSuite(LoginTestCase)) return suite if __name__ == '__main__':