diff --git a/helios_auth/auth_systems/ldapbackend/backend.py b/helios_auth/auth_systems/ldapbackend/backend.py index ef1930dcb81b3be2c052d0b6682f1a08ab3f7998..5d3060120b0dc860e61d942a2a29715fa11ca999 100644 --- a/helios_auth/auth_systems/ldapbackend/backend.py +++ b/helios_auth/auth_systems/ldapbackend/backend.py @@ -1,4 +1,4 @@ -from django.conf import settings +from django.core.exceptions import ImproperlyConfigured from django_auth_ldap.backend import LDAPBackend from django_auth_ldap.config import LDAPSearch @@ -8,14 +8,14 @@ from django_auth_ldap.backend import populate_user class CustomLDAPBackend(LDAPBackend): def authenticate(self, username, password): """ - Some ldap servers allow anonymous search but naturally return just a set - of user attributes. So, here we re-perform search after user is authenticated, + Some ldap servers allow anonymous search, returning just a subset + of user attributes. So here we re-perform search after user is authenticated, in order to populate other user attributes. For now, just in cases where AUTH_LDAP_BIND_PASSWORD is empty """ user = super(CustomLDAPBackend, self).authenticate(username, password) - if user and settings.AUTH_LDAP_BIND_PASSWORD == '' : + if user and self.settings.BIND_PASSWORD == '' : search = self.settings.USER_SEARCH if search is None: raise ImproperlyConfigured('AUTH_LDAP_USER_SEARCH must be an LDAPSearch instance.') @@ -24,5 +24,5 @@ class CustomLDAPBackend(LDAPBackend): (user.ldap_user._user_dn, user.ldap_user.user_attrs) = results[0] user.ldap_user._load_user_attrs() user.ldap_user._populate_user_from_attributes() - user.save() + user.save() return user diff --git a/helios_auth/tests.py b/helios_auth/tests.py index 9b097cc97e0a346e3d13da87f1eb9156f1ee3094..b6cb5ddd6ceb77ba0e13dfdac783fa16dcb08e99 100644 --- a/helios_auth/tests.py +++ b/helios_auth/tests.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Unit Tests for Auth Systems """ @@ -26,10 +27,10 @@ class UserModelTests(unittest.TestCase): """ for auth_system, auth_system_module in AUTH_SYSTEMS.iteritems(): models.User.objects.create(user_type = auth_system, user_id = 'foobar', info={'name':'Foo Bar'}) - + def double_insert(): models.User.objects.create(user_type = auth_system, user_id = 'foobar', info={'name': 'Foo2 Bar'}) - + self.assertRaises(IntegrityError, double_insert) transaction.rollback() @@ -56,7 +57,7 @@ class UserModelTests(unittest.TestCase): assert(hasattr(auth_system_module, 'can_create_election')) if auth_system != 'clever': assert(auth_system_module.can_create_election('foobar', {})) - + def test_status_update(self): """ @@ -118,7 +119,7 @@ class UserBlackboxTests(TestCase): def test_logout(self): response = self.client.post(reverse(views.logout), follow=True) - + self.assertContains(response, "not logged in") self.assertNotContains(response, "Foobar User") @@ -131,41 +132,195 @@ class UserBlackboxTests(TestCase): self.assertEquals(mail.outbox[0].to[0], "\"Foobar User\" <foobar-test@adida.net>") +''' + tests for LDAP auth module. + Much of the code below was get or inspired on django-auth-ldap package. + See site-packages/django_ldap_auth/tests.py +''' +import ldap + import auth_systems.ldapauth as ldap_views +from mockldap import MockLdap +from django_auth_ldap.backend import LDAPSettings +from django_auth_ldap.config import LDAPSearch +from helios_auth.auth_systems.ldapbackend.backend import CustomLDAPBackend +try: + from django.test.utils import override_settings +except ImportError: + override_settings = lambda *args, **kwargs: (lambda v: v) + + +class TestSettings(LDAPSettings): + """ + A replacement for backend.LDAPSettings that does not load settings + from django.conf. + """ + def __init__(self, **kwargs): + for name, default in self.defaults.items(): + value = kwargs.get(name, default) + setattr(self, name, value) class LDAPAuthTests(TestCase): """ - These tests relies on OnLine LDAP Test Server, provided by forum Systems: - http://www.forumsys.com/tutorials/integration-how-to/ldap/online-ldap-test-server/ + These tests uses mockldap 0.2.7 https://pypi.python.org/pypi/mockldap/ """ + top = ("o=test", {"o": "test"}) + people = ("ou=people,o=test", {"ou": "people"}) + groups = ("ou=groups,o=test", {"ou": "groups"}) + + alice = ("uid=alice,ou=people,o=test", { + "uid": ["alice"], + "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"], + "userPassword": ["password"], + "uidNumber": ["1000"], + "gidNumber": ["1000"], + "givenName": ["Alice"], + "sn": ["Adams"], + "mail": ["alice@example.com"] + }) + bob = ("uid=bob,ou=people,o=test", { + "uid": ["bob"], + "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"], + "userPassword": ["password"], + "uidNumber": ["1001"], + "gidNumber": ["50"], + "givenName": ["Robert"], + "sn": ["Barker"], + "mail": ["bob@example.com"] + }) + john = ("uid=john,ou=people,o=test", { + "uid": ["john"], + "objectClass": ["person", "organizationalPerson", "inetOrgPerson", "posixAccount"], + "userPassword": ["password"], + "uidNumber": ["1002"], + "gidNumber": ["60"], + "givenName": ["Robert"], + "sn": ["Doe"] + }) + + directory = dict([top, people, groups, alice, bob, john]) + + def _init_settings(self, **kwargs): + self.backend.settings = TestSettings(**kwargs) + + @classmethod + def setUpClass(cls): + cls.mockldap = MockLdap(cls.directory) + + @classmethod + def tearDownClass(cls): + del cls.mockldap + def setUp(self): - """ set up necessary django-auth-ldap settings """ - self.password = 'password' - self.username = 'euclid' + self.mockldap.start() + self.ldapobj = self.mockldap['ldap://localhost'] + + self.backend = CustomLDAPBackend() + self.backend.ldap # Force global configuration + + def tearDown(self): + self.mockldap.stop() + del self.ldapobj def test_backend_login(self): - """ test if authenticates using the backend """ + """ Test authentication usign correct username/password """ if 'ldap' in ENABLED_AUTH_SYSTEMS: - from helios_auth.auth_systems.ldapbackend import backend - auth = backend.CustomLDAPBackend() - user = auth.authenticate(self.username, self.password) - self.assertEqual(user.username, 'euclid') - - def test_ldap_view_login(self): - """ test if authenticates using the auth system login view """ + self._init_settings( + BIND_DN='uid=bob,ou=people,o=test', + BIND_PASSWORD='password', + USER_SEARCH=LDAPSearch( + "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' + ) + ) + user = self.backend.authenticate(username='alice', password='password') + self.assertTrue(user is not None) + + def test_backend_bad_login(self): + """ Test authentication using incorrect username/password""" + if 'ldap' in ENABLED_AUTH_SYSTEMS: + self._init_settings( + BIND_DN='uid=bob,ou=people,o=test', + BIND_PASSWORD='password', + USER_SEARCH=LDAPSearch( + "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' + ) + ) + user = self.backend.authenticate(username='maria', password='password') + self.assertTrue(user is None) + + @override_settings(AUTH_LDAP_BIND_DN='uid=bob,ou=people,o=test', + AUTH_LDAP_BIND_PASSWORD='password',AUTH_LDAP_USER_SEARCH=LDAPSearch( + "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' + ) + ) + def test_ldap_view_login_with_bind_credentials(self): + """ Test if authenticates using the auth system login view """ + if 'ldap' in ENABLED_AUTH_SYSTEMS: + response = self.client.post(reverse(ldap_views.ldap_login_view), { + 'username' : 'bob', + 'password': 'password' + }, follow=True) + self.assertEqual(self.client.session['user']['name'], 'Robert Barker') + self.assertEqual(self.client.session['user']['type'], 'ldap') + self.assertEqual(self.client.session['user']['info']['email'],'bob@example.com') + + @override_settings(AUTH_LDAP_BIND_DN='uid=bob,ou=people,o=test', + AUTH_LDAP_BIND_PASSWORD='password',AUTH_LDAP_USER_SEARCH=LDAPSearch( + "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' + ) + ) + def test_ldap_view_login_with_bad_password(self): + """ Test if given a wrong password the user can't login """ if 'ldap' in ENABLED_AUTH_SYSTEMS: - resp = self.client.post(reverse(ldap_views.ldap_login_view), { - 'username' : self.username, - 'password': self.password - }, follow=True) - self.assertEqual(resp.status_code, 200) + response = self.client.post(reverse(ldap_views.ldap_login_view), { + 'username' : 'john', + 'password': 'passworddd' + }, follow=True) + self.assertEqual(response.status_code, 200) + self.assertFalse(self.client.session.has_key('user')) + + def test_ldap_view_login_anonymous_bind(self): + """ + Test anonymous search/bind + See https://pythonhosted.org/django-auth-ldap/authentication.html#search-bind + """ + self._init_settings( + BIND_PASSWORD='', + USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn','email':'mail'}, + USER_SEARCH=LDAPSearch( + "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' + ) + ) + user = self.backend.authenticate(username='alice', password='password') + self.assertEqual(user.username, 'alice') + self.assertEqual(user.first_name, 'Alice') + self.assertEqual(user.last_name, 'Adams') + self.assertEqual(user.email,'alice@example.com') + + def test_ldap_bind_as_user(self): + """ + Test direct bind + See https://pythonhosted.org/django-auth-ldap/authentication.html#direct-bind + """ + self._init_settings( + USER_DN_TEMPLATE='uid=%(user)s,ou=people,o=test', + USER_ATTR_MAP={'first_name': 'givenName', 'last_name': 'sn','email':'mail'}, + BIND_AS_AUTHENTICATING_USER=True, + USER_SEARCH=LDAPSearch( + "ou=people,o=test", ldap.SCOPE_SUBTREE, '(uid=%(user)s)' + ) + ) + user = self.backend.authenticate(username='alice', password='password') + self.assertEqual(user.username, 'alice') + self.assertEqual(user.first_name, 'Alice') + self.assertEqual(user.last_name, 'Adams') + self.assertEqual(user.email,'alice@example.com') def test_logout(self): - """ test if logs out using the auth system logout view """ + """ test logging out using the auth system logout view """ if 'ldap' in ENABLED_AUTH_SYSTEMS: response = self.client.post(reverse(views.logout), follow=True) self.assertContains(response, "not logged in") - self.assertNotContains(response, "euclid") - + self.assertNotContains(response, "alice") diff --git a/requirements.txt b/requirements.txt index eb774f3c76615017da3351d362d8ed7b56a6687f..3d493a5ad09c5e9870ff631ff1f5ad48956cc60a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,3 +23,4 @@ django-ses==0.6.0 validate_email==1.2 oauth2client==1.2 django-auth-ldap==1.2.7 +mockldap==0.2.7