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

Last change on this file since 5897 was 5897, checked in by uli, 13 years ago

Make sure applicants auth components are registered on University
creation.

The applicants package defines two plugins for the
PluggableAuthenticationUtility? (PAU): an authenticator plugin and a
credentials plugin. While the credentials plugin knows, where to look
in a posted form for credentials (accesscode or PIN), the
authenticator plugin knows how to check these credentials against
local access codes. Both plugins have to be registered with the PAU of
any University instance to become active (admins can also use the ZMI
to register/unregister auth components). This is not done
automatically yet while it should. The test currently fails and we
have to fix that behaviour, before tests will pass again.

File size: 12.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.authentication import (
41    ApplicantsAuthenticatorPlugin, WAeUPApplicantCredentialsPlugin,
42    ApplicantCredentials, AuthenticatedApplicantPrincipalFactory,
43    ApplicantPrincipalInfo, ApplicantPrincipal)
44
45
46class FakeBatch(dict):
47    def getAccessCode(self, id):
48        return self.get(id)
49
50class FakeAccessCode(object):
51    def __init__(self, repr, inv_date=None):
52        self.invalidation_date = inv_date
53        self.representation = repr
54
55class FakeInvAccessCode(object):
56    invalidation_date = True
57
58class FakeApplication(object):
59    def __init__(self, ac=None):
60        self.access_code = ac
61
62class FakeSite(grok.Application, grok.Container):
63    def __init__(self):
64        super(FakeSite, self).__init__()
65        self['applicants'] = {
66            'APP': {
67                'APP-12345': FakeApplication('APP-12345'),
68                'APP-54321': FakeApplication('APP-54321'),
69                'APP-22222': FakeApplication('APP-OTHER'),
70                'APP-44444': FakeApplication(),
71                'APP-55555': FakeApplication('APP-OTHER'),
72                },
73            'JAMB': {
74                'JAMB1': FakeApplication(),
75                'JAMB2': FakeApplication('APP-12345'),
76                'JAMB3': FakeApplication('APP-54321'),
77                'JAMB4': FakeApplication('APP-44444'),
78                },
79            }
80        self['accesscodes'] = {
81            'APP': FakeBatch({
82                    'APP-12345': FakeAccessCode('APP-12345'),
83                    'APP-54321': FakeAccessCode('APP-54321', True),
84                    'APP-11111': FakeAccessCode('APP-11111'),
85                    'APP-22222': FakeAccessCode('APP-22222'),
86                    'APP-33333': FakeAccessCode('APP-33333', True),
87                    'APP-44444': FakeAccessCode('APP-44444', True),
88                    'APP-55555': FakeAccessCode('APP-55555', True),
89                    })
90            }
91
92class AuthenticatorPluginTest(unittest.TestCase):
93
94    def setUp(self):
95        self.app = FakeSite()
96        self.app.setSiteManager(LocalSiteManager(self.app))
97        self.plugin = ApplicantsAuthenticatorPlugin()
98        setSite(self.app)
99        return
100
101    def tearDown(self):
102        clearSite()
103        return
104
105    def test_invalid_credentials(self):
106        result = self.plugin.authenticateCredentials('not-a-dict')
107        assert result is None
108
109        result = self.plugin.authenticateCredentials(
110            dict(accesscode=None, foo='blah'))
111        assert result is None
112
113        result = self.plugin.authenticateCredentials(
114            dict(accesscode='Nonsense',))
115        assert result is None
116
117        # Possible cases, where formal correct authentication
118        # data is not valid:
119        result = self.plugin.authenticateCredentials(
120            dict(accesscode='APP-22222'))
121        assert result is None
122
123        result = self.plugin.authenticateCredentials(
124            dict(accesscode='APP-33333'))
125        assert result is None
126
127        result = self.plugin.authenticateCredentials(
128            dict(accesscode='APP-44444'))
129        assert result is None
130       
131        result = self.plugin.authenticateCredentials(
132            dict(accesscode='APP-55555'))
133        assert result is None
134        return
135
136    def test_valid_credentials(self):
137        """The six different cases where we allow login.
138
139        All other combinations should be forbidden.
140        """
141        result = self.plugin.authenticateCredentials(
142            dict(accesscode='APP-11111'))
143        assert result is not None
144
145        result = self.plugin.authenticateCredentials(
146            dict(accesscode='APP-12345'))
147        assert result is not None
148
149        result = self.plugin.authenticateCredentials(
150            dict(accesscode='APP-54321'))
151        assert result is not None
152
153        # check the `principalInfo` method of authenticator
154        # plugin. This is only here to satisfy the coverage report.
155        assert self.plugin.principalInfo('not-an-id') is None
156        return
157
158session_data = {
159    'zope.pluggableauth.browserplugins': {}
160    }
161
162class FakeSession(dict):
163    def __init__(self, request):
164        pass
165
166    def get(self, key, default=None):
167        return self.__getitem__(key, default)
168   
169    def __getitem__(self, key, default=None):
170        return session_data.get(key, default)
171
172    def __setitem__(self, key, value):
173        session_data[key] = value
174        return
175
176class CredentialsPluginTest(unittest.TestCase):
177
178    def setUp(self):
179        self.request = TestRequest()
180        provideAdapter(FakeSession, (IRequest,), ISession)
181        self.plugin = WAeUPApplicantCredentialsPlugin()
182        self.full_request = TestRequest()
183        session_data['zope.pluggableauth.browserplugins'] = {}
184        return
185
186    def tearDown(self):
187        cleanup.tearDown()
188        return
189
190    def filled_request(self, form_dict):
191        request = TestRequest()
192        for key, value in form_dict.items():
193            request.form[key] = value
194        return request
195
196    def test_extractCredentials_invalid(self):
197        result = self.plugin.extractCredentials('not-a-request')
198        assert result is None
199        return
200
201    def test_extractCredentials_empty(self):
202        result = self.plugin.extractCredentials(self.request)
203        assert result is None
204        return
205
206    def test_extractCredentials_full_set(self):
207        request = self.filled_request({
208                'form.prefix': 'APP',
209                'form.ac_series': '1',
210                'form.ac_number': '1234567890',
211                #'form.jamb_reg_no': 'JAMB_NUMBER',
212                })
213        result = self.plugin.extractCredentials(request)
214        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
215        return
216
217    def test_extractCredentials_accesscode_only(self):
218        request = self.filled_request({
219                'form.prefix': 'APP',
220                'form.ac_series': '1',
221                'form.ac_number': '1234567890',
222                })
223        result = self.plugin.extractCredentials(request)
224        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
225        return
226
227    def test_extractCredentials_from_empty_session(self):
228        session_data['zope.pluggableauth.browserplugins']['credentials'] = None
229        result = self.plugin.extractCredentials(self.request)
230        assert result is None
231        return
232
233    def test_extractCredentials_from_nonempty_session(self):
234        credentials = ApplicantCredentials('APP-1-12345')
235        session_data['zope.pluggableauth.browserplugins'][
236            'credentials'] = credentials
237        result = self.plugin.extractCredentials(self.request)
238        self.assertEqual(result, {'accesscode': 'APP-1-12345'})
239        return
240
241
242class ApplicantCredentialsTest(unittest.TestCase):
243
244    def setUp(self):
245        self.credentials = ApplicantCredentials('SOME_ACCESSCODE')
246        return
247
248    def tearDown(self):
249        return
250
251    def test_methods(self):
252        self.assertEqual(self.credentials.getAccessCode(), 'SOME_ACCESSCODE')
253        assert self.credentials.getLogin() is None
254        assert self.credentials.getPassword() is None
255        return
256
257class FakePluggableAuth(object):
258    prefix = 'foo'
259
260class PrincipalFactoryTest(unittest.TestCase):
261
262    def setUp(self):
263        self.info = ApplicantPrincipalInfo('APP-1-1234567890')
264        return
265
266    def tearDown(self):
267        pass
268
269    def test_principalFactory_interface(self):
270        verify.verifyClass(IAuthenticatedPrincipalFactory,
271                           AuthenticatedApplicantPrincipalFactory
272                           )
273        return
274
275    def test_principalFactory_create(self):
276        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
277
278        assert factory.info is self.info
279        assert factory.request is None
280        return
281
282    def test_principalFactory_call_w_prefix(self):
283        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
284        principal = factory(FakePluggableAuth())
285
286        assert isinstance(principal, ApplicantPrincipal)
287        self.assertEqual(principal.__repr__(),
288                         "ApplicantPrincipal('foo.APP-1-1234567890')")
289        self.assertEqual(principal.id, 'foo.APP-1-1234567890')
290        return
291
292    def test_principalFactory_call_wo_prefix(self):
293        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
294        fake_auth = FakePluggableAuth()
295        fake_auth.prefix = None
296        principal = factory(fake_auth)
297        self.assertEqual(principal.id, 'APP-1-1234567890')
298        return
299
300class PAUSetupTest(FunctionalTestCase):
301    # Check correct setup of authentication components in the
302    # applicants subpackage.
303
304    # When a university is created, we want by default have our local
305    # authentication components (an authenticator plugin and a
306    # credentials plugin) to be registered with the local PAU. Admins
307    # can remove these components on the fly later-on if they wish.
308   
309    layer = FunctionalLayer
310   
311    def setUp(self):
312        super(PAUSetupTest, self).setUp()
313
314        # Setup a sample site for each test
315        app = University()
316        self.dc_root = tempfile.mkdtemp()
317        app['datacenter'].setStoragePath(self.dc_root)
318
319        # Prepopulate the ZODB...
320        self.getRootFolder()['app'] = app
321        self.app = self.getRootFolder()['app']
322        self.browser = Browser()
323        self.browser.handleErrors = False
324
325    def tearDown(self):
326        super(PAUSetupTest, self).tearDown()
327        shutil.rmtree(self.dc_root)
328
329    def test_extra_auth_plugins_installed(self):
330        # Check whether the auth plugins defined in here are setup
331        # automatically when a university is created
332
333        # Get the PAU responsible for the local site ('app')
334        pau = getUtility(IAuthentication, context=self.app)
335        cred_plugins = pau.getCredentialsPlugins()
336        auth_plugins = pau.getAuthenticatorPlugins()
337        cred_names = [name for name, plugin in cred_plugins]
338        auth_names = [name for name, plugin in auth_plugins]
339       
340        # Make sure our local ApplicantsAuthenticatorPlugin is registered...
341        self.assertTrue('applicants' in auth_names)
342        # Make sure our local WAeUPApplicantCredentialsPlugin is registered...
343        self.assertTrue('applicant_credentials' in cred_names)
344        return
345
346def test_suite():
347    suite = unittest.TestSuite()
348    for testcase in [
349        AuthenticatorPluginTest, CredentialsPluginTest,
350        ApplicantCredentialsTest, PrincipalFactoryTest,
351        PAUSetupTest,
352        ]:
353        suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
354                testcase
355                )
356        )
357    return suite
Note: See TracBrowser for help on using the repository browser.