source: main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_authentication.py @ 6123

Last change on this file since 6123 was 6123, checked in by uli, 13 years ago
  • Remove whitespaces
  • Make authentication tests work again.

Authentication tests now require a complete functional setup, because
we the get_applicant_data() function used by the authentication
plugins now looks up catalogs and catalogs are only filled when real
Applicant objects are added to the ZODB (no FakeApplicants? as we did
yet).

The tests still fail, but this is due to a real bug we have to fix:
get_applicant_data does not lookup the name attribute of
applicants any more, but the access_code attribute. As the authenticator
plugin looks for some applicant stored under a certain name (and not
with a certain access_code attribute) the authentication can succeed
with invalid applicant data.

It's very good, we had these test, because this one was really
difficult to discover. Took me plenty hours to realize what happened
here.

File size: 15.0 KB
Line 
1##
2## test_authentication.py
3## Login : <uli@pu.smp.net>
4## Started on  Fri Aug 20 08:18:58 2010 Uli Fouquet
5## $Id$
6##
7## Copyright (C) 2010 Uli Fouquet
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published by
10## the Free Software Foundation; either version 2 of the License, or
11## (at your option) any later version.
12##
13## This program is distributed in the hope that it will be useful,
14## but WITHOUT ANY WARRANTY; without even the implied warranty of
15## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16## GNU General Public License for more details.
17##
18## You should have received a copy of the GNU General Public License
19## along with this program; if not, write to the Free Software
20## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21##
22import grok
23import shutil
24import tempfile
25import unittest
26from zope.app.testing.functional import FunctionalTestCase
27from zope.authentication.interfaces import IAuthentication
28from zope.component import provideAdapter, getUtility
29from zope.component.hooks import setSite, clearSite
30from zope.interface import verify
31from zope.pluggableauth.interfaces import  IAuthenticatedPrincipalFactory
32from zope.publisher.browser import TestRequest
33from zope.publisher.interfaces import IRequest
34from zope.session.interfaces import ISession
35from zope.site import LocalSiteManager
36from zope.testbrowser.testing import Browser
37from zope.testing import cleanup
38from waeup.sirp.testing import FunctionalLayer
39from waeup.sirp.app import University
40from waeup.sirp.applicants import  ApplicantsContainer, Applicant
41from waeup.sirp.applicants.authentication import (
42    ApplicantsAuthenticatorPlugin, WAeUPApplicantCredentialsPlugin,
43    ApplicantCredentials, AuthenticatedApplicantPrincipalFactory,
44    ApplicantPrincipalInfo, ApplicantPrincipal,)
45
46
47class FakeBatch(dict):
48    def getAccessCode(self, id):
49        return self.get(id)
50
51class FakeAccessCode(object):
52    def __init__(self, repr, inv_date=None, disabled=False):
53        self.invalidation_date = inv_date
54        self.representation = repr
55        self.disabled = disabled
56
57class FakeInvAccessCode(object):
58    invalidation_date = True
59
60class FakeApplication(object):
61    def __init__(self, ac=None):
62        self.access_code = ac
63
64class FakeSite(grok.Application, grok.Container):
65    def __init__(self):
66        super(FakeSite, self).__init__()
67        self['applicants'] = {
68            'APP': {
69                'APP-12345': FakeApplication(u'APP-12345'),
70                'APP-54321': FakeApplication(u'APP-54321'),
71                'APP-22222': FakeApplication(u'APP-OTHER'),
72                'APP-44444': FakeApplication(),
73                'APP-55555': FakeApplication(u'APP-OTHER'),
74                },
75            'JAMB': {
76                'JAMB1': FakeApplication(),
77                'JAMB2': FakeApplication(u'APP-12345'),
78                'JAMB3': FakeApplication(u'APP-54321'),
79                'JAMB4': FakeApplication(u'APP-44444'),
80                },
81            }
82        self['accesscodes'] = {
83            'APP': FakeBatch({
84                    'APP-12345': FakeAccessCode('APP-12345'),
85                    'APP-54321': FakeAccessCode('APP-54321', True),
86                    'APP-11111': FakeAccessCode('APP-11111'),
87                    'APP-22222': FakeAccessCode('APP-22222'),
88                    'APP-33333': FakeAccessCode('APP-33333', True),
89                    'APP-44444': FakeAccessCode('APP-44444', True),
90                    'APP-55555': FakeAccessCode('APP-55555', True),
91                    'APP-66666': FakeAccessCode('APP-66666', True, False),
92                    'APP-77777': FakeAccessCode('APP-77777', False, False),
93                    })
94            }
95
96def FakeSite():
97    return {
98        'applicants': {
99            'APP': {
100                'APP-12345': FakeApplication(u'APP-12345'),
101                'APP-54321': FakeApplication(u'APP-54321'),
102                'APP-22222': FakeApplication(u'APP-OTHER'),
103                'APP-44444': FakeApplication(),
104                'APP-55555': FakeApplication(u'APP-OTHER'),
105                },
106            'JAMB': {
107                'JAMB1': FakeApplication(),
108                'JAMB2': FakeApplication(u'APP-12345'),
109                'JAMB3': FakeApplication(u'APP-54321'),
110                'JAMB4': FakeApplication(u'APP-44444'),
111                },
112            },
113        'accesscodes': {
114            'APP': FakeBatch({
115                    'APP-12345': FakeAccessCode('APP-12345'),
116                    'APP-54321': FakeAccessCode('APP-54321', True),
117                    'APP-11111': FakeAccessCode('APP-11111'),
118                    'APP-22222': FakeAccessCode('APP-22222'),
119                    'APP-33333': FakeAccessCode('APP-33333', True),
120                    'APP-44444': FakeAccessCode('APP-44444', True),
121                    'APP-55555': FakeAccessCode('APP-55555', True),
122                    'APP-66666': FakeAccessCode('APP-66666', True, False),
123                    'APP-77777': FakeAccessCode('APP-77777', False, False),
124                    })
125            }
126        }
127
128class AuthenticatorPluginTest(FunctionalTestCase):
129
130    layer = FunctionalLayer
131
132    def create_applicant(self, cname, aname, ac=None):
133        # Create an applicant in an applicants container
134        setSite(self.app)
135        if not cname in self.app['applicants'].keys():
136            container = ApplicantsContainer()
137            self.app['applicants'][cname] = container
138        applicant = Applicant()
139        if ac is not None:
140            applicant.access_code = ac
141        self.app['applicants'][cname][aname] = applicant
142        return
143
144    def setUp(self):
145        super(AuthenticatorPluginTest, self).setUp()
146
147        # Setup a sample site for each test
148        app = University()
149        self.dc_root = tempfile.mkdtemp()
150        app['datacenter'].setStoragePath(self.dc_root)
151
152        # Prepopulate the ZODB...
153        self.getRootFolder()['app'] = app
154        self.app = self.getRootFolder()['app']
155
156        fake_site = FakeSite()
157        for ckey, fake_container in fake_site['applicants'].items():
158            for akey, fake_appl in fake_container.items():
159                self.create_applicant(ckey, akey, fake_appl.access_code)
160        del self.app['accesscodes']
161        self.app['accesscodes'] = fake_site['accesscodes']
162
163        self.plugin = ApplicantsAuthenticatorPlugin()
164        return
165
166    def tearDown(self):
167        super(AuthenticatorPluginTest, self).tearDown()
168        shutil.rmtree(self.dc_root)
169        return
170
171    def test_invalid_credentials(self):
172        result = self.plugin.authenticateCredentials('not-a-dict')
173        assert result is None
174
175        result = self.plugin.authenticateCredentials(
176            dict(accesscode=None, foo='blah'))
177        assert result is None
178
179        result = self.plugin.authenticateCredentials(
180            dict(accesscode='Nonsense',))
181        assert result is None
182
183        # Possible cases, where formal correct authentication
184        # data is not valid:
185        # XXX: fails
186        result = self.plugin.authenticateCredentials(
187            dict(accesscode='APP-22222'))
188        assert result is None
189
190        result = self.plugin.authenticateCredentials(
191            dict(accesscode='APP-33333'))
192        assert result is None
193
194        result = self.plugin.authenticateCredentials(
195            dict(accesscode='APP-44444'))
196        assert result is None
197
198        result = self.plugin.authenticateCredentials(
199            dict(accesscode='APP-55555'))
200        assert result is None
201
202        result = self.plugin.authenticateCredentials(
203            dict(accesscode='APP-66666'))
204        assert result is None
205
206        result = self.plugin.authenticateCredentials(
207            dict(accesscode='APP-77777'))
208        assert result is None
209        return
210
211    def test_valid_credentials(self):
212        """The six different cases where we allow login.
213
214        All other combinations should be forbidden.
215        """
216        result = self.plugin.authenticateCredentials(
217            dict(accesscode='APP-11111'))
218        assert result is not None
219
220        result = self.plugin.authenticateCredentials(
221            dict(accesscode='APP-12345'))
222        assert result is not None
223
224        result = self.plugin.authenticateCredentials(
225            dict(accesscode='APP-54321'))
226        assert result is not None
227
228        # check the `principalInfo` method of authenticator
229        # plugin. This is only here to satisfy the coverage report.
230        assert self.plugin.principalInfo('not-an-id') is None
231        return
232
233session_data = {
234    'zope.pluggableauth.browserplugins': {}
235    }
236
237class FakeSession(dict):
238    def __init__(self, request):
239        pass
240
241    def get(self, key, default=None):
242        return self.__getitem__(key, default)
243
244    def __getitem__(self, key, default=None):
245        return session_data.get(key, default)
246
247    def __setitem__(self, key, value):
248        session_data[key] = value
249        return
250
251class CredentialsPluginTest(unittest.TestCase):
252
253    def setUp(self):
254        self.request = TestRequest()
255        provideAdapter(FakeSession, (IRequest,), ISession)
256        self.plugin = WAeUPApplicantCredentialsPlugin()
257        self.full_request = TestRequest()
258        session_data['zope.pluggableauth.browserplugins'] = {}
259        return
260
261    def tearDown(self):
262        cleanup.tearDown()
263        return
264
265    def filled_request(self, form_dict):
266        request = TestRequest()
267        for key, value in form_dict.items():
268            request.form[key] = value
269        return request
270
271    def test_extractCredentials_invalid(self):
272        result = self.plugin.extractCredentials('not-a-request')
273        assert result is None
274        return
275
276    def test_extractCredentials_empty(self):
277        result = self.plugin.extractCredentials(self.request)
278        assert result is None
279        return
280
281    def test_extractCredentials_full_set(self):
282        request = self.filled_request({
283                'form.ac_prefix': 'APP',
284                'form.ac_series': '1',
285                'form.ac_number': '1234567890',
286                #'form.jamb_reg_no': 'JAMB_NUMBER',
287                })
288        result = self.plugin.extractCredentials(request)
289        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
290        return
291
292    def test_extractCredentials_accesscode_only(self):
293        request = self.filled_request({
294                'form.ac_prefix': 'APP',
295                'form.ac_series': '1',
296                'form.ac_number': '1234567890',
297                })
298        result = self.plugin.extractCredentials(request)
299        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
300        return
301
302    def test_extractCredentials_from_empty_session(self):
303        session_data['zope.pluggableauth.browserplugins']['credentials'] = None
304        result = self.plugin.extractCredentials(self.request)
305        assert result is None
306        return
307
308    def test_extractCredentials_from_nonempty_session(self):
309        credentials = ApplicantCredentials('APP-1-12345')
310        session_data['zope.pluggableauth.browserplugins'][
311            'credentials'] = credentials
312        result = self.plugin.extractCredentials(self.request)
313        self.assertEqual(result, {'accesscode': 'APP-1-12345'})
314        return
315
316
317class ApplicantCredentialsTest(unittest.TestCase):
318
319    def setUp(self):
320        self.credentials = ApplicantCredentials('SOME_ACCESSCODE')
321        return
322
323    def tearDown(self):
324        return
325
326    def test_methods(self):
327        self.assertEqual(self.credentials.getAccessCode(), 'SOME_ACCESSCODE')
328        assert self.credentials.getLogin() is None
329        assert self.credentials.getPassword() is None
330        return
331
332class FakePluggableAuth(object):
333    prefix = 'foo'
334
335class PrincipalFactoryTest(unittest.TestCase):
336
337    def setUp(self):
338        self.info = ApplicantPrincipalInfo('APP-1-1234567890')
339        return
340
341    def tearDown(self):
342        pass
343
344    def test_principalFactory_interface(self):
345        verify.verifyClass(IAuthenticatedPrincipalFactory,
346                           AuthenticatedApplicantPrincipalFactory
347                           )
348        return
349
350    def test_principalFactory_create(self):
351        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
352
353        assert factory.info is self.info
354        assert factory.request is None
355        return
356
357    def test_principalFactory_call_w_prefix(self):
358        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
359        principal = factory(FakePluggableAuth())
360
361        assert isinstance(principal, ApplicantPrincipal)
362        self.assertEqual(principal.__repr__(),
363                         "ApplicantPrincipal('foo.APP-1-1234567890')")
364        self.assertEqual(principal.id, 'foo.APP-1-1234567890')
365        return
366
367    def test_principalFactory_call_wo_prefix(self):
368        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
369        fake_auth = FakePluggableAuth()
370        fake_auth.prefix = None
371        principal = factory(fake_auth)
372        self.assertEqual(principal.id, 'APP-1-1234567890')
373        return
374
375class PAUSetupTest(FunctionalTestCase):
376    # Check correct setup of authentication components in the
377    # applicants subpackage.
378
379    # When a university is created, we want by default have our local
380    # authentication components (an authenticator plugin and a
381    # credentials plugin) to be registered with the local PAU. Admins
382    # can remove these components on the fly later-on if they wish.
383
384    layer = FunctionalLayer
385
386    def setUp(self):
387        super(PAUSetupTest, self).setUp()
388
389        # Setup a sample site for each test
390        app = University()
391        self.dc_root = tempfile.mkdtemp()
392        app['datacenter'].setStoragePath(self.dc_root)
393
394        # Prepopulate the ZODB...
395        self.getRootFolder()['app'] = app
396        self.app = self.getRootFolder()['app']
397        self.browser = Browser()
398        self.browser.handleErrors = False
399
400    def tearDown(self):
401        super(PAUSetupTest, self).tearDown()
402        shutil.rmtree(self.dc_root)
403
404    def test_extra_auth_plugins_installed(self):
405        # Check whether the auth plugins defined in here are setup
406        # automatically when a university is created
407
408        # Get the PAU responsible for the local site ('app')
409        pau = getUtility(IAuthentication, context=self.app)
410        cred_plugins = pau.getCredentialsPlugins()
411        auth_plugins = pau.getAuthenticatorPlugins()
412        cred_names = [name for name, plugin in cred_plugins]
413        auth_names = [name for name, plugin in auth_plugins]
414
415        # Make sure our local ApplicantsAuthenticatorPlugin is registered...
416        self.assertTrue('applicants' in auth_names)
417        # Make sure our local WAeUPApplicantCredentialsPlugin is registered...
418        self.assertTrue('applicant_credentials' in cred_names)
419        return
420
421def test_suite():
422    suite = unittest.TestSuite()
423    for testcase in [
424        AuthenticatorPluginTest, CredentialsPluginTest,
425        ApplicantCredentialsTest, PrincipalFactoryTest,
426        PAUSetupTest,
427        ]:
428        suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
429                testcase
430                )
431        )
432    return suite
Note: See TracBrowser for help on using the repository browser.