## $Id: test_authentication.py 11949 2014-11-13 14:40:27Z henrik $ ## ## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ## import grok import logging import time import unittest from cStringIO import StringIO from zope.component import getGlobalSiteManager from zope.component.hooks import setSite, clearSite from zope.interface.verify import verifyClass, verifyObject from zope.password.testing import setUpPasswordManagers from zope.pluggableauth.interfaces import IAuthenticatorPlugin from zope.securitypolicy.interfaces import IPrincipalRoleManager from waeup.ikoba.testing import FunctionalTestCase, FunctionalLayer from waeup.ikoba.authentication import ( UserAuthenticatorPlugin, Account, IkobaPrincipalInfo, FailedLoginInfo, get_principal_role_manager, UsersPlugin,) from waeup.ikoba.interfaces import ( IUserAccount, IFailedLoginInfo, IIkobaPrincipalInfo, IIkobaPluggable) class FakeSite(grok.Site, grok.Container): #def getSiteManager(self): # return None # return getGlobalSiteManager() pass class UserAuthenticatorPluginTests(FunctionalTestCase): # Must be functional because of various utility lookups and the like layer = FunctionalLayer def setUp(self): super(UserAuthenticatorPluginTests, self).setUp() self.getRootFolder()['app'] = FakeSite() self.site = self.getRootFolder()['app'] self.site['users'] = {'bob': Account('bob', 'secret')} setSite(self.site) return def tearDown(self): super(UserAuthenticatorPluginTests, self).tearDown() clearSite() return def test_ifaces(self): # make sure, interfaces requirements are met plugin = UserAuthenticatorPlugin() plugin.__parent__ = None # This attribute is required by iface self.assertTrue( verifyClass(IAuthenticatorPlugin, UserAuthenticatorPlugin)) self.assertTrue(verifyObject(IAuthenticatorPlugin, plugin)) return def test_authenticate_credentials(self): # make sure authentication works as expected plugin = UserAuthenticatorPlugin() result1 = plugin.authenticateCredentials( dict(login='bob', password='secret')) result2 = plugin.authenticateCredentials( dict(login='bob', password='nonsense')) self.assertTrue(isinstance(result1, IkobaPrincipalInfo)) self.assertTrue(result2 is None) return def test_principal_info(self): # make sure we can get a principal info plugin = UserAuthenticatorPlugin() result1 = plugin.principalInfo('bob') result2 = plugin.principalInfo('manfred') self.assertTrue(isinstance(result1, IkobaPrincipalInfo)) self.assertTrue(result2 is None) return def test_get_principal_role_manager(self): # make sure we get different role managers for different situations prm1 = get_principal_role_manager() clearSite(None) prm2 = get_principal_role_manager() self.assertTrue(IPrincipalRoleManager.providedBy(prm1)) self.assertTrue(IPrincipalRoleManager.providedBy(prm2)) self.assertTrue(prm1._context is self.site) self.assertTrue(hasattr(prm2, '_context') is False) return def make_failed_logins(self, num): # do `num` failed logins and a valid one afterwards del self.site['users'] self.site['users'] = {'bob': Account('bob', 'secret')} plugin = UserAuthenticatorPlugin() resultlist = [] # reset accounts for x in range(num): resultlist.append(plugin.authenticateCredentials( dict(login='bob', password='wrongsecret'))) resultlist.append(plugin.authenticateCredentials( dict(login='bob', password='secret'))) return resultlist def DISABLED_test_failed_logins(self): # after three failed logins, an account is blocked # XXX: this tests authenticator with time penalty (currently # disabled) results = [] succ_principal = IkobaPrincipalInfo( id='bob', title='bob', description=None, email=None, phone=None, public_name=None, user_type=u'user') for x in range(4): results.append(self.make_failed_logins(x)) self.assertEqual(results[2], [None, None, succ_principal]) # last login was blocked although correctly entered due to # time penalty self.assertEqual(results[3], [None, None, None, None]) return class IkobaPrincipalInfoTests(unittest.TestCase): def create_info(self): return IkobaPrincipalInfo( id='bob', title='bob', description=None, email=None, phone=None, public_name=None, user_type=u'user') def test_iface(self): # make sure we implement the promised interfaces info = self.create_info() verifyClass(IIkobaPrincipalInfo, IkobaPrincipalInfo) verifyObject(IIkobaPrincipalInfo, info) return def test_equality(self): # we can test two infos for equality info1 = self.create_info() info2 = self.create_info() self.assertEqual(info1, info2) self.assertTrue(info1 == info2) info1.id = 'blah' self.assertTrue(info1 != info2) self.assertTrue((info1 == info2) is False) info1.id = 'bob' info2.id = 'blah' self.assertTrue(info1 != info2) self.assertTrue((info1 == info2) is False) return class FailedLoginInfoTests(unittest.TestCase): def test_iface(self): # make sure we fullfill the promised interfaces info1 = FailedLoginInfo() info2 = FailedLoginInfo(num=1, last=time.time()) self.assertTrue( verifyClass(IFailedLoginInfo, FailedLoginInfo)) self.assertTrue(verifyObject(IFailedLoginInfo, info1)) # make sure the stored values have correct type if not None self.assertTrue(verifyObject(IFailedLoginInfo, info2)) return def test_default_values(self): # By default we get 0, None info = FailedLoginInfo() self.assertEqual(info.num, 0) self.assertEqual(info.last, None) return def test_set_values_by_attribute(self): # we can set values by attribute ts = time.gmtime(0) info = FailedLoginInfo() info.num = 5 info.last = ts self.assertEqual(info.num, 5) self.assertEqual(info.last, ts) return def test_set_values_by_constructor(self): # we can set values by constructor args ts = time.gmtime(0) info = FailedLoginInfo(5, ts) self.assertEqual(info.num, 5) self.assertEqual(info.last, ts) return def test_set_values_by_keywords(self): # we can set values by constructor keywords ts = time.gmtime(0) info = FailedLoginInfo(last=ts, num=3) self.assertEqual(info.num, 3) self.assertEqual(info.last, ts) return def test_as_tuple(self): # we can get the info values as tuple ts = time.gmtime(0) info = FailedLoginInfo(last=ts, num=3) self.assertEqual(info.as_tuple(), (3, ts)) return def test_set_values(self): # we can set the values of a an info instance ts = time.time() info = FailedLoginInfo() info.set_values(num=3, last=ts) self.assertEqual(info.num, 3) self.assertEqual(info.last, ts) return def test_increase(self): # we can increase the number of failed logins ts1 = time.time() info = FailedLoginInfo() info.increase() self.assertEqual(info.num, 1) self.assertTrue(info.last > ts1) ts2 = info.last info.increase() self.assertEqual(info.num, 2) self.assertTrue(info.last > ts2) return def test_reset(self): # we can reset failed login infos. info = FailedLoginInfo() info.increase() info.reset() self.assertEqual(info.num, 0) self.assertEqual(info.last, None) return class AccountTests(unittest.TestCase): def setUp(self): setUpPasswordManagers() return def test_iface(self): acct = Account('bob', 'mypasswd') self.assertTrue( verifyClass(IUserAccount, Account)) self.assertTrue( verifyObject(IUserAccount, acct)) return def test_failed_logins(self): # we can retrieve infos about failed logins ts = time.time() acct = Account('bob', 'mypasswd') self.assertTrue(hasattr(acct, 'failed_logins')) acct.failed_logins.set_values(num=3, last=ts) self.assertEqual(acct.failed_logins.last, ts) self.assertEqual(acct.failed_logins.num, 3) return def test_failed_logins_per_inst(self): # we get a different counter for each Account instance acct1 = Account('bob', 'secret') acct2 = Account('alice', 'alsosecret') self.assertTrue(acct1.failed_logins is not acct2.failed_logins) return class FakeUserAccount(object): pass class UsersPluginTests(unittest.TestCase): def setUp(self): setUpPasswordManagers() self.site = FakeSite() self.site['users'] = grok.Container() return def get_logger(self): logger = logging.getLogger('waeup.test') stream = StringIO() handler = logging.StreamHandler(stream) logger.setLevel(logging.DEBUG) logger.propagate = False logger.addHandler(handler) return logger, stream def test_ifaces(self): # make sure we implement the promised interfaces plugin = UsersPlugin() verifyClass(IIkobaPluggable, UsersPlugin) verifyObject(IIkobaPluggable, plugin) return def test_update(self): # make sure user accounts are updated properly. plugin = UsersPlugin() logger, stream = self.get_logger() plugin.update(self.site, 'app', logger) stream.seek(0) self.assertEqual(stream.read(), '') self.site['users']['bob'] = FakeUserAccount() logger, stream = self.get_logger() plugin.update(self.site, 'app', logger) stream.seek(0) log_content = stream.read() self.assertTrue(hasattr(self.site['users']['bob'], 'description')) self.assertTrue(hasattr(self.site['users']['bob'], 'failed_logins')) self.assertTrue( isinstance(self.site['users']['bob'].failed_logins, FailedLoginInfo)) self.assertTrue('attribute description added' in log_content) self.assertTrue('attribute failed_logins added' in log_content) return