[10523] | 1 | 3# 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-', |
---|
[10512] | 90 | moodle_url = 'http://localhost:7777/', |
---|
[10511] | 91 | ) |
---|
| 92 | ) |
---|
[10478] | 93 | |
---|
[10462] | 94 | class 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] | 135 | class 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] | 156 | class 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] | 215 | class 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 | |
---|
| 254 | class 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') |
---|