source: main/waeup.cas/trunk/waeup/cas/tests/test_authenticators.py @ 15176

Last change on this file since 15176 was 10535, checked in by uli, 11 years ago

pep8.

File size: 15.5 KB
RevLine 
[10535]1# Tests for waeup.cas.authentictors
[10462]2import os
[10478]3import threading
[10394]4import unittest
[10462]5from paste.deploy import loadapp
[10478]6try:
7    from SimpleXMLRPCServer import SimpleXMLRPCServer  # Python 2.x
8except ImportError:
9    from xmlrpc.server import SimpleXMLRPCServer       # Python 3.x
10try:
11    import xmlrpclib                     # Python 2.x
12except ImportError:
13    import xmlrpc.client as xmlrpclib    # Python 3.x
[10394]14from waeup.cas.authenticators import (
15    get_all_authenticators, get_authenticator, filter_auth_opts,
[10511]16    Authenticator, DummyAuthenticator, KofaAuthenticator,
17    KofaMoodleAuthenticator
[10394]18    )
[10462]19from waeup.cas.server import CASServer
[10394]20
21
22class TestHelpers(unittest.TestCase):
23
24    def test_get_all_authenticators(self):
25        # we can get authenticators via entry points
26        auths = get_all_authenticators()
27        assert 'dummy' in auths
28        assert auths['dummy'] is DummyAuthenticator
29
30    def test_filter_auth_opts(self):
31        # we can filter auth opts
32        result = filter_auth_opts(
33            dict(auth='foo', auth_bar='baz', auth_baz='blah')
34            )
35        assert result == (
36            {'auth': 'foo'},
37            {'auth_bar': 'baz', 'auth_baz': 'blah'}
38            )
39
40    def test_get_authenticator(self):
41        # we can parse a paste.deploy config dict for auth
42        result = get_authenticator({})
43        assert result == {}
44        result = get_authenticator({'auth': 'dummy'})
45        assert isinstance(result['auth'], DummyAuthenticator)
46
47
48class AuthenticatorTests(unittest.TestCase):
49
50    def test_create(self):
51        # we can create Authenticator instances
52        auth = Authenticator()
53        assert isinstance(auth, Authenticator)
54
55
56class DummyAuthenticatorTests(unittest.TestCase):
57
58    def test_create(self):
59        # we can create DummyAuthenticator instances
60        auth = DummyAuthenticator()
61        assert isinstance(auth, DummyAuthenticator)
62
63    def test_check_credentials(self):
64        # we can succeed with 'bird'/'bebop'. Others will fail.
65        auth = DummyAuthenticator()
66        result1 = auth.check_credentials('bird', 'bebop')
[10396]67        assert result1 == (True, '')
[10394]68        result2 = auth.check_credentials('foo', 'bar')
[10396]69        assert result2 == (False, 'Invalid username or password.')
[10462]70
71
[10478]72BACKENDS1 = dict(
[10476]73    inst1=dict(
74        url='http://localhost:6666/app',
[10478]75        marker='^MA-',
[10474]76        )
[10462]77    )
78
[10478]79BACKENDS2 = dict(
80    inst1=dict(
81        url='http://localhost:6666/',
82        marker='^SCHOOL1-',
83        )
84    )
[10476]85
[10511]86BACKENDS3 = dict(
87    inst1=dict(
88        url='http://localhost:6666/',
89        marker='^SCHOOL1-',
[10535]90        moodle_url='http://localhost:7777/',
[10511]91        )
92    )
[10478]93
[10535]94
[10462]95class KofaAuthenticatorTests(unittest.TestCase):
96
97    def test_create(self):
98        # we can create KofaAuthenticator instances
99        auth = KofaAuthenticator()
100        assert isinstance(auth, KofaAuthenticator)
101
102    def test_options_defaults(self):
103        # all options have sensible defaults
104        auth = KofaAuthenticator()
[10475]105        assert auth.backends == {}
[10462]106
107    def test_options(self):
108        # we can pass options
[10478]109        auth = KofaAuthenticator(auth_backends=str(BACKENDS1))
110        assert auth.backends == BACKENDS1
[10475]111        auth = KofaAuthenticator(auth_backends='{"foo": {"url": "bar"}}')
112        assert auth.backends['foo']['marker'] == '.+'
113        self.assertRaises(
114            ValueError, KofaAuthenticator, auth_backends='not-a-py-expr')
115        self.assertRaises(
116            ValueError, KofaAuthenticator, auth_backends='"Not a dict"')
117        self.assertRaises(
118            ValueError, KofaAuthenticator,
119            auth_backends='{"foo": "not-a-dict"}')
120        self.assertRaises(
121            ValueError, KofaAuthenticator,
122            auth_backends='{"foo": {"no-url-key": "bar"}}')
123        self.assertRaises(
124            ValueError, KofaAuthenticator,
125            auth_backends='{"foo": {"url": "bar", "marker": "inv_re)"}}')
[10462]126
127    def test_paste_deploy_options(self):
128        # we can set CAS server-related options via paste.deploy config
129        paste_conf = os.path.join(
130            os.path.dirname(__file__), 'sample3.ini')
131        app = loadapp('config:%s' % paste_conf)
132        assert isinstance(app, CASServer)
133        assert app.db_connection_string == 'sqlite:///:memory:'
134        assert isinstance(app.auth, KofaAuthenticator)
135
[10535]136
[10511]137class KofaMoodleAuthenticatorTests(unittest.TestCase):
[10478]138
[10511]139    def test_paste_deploy_options(self):
140        # we can set CAS server-related options via paste.deploy config
141        paste_conf = os.path.join(
142            os.path.dirname(__file__), 'sample4.ini')
143        app = loadapp('config:%s' % paste_conf)
144        assert isinstance(app, CASServer)
145        assert app.db_connection_string == 'sqlite:///:memory:'
146        assert isinstance(app.auth, KofaAuthenticator)
147        assert app.auth.name == 'kofa_moodle1'
148        auth_backends = {
149          'kofa_moodle1':
150            {'url': 'http://xmlrpcuser1:xmlrpcuser1@localhost:8080/app1/',
151             'marker': '^K',
[10535]152             'moodle_url': ('http://localhost/moodle/webservice/xmlrpc'
153                            '/server.php?wstoken=de1e1cbacf91ec6290bb6'
154                            'f6339122e7d'),
[10511]155            },
156          }
157        assert app.auth.backends == auth_backends
158
159
[10478]160class FakeKofaServer(SimpleXMLRPCServer):
161    # A fake Kofa server that provides only XMLRPC methods
162
163    allow_reuse_address = True
164
165    def __init__(self, *args, **kw):
166        kw.update(allow_none=True)
167        SimpleXMLRPCServer.__init__(self, *args, **kw)
[10506]168        self.register_function(
169            self._check_credentials, 'check_applicant_credentials')
170        self.register_function(
171            self._check_credentials, 'check_student_credentials')
[10512]172        self.register_function(
[10518]173            self._get_moodle_data, 'get_student_moodle_data')
174        self.register_function(
175            self._get_moodle_data, 'get_applicant_moodle_data')
[10478]176
177    def _check_credentials(self, username, password):
178        # fake waeup.kofa check_credentials method.
179        #
180        # This method is supposed to mimic the behaviour of an
181        # original waeup.kofa check_credentials method. It returns a
182        # positive result for the credentials `bird`/`bebop`.
183        if username == 'bird' and password == 'bebop':
184            return {'id': 'bird', 'email': 'bird@gods.net',
[10511]185                    'description': 'Mr. Charles Parker',
186                    'type': 'student'}
187        if username == 'pig' and password == 'pog':
188            return {'id': 'pig', 'email': 'pig@gods.net',
189                    'description': 'Mr. Ray Charles',
190                    'type': 'applicant'}
[10514]191        if username in (
[10526]192            'fault1', 'fault2',
193            'fault3', 'fault4', 'fault7') and password == 'biz':
[10514]194            return {'type': 'student'}
[10518]195        if username == 'fault5' and password == 'biz':
196            return {'type': 'applicant'}
[10522]197        if username == 'fault6' and password == 'biz':
198            return {'type': 'boss'}
[10478]199        return None
200
[10518]201    def _get_moodle_data(self, username):
[10515]202        # fake waeup.kofa get_student_moodle_data method.
[10512]203        if username == 'bird':
204            return dict(email='aa@aa.aa',
205                        firstname='Charles',
206                        lastname='Parker',
207                        )
208        if username == 'pig':
209            return dict(email='bb@bb.bb',
210                        firstname='Ray',
211                        lastname='Charles',
212                        )
[10524]213        if username in ('fault1', 'fault2', 'fault3',
214                        'fault4', 'fault5', 'fault7'):
[10514]215            return dict(email='ff@ff.ff',
216                        firstname='John',
217                        lastname='Fault',
218                        )
[10478]219
[10535]220
[10512]221class FakeMoodleServer(SimpleXMLRPCServer):
222    # A fake Moodle server that provides only XMLRPC methods
223
224    allow_reuse_address = True
225
226    def __init__(self, *args, **kw):
227        kw.update(allow_none=True)
228        SimpleXMLRPCServer.__init__(self, *args, **kw)
229        self.register_function(
230            self._core_user_create_users, 'core_user_create_users')
231        self.register_function(
232            self._core_user_get_users, 'core_user_get_users')
233        self.register_function(
234            self._core_user_update_users, 'core_user_update_users')
235
[10513]236    def _core_user_create_users(self, arg):
[10512]237        # fake Moodle core_user_create_users method.
[10523]238        if arg[0]['username'] == 'school1-fault1':
[10526]239            raise xmlrpclib.Fault('faultCode', 'core_user_create_users failed')
[10524]240        if arg[0]['username'] == 'school1-fault7':
241            raise xmlrpclib.Fault('faultCode', 'Email address already exists')
[10526]242        return [{'username': 'any name', 'id': 37}]
[10512]243
[10513]244    def _core_user_get_users(self, arg):
[10512]245        # fake Moodle core_user_get_users method.
[10523]246        if arg[0]['value'] == 'SCHOOL1-fault2':
[10526]247            raise xmlrpclib.Fault('faultCode', 'core_user_get_users failed')
[10523]248        if arg[0]['value'] == 'SCHOOL1-fault3':
[10535]249            return {'users': [{'id': 'SCHOOL1-fault3'}]}
[10526]250        if arg[0]['value'] in ('SCHOOL1-fault4', 'SCHOOL1-fault5'):
[10535]251            return {'users': [{'id': 123}]}
[10526]252        return {'users': []}
[10512]253
[10513]254    def _core_user_update_users(self, arg):
[10512]255        # fake Moodle core_user_update_users method.
[10523]256        if arg[0]['id'] == 'SCHOOL1-fault3':
[10526]257            raise xmlrpclib.Fault('faultCode', 'core_user_update_users failed')
[10512]258        return None
259
[10535]260
[10512]261class KofaAuthenticatorsIntegrationTests(unittest.TestCase):
[10524]262    # A test case where a fake Kofa server and a fake Moodle server
263    # are started before tests (and shut down afterwards).
[10478]264
[10512]265    kofa_server = None
266    kofa_th = None
267    moodle_server = None
268    moodle_th = None
[10478]269
270    @classmethod
271    def setUpClass(cls):
[10507]272        # set up a fake kofa server
[10512]273        cls.kofa_server = FakeKofaServer(('localhost', 6666))
274        cls.kofa_th = threading.Thread(target=cls.kofa_server.serve_forever)
275        cls.kofa_th.daemon = True
276        cls.kofa_th.start()
277        # set up a fake moodle server
278        cls.moodle_server = FakeMoodleServer(('localhost', 7777))
[10535]279        cls.moodle_th = threading.Thread(
280            target=cls.moodle_server.serve_forever)
[10512]281        cls.moodle_th.daemon = True
282        cls.moodle_th.start()
[10478]283
284    @classmethod
285    def tearDownClass(cls):
[10507]286        # tear down fake kofa server
[10512]287        cls.kofa_server.shutdown()
288        cls.kofa_server.server_close()
289        # tear down fake moodle server
290        cls.moodle_server.shutdown()
291        cls.moodle_server.server_close()
[10478]292
293    def test_fake_kofa_works(self):
294        # make sure the local fake kofa works
295        proxy = xmlrpclib.ServerProxy("http://localhost:6666", allow_none=True)
[10506]296        result = proxy.check_student_credentials('bird', 'bebop')
[10478]297        assert result == {
298            'description': 'Mr. Charles Parker',
299            'email': 'bird@gods.net',
[10511]300            'id': 'bird',
301            'type': 'student'}
302        result = proxy.check_applicant_credentials('pig', 'pog')
[10506]303        assert result == {
[10511]304            'description': 'Mr. Ray Charles',
305            'email': 'pig@gods.net',
306            'id': 'pig',
307            'type': 'applicant'}
[10515]308        result = proxy.get_student_moodle_data('pig')
[10512]309        assert result == {
310            'lastname': 'Charles',
311            'email': 'bb@bb.bb',
312            'firstname': 'Ray',
313            }
[10518]314        result = proxy.get_applicant_moodle_data('pig')
315        assert result == {
316            'lastname': 'Charles',
317            'email': 'bb@bb.bb',
318            'firstname': 'Ray',
319            }
[10478]320        return
321
[10513]322    def test_fake_moodle_works(self):
[10512]323        # make sure the local fake moodle works
324        proxy = xmlrpclib.ServerProxy("http://localhost:7777", allow_none=True)
[10513]325        # test core_user_create_users
326        result = proxy.core_user_create_users(
327            [{'username': 'any name'}])
[10535]328        assert result == [{'id': 37, 'username': 'any name'}]
[10513]329        self.assertRaises(
330            xmlrpclib.Fault, proxy.core_user_create_users,
[10523]331            [{'username': 'school1-fault1'}])
[10524]332        self.assertRaises(
333            xmlrpclib.Fault, proxy.core_user_create_users,
334            [{'username': 'school1-fault7'}])
[10513]335        # test core_user_get_users
336        self.assertRaises(
337            xmlrpclib.Fault, proxy.core_user_get_users,
[10523]338            [{'key': 'username', 'value': 'SCHOOL1-fault2'}])
[10513]339        # test core_user_update_users
340        result = proxy.core_user_update_users(
[10526]341            [{'id': 234}])
[10512]342        assert result == None
[10513]343        self.assertRaises(
344            xmlrpclib.Fault, proxy.core_user_update_users,
[10523]345            [{'id': 'SCHOOL1-fault3'}])
[10512]346        return
347
[10518]348    def test_check_credentials_KofaAuthenticator(self):
[10462]349        # we get real responses when querying Kofa instances
[10478]350        auth = KofaAuthenticator(auth_backends=str(BACKENDS2))
351        result1 = auth.check_credentials('SCHOOL1-bird', 'bebop')
[10462]352        assert result1 == (True, '')
[10478]353        result2 = auth.check_credentials('SCHOOL1-foo', 'bar')
[10462]354        assert result2 == (False, 'Invalid username or password.')
[10478]355        result3 = auth.check_credentials('SCHOOL2-bar', 'baz')
356        assert result3 == (False, 'Invalid username or password.')
[10511]357
[10518]358    def test_check_credentials_KofaMoodleAuthenticator(self):
[10512]359        # we get real responses when querying Kofa instances
360        auth = KofaMoodleAuthenticator(auth_backends=str(BACKENDS3))
[10518]361        result1s = auth.check_credentials('SCHOOL1-bird', 'bebop')
362        assert result1s == (True, '')
[10535]363        result1a = auth.check_credentials('SCHOOL1-pig', 'pog')
[10518]364        assert result1a == (True, '')
[10526]365        # user does not exist
[10518]366        result2s = auth.check_credentials('SCHOOL1-foo', 'bar')
[10522]367        assert result2s == (False, 'Invalid username or password')
[10526]368        # school does not exist
[10518]369        result3s = auth.check_credentials('SCHOOL2-bar', 'baz')
[10522]370        assert result3s == (False, 'Invalid username or password')
[10526]371        # user is neither a student nor an applicant
[10522]372        result8 = auth.check_credentials('SCHOOL1-fault6', 'biz')
373        assert result8 == (False, 'User not eligible')
[10526]374        # user has an existing email address
[10524]375        result9 = auth.check_credentials('SCHOOL1-fault7', 'biz')
376        assert result9 == (False,
377                    'Another Moodle user is using the same email address.'
378                    ' Email addresses can\'t be used twice in Moodle.')
[10518]379
[10526]380        # exception is catched if core_user_create_users fails
[10518]381        result4s = auth.check_credentials('SCHOOL1-fault1', 'biz')
[10526]382        assert result4s == (False, 'core_user_create_users failed')
383        # exception is catched if core_user_get_users fails
[10518]384        result5s = auth.check_credentials('SCHOOL1-fault2', 'biz')
[10526]385        assert result5s == (False, 'core_user_get_users failed')
386        # exception is catched if core_user_update_users fails
[10518]387        result6s = auth.check_credentials('SCHOOL1-fault3', 'biz')
[10526]388        assert result6s == (False, 'core_user_update_users failed')
[10518]389
[10526]390        # no exception is raised if user exists
[10518]391        result7s = auth.check_credentials('SCHOOL1-fault4', 'biz')
392        assert result7s == (True, '')
393        result7a = auth.check_credentials('SCHOOL1-fault5', 'biz')
394        assert result7a == (True, '')
[10524]395
[10526]396        # if marker is an empty string, school markers are neither
397        # checked nor removed
[10524]398        BACKENDS3['inst1']['marker'] = ''
399        auth = KofaMoodleAuthenticator(auth_backends=str(BACKENDS3))
400        result10s = auth.check_credentials('bird', 'bebop')
401        assert result10s == (True, '')
402        result10a = auth.check_credentials('pig', 'pog')
403        assert result10a == (True, '')
404        result11s = auth.check_credentials('SCHOOL1-bird', 'bebop')
405        assert result11s == (False, 'Invalid username or password')
406        result11a = auth.check_credentials('SCHOOL1-pig', 'pog')
407        assert result11a == (False, 'Invalid username or password')
Note: See TracBrowser for help on using the repository browser.