diff --git a/conf/ldapcherry.ini b/conf/ldapcherry.ini index 4415cf5..4962c55 100644 --- a/conf/ldapcherry.ini +++ b/conf/ldapcherry.ini @@ -68,7 +68,7 @@ roles.file = '/etc/ldapcherry/roles.yml' ldap.module = 'ldapcherry.backend.backendLdap' ldap.groupdn = 'ou=group,dc=example,dc=com' -ldap.userdn = 'ou=group,dc=example,dc=com' +ldap.userdn = 'ou=people,dc=example,dc=com' ldap.binddn = 'cn=ldapcherry,dc=example,dc=com' ldap.password = 'password' ldap.uri = 'ldaps://ldap.ldapcherry.org' @@ -79,6 +79,7 @@ ldap.user_filter_tmpl = '(uid=%(username)s)' ldap.group_filter_tmpl = '(member=%(username)s)' ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))' ldap.objectclasses = 'top, person, organizationalPerson, user' +ldap.dn_user_attr = 'uid' ldap.timeout = 1 diff --git a/ldapcherry/backend/backendLdap.py b/ldapcherry/backend/backendLdap.py index 94a89a4..1130166 100644 --- a/ldapcherry/backend/backendLdap.py +++ b/ldapcherry/backend/backendLdap.py @@ -7,8 +7,16 @@ import cherrypy import ldap +import ldap.modlist as modlist import logging import ldapcherry.backend +import re + +class DelUserDontExists(Exception): + def __init__(self, user): + self.user = user + self.log = "cannot remove user, user <%(user)s> does not exist" % { 'user' : user} + class Backend(ldapcherry.backend.Backend): @@ -28,14 +36,20 @@ class Backend(ldapcherry.backend.Backend): self.user_filter_tmpl = self.get_param('user_filter_tmpl') self.group_filter_tmpl = self.get_param('group_filter_tmpl') self.search_filter_tmpl = self.get_param('search_filter_tmpl') - self.objectclasses = self.get_param('objectclasses') + self.dn_user_attr = self.get_param('dn_user_attr') + self.objectclasses = [] + for o in re.split('\W+', self.get_param('objectclasses')): + self.objectclasses.append(self._str(o)) + self.attrlist = [] for a in attrslist: + self.attrlist.append(self._str(a)) + + def _str(self, s): try: - self.attrlist.append(str(a)) + return str(s) except UnicodeEncodeError: - tmp = unicode(a).encode('unicode_escape') - self.attrlist.append(tmp) + return unicode(s).encode('unicode_escape') def auth(self, username, password): @@ -61,11 +75,47 @@ class Backend(ldapcherry.backend.Backend): def rm_from_group(self,username): pass - def add_user(self, username): - pass + def add_user(self, attrs): + ldap_client = self._bind() + attrs_str = {} + for a in attrs: + attrs_str[self._str(a)] = self._str(attrs[a]) + attrs_str['objectClass'] = self.objectclasses + dn = self.dn_user_attr + '=' + attrs[self.dn_user_attr] + ',' + self.userdn + ldif = modlist.addModlist(attrs_str) + try: + ldap_client.add_s(dn,ldif) + except ldap.OBJECT_CLASS_VIOLATION as e: + info = e[0]['info'] + desc = e[0]['desc'] + self._logger( + logging.ERROR, + "Configuration error, " + desc + ", " + info, + ) + raise e + except ldap.INSUFFICIENT_ACCESS as e: + info = e[0]['info'] + desc = e[0]['desc'] + self._logger( + logging.ERROR, + "Access error, " + desc + ", " + info, + ) + raise e + except ldap.ALREADY_EXISTS as e: + desc = e[0]['desc'] + self._logger( + logging.ERROR, + "adding user failed, " + desc, + ) + raise e def del_user(self, username): - pass + ldap_client = self._bind() + dn = self.get_user(username, False) + if not dn is None: + ldap_client.delete_s(dn) + else: + raise DelUserDontExists(username) def _bind(self): ldap_client = self._connect() diff --git a/tests/cfg/ldapcherry.ini b/tests/cfg/ldapcherry.ini index 18ff3d7..e9d3adc 100644 --- a/tests/cfg/ldapcherry.ini +++ b/tests/cfg/ldapcherry.ini @@ -79,6 +79,7 @@ ldap.user_filter_tmpl = '(uid=%(username)s)' ldap.group_filter_tmpl = '(member=%(userdn)s)' ldap.search_filter_tmpl = '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))' ldap.objectclasses = 'top, person, organizationalPerson, user' +ldap.dn_user_attr = 'uid' ldap.timeout = 1 ad.module = 'ldapcherry.backend.backendSamba4' diff --git a/tests/test_BackendLdap.py b/tests/test_BackendLdap.py index 7333b96..df22b77 100644 --- a/tests/test_BackendLdap.py +++ b/tests/test_BackendLdap.py @@ -8,10 +8,10 @@ import pytest import sys from sets import Set from ldapcherry.backend.backendLdap import Backend -from ldapcherry import syslog_error from ldapcherry.exceptions import * import cherrypy -from ldap import SERVER_DOWN +import logging +import ldap cfg = { 'module' : 'ldapcherry.backend.ldap', @@ -26,9 +26,14 @@ cfg = { 'user_filter_tmpl' : '(uid=%(username)s)', 'group_filter_tmpl' : '(member=%(userdn)s)', 'search_filter_tmpl' : '(|(uid=%(searchstring)s*)(sn=%(searchstring)s*))', -'objectclasses' : 'top, person, organizationalPerson, user', +'objectclasses' : 'top, person, organizationalPerson, simpleSecurityObject, posixAccount', +'dn_user_attr' : 'uid', } +def syslog_error(msg='', context='', + severity=logging.INFO, traceback=False): + pass + cherrypy.log.error = syslog_error attr = ['shéll', 'shell', 'cn', 'uid', 'uidNumber', 'gidNumber', 'home', 'userPassword', 'givenName', 'email', 'sn'] @@ -61,7 +66,7 @@ class TestError(object): ldapc = inv._connect() try: ldapc.simple_bind_s(inv.binddn, inv.bindpassword) - except SERVER_DOWN as e: + except ldap.SERVER_DOWN as e: return else: raise AssertionError("expected an exception") @@ -75,7 +80,7 @@ class TestError(object): ldapc = inv._connect() try: ldapc.simple_bind_s(inv.binddn, inv.bindpassword) - except SERVER_DOWN as e: + except ldap.SERVER_DOWN as e: assert e[0]['info'] == 'TLS: hostname does not match CN in peer certificate' # def testConnectSSLNoCheck(self): @@ -116,8 +121,61 @@ class TestError(object): expected = ('cn=John Watson,ou=People,dc=example,dc=org', {'uid': ['jwatson'], 'cn': ['John Watson'], 'sn': ['watson']}) assert ret == expected - def testSearchtUser(self): + def testSearchUser(self): inv = Backend(cfg, cherrypy.log, 'ldap', attr) ret = inv.search('smith') expected = [('cn=Sheri Smith,ou=People,dc=example,dc=org', {'uid': ['ssmith'], 'objectClass': ['inetOrgPerson'], 'carLicense': ['HERCAR 125'], 'sn': ['smith'], 'mail': ['s.smith@example.com', 'ssmith@example.com', 'sheri.smith@example.com'], 'homePhone': ['555-111-2225'], 'cn': ['Sheri Smith']}), ('cn=John Smith,ou=People,dc=example,dc=org', {'uid': ['jsmith'], 'objectClass': ['inetOrgPerson'], 'carLicense': ['HISCAR 125'], 'sn': ['Smith'], 'mail': ['j.smith@example.com', 'jsmith@example.com', 'jsmith.smith@example.com'], 'homePhone': ['555-111-2225'], 'cn': ['John Smith']})] assert ret == expected + + def testAddUser(self): + inv = Backend(cfg, cherrypy.log, 'ldap', attr) + user = { + 'uid': 'test', + 'sn': 'test', + 'cn': 'test', + 'userPassword': 'test', + 'uidNumber': '42', + 'gidNumber': '42', + 'homeDirectory': '/home/test/' + } + inv.add_user(user) + inv.del_user('test') + + def testAddUserDuplicate(self): + inv = Backend(cfg, cherrypy.log, 'ldap', attr) + user = { + 'uid': 'test', + 'sn': 'test', + 'cn': 'test', + 'uidNumber': '42', + 'userPassword': 'test', + 'gidNumber': '42', + 'homeDirectory': '/home/test/' + } + try: + inv.add_user(user) + inv.add_user(user) + except ldap.ALREADY_EXISTS: + inv.del_user('test') + return + else: + inv.del_user('test') + raise AssertionError("expected an exception") + + def testAddUserMissingMustAttribute(self): + inv = Backend(cfg, cherrypy.log, 'ldap', attr) + user = { + 'uid': 'test', + 'sn': 'test', + 'cn': 'test', + 'userPassword': 'test', + 'gidNumber': '42', + 'homeDirectory': '/home/test/' + } + try: + inv.add_user(user) + except ldap.OBJECT_CLASS_VIOLATION: + return + else: + inv.del_user('test') + raise AssertionError("expected an exception") diff --git a/tests/test_env/etc/ldap/slapd.d/cn=config/olcDatabase={1}hdb.ldif b/tests/test_env/etc/ldap/slapd.d/cn=config/olcDatabase={1}hdb.ldif index e35ae5b..f767ece 100644 --- a/tests/test_env/etc/ldap/slapd.d/cn=config/olcDatabase={1}hdb.ldif +++ b/tests/test_env/etc/ldap/slapd.d/cn=config/olcDatabase={1}hdb.ldif @@ -7,7 +7,7 @@ olcSuffix: dc=example,dc=org olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymou s auth by dn="cn=admin,dc=example,dc=org" write by * none olcAccess: {1}to dn.base="" by * read -olcAccess: {2}to * by self write by dn="cn=admin,dc=example,dc=org" write by * read +olcAccess: {2}to * by self write by dn="cn=dnscherry,dc=example,dc=org" write by * read olcLastMod: TRUE olcRootDN: cn=admin,dc=example,dc=org olcRootPW: {SSHA}Fp+rSxe5eFsj0DGITJts4DwdSDFDZG9P