source: main/waeup.kofa/trunk/src/waeup/kofa/tests/test_authentication.py @ 12704

Last change on this file since 12704 was 10055, checked in by uli, 12 years ago

Provide infrastructure to remember failed logins.

  • Property svn:keywords set to Id
File size: 11.3 KB
RevLine 
[7193]1## $Id: test_authentication.py 10055 2013-04-04 15:12:43Z uli $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
[7194]17##
18import grok
[10055]19import logging
20import time
[6615]21import unittest
[10055]22from cStringIO import StringIO
23from zope.component import getGlobalSiteManager
[6615]24from zope.component.hooks import setSite, clearSite
25from zope.interface.verify import verifyClass, verifyObject
[10055]26from zope.password.testing import setUpPasswordManagers
[6615]27from zope.pluggableauth.interfaces import IAuthenticatorPlugin
[6673]28from zope.securitypolicy.interfaces import IPrincipalRoleManager
[7811]29from waeup.kofa.testing import FunctionalTestCase, FunctionalLayer
30from waeup.kofa.authentication import (
[10055]31    UserAuthenticatorPlugin, Account, KofaPrincipalInfo, FailedLoginInfo,
32    get_principal_role_manager, UsersPlugin,)
33from waeup.kofa.interfaces import (
34    IUserAccount, IFailedLoginInfo, IKofaPrincipalInfo, IKofaPluggable)
[6615]35
36class FakeSite(grok.Site, grok.Container):
[10055]37    #def getSiteManager(self):
38    #    return None
39    #    return getGlobalSiteManager()
[6615]40    pass
[6617]41
[6615]42class UserAuthenticatorPluginTests(FunctionalTestCase):
43    # Must be functional because of various utility lookups and the like
44
45    layer = FunctionalLayer
46
47    def setUp(self):
48        super(UserAuthenticatorPluginTests, self).setUp()
49        self.getRootFolder()['app'] = FakeSite()
50        self.site = self.getRootFolder()['app']
51        self.site['users'] = {'bob': Account('bob', 'secret')}
52        setSite(self.site)
53        return
54
55    def tearDown(self):
56        super(UserAuthenticatorPluginTests, self).tearDown()
[8427]57        clearSite()
[6615]58        return
59
60    def test_ifaces(self):
61        # make sure, interfaces requirements are met
62        plugin = UserAuthenticatorPlugin()
63        plugin.__parent__ = None # This attribute is required by iface
64        self.assertTrue(
65            verifyClass(IAuthenticatorPlugin, UserAuthenticatorPlugin))
66        self.assertTrue(verifyObject(IAuthenticatorPlugin, plugin))
67        return
68
69    def test_authenticate_credentials(self):
70        # make sure authentication works as expected
71        plugin = UserAuthenticatorPlugin()
72        result1 = plugin.authenticateCredentials(
73            dict(login='bob', password='secret'))
74        result2 = plugin.authenticateCredentials(
75            dict(login='bob', password='nonsense'))
[7819]76        self.assertTrue(isinstance(result1, KofaPrincipalInfo))
[6615]77        self.assertTrue(result2 is None)
78        return
79
80    def test_principal_info(self):
81        # make sure we can get a principal info
82        plugin = UserAuthenticatorPlugin()
83        result1 = plugin.principalInfo('bob')
84        result2 = plugin.principalInfo('manfred')
[7819]85        self.assertTrue(isinstance(result1, KofaPrincipalInfo))
[6615]86        self.assertTrue(result2 is None)
87        return
[6673]88
89    def test_get_principal_role_manager(self):
90        # make sure we get different role managers for different situations
91        prm1 = get_principal_role_manager()
92        clearSite(None)
93        prm2 = get_principal_role_manager()
94        self.assertTrue(IPrincipalRoleManager.providedBy(prm1))
95        self.assertTrue(IPrincipalRoleManager.providedBy(prm2))
96        self.assertTrue(prm1._context is self.site)
97        self.assertTrue(hasattr(prm2, '_context') is False)
98        return
[10055]99
100    def make_failed_logins(self, num):
101        # do `num` failed logins and a valid one afterwards
102        del self.site['users']
103        self.site['users'] = {'bob': Account('bob', 'secret')}
104        plugin = UserAuthenticatorPlugin()
105        resultlist = []
106        # reset accounts
107        for x in range(num):
108            resultlist.append(plugin.authenticateCredentials(
109                dict(login='bob', password='wrongsecret')))
110        resultlist.append(plugin.authenticateCredentials(
111            dict(login='bob', password='secret')))
112        return resultlist
113
114    def DISABLED_test_failed_logins(self):
115        # after three failed logins, an account is blocked
116        # XXX: this tests authenticator with time penalty (currently
117        # disabled)
118        results = []
119        succ_principal = KofaPrincipalInfo(
120            id='bob',
121            title='bob',
122            description=None,
123            email=None,
124            phone=None,
125            public_name=None,
126            user_type=u'user')
127        for x in range(4):
128            results.append(self.make_failed_logins(x))
129        self.assertEqual(results[2], [None, None, succ_principal])
130        # last login was blocked although correctly entered due to
131        # time penalty
132        self.assertEqual(results[3], [None, None, None, None])
133        return
134
135class KofaPrincipalInfoTests(unittest.TestCase):
136
137    def create_info(self):
138        return KofaPrincipalInfo(
139            id='bob',
140            title='bob',
141            description=None,
142            email=None,
143            phone=None,
144            public_name=None,
145            user_type=u'user')
146
147    def test_iface(self):
148        # make sure we implement the promised interfaces
149        info = self.create_info()
150        verifyClass(IKofaPrincipalInfo, KofaPrincipalInfo)
151        verifyObject(IKofaPrincipalInfo, info)
152        return
153
154    def test_equality(self):
155        # we can test two infos for equality
156        info1 = self.create_info()
157        info2 = self.create_info()
158        self.assertEqual(info1, info2)
159        self.assertTrue(info1 == info2)
160        info1.id = 'blah'
161        self.assertTrue(info1 != info2)
162        self.assertTrue((info1 == info2) is False)
163        info1.id = 'bob'
164        info2.id = 'blah'
165        self.assertTrue(info1 != info2)
166        self.assertTrue((info1 == info2) is False)
167        return
168
169class FailedLoginInfoTests(unittest.TestCase):
170
171    def test_iface(self):
172        # make sure we fullfill the promised interfaces
173        info1 = FailedLoginInfo()
174        info2 = FailedLoginInfo(num=1, last=time.time())
175        self.assertTrue(
176            verifyClass(IFailedLoginInfo, FailedLoginInfo))
177        self.assertTrue(verifyObject(IFailedLoginInfo, info1))
178        # make sure the stored values have correct type if not None
179        self.assertTrue(verifyObject(IFailedLoginInfo, info2))
180        return
181
182    def test_default_values(self):
183        # By default we get 0, None
184        info = FailedLoginInfo()
185        self.assertEqual(info.num, 0)
186        self.assertEqual(info.last, None)
187        return
188
189    def test_set_values_by_attribute(self):
190        # we can set values by attribute
191        ts = time.gmtime(0)
192        info = FailedLoginInfo()
193        info.num = 5
194        info.last = ts
195        self.assertEqual(info.num, 5)
196        self.assertEqual(info.last, ts)
197        return
198
199    def test_set_values_by_constructor(self):
200        # we can set values by constructor args
201        ts = time.gmtime(0)
202        info = FailedLoginInfo(5, ts)
203        self.assertEqual(info.num, 5)
204        self.assertEqual(info.last, ts)
205        return
206
207    def test_set_values_by_keywords(self):
208        # we can set values by constructor keywords
209        ts = time.gmtime(0)
210        info = FailedLoginInfo(last=ts, num=3)
211        self.assertEqual(info.num, 3)
212        self.assertEqual(info.last, ts)
213        return
214
215    def test_as_tuple(self):
216        # we can get the info values as tuple
217        ts = time.gmtime(0)
218        info = FailedLoginInfo(last=ts, num=3)
219        self.assertEqual(info.as_tuple(), (3, ts))
220        return
221
222    def test_set_values(self):
223        # we can set the values of a an info instance
224        ts = time.time()
225        info = FailedLoginInfo()
226        info.set_values(num=3, last=ts)
227        self.assertEqual(info.num, 3)
228        self.assertEqual(info.last, ts)
229        return
230
231    def test_increase(self):
232        # we can increase the number of failed logins
233        ts1 = time.time()
234        info = FailedLoginInfo()
235        info.increase()
236        self.assertEqual(info.num, 1)
237        self.assertTrue(info.last > ts1)
238        ts2 = info.last
239        info.increase()
240        self.assertEqual(info.num, 2)
241        self.assertTrue(info.last > ts2)
242        return
243
244    def test_reset(self):
245        # we can reset failed login infos.
246        info = FailedLoginInfo()
247        info.increase()
248        info.reset()
249        self.assertEqual(info.num, 0)
250        self.assertEqual(info.last, None)
251        return
252
253class AccountTests(unittest.TestCase):
254
255    def setUp(self):
256        setUpPasswordManagers()
257        return
258
259    def test_iface(self):
260        acct = Account('bob', 'mypasswd')
261        self.assertTrue(
262            verifyClass(IUserAccount, Account))
263        self.assertTrue(
264            verifyObject(IUserAccount, acct))
265        return
266
267    def test_failed_logins(self):
268        # we can retrieve infos about failed logins
269        ts = time.time()
270        acct = Account('bob', 'mypasswd')
271        self.assertTrue(hasattr(acct, 'failed_logins'))
272        acct.failed_logins.set_values(num=3, last=ts)
273        self.assertEqual(acct.failed_logins.last, ts)
274        self.assertEqual(acct.failed_logins.num, 3)
275        return
276
277    def test_failed_logins_per_inst(self):
278        # we get a different counter for each Account instance
279        acct1 = Account('bob', 'secret')
280        acct2 = Account('alice', 'alsosecret')
281        self.assertTrue(acct1.failed_logins is not acct2.failed_logins)
282        return
283
284class FakeUserAccount(object):
285    pass
286
287class UsersPluginTests(unittest.TestCase):
288
289    def setUp(self):
290        setUpPasswordManagers()
291        self.site = FakeSite()
292        self.site['users'] = grok.Container()
293        return
294
295    def get_logger(self):
296        logger = logging.getLogger('waeup.test')
297        stream = StringIO()
298        handler = logging.StreamHandler(stream)
299        logger.setLevel(logging.DEBUG)
300        logger.propagate = False
301        logger.addHandler(handler)
302        return logger, stream
303
304    def test_ifaces(self):
305        # make sure we implement the promised interfaces
306        plugin = UsersPlugin()
307        verifyClass(IKofaPluggable, UsersPlugin)
308        verifyObject(IKofaPluggable, plugin)
309        return
310
311    def test_update(self):
312        # make sure user accounts are updated properly.
313        plugin = UsersPlugin()
314        logger, stream = self.get_logger()
315        plugin.update(self.site, 'app', logger)
316        stream.seek(0)
317        self.assertEqual(stream.read(), '')
318        self.site['users']['bob'] = FakeUserAccount()
319        logger, stream = self.get_logger()
320        plugin.update(self.site, 'app', logger)
321        stream.seek(0)
322        log_content = stream.read()
323        self.assertTrue(hasattr(self.site['users']['bob'], 'description'))
324        self.assertTrue(hasattr(self.site['users']['bob'], 'failed_logins'))
325        self.assertTrue(
326            isinstance(self.site['users']['bob'].failed_logins,
327                       FailedLoginInfo))
328        self.assertTrue('attribute description added' in log_content)
329        self.assertTrue('attribute failed_logins added' in log_content)
330        return
Note: See TracBrowser for help on using the repository browser.