diff --git a/.travis.yml b/.travis.yml index d10fde17e..40e25fa3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,11 @@ sudo: false language: python python: - "2.7" +addons: + apt: + packages: + - ldap-utils + - slapd env: - DB="POSTGRESQL" @@ -17,6 +22,7 @@ before_install: install: - python setup.py -q develop - pip install -q psycopg2 mysqlclient + - pip install -q -r ldap-requirements.txt - pip install -q -r test-requirements.txt before_script: @@ -24,6 +30,10 @@ before_script: - mysql -e "create database IF NOT EXISTS modoboa_test;" -uroot - mysql -e "CREATE USER 'modoboa'@'localhost' IDENTIFIED BY 'modoboa'" -uroot; - mysql -e "GRANT ALL PRIVILEGES ON * . * TO 'modoboa'@'localhost';" -uroot; + - mkdir /tmp/slapd + - slapd -f test_data/slapd.conf -h ldap://localhost:3389 & + - sleep 3 + - ldapadd -h localhost:3389 -D cn=admin,dc=example,dc=com -w test -f test_data/base.ldif script: - python ./tests.py @@ -32,4 +42,3 @@ script: after_success: - codecov - diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..f064df3d1 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,22 @@ +comment: + layout: header, changes, diff +coverage: + ignore: + - modoboa/core/migrations/* + - modoboa/core/tests.py + - modoboa/core/dev_settings.py + - modoboa/lib/migrations/* + - modoboa/lib/tests.py + - modoboa/admin/migrations/* + - modoboa/admin/tests/* + - modoboa/limits/migrations/* + - modoboa/limits/tests.py + - modoboa/relaydomains/migrations/* + - modoboa/relaydomains/tests.py + status: + patch: + default: + target: '70' + project: + default: + target: auto diff --git a/ldap-requirements.txt b/ldap-requirements.txt new file mode 100644 index 000000000..926c4e036 --- /dev/null +++ b/ldap-requirements.txt @@ -0,0 +1,2 @@ +python-ldap +django-auth-ldap diff --git a/modoboa/core/tests.py b/modoboa/core/tests.py index 236f859e1..ca0bfedc1 100644 --- a/modoboa/core/tests.py +++ b/modoboa/core/tests.py @@ -1,8 +1,11 @@ """Tests for core application.""" from django.core.urlresolvers import reverse +from django.test import override_settings +from modoboa.lib import parameters from modoboa.lib.tests import ModoTestCase + from . import factories from . import models @@ -36,6 +39,74 @@ class AuthenticationTestCase(ModoTestCase): self.assertTrue(response.url.endswith(reverse("admin:domain_list"))) +@override_settings(AUTHENTICATION_BACKENDS=( + 'modoboa.lib.authbackends.LDAPBackend', + 'modoboa.lib.authbackends.SimpleBackend', +)) +class LDAPAuthenticationTestCase(ModoTestCase): + + """Validate LDAP authentication scenarios.""" + + def setUp(self): + """Create test data.""" + super(LDAPAuthenticationTestCase, self).setUp() + parameters.save_admin( + "AUTHENTICATION_TYPE", "ldap") + parameters.save_admin("LDAP_SERVER_PORT", "3389") + + def check_created_user(self, username, group="SimpleUsers", with_mb=True): + """Check that created user is valid.""" + user = models.User.objects.get(username=username) + self.assertEqual(user.group, group) + if with_mb: + self.assertEqual(user.email, username) + self.assertEqual(user.mailbox.domain.name, "example.com") + self.assertEqual(user.mailbox.full_address, username) + + @override_settings(AUTH_LDAP_USER_DN_TEMPLATE=None) + def test_searchbind_authentication(self): + """Test the bind&search method.""" + self.client.logout() + parameters.save_admin("LDAP_AUTH_METHOD", "searchbind") + parameters.save_admin("LDAP_BIND_DN", "cn=admin,dc=example,dc=com") + parameters.save_admin("LDAP_BIND_PASSWORD", "test") + parameters.save_admin("LDAP_SEARCH_BASE", "ou=users,dc=example,dc=com") + username = "testuser@example.com" + self.assertTrue(self.client.login(username=username, password="test")) + self.check_created_user(username) + self.client.logout() + + parameters.save_admin("LDAP_ADMIN_GROUPS", "admins") + parameters.save_admin( + "LDAP_GROUPS_SEARCH_BASE", "ou=groups,dc=example,dc=com") + username = "mailadmin@example.com" + self.assertTrue(self.client.login(username=username, password="test")) + self.check_created_user(username, "DomainAdmins") + + def test_directbind_authentication(self): + """Test the directbind method.""" + self.client.logout() + parameters.save_admin("LDAP_AUTH_METHOD", "directbind") + parameters.save_admin( + "LDAP_USER_DN_TEMPLATE", "cn=%(user)s,ou=users,dc=example,dc=com") + + # 1: must fail because usernames of simple users must be email + # addresses + username = "testuser" + with self.assertRaises(TypeError): + self.assertTrue(self.client.login( + username=username, password="test")) + + # 1: must work because usernames of domain admins are not + # always email addresses + parameters.save_admin("LDAP_ADMIN_GROUPS", "admins") + parameters.save_admin( + "LDAP_GROUPS_SEARCH_BASE", "ou=groups,dc=example,dc=com") + username = "mailadmin" + self.assertTrue(self.client.login(username=username, password="test")) + self.check_created_user(username, "DomainAdmins", False) + + class ProfileTestCase(ModoTestCase): @classmethod diff --git a/modoboa/lib/authbackends.py b/modoboa/lib/authbackends.py index 92509fd28..956a4f314 100644 --- a/modoboa/lib/authbackends.py +++ b/modoboa/lib/authbackends.py @@ -34,6 +34,11 @@ try: class LDAPBackend(orig_LDAPBackend): + def __init__(self, *args, **kwargs): + """Load LDAP settings.""" + parameters.apply_to_django_settings() + super(LDAPBackend, self).__init__(*args, **kwargs) + def get_or_create_user(self, username, ldap_user): """ This must return a (User, created) 2-tuple for the given @@ -72,7 +77,8 @@ try: def authenticate(self, username, password): auth_type = parameters.get_admin("AUTHENTICATION_TYPE", app="core") if auth_type == "ldap": - return super(LDAPBackend, self).authenticate(username, password) + return super(LDAPBackend, self).authenticate( + username, password) return None except ImportError: diff --git a/modoboa/urls.py b/modoboa/urls.py index 30192a6f9..0d5069c95 100644 --- a/modoboa/urls.py +++ b/modoboa/urls.py @@ -2,7 +2,7 @@ from django.conf import settings from django.conf.urls import patterns, include, url from modoboa.core.extensions import exts_pool -from modoboa.lib import parameters, events +from modoboa.lib import events urlpatterns = patterns( @@ -28,8 +28,6 @@ urlpatterns += patterns( *events.raiseQueryEvent("ExtraUprefsRoutes") ) -parameters.apply_to_django_settings() - # API urls urlpatterns += patterns( "", diff --git a/test_data/base.ldif b/test_data/base.ldif new file mode 100644 index 000000000..28bfb14c6 --- /dev/null +++ b/test_data/base.ldif @@ -0,0 +1,42 @@ +dn: dc=example,dc=com +objectClass: dcObject +objectClass: organizationalUnit +ou: test + +dn: ou=groups,dc=example,dc=com +objectClass: organizationalUnit +ou: groups + +dn: ou=users,dc=example,dc=com +objectClass: organizationalUnit +ou: users + +dn: cn=admins,ou=groups,dc=example,dc=com +cn: admins +gidnumber: 10001 +memberuid: mailadmin +objectclass: posixGroup +objectclass: top + +dn: cn=mailadmin,ou=users,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: mailadmin +uid: mailadmin +givenName: Mail +sn: Admin +mail: mailadmin@example.com +userPassword: {SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP + +dn: cn=testuser,ou=users,dc=example,dc=com +objectClass: inetOrgPerson +objectClass: organizationalPerson +objectClass: person +objectClass: top +cn: testuser +givenName: Test +sn: User +mail: testuser@example.com +userPassword: {SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP diff --git a/test_data/slapd.conf b/test_data/slapd.conf new file mode 100644 index 000000000..c6ae7e207 --- /dev/null +++ b/test_data/slapd.conf @@ -0,0 +1,17 @@ +# See slapd.conf(5) for details on configuration options. +include /etc/ldap/schema/core.schema +include /etc/ldap/schema/cosine.schema +include /etc/ldap/schema/inetorgperson.schema +include /etc/ldap/schema/nis.schema + +pidfile /tmp/slapd/slapd.pid +argsfile /tmp/slapd/slapd.args + +modulepath /usr/lib/openldap + +database ldif +directory /tmp/slapd + +suffix "dc=example,dc=com" +rootdn "cn=admin,dc=example,dc=com" +rootpw {SSHA}AIzygLSXlArhAMzddUriXQxf7UlkqopP