diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ff8ead5..7d505130 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,10 @@ install_re2o.sh help ## MR 172: Refactor API Creates a new (nearly) REST API to expose all models of Re2o. See [the dedicated wiki page](https://gitlab.federez.net/federez/re2o/wikis/API/Raw-Usage) for more details on how to use it. +* For testing purpose, add `volatildap` package: +``` +pip3 install volatildap +``` * Activate HTTP Authorization passthrough in by adding the following in `/etc/apache2/site-available/re2o.conf` (example in `install_utils/apache2/re2o.conf`): ``` WSGIPassAuthorization On diff --git a/pip_requirements.txt b/pip_requirements.txt index 0960c796..b40fa8c5 100644 --- a/pip_requirements.txt +++ b/pip_requirements.txt @@ -1,3 +1,5 @@ django-bootstrap3 django-ldapdb==0.9.0 django-macaddress +# For testing purpose +volatildap diff --git a/test_utils/__init__.py b/test_utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test_utils/runner.py b/test_utils/runner.py new file mode 100644 index 00000000..b715762f --- /dev/null +++ b/test_utils/runner.py @@ -0,0 +1,164 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2017 Gabriel Détraz +# Copyright © 2017 Goulven Kermarec +# Copyright © 2017 Augustin Lemesle +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +"""Defines the custom runners for Re2o. +""" + +import volatildap +import os.path + +from django.test.runner import DiscoverRunner +from django.conf import settings + +from users.models import LdapUser, LdapUserGroup, LdapServiceUser, LdapServiceUserGroup + +# The path of this file +__here = os.path.dirname(os.path.realpath(__file__)) +# The absolute path where to find the schemas for the LDAP +schema_path = os.path.abspath(os.path.join(__here, 'ldap', 'schema')) +# The absolute path of the "radius.schema" file +radius_schema_path = os.path.join(schema_path, 'radius.schema') +# The absolute path of the "samba.schema" file +samba_schema_path = os.path.join(schema_path, 'samba.schema') + +# The suffix for the LDAP +suffix = 'dc=example,dc=net' +# The admin CN of the LDAP +rootdn = 'cn=admin,'+suffix + +# Defines all ldap_entry mandatory for Re2o under a key-value list format +# that can be used directly by volatildap. For more on how to generate this +# data, see https://gitlab.federez.net/re2o/scripts/blob/master/print_ldap_entries.py +ldapentry_Utilisateurs = ('cn=Utilisateurs,'+suffix, { + 'cn': ['Utilisateurs'], + 'sambaSID': ['500'], + 'uid': ['Users'], + 'objectClass': ['posixGroup', 'top', 'sambaSamAccount', 'radiusprofile'], + 'gidNumber': ['500'], +}) +ldapentry_groups = ('ou=groups,'+suffix, { + 'ou': ['groups'], + 'objectClass': ['organizationalUnit'], + 'description': ["Groupes d'utilisateurs"], +}) +ldapentry_services = ('ou=services,ou=groups,'+suffix, { + 'ou': ['services'], + 'objectClass': ['organizationalUnit'], + 'description': ['Groupes de comptes techniques'], +}) +ldapentry_service_users = ('ou=service-users,'+suffix, { + 'ou': ['service-users'], + 'objectClass': ['organizationalUnit'], + 'description': ["Utilisateurs techniques de l'annuaire"], +}) +ldapentry_freeradius = ('cn=freeradius,ou=service-users,'+suffix, { + 'cn': ['freeradius'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_nssauth = ('cn=nssauth,ou=service-users,'+suffix, { + 'cn': ['nssauth'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_auth = ('cn=auth,ou=services,ou=groups,'+suffix, { + 'cn': ['auth'], + 'objectClass': ['groupOfNames'], + 'member': ['cn=nssauth,ou=service-users,'+suffix], +}) +ldapentry_posix = ('ou=posix,ou=groups,'+suffix, { + 'ou': ['posix'], + 'objectClass': ['organizationalUnit'], + 'description': ['Groupes de comptes POSIX'], +}) +ldapentry_wifi = ('cn=wifi,ou=service-users,'+suffix, { + 'cn': ['wifi'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_usermgmt = ('cn=usermgmt,ou=services,ou=groups,'+suffix, { + 'cn': ['usermgmt'], + 'objectClass': ['groupOfNames'], + 'member': ['cn=wifi,ou=service-users,'+suffix], +}) +ldapentry_replica = ('cn=replica,ou=service-users,'+suffix, { + 'cn': ['replica'], + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['FILL_IT'], +}) +ldapentry_readonly = ('cn=readonly,ou=services,ou=groups,'+suffix, { + 'cn': ['readonly'], + 'objectClass': ['groupOfNames'], + 'member': ['cn=replica,ou=service-users,'+suffix, 'cn=freeradius,ou=service-users,'+suffix], +}) +ldapbasic = dict([ldapentry_Utilisateurs, ldapentry_groups, + ldapentry_services, ldapentry_service_users, + ldapentry_freeradius, ldapentry_nssauth, ldapentry_auth, + ldapentry_posix, ldapentry_wifi, ldapentry_usermgmt, + ldapentry_replica, ldapentry_readonly]) + + +class DiscoverLdapRunner(DiscoverRunner): + """Discovers all the tests in the project + + This is a simple subclass of the default test runner + `django.test.runner.DiscoverRunner` that creates a test LDAP + right after the test databases are setup and destroys it right + before the test databases are setup. + It also ensure re2o's settings are using this new LDAP. + """ + + # The `volatildap.LdapServer` instance initiated with the minimal + # structure required by Re2o + ldap_server = volatildap.LdapServer( + suffix=suffix, + rootdn=rootdn, + initial_data=ldapbasic, + schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema', + 'nis.schema', radius_schema_path, samba_schema_path] + ) + + def __init__(self, *args, **kwargs): + settings.DATABASES['ldap']['USER'] = self.ldap_server.rootdn + settings.DATABASES['ldap']['PASSWORD'] = self.ldap_server.rootpw + settings.DATABASES['ldap']['NAME'] = self.ldap_server.uri + settings.LDAP['base_user_dn'] = ldapentry_Utilisateurs[0] + settings.LDAP['base_userservice_dn'] = ldapentry_service_users[0] + settings.LDAP['base_usergroup_dn'] = ldapentry_posix[0] + settings.LDAP['base_userservicegroup_dn'] = ldapentry_services[0] + settings.LDAP['user_gid'] = ldapentry_Utilisateurs[1].get('gidNumber', ["500"])[0] + LdapUser.base_dn = settings.LDAP['base_user_dn'] + LdapUserGroup.base_dn = settings.LDAP['base_usergroup_dn'] + LdapServiceUser.base_dn = settings.LDAP['base_userservice_dn'] + LdapServiceUserGroup.base_dn = settings.LDAP['base_userservicegroup_dn'] + super(DiscoverLdapRunner, self).__init__(*args, **kwargs) + + + def setup_databases(self, *args, **kwargs): + ret = super(DiscoverLdapRunner, self).setup_databases(*args, **kwargs) + self.ldap_server.start() + return ret + + def teardown_databases(self, *args, **kwargs): + self.ldap_server.stop() + super(DiscoverLdapRunner, self).teardown_databases(*args, **kwargs) + diff --git a/users/tests.py b/users/tests.py index a7214cbb..6b2bfb41 100644 --- a/users/tests.py +++ b/users/tests.py @@ -23,121 +23,65 @@ The tests for the Users module. """ +import os.path + from django.test import TestCase from django.conf import settings -from .models import School, ListShell, LdapUserGroup, ListRight +from . import models import volatildap -# Réglages bidon pour volatildap -LdapUserGroup.base_dn='ou=groups,dc=example,dc=org' - - -groups = ('ou=groups,dc=example,dc=org', { - 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) -people = ('ou=people,dc=example,dc=org', { - 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) -contacts = ('ou=contacts,ou=groups,dc=example,dc=org', { - 'objectClass': ['top', 'organizationalUnit'], 'ou': ['groups']}) -foogroup = ('cn=foogroup,ou=groups,dc=example,dc=org', { - 'objectClass': ['posixGroup'], 'memberUid': ['foouser', 'baruser'], - 'gidNumber': ['1000'], 'cn': ['foogroup']}) -bargroup = ('cn=bargroup,ou=groups,dc=example,dc=org', { - 'objectClass': ['posixGroup'], 'memberUid': ['zoouser', 'baruser'], - 'gidNumber': ['1001'], 'cn': ['bargroup']}) -wizgroup = ('cn=wizgroup,ou=groups,dc=example,dc=org', { - 'objectClass': ['posixGroup'], 'memberUid': ['wizuser', 'baruser'], - 'gidNumber': ['1002'], 'cn': ['wizgroup']}) -foouser = ('uid=foouser,ou=people,dc=example,dc=org', { - 'cn': [b'F\xc3\xb4o Us\xc3\xa9r'], - 'objectClass': ['posixAccount', 'shadowAccount', 'inetOrgPerson'], - 'loginShell': ['/bin/bash'], - 'jpegPhoto': [ - b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff' - b'\xfe\x00\x1cCreated with GIMP on a Mac\xff\xdb\x00C\x00\x05\x03\x04' - b'\x04\x04\x03\x05\x04\x04\x04\x05\x05\x05\x06\x07\x0c\x08\x07\x07\x07' - b'\x07\x0f\x0b\x0b\t\x0c\x11\x0f\x12\x12\x11\x0f\x11\x11\x13\x16\x1c' - b'\x17\x13\x14\x1a\x15\x11\x11\x18!\x18\x1a\x1d\x1d\x1f\x1f\x1f\x13' - b'\x17"$"\x1e$\x1c\x1e\x1f\x1e\xff\xdb\x00C\x01\x05\x05\x05\x07\x06\x07' - b'\x0e\x08\x08\x0e\x1e\x14\x11\x14\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' - b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' - b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e\x1e' - b'\x1e\x1e\x1e\x1e\x1e\x1e\x1e\xff\xc0\x00\x11\x08\x00\x08\x00\x08\x03' - b'\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x15\x00\x01\x01\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00' - b'\x19\x10\x00\x03\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00\x00\x01\x02\x06\x11A\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xc4\x00\x14\x11\x01' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff' - b'\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x9d\xf29wU5Q\xd6' - b'\xfd\x00\x01\xff\xd9'], - 'uidNumber': ['2000'], 'gidNumber': ['1000'], 'sn': [b'Us\xc3\xa9r'], - 'homeDirectory': ['/home/foouser'], 'givenName': [b'F\xc3\xb4o'], - 'uid': ['foouser']}) - -class LdapTestCase(TestCase): - directory = {} - - @classmethod - def setUpClass(cls): - super(LdapTestCase, cls).setUpClass() - cls.ldap_server = volatildap.LdapServer( - initial_data=cls.directory, - schemas=['core.schema', 'cosine.schema', 'inetorgperson.schema', 'nis.schema'], - ) - settings.DATABASES['ldap']['USER'] = cls.ldap_server.rootdn - settings.DATABASES['ldap']['PASSWORD'] = cls.ldap_server.rootpw - settings.DATABASES['ldap']['NAME'] = cls.ldap_server.uri - - @classmethod - def tearDownClass(cls): - cls.ldap_server.stop() - super(LdapTestCase, cls).tearDownClass() - - def setUp(self): - super(LdapTestCase, self).setUp() - self.ldap_server.start() - class SchoolTestCase(TestCase): - def setUp(self): - School.objects.create(name="ENS Paris-Saclay") - School.objects.create(name="Supelec") - def test_school_are_created(self): - pass + s = models.School.objects.create(name="My awesome school") + self.assertEqual(s.name, "My awesome school") + class ListShellTestCase(TestCase): - def setUp(self): - ListShell.objects.create(shell="/bin/zsh") - ListShell.objects.create(shell="/bin/bash") - def test_shell_are_created(self): - pass - -class GroupTestCase(LdapTestCase): - directory = dict([groups, foogroup, bargroup, wizgroup, people, foouser]) - - def test_create_ldapgroup(self): - mygroup = LdapUserGroup() - mygroup.name='re2o' - mygroup.gid=1010 - mygroup.members=['someuser', 'foouser'] - mygroup.save() - - # check ldap group was created - new = LdapUserGroup.objects.get(name='re2o') - self.assertEqual(new.name, 're2o') - self.assertEqual(new.gid, 1010) - self.assertEqual(new.members, ['someuser', 'foouser']) - - def test_create_re2ogroup(self): - ListRight.objects.create(gid='1011', unix_name='admins', details='test') - - #check re2o - lr = ListRight.objects.get(gid=1011) - self.asserEqual(lr.gid, 1011) - self.asserEqual(lr.details, 'test') + s = models.ListShell.objects.create(shell="/bin/zsh") + self.assertEqual(s.shell, "/bin/zsh") +class LdapUserTestCase(TestCase): + def test_create_ldap_user(self): + g = models.LdapUser.objects.create( + gid="500", + name="users_test_ldapuser", + uid="users_test_ldapuser", + uidNumber="21001", + sn="users_test_ldapuser", + login_shell="/bin/false", + mail="user@example.net", + given_name="users_test_ldapuser", + home_directory="/home/moamoak", + display_name="users_test_ldapuser", + dialupAccess="False", + sambaSID="21001", + user_password="{SSHA}aBcDeFgHiJkLmNoPqRsTuVwXyZ012345", + sambat_nt_password="0123456789ABCDEF0123456789ABCDEF", + macs=[], + shadowexpire="0" + ) + self.assertEqual(g.name, 'users_test_ldapuser') + + +class LdapUserGroupTestCase(TestCase): + def test_create_ldap_user_group(self): + g = models.LdapUserGroup.objects.create( + gid="501", + members=[], + name="users_test_ldapusergroup" + ) + self.assertEqual(g.name, 'users_test_ldapusergroup') + + +class LdapServiceUserTestCase(TestCase): + def test_create_ldap_service_user(self): + g = models.LdapServiceUser.objects.create( + name="users_test_ldapserviceuser", + user_password="{SSHA}AbCdEfGhIjKlMnOpQrStUvWxYz987654" + ) + self.assertEqual(g.name, 'users_test_ldapserviceuser')