[10535] | 1 | # Tests for waeup.cas.authentictors |
---|
[10462] | 2 | import os |
---|
[10478] | 3 | import threading |
---|
[10394] | 4 | import unittest |
---|
[10462] | 5 | from paste.deploy import loadapp |
---|
[10478] | 6 | try: |
---|
| 7 | from SimpleXMLRPCServer import SimpleXMLRPCServer # Python 2.x |
---|
| 8 | except ImportError: |
---|
| 9 | from xmlrpc.server import SimpleXMLRPCServer # Python 3.x |
---|
| 10 | try: |
---|
| 11 | import xmlrpclib # Python 2.x |
---|
| 12 | except ImportError: |
---|
| 13 | import xmlrpc.client as xmlrpclib # Python 3.x |
---|
[10394] | 14 | from waeup.cas.authenticators import ( |
---|
| 15 | get_all_authenticators, get_authenticator, filter_auth_opts, |
---|
[10511] | 16 | Authenticator, DummyAuthenticator, KofaAuthenticator, |
---|
| 17 | KofaMoodleAuthenticator |
---|
[10394] | 18 | ) |
---|
[10462] | 19 | from waeup.cas.server import CASServer |
---|
[10394] | 20 | |
---|
| 21 | |
---|
| 22 | class 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 | |
---|
| 48 | class 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 | |
---|
| 56 | class 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] | 72 | BACKENDS1 = dict( |
---|
[10476] | 73 | inst1=dict( |
---|
| 74 | url='http://localhost:6666/app', |
---|
[10478] | 75 | marker='^MA-', |
---|
[10474] | 76 | ) |
---|
[10462] | 77 | ) |
---|
| 78 | |
---|
[10478] | 79 | BACKENDS2 = dict( |
---|
| 80 | inst1=dict( |
---|
| 81 | url='http://localhost:6666/', |
---|
| 82 | marker='^SCHOOL1-', |
---|
| 83 | ) |
---|
| 84 | ) |
---|
[10476] | 85 | |
---|
[10511] | 86 | BACKENDS3 = 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] | 95 | class 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] | 137 | class 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] | 160 | class 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] | 221 | class 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] | 261 | class 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') |
---|