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

Last change on this file since 10524 was 10524, checked in by Henrik Bettermann, 11 years ago

Test coverage 100%.

File size: 15.2 KB
RevLine 
[10523]13# 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-',
[10512]90        moodle_url = 'http://localhost:7777/',
[10511]91        )
92    )
[10478]93
[10462]94class KofaAuthenticatorTests(unittest.TestCase):
95
96    def test_create(self):
97        # we can create KofaAuthenticator instances
98        auth = KofaAuthenticator()
99        assert isinstance(auth, KofaAuthenticator)
100
101    def test_options_defaults(self):
102        # all options have sensible defaults
103        auth = KofaAuthenticator()
[10475]104        assert auth.backends == {}
[10462]105
106    def test_options(self):
107        # we can pass options
[10478]108        auth = KofaAuthenticator(auth_backends=str(BACKENDS1))
109        assert auth.backends == BACKENDS1
[10475]110        auth = KofaAuthenticator(auth_backends='{"foo": {"url": "bar"}}')
111        assert auth.backends['foo']['marker'] == '.+'
112        self.assertRaises(
113            ValueError, KofaAuthenticator, auth_backends='not-a-py-expr')
114        self.assertRaises(
115            ValueError, KofaAuthenticator, auth_backends='"Not a dict"')
116        self.assertRaises(
117            ValueError, KofaAuthenticator,
118            auth_backends='{"foo": "not-a-dict"}')
119        self.assertRaises(
120            ValueError, KofaAuthenticator,
121            auth_backends='{"foo": {"no-url-key": "bar"}}')
122        self.assertRaises(
123            ValueError, KofaAuthenticator,
124            auth_backends='{"foo": {"url": "bar", "marker": "inv_re)"}}')
[10462]125
126    def test_paste_deploy_options(self):
127        # we can set CAS server-related options via paste.deploy config
128        paste_conf = os.path.join(
129            os.path.dirname(__file__), 'sample3.ini')
130        app = loadapp('config:%s' % paste_conf)
131        assert isinstance(app, CASServer)
132        assert app.db_connection_string == 'sqlite:///:memory:'
133        assert isinstance(app.auth, KofaAuthenticator)
134
[10511]135class KofaMoodleAuthenticatorTests(unittest.TestCase):
[10478]136
[10511]137    def test_paste_deploy_options(self):
138        # we can set CAS server-related options via paste.deploy config
139        paste_conf = os.path.join(
140            os.path.dirname(__file__), 'sample4.ini')
141        app = loadapp('config:%s' % paste_conf)
142        assert isinstance(app, CASServer)
143        assert app.db_connection_string == 'sqlite:///:memory:'
144        assert isinstance(app.auth, KofaAuthenticator)
145        assert app.auth.name == 'kofa_moodle1'
146        auth_backends = {
147          'kofa_moodle1':
148            {'url': 'http://xmlrpcuser1:xmlrpcuser1@localhost:8080/app1/',
149             'marker': '^K',
150             'moodle_url': 'http://localhost/moodle/webservice/xmlrpc/server.php?wstoken=de1e1cbacf91ec6290bb6f6339122e7d',
151            },
152          }
153        assert app.auth.backends == auth_backends
154
155
[10478]156class FakeKofaServer(SimpleXMLRPCServer):
157    # A fake Kofa server that provides only XMLRPC methods
158
159    allow_reuse_address = True
160
161    def __init__(self, *args, **kw):
162        kw.update(allow_none=True)
163        SimpleXMLRPCServer.__init__(self, *args, **kw)
[10506]164        self.register_function(
165            self._check_credentials, 'check_applicant_credentials')
166        self.register_function(
167            self._check_credentials, 'check_student_credentials')
[10512]168        self.register_function(
[10518]169            self._get_moodle_data, 'get_student_moodle_data')
170        self.register_function(
171            self._get_moodle_data, 'get_applicant_moodle_data')
[10478]172
173    def _check_credentials(self, username, password):
174        # fake waeup.kofa check_credentials method.
175        #
176        # This method is supposed to mimic the behaviour of an
177        # original waeup.kofa check_credentials method. It returns a
178        # positive result for the credentials `bird`/`bebop`.
179        if username == 'bird' and password == 'bebop':
180            return {'id': 'bird', 'email': 'bird@gods.net',
[10511]181                    'description': 'Mr. Charles Parker',
182                    'type': 'student'}
183        if username == 'pig' and password == 'pog':
184            return {'id': 'pig', 'email': 'pig@gods.net',
185                    'description': 'Mr. Ray Charles',
186                    'type': 'applicant'}
[10514]187        if username in (
[10524]188            'fault1', 'fault2', 'fault3', 'fault4', 'fault7') and password == 'biz':
[10514]189            return {'type': 'student'}
[10518]190        if username == 'fault5' and password == 'biz':
191            return {'type': 'applicant'}
[10522]192        if username == 'fault6' and password == 'biz':
193            return {'type': 'boss'}
[10478]194        return None
195
[10518]196    def _get_moodle_data(self, username):
[10515]197        # fake waeup.kofa get_student_moodle_data method.
[10512]198        if username == 'bird':
199            return dict(email='aa@aa.aa',
200                        firstname='Charles',
201                        lastname='Parker',
202                        )
203        if username == 'pig':
204            return dict(email='bb@bb.bb',
205                        firstname='Ray',
206                        lastname='Charles',
207                        )
[10524]208        if username in ('fault1', 'fault2', 'fault3',
209                        'fault4', 'fault5', 'fault7'):
[10514]210            return dict(email='ff@ff.ff',
211                        firstname='John',
212                        lastname='Fault',
213                        )
[10478]214
[10512]215class FakeMoodleServer(SimpleXMLRPCServer):
216    # A fake Moodle server that provides only XMLRPC methods
217
218    allow_reuse_address = True
219
220    def __init__(self, *args, **kw):
221        kw.update(allow_none=True)
222        SimpleXMLRPCServer.__init__(self, *args, **kw)
223        self.register_function(
224            self._core_user_create_users, 'core_user_create_users')
225        self.register_function(
226            self._core_user_get_users, 'core_user_get_users')
227        self.register_function(
228            self._core_user_update_users, 'core_user_update_users')
229
[10513]230    def _core_user_create_users(self, arg):
[10512]231        # fake Moodle core_user_create_users method.
[10523]232        if arg[0]['username'] == 'school1-fault1':
[10513]233            raise xmlrpclib.Fault('faultCode', 'faultString')
[10523]234        if arg[0]['username'] in ('school1-fault4', 'school1-fault5'):
[10514]235            raise xmlrpclib.Fault('faultCode', 'Username already exists')
[10524]236        if arg[0]['username'] == 'school1-fault7':
237            raise xmlrpclib.Fault('faultCode', 'Email address already exists')
[10512]238        return None
239
[10513]240    def _core_user_get_users(self, arg):
[10512]241        # fake Moodle core_user_get_users method.
[10523]242        if arg[0]['value'] == 'SCHOOL1-fault2':
[10513]243            raise xmlrpclib.Fault('faultCode', 'faultString')
[10523]244        if arg[0]['value'] == 'SCHOOL1-fault3':
245            return {'users':[{'id':'SCHOOL1-fault3'}]}
[10512]246        return {'users':[{'id':'any id'}]}
247
[10513]248    def _core_user_update_users(self, arg):
[10512]249        # fake Moodle core_user_update_users method.
[10523]250        if arg[0]['id'] == 'SCHOOL1-fault3':
[10513]251            raise xmlrpclib.Fault('faultCode', 'faultString')
[10512]252        return None
253
254class KofaAuthenticatorsIntegrationTests(unittest.TestCase):
[10524]255    # A test case where a fake Kofa server and a fake Moodle server
256    # are started before tests (and shut down afterwards).
[10478]257
[10512]258    kofa_server = None
259    kofa_th = None
260    moodle_server = None
261    moodle_th = None
[10478]262
263    @classmethod
264    def setUpClass(cls):
[10507]265        # set up a fake kofa server
[10512]266        cls.kofa_server = FakeKofaServer(('localhost', 6666))
267        cls.kofa_th = threading.Thread(target=cls.kofa_server.serve_forever)
268        cls.kofa_th.daemon = True
269        cls.kofa_th.start()
270        # set up a fake moodle server
271        cls.moodle_server = FakeMoodleServer(('localhost', 7777))
272        cls.moodle_th = threading.Thread(target=cls.moodle_server.serve_forever)
273        cls.moodle_th.daemon = True
274        cls.moodle_th.start()
[10478]275
276    @classmethod
277    def tearDownClass(cls):
[10507]278        # tear down fake kofa server
[10512]279        cls.kofa_server.shutdown()
280        cls.kofa_server.server_close()
281        # tear down fake moodle server
282        cls.moodle_server.shutdown()
283        cls.moodle_server.server_close()
[10478]284
285    def test_fake_kofa_works(self):
286        # make sure the local fake kofa works
287        proxy = xmlrpclib.ServerProxy("http://localhost:6666", allow_none=True)
[10506]288        result = proxy.check_student_credentials('bird', 'bebop')
[10478]289        assert result == {
290            'description': 'Mr. Charles Parker',
291            'email': 'bird@gods.net',
[10511]292            'id': 'bird',
293            'type': 'student'}
294        result = proxy.check_applicant_credentials('pig', 'pog')
[10506]295        assert result == {
[10511]296            'description': 'Mr. Ray Charles',
297            'email': 'pig@gods.net',
298            'id': 'pig',
299            'type': 'applicant'}
[10515]300        result = proxy.get_student_moodle_data('pig')
[10512]301        assert result == {
302            'lastname': 'Charles',
303            'email': 'bb@bb.bb',
304            'firstname': 'Ray',
305            }
[10518]306        result = proxy.get_applicant_moodle_data('pig')
307        assert result == {
308            'lastname': 'Charles',
309            'email': 'bb@bb.bb',
310            'firstname': 'Ray',
311            }
[10478]312        return
313
[10513]314    def test_fake_moodle_works(self):
[10512]315        # make sure the local fake moodle works
316        proxy = xmlrpclib.ServerProxy("http://localhost:7777", allow_none=True)
[10513]317        # test core_user_create_users
318        result = proxy.core_user_create_users(
319            [{'username': 'any name'}])
[10512]320        assert result == None
[10513]321        self.assertRaises(
322            xmlrpclib.Fault, proxy.core_user_create_users,
[10523]323            [{'username': 'school1-fault1'}])
[10524]324        self.assertRaises(
325            xmlrpclib.Fault, proxy.core_user_create_users,
326            [{'username': 'school1-fault7'}])
[10513]327        # test core_user_get_users
328        result = proxy.core_user_get_users(
329            [{'key': 'username', 'value': 'any name'}])
[10512]330        assert result == {'users':[{'id':'any id'}]}
[10513]331        self.assertRaises(
332            xmlrpclib.Fault, proxy.core_user_get_users,
[10523]333            [{'key': 'username', 'value': 'SCHOOL1-fault2'}])
[10513]334        # test core_user_update_users
335        result = proxy.core_user_update_users(
336            [{'id': 'any id'}])
[10512]337        assert result == None
[10513]338        self.assertRaises(
339            xmlrpclib.Fault, proxy.core_user_update_users,
[10523]340            [{'id': 'SCHOOL1-fault3'}])
[10512]341        return
342
[10518]343    def test_check_credentials_KofaAuthenticator(self):
[10462]344        # we get real responses when querying Kofa instances
[10478]345        auth = KofaAuthenticator(auth_backends=str(BACKENDS2))
346        result1 = auth.check_credentials('SCHOOL1-bird', 'bebop')
[10462]347        assert result1 == (True, '')
[10478]348        result2 = auth.check_credentials('SCHOOL1-foo', 'bar')
[10462]349        assert result2 == (False, 'Invalid username or password.')
[10478]350        result3 = auth.check_credentials('SCHOOL2-bar', 'baz')
351        assert result3 == (False, 'Invalid username or password.')
[10511]352
[10518]353    def test_check_credentials_KofaMoodleAuthenticator(self):
[10512]354        # we get real responses when querying Kofa instances
355        auth = KofaMoodleAuthenticator(auth_backends=str(BACKENDS3))
[10518]356        result1s = auth.check_credentials('SCHOOL1-bird', 'bebop')
357        assert result1s == (True, '')
358        result1a  = auth.check_credentials('SCHOOL1-pig', 'pog')
359        assert result1a == (True, '')
360        result2s = auth.check_credentials('SCHOOL1-foo', 'bar')
[10522]361        assert result2s == (False, 'Invalid username or password')
[10518]362        result3s = auth.check_credentials('SCHOOL2-bar', 'baz')
[10522]363        assert result3s == (False, 'Invalid username or password')
364        result8 = auth.check_credentials('SCHOOL1-fault6', 'biz')
365        assert result8 == (False, 'User not eligible')
[10524]366        result9 = auth.check_credentials('SCHOOL1-fault7', 'biz')
367        assert result9 == (False,
368                    'Another Moodle user is using the same email address.'
369                    ' Email addresses can\'t be used twice in Moodle.')
[10518]370
[10523]371        # exceptions are raised in the following cases
[10518]372        result4s = auth.check_credentials('SCHOOL1-fault1', 'biz')
373        assert result4s == (False, 'faultString')
374        result5s = auth.check_credentials('SCHOOL1-fault2', 'biz')
375        assert result5s == (False, 'faultString')
376        result6s = auth.check_credentials('SCHOOL1-fault3', 'biz')
377        assert result6s == (False, 'faultString')
378
[10514]379        # no exception if user exists
[10518]380        result7s = auth.check_credentials('SCHOOL1-fault4', 'biz')
381        assert result7s == (True, '')
382        result7a = auth.check_credentials('SCHOOL1-fault5', 'biz')
383        assert result7a == (True, '')
[10524]384
385        # If marker is an empty sring school markers are neither
386        # checked nor removed.
387        BACKENDS3['inst1']['marker'] = ''
388        auth = KofaMoodleAuthenticator(auth_backends=str(BACKENDS3))
389        result10s = auth.check_credentials('bird', 'bebop')
390        assert result10s == (True, '')
391        result10a = auth.check_credentials('pig', 'pog')
392        assert result10a == (True, '')
393        result11s = auth.check_credentials('SCHOOL1-bird', 'bebop')
394        assert result11s == (False, 'Invalid username or password')
395        result11a = auth.check_credentials('SCHOOL1-pig', 'pog')
396        assert result11a == (False, 'Invalid username or password')
Note: See TracBrowser for help on using the repository browser.