diff --git a/ceo_common/model/Config.py b/ceo_common/model/Config.py index da7570382..87af65cb0 100644 --- a/ceo_common/model/Config.py +++ b/ceo_common/model/Config.py @@ -18,5 +18,13 @@ class Config: def get(self, key: str) -> str: section, subkey = key.split('_', 1) if section in self.config: - return self.config[section][subkey] - return self.config['DEFAULT'][key] + val = self.config[section][subkey] + else: + val = self.config['DEFAULT'][key] + if val.isdigit(): + return int(val) + if val.lower() in ['true', 'yes']: + return True + if val.lower() in ['false', 'no']: + return False + return val diff --git a/ceo_common/test/ceod_test_local.ini b/ceo_common/test/ceod_test_local.ini new file mode 100644 index 000000000..ab1aef6da --- /dev/null +++ b/ceo_common/test/ceod_test_local.ini @@ -0,0 +1,43 @@ +[DEFAULT] +base_domain = csclub.internal + +[ceod] +admin_host = phosphoric-acid +fs_root_host = phosphoric-acid +mailman_host = mail +use_https = false +port = 9987 + +[ldap] +admin_principal = ceod/admin +server_url = ldap://ldap-master.csclub.internal +sasl_realm = CSCLUB.INTERNAL +users_base = ou=TestPeople,dc=csclub,dc=internal +groups_base = ou=TestGroup,dc=csclub,dc=internal +sudo_base = ou=TestSUDOers,dc=csclub,dc=internal + +[uwldap] +server_url = ldap://uwldap.uwaterloo.ca +base = dc=uwaterloo,dc=ca + +[members] +min_id = 20001 +max_id = 29999 +home = /tmp/test_users +skel = /users/skel + +[clubs] +min_id = 30001 +max_id = 39999 +home = /tmp/test_users +skel = /users/skel + +[mail] +smtp_url = smtp://mail.csclub.internal +smtp_starttls = false + +[mailman3] +api_base_url = http://localhost:8001/3.1 +api_username = restadmin +api_password = mailman3 +new_member_list = csc-general diff --git a/ceod/model/KerberosService.py b/ceod/model/KerberosService.py index 1a98edf0c..f0388044a 100644 --- a/ceod/model/KerberosService.py +++ b/ceod/model/KerberosService.py @@ -8,10 +8,13 @@ from ceo_common.interfaces import IKerberosService @implementer(IKerberosService) class KerberosService: - def __init__(self, admin_principal: str): + def __init__( + self, + admin_principal: str, + cache_file: str = '/run/ceod/krb5_cache', + ): self.admin_principal = admin_principal - cache_file = '/run/ceod/krb5_cache' - os.makedirs('/run/ceod', exist_ok=True) + os.makedirs(os.path.dirname(cache_file), exist_ok=True) os.putenv('KRB5CCNAME', 'FILE:' + cache_file) self.kinit() diff --git a/ceod/model/test/__init__.py b/ceod/model/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ceod/model/test/conftest.py b/ceod/model/test/conftest.py new file mode 100644 index 000000000..605989ce9 --- /dev/null +++ b/ceod/model/test/conftest.py @@ -0,0 +1,80 @@ +import os +import importlib.resources + +import ldap +import pytest +import socket +from zope import component + +from ceo_common.interfaces import IConfig, IKerberosService, ILDAPService, \ + IFileService +from ceo_common.model import Config +from ceod.model import KerberosService, LDAPService, FileService + + +@pytest.fixture +def cfg(): + with importlib.resources.path('ceo_common.test', 'ceod_test_local.ini') as p: + config_file = p.__fspath__() + _cfg = Config(config_file) + component.provideUtility(_cfg, IConfig) + return _cfg + + +@pytest.fixture +def krb_srv(cfg): + # we need to be root to read the keytab + assert os.geteuid() == 0 + # this dance again... ugh + if socket.gethostname() == cfg.get('ceod_admin_host'): + principal = 'ceod/admin' + else: + principal = 'ceod/' + socket.getfqdn() + cache_file = '/tmp/ceod_test/krb5_cache' + if os.path.isfile(cache_file): + os.unlink(cache_file) + krb = KerberosService(principal, cache_file) + component.provideUtility(krb, IKerberosService) + yield krb + os.unlink(cache_file) + + +def recursively_delete_subtree(conn: ldap.ldapobject.LDAPObject, base_dn: str): + try: + records = conn.search_s(base_dn, ldap.SCOPE_ONELEVEL, attrlist=['']) + for dn, _ in records: + conn.delete_s(dn) + conn.delete_s(base_dn) + except ldap.NO_SUCH_OBJECT: + pass + + +@pytest.fixture +def ldap_srv(cfg, krb_srv): + conn = ldap.initialize(cfg.get('ldap_server_url')) + conn.sasl_gssapi_bind_s() + users_base = cfg.get('ldap_users_base') + groups_base = cfg.get('ldap_groups_base') + + recursively_delete_subtree(conn, users_base) + recursively_delete_subtree(conn, groups_base) + + for base_dn in [users_base, groups_base]: + ou = base_dn.split(',', 1)[0].split('=')[1] + conn.add_s(base_dn, ldap.modlist.addModlist({ + 'objectClass': [b'organizationalUnit'], + 'ou': [ou.encode()] + })) + _ldap_srv = LDAPService() + component.provideUtility(_ldap_srv, ILDAPService) + yield _ldap_srv + + recursively_delete_subtree(conn, users_base) + recursively_delete_subtree(conn, groups_base) + + +@pytest.fixture +def file_srv(cfg): + _file_srv = FileService() + component.provideUtility(_file_srv, IFileService) + return _file_srv diff --git a/ceod/model/test/test_user.py b/ceod/model/test/test_user.py new file mode 100644 index 000000000..fcbf07e07 --- /dev/null +++ b/ceod/model/test/test_user.py @@ -0,0 +1,22 @@ +import pytest + +from ceo_common.errors import UserNotFoundError +from ceod.model import User + + +def test_user_add_to_ldap(cfg, ldap_srv, file_srv): + min_id = cfg.get('members_min_id') + user = User( + uid='jdoe', + cn='John Doe', + program='Math', + terms=['s2021'], + ) + user.add_to_ldap() + retrieved_user = ldap_srv.get_user(user.uid) + assert retrieved_user.uid == user.uid + assert retrieved_user.uid_number >= min_id + + user.remove_from_ldap() + with pytest.raises(UserNotFoundError): + ldap_srv.get_user(user.uid) diff --git a/dev-requirements.txt b/dev-requirements.txt index 7a3d38c27..3c7b184dd 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ flake8==3.9.2 setuptools==40.8.0 wheel==0.36.2 +pytest==6.2.4 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..428a656ac --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[flake8] +ignore = + # line too long + E501 +exclude = .git,.vscode,venv,__pycache__,__init__.py,build,dist