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

Last change on this file since 13958 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
Line 
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
17##
18import grok
19import logging
20import time
21import unittest
22from cStringIO import StringIO
23from zope.component import getGlobalSiteManager
24from zope.component.hooks import setSite, clearSite
25from zope.interface.verify import verifyClass, verifyObject
26from zope.password.testing import setUpPasswordManagers
27from zope.pluggableauth.interfaces import IAuthenticatorPlugin
28from zope.securitypolicy.interfaces import IPrincipalRoleManager
29from waeup.kofa.testing import FunctionalTestCase, FunctionalLayer
30from waeup.kofa.authentication import (
31    UserAuthenticatorPlugin, Account, KofaPrincipalInfo, FailedLoginInfo,
32    get_principal_role_manager, UsersPlugin,)
33from waeup.kofa.interfaces import (
34    IUserAccount, IFailedLoginInfo, IKofaPrincipalInfo, IKofaPluggable)
35
36class FakeSite(grok.Site, grok.Container):
37    #def getSiteManager(self):
38    #    return None
39    #    return getGlobalSiteManager()
40    pass
41
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()
57        clearSite()
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'))
76        self.assertTrue(isinstance(result1, KofaPrincipalInfo))
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')
85        self.assertTrue(isinstance(result1, KofaPrincipalInfo))
86        self.assertTrue(result2 is None)
87        return
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
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.