class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True)) self.connection = Connection(server, auto_bind=True, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1') result = self.connection.add(dn_for_test(test_base, 'test-ldif-1'), 'iNetOrgPerson', {'objectClass': 'iNetOrgPerson', 'sn': 'test-ldif-1', test_name_attr: 'test-ldif-1'}) if not isinstance(result, bool): self.connection.get_response(result) result = self.connection.add(dn_for_test(test_base, 'test-ldif-2'), 'iNetOrgPerson', {'objectClass': 'iNetOrgPerson', 'sn': 'test-ldif-2', test_name_attr: 'test-ldif-2'}) if not isinstance(result, bool): self.connection.get_response(result) def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) def test_single_search_result_to_ldif(self): result = self.connection.search(search_base=test_base, search_filter='(' + test_name_attr + '=test-ldif-1)', attributes=[test_name_attr, 'givenName', 'jpegPhoto', 'sn', 'cn', 'objectClass']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result l = self.connection.response_to_ldif(response) self.assertTrue('version: 1' in l) self.assertTrue('dn: cn=test-ldif-1,o=test' in l) self.assertTrue('objectClass: inetOrgPerson' in l) self.assertTrue('objectClass: Top' in l) self.assertTrue('cn: test-ldif-1' in l) self.assertTrue('sn: test-ldif-1' in l) self.assertTrue('total number of entries: 1' in l) def test_multiple_search_result_to_ldif(self): result = self.connection.search(search_base=test_base, search_filter='(sn=test-ldif*)', attributes=[test_name_attr, 'givenName', 'sn', 'objectClass']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result l = self.connection.response_to_ldif(response) self.assertTrue('version: 1' in l) self.assertTrue('dn: cn=test-ldif-1,o=test' in l) self.assertTrue('objectClass: inetOrgPerson' in l) self.assertTrue('objectClass: Top' in l) self.assertTrue('cn: test-ldif-1' in l) self.assertTrue('sn: test-ldif-1' in l) self.assertTrue('dn: cn=test-ldif-2,o=test' in l) self.assertTrue('cn: test-ldif-2' in l) self.assertTrue('sn: test-ldif-2' in l) self.assertTrue('total number of entries: 2' in l)
class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True)) self.connection = Connection(server, auto_bind=True, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1') result = self.connection.add(dn_for_test(test_base, 'test-add-for-delete'), [], {'objectClass': 'iNetOrgPerson', 'sn': 'test-add'}) if not isinstance(result, bool): self.connection.get_response(result) def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) def test_delete(self): result = self.connection.delete(dn_for_test(test_base, 'test-add-for-delete')) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'noSuchObject'])
class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True), get_info=test_get_info) self.connection = Connection(server, auto_bind=True, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1', check_names=test_check_names) result = self.connection.delete(dn_for_test(test_base, 'test-add-operation')) if not isinstance(result, bool): self.connection.get_response(result) def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) @pytest.mark.skipif(True, reason="needs rework") def test_add(self): result = self.connection.add(dn_for_test(test_base, 'test-add-operation'), 'iNetOrgPerson', {'objectClass': 'iNetOrgPerson', 'sn': 'test-add', test_name_attr: 'test-add-operation'}) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'entryAlreadyExists'])
class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True), get_info=3) self.connection = Connection(server, auto_bind=True, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1', check_names=True) def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) def test_wrong_assertion(self): ok = False try: result = self.connection.search(search_base=test_base, search_filter='(xxx=yyy)', attributes=[test_name_attr]) except LDAPException: ok = True self.assertTrue(ok) def test_wrong_attribute(self): ok = False try: result = self.connection.search(search_base=test_base, search_filter='(cn=yyy)', attributes=[test_name_attr, 'xxx']) except LDAPException: ok = True self.assertTrue(ok) def test_wrong_object_class_add(self): ok = False try: result = self.connection.add(dn_for_test(test_base, 'test-add-operation-wrong'), 'iNetOrgPerson', {'objectClass': ['iNetOrgPerson', 'xxx'], 'sn': 'test-add', test_name_attr: 'test-add-operation'}) except LDAPException: ok = True self.assertTrue(ok) def test_valid_assertion(self): result = self.connection.search(search_base=test_base, search_filter='(cn=test*)', attributes=[test_name_attr]) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 1) def test_valid_attribute(self): result = self.connection.search(search_base=test_base, search_filter='(cn=test*)', attributes=[test_name_attr, 'givenName']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 1) def test_valid_object_class_add(self): result = self.connection.add(dn_for_test(test_base, 'test-add-operation-check-names'), 'iNetOrgPerson', {'objectClass': ['iNetOrgPerson', 'Person'], 'sn': 'test-add', test_name_attr: 'test-add-operation-check-names'}) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'entryAlreadyExists'])
class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True)) self.connection = Connection(server, auto_bind=True, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1') def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) def test_modify_dn_operation(self): result = self.connection.delete(dn_for_test(test_base, 'test-add-modified-dn')) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'noSuchObject']) result = self.connection.delete(dn_for_test(test_base, 'test-add-for-modify-dn')) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'noSuchObject']) result = self.connection.add(dn_for_test(test_base, 'test-add-for-modify-dn'), [], {'objectClass': 'iNetOrgPerson', 'sn': 'test-compare', 'givenName': 'modify-dn'}) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'entryAlreadyExists']) result = self.connection.modify_dn(dn_for_test(test_base, 'test-add-for-modify-dn'), test_name_attr + '=test-add-modified-dn') if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'noSuchObject']) def test_move_dn(self): result = self.connection.delete(dn_for_test(test_base, 'test-add-for-move-dn')) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'noSuchObject']) result = self.connection.add(dn_for_test(test_base, 'test-add-for-move-dn'), [], {'objectClass': 'iNetOrgPerson', 'sn': 'test-add-for-move-dn', 'givenName': 'move-dn'}) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'entryAlreadyExists']) result = self.connection.delete(dn_for_test(test_moved, 'test-add-for-move-dn')) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['success', 'noSuchObject', 'busy']) result = self.connection.modify_dn(dn_for_test(test_base, 'test-add-for-move-dn'), test_name_attr + '=test-add-for-move-dn', new_superior=test_moved) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertTrue(result['description'] in ['other', 'success', 'entryAlreadyExists', 'noSuchObject'])
conn.search('ou = students, dc = security, dc=ch', '(objectclass=person)') print(conn.entries) # If client wants to add an entry elif operation == '2' or operation == 'a': print('Entry will be added in ou=students, dc=security, dc=ch\n') to_add=[] to_add.append(input("Common name: > ")) to_add.append(input("Surname: > ")) to_add.append(input("Description: > ")) to_add.append(input("User Password: > ")) to_add.append(input("Telephone Number: > ")) conn.add('cn = '+to_add[0]+', ou = students, dc = security, dc = ch', attributes={'objectClass': 'person', 'sn': to_add[1] if to_add[1] != '' else ' ', 'description': to_add[2] if to_add[2] != '' else ' ', 'userPassword': to_add[3] if to_add[3] != '' else ' ', 'telephoneNumber': to_add[4] if to_add[4] != '' else '00000000000000'} ) print(conn.result) # If clients wants to remove an entry elif operation == '3' or operation == 'r': to_delete = input("Which user would you like to remove: > ") sure = input("Are you sure you want to delete " +to_delete+"? y/n : ") y_or_n = False # Just a little security to be sure what to delete while not y_or_n: if (sure == 'y' or sure == 'n'): y_or_n = True else:
def set_init(self, app, request): asyncio.get_event_loop().set_debug(True) logging.basicConfig(level=logging.DEBUG) ldap_server = app.app['settings']['ldap.server'] conn = Connection(ldap_server, auto_bind=True) self.conn = conn # test entry is empty conn.search(self.base_dn,'(objectclass=top)') assert len(conn.entries) == 1, 'Refusing test: ldap entry is not empty' # set app testing settings = app.app['settings'].copy() config_dn = 'ou=config,' + self.base_dn userFilter = 'mail={username},ou=users,' + self.base_dn settings['ldap.base_dn'] = self.base_dn settings['ldap.config_dn'] = config_dn settings['ldap.user_filter'] = userFilter settings['valid_password'] = '******' settings['password_policy'] = 'plone.oauth.password.password_policy' new_app = main(settings) self.app = app self.params = {} self.headers = {} self.app.app['settings'] = new_app['settings'] # mock redis if self.DISABLE_CACHE_REDIS: class AsyncMock(MagicMock): async def __call__(self, *args, **kwargs): return super(AsyncMock, self).__call__(*args, **kwargs) self.original_redis_cache = redis.cache self.original_redis_get = redis.get self.mock_redis_cache = AsyncMock() self.mock_redis_cache.return_value = iter([None]) self.mock_redis_get = AsyncMock(side_effect=KeyError) redis.cache = self.mock_redis_cache redis.get = self.mock_redis_get # pre add conn.add('ou=scopes,'+self.base_dn, 'organizationalUnit') conn.add('ou=users,'+self.base_dn, 'organizationalUnit') conn.add('ou=config,'+self.base_dn, 'organizationalUnit') conn.add('ou=clients,ou=config,'+self.base_dn, 'organizationalUnit') # post delete if request is not None: request.addfinalizer(self.set_teardown) # load data self.ldap = self.app.app['settings']['user_manager'] self.create_scope() self.create_client() self.create_superuser() # load extra data self.create_manager() self.create_user() self.create_group()
class __Ldap: ldap = None def __init__(self, settings=None): self.settings = settings or Settings() self.logger = Logger(__name__) def getInstance(self): settings = self.settings.getSettings() user = settings['ldap']['admin_ldap_username'] password = settings['ldap']['admin_ldap_password'] host = self.settings.getServiceIp('ldap') self.logger.debug("Connecting to " + host + " with user " + user) self.dn_base = settings['ldap']['ldap_cn_base'] server = Server(host, get_info=ALL) self.ldapClient = Connection(server, user=user, password=password, raise_exceptions=True) try: self.ldapClient.bind() except ldap.LDAPSocketOpenError as e: self.logger.error("Could not connect to LDAP - SocketOpenError: " + str(e)) return self def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): return self.ldapClient.unbind() def disconnect(self): self.ldapClient.unbind() def createUser(self, user, firstname, lastname, password, email): password = password.encode('utf-8') hashPassword = hashlib.md5() hashPassword.update(password) password = base64.b64encode(hashPassword.digest()) dn = "cn=" + user + "," + self.dn_base attrs = {} attrs['objectClass'] = ['inetOrgPerson', 'person', 'top', 'organizationalPerson'] attrs['cn'] = user attrs['userPassword'] = "******" + password.decode('utf-8') attrs['sn'] = lastname attrs['givenName'] = firstname attrs['mail'] = email try: self.ldapClient.add(dn, attributes=attrs) self.logger.info(self.ldapClient.result) except ldap.LDAPEntryAlreadyExistsResult: self.logger.error("Could not create user, duplicated " + user) return False except Exception as e: self.logger.error("Could not create user: "******"cn="+user+","+self.dn_base self.ldapClient.delete(deleteDN) return True def findUser(self, user): self.ldapClient.search(search_base=self.dn_base, search_filter='(&(objectClass=inetOrgPerson)(cn=' + user + '))', search_scope=SUBTREE, attributes=['cn']) usernames = [] for result in self.ldapClient.response: cn = result['attributes']['cn'][0] if cn: usernames.append(cn) return usernames
class Generator: def __init__(self, configFile, hostFile, owner): self.configFile = configFile self.hostFile = hostFile self.owner = owner self.initLogging() self.initialise() self.connectLDAP() self.processHostFile() self.disconnectLDAP() def initialise(self): parser = configparser.ConfigParser() with open(self.configFile) as f: lines = '[top]\n' + f.read( ) # hack, do not want [top] in config file, so add it here parser.read_string(lines) # we expect certain entries in the config file, or we will bail. There are no defaults parser = parser['top'] self.setConfig('ldaps_url', parser) self.setConfig('username', parser) self.setConfig('password', parser) self.setConfig('realm', parser) self.setConfig('service_password', parser) self.setConfig('base_dn', parser) def initLogging(self): self.logger = logging.getLogger('create-service-user') self.logger.setLevel( logging.DEBUG) # change to INFO or DEBUG for more output handler = logging.StreamHandler() handler.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) self.logger.addHandler(handler) def setConfig(self, key, parser): if key not in parser: self.logger.error(f"Cannot find {key} in config file. Aborting") sys.exit(2) self.__setattr__(key, parser[key]) self.logger.info(f"{key} : {parser[key]}") def connectLDAP(self): self.ldap = Connection(self.ldaps_url, user=self.username, password=self.password, auto_bind=True) self.logger.info(self.ldap) def archiveAndDeleteFiles(self, files): with zipfile.ZipFile(f"{self.owner}.zip", "w") as archive: for f in files: archive.write(f) for f in files: p = Path(f) p.unlink() def processHostFile(self): with open(self.hostFile) as f: content = f.read() # split into lines lines = content.split("\n") files = [] for line in lines: if line != "": entries = line.split(",") principal = entries[0] host = entries[1] print(f"{principal} --> {host}") (service_name, filename) = self.createServiceUser(principal, host) self.create_keytab(service_name, filename) files.append(filename) self.archiveAndDeleteFiles(files) def createServiceUser(self, principal, host): short_host = host.split('.')[0] cn = f"{principal} {short_host}" dn = f"CN={cn},{self.base_dn}" service_name = f"{principal}/{host}" user_principal_name = f"{service_name}@{self.realm}" user_attrs = {} user_attrs['objectClass'] = [ 'top', 'person', 'organizationalPerson', 'user' ] user_attrs['cn'] = cn user_attrs['accountExpires'] = '0' user_attrs['userPrincipalName'] = user_principal_name user_attrs['servicePrincipalName'] = service_name self.logger.info(user_attrs) self.ldap.add(dn, attributes=user_attrs) self.logger.info(self.ldap.result) # set the password self.ldap.extend.microsoft.modify_password(dn, self.service_password) self.logger.info(self.ldap.result) # set the account active and password non-expiring self.ldap.modify(dn, {"userAccountControl": [('MODIFY_REPLACE', 66048)]}) self.logger.info(self.ldap.result) filename = f"{principal}-{short_host}.keytab" return (service_name, filename) def create_keytab(self, service_name, filename): # expects ktutil to be installed in the path encryptions = ["aes256-cts", "aes128-cts", "rc4-hmac"] prompt = "ktutil: " child = pexpect.spawn("ktutil") for encryption in encryptions: cmd = f"addent -password -p {service_name} -k 1 -e {encryption}" child.expect(prompt) child.sendline(cmd) child.expect("Password for .*:") child.sendline(self.service_password) child.expect(prompt) child.sendline(f"write_kt {filename}") child.expect(prompt) child.sendline("q") def disconnectLDAP(self): self.ldap.unbind()
class LDAPUserManager(object): def __init__( self, ldap_server, user_filter, base_dn=None, root_dn=None, passwd_dn=None, user_profile=[], read_only=True, cache_users=None, cache_groups=None): self.ldap_server = ldap_server self.root_dn = root_dn self.passwd_dn = passwd_dn self.base_dn = base_dn self.user_filter = user_filter self.user_profile = user_profile self.read_only = read_only self.cache_users = cache_users self.cache_groups = cache_groups self.ttl_users = 3660 self.bind() def scopes_filter(self): f = 'ou=scopes,' + self.base_dn return f def scope_filter(self, scope): f = 'ou={scope},' + self.scopes_filter() return f.format(scope=scope) def groups_filter(self, scope): return 'ou=groups,' + self.scope_filter(scope=scope) def group_filter(self, scope, group): f = 'cn={group},' + self.groups_filter(scope=scope) return f.format(group=group) def roles_filter(self, scope): return 'ou=roles,' + self.scope_filter(scope=scope) def role_filter(self, scope, role): f = 'cn={role},' + self.roles_filter(scope) return f.format(role=role) def users_filter(self): f = 'ou=users,' + self.base_dn return f def userdn2id(self, user_dn): """ :param user_dn: can be a user or a group dn :type user_dn: str """ parse_dn = None if user_dn.startswith('mail='): parse_dn = 'mail=' elif user_dn.startswith('cn='): parse_dn = 'cn=' if parse_dn: return user_dn[len(parse_dn):user_dn.find(',')] def bind(self): if self.root_dn is None: raise Exception('No LDAP Admin Configuration') authentication_method = SIMPLE bind_type = ASYNC self.ldap_conn_mng = Connection( self.ldap_server, authentication=authentication_method, client_strategy=bind_type, user=self.root_dn, password=self.passwd_dn, read_only=self.read_only) self.ldap_conn_mng.bind() def bind_root_readonly(self): """ A root connection to LDAP server only read_only """ if self.root_dn is None: raise Exception('No LDAP Admin Configuration') authentication_method = SIMPLE bind_type = ASYNC ldap_conn_mng = Connection( self.ldap_server, authentication=authentication_method, client_strategy=bind_type, user=self.root_dn, password=self.passwd_dn, read_only=True) ldap_conn_mng.bind() return ldap_conn_mng def unbind(self): self.ldap_conn_mng.unbind() def checkUserName(self, username): if ',' in username: raise TypeError('No , in username') def parse_async_add(self, return_code): """ Parser the response for an LDAP async ADD """ result = 'Not done' if return_code: result = self.ldap_conn_mng.get_response(return_code) result = result[1].get('description', 'Error no description') return result async def addScope(self, scope): """ !!!Admin add: must be protected when calling """ # Needs admin if self.read_only: raise Exception('LDAP in Read only mode') self.bind() scope_dn = self.scope_filter(scope=scope) done = self.ldap_conn_mng.add(scope_dn, 'organizationalUnit') result = self.parse_async_add(done) if result == 'success': #add subelements for scope roles_dn = self.roles_filter(scope=scope) done = self.ldap_conn_mng.add(roles_dn, 'organizationalUnit') if self.parse_async_add(done) != 'success': result = 'Failed creating roles' groups_dn = self.groups_filter(scope=scope) done = self.ldap_conn_mng.add(groups_dn, 'organizationalUnit') if self.parse_async_add(done) != 'success': result = 'Failed creating groups' self.unbind() return result def addUser(self, username, password): """ !!!Admin add: must be protected when calling """ # Needs admin if self.read_only: raise Exception('LDAP in Read only mode') self.bind() self.checkUserName(username) user_dn = self.user_filter.format(username=username) if '@' in username: sn = username.split('@')[0] else: sn = username done = self.ldap_conn_mng.add( user_dn, self.user_profile, { 'cn': username, 'sn': sn, 'mail': username, 'userPassword': password, }) result = self.parse_async_add(done) self.unbind() return result async def addGroup(self, scope, group): """ !!!Admin add: must be protected when calling """ # Needs admin if self.read_only: raise Exception('LDAP in Read only mode') self.bind() self.checkUserName(group) group_dn = self.group_filter(scope=scope, group=group) done = self.ldap_conn_mng.add( group_dn, 'groupOfUniqueNames', { 'cn': group, 'uniqueMember': group_dn # group itself as an empty members list }) result = self.parse_async_add(done) self.unbind() return result async def setPassword(self, username, password): """ !!!Admin add: must be protected when calling """ # Needs admin if self.read_only: raise Exception('LDAP in Read only mode') self.bind() user_dn = self.user_filter.format(username=username) hashed_password = hashed(HASHED_SHA512, password) done = self.ldap_conn_mng.modify( user_dn, {'userPassword': [(MODIFY_REPLACE, [hashed_password])]} ) result = self.parse_async_add(done) if result == 'success': return True else: return False async def addScopeRole(self, scope, user_dn, role): """ !!!Admin add: must be protected when calling `user_dn` can be a user or a group dn. """ # Needs admin if self.read_only: raise Exception('LDAP in Read only mode') self.bind() role_dn = self.role_filter(scope=scope, role=role.lower()) #Create role for first time done = self.ldap_conn_mng.add( role_dn, 'groupOfUniqueNames', {'cn': role, 'uniqueMember': user_dn} ) result = self.parse_async_add(done) if result == 'entryAlreadyExists': #Extend role with new user done = self.ldap_conn_mng.modify( role_dn, {'uniqueMember': [(MODIFY_ADD, [user_dn])]} ) result = self.parse_async_add(done) self.unbind() return result async def addScopeRoleUser(self, scope, username, role): user_dn = self.user_filter.format(username=username) return await self.addScopeRole(scope, user_dn, role) async def addScopeRoleGroup(self, scope, groupname, role): group_dn = self.group_filter(scope, groupname) return await self.addScopeRole(scope, group_dn, role) async def delScopeRole(self, scope, username, role): """ !!!Admin del: must be protected when calling """ # Needs admin if self.read_only: raise Exception('LDAP in Read only mode') self.bind() role_dn = self.role_filter(scope=scope, role=role.lower()) user_dn = self.user_filter.format(username=username) #Find role for first time done = self.ldap_conn_mng.modify( role_dn, {'uniqueMember': [(MODIFY_DELETE, [user_dn])]} ) result = self.parse_async_add(done) if result == 'objectClassViolation': # member was the last remaining done = self.ldap_conn_mng.delete(role_dn) result = self.parse_async_add(done) self.unbind() return result async def searchUser(self, scope, criteria, exact_match, attrs, page=None, num_x_page=0): """ !!!Admin search: must be protected when calling """ total = 0 result = [] paged_size = num_x_page paged_cookie = page if exact_match is False and any(criteria.values()): for k in criteria.keys(): criteria[k] = "*" + criteria[k] + "*" for objclass in self.user_profile: criteria['objectClass'] = objclass filter_ldap = "" for j, v in criteria.items(): filter_ldap += "(%s=%s)" % (j, v) filter_ldap = "(&%s)" % filter_ldap self.ldap_conn_mng.bind() done = self.ldap_conn_mng.search( self.base_dn, filter_ldap, search_scope=SUBTREE, dereference_aliases=DEREF_ALWAYS, attributes=USER_ATTRIBUTES, size_limit=0, time_limit=0, types_only=False, get_operational_attributes=False, controls=None, paged_size=paged_size, paged_criticality=False, paged_cookie=paged_cookie) if done: result = self.ldap_conn_mng.get_response(done)[0] total = len(result) self.ldap_conn_mng.unbind() return [dict(r['attributes']) for r in result], total async def getUser(self, dn, ldap_conn): # We should be logged in with the user with (await self.cache_users) as redis: user = await redis.get(dn) if user and user != b'{}': return ujson.loads(user) r = ldap_conn.search( dn, '(objectClass=*)', search_scope=BASE, attributes=USER_ATTRIBUTES) if r: res = ldap_conn.response[0] with (await self.cache_users) as redis: redis.set(res['dn'], ujson.dumps(dict(res['attributes']))) redis.expire(res['dn'], self.ttl_users) return res['attributes'] async def loginUser(self, username, password): user_dn = self.user_filter.format(username=username) bind_type = SYNC authentication_method = SIMPLE ldap_conn = Connection( self.ldap_server, authentication=authentication_method, client_strategy=bind_type, user=user_dn, password=password, read_only=True) try: result = ldap_conn.bind() if result: user = await self.getUser(user_dn, ldap_conn) ldap_conn.unbind() return user else: return None except LDAPException: return None async def getUserName(self, username): dn = self.user_filter.format(username=username) with (await self.cache_users) as redis: user = await redis.get(dn) if user and user != b'{}': return ujson.loads(user) ldap_conn = self.bind_root_readonly() r = ldap_conn.search( dn, '(objectClass=*)', search_scope=BASE, attributes=USER_ATTRIBUTES) if r: names = ldap_conn.get_response(r)[0] res = [res['attributes']['cn'][0] for res in names] else: res = [] ldap_conn.unbind() return ' '.join(res) async def get_user_groups(self, ldap_conn, scope, user_dn): """ Return all groups cn that `user_dn` has in `scope` :param user_dn: can be a user or a group dn :type user_dn: str """ groups_dn = self.groups_filter(scope=scope) search_filter = '(uniqueMember={0})'.format(user_dn) r = ldap_conn.search( groups_dn, search_filter, search_scope=SUBTREE, attributes=['cn'] ) if r: groups = ldap_conn.get_response(r)[0] groups = filter(lambda x: x['dn'] != user_dn, groups) # filter self return [res['attributes']['cn'][0] for res in groups] async def get_user_roles(self, ldap_conn, scope, user_dn, groups=None): """ Return all roles cn that `user_dn` has in `scope`. Return all roles cn that each of `groups` has in `scope`. :param user_dn: can be a user or a group dn :type user_dn: str :param groups: (Optionally) :type groups: list """ roles_dn = self.roles_filter(scope=scope) search_filter = '(uniqueMember={0})'.format(user_dn) if groups is not None: search_filter = '(|' + search_filter for group_cn in groups: group_dn = self.group_filter(scope=scope, group=group_cn) search_filter += '(uniqueMember={0})'.format(group_dn) search_filter += ')' r = ldap_conn.search( roles_dn, search_filter, search_scope=SUBTREE, attributes=['cn'] ) if r: roles = ldap_conn.get_response(r)[0] return [res['attributes']['cn'][0] for res in roles] async def get_info_user_or_group(self, user_dn, scope): """ !!!Admin search: must be protected when calling :returns: { 'groups': { 'group1': 1, }, 'roles': { 'plone.Manager': 1, } } """ ldap_conn = self.bind_root_readonly() groups = await self.get_user_groups(ldap_conn, scope, user_dn) roles = await self.get_user_roles( ldap_conn, scope, user_dn, groups=groups, ) ldap_conn.unbind() return { 'roles': {e: 1 for e in roles}, 'groups': {e: 1 for e in groups}, } async def getUserInfo(self, username, scope): """ !!!Admin search: must be protected when calling :returns: { 'groups': { 'group1': 1, }, 'roles': { 'plone.Manager': 1, } 'name': 'Name' } """ user_dn = self.user_filter.format(username=username) info = await self.get_info_user_or_group(user_dn, scope) info['name'] = username return info async def getGroupInfo(self, scope, group=None): """ !!!Admin search: must be protected when calling :rtype: dict or list of dict or None :returns: { 'members': [ member1, ], 'name': 'Name' } or list of groups if group is None or None if group is not found """ ldap_conn = self.bind_root_readonly() groups_dn = self.groups_filter(scope=scope) if group is None: search_filter = '(objectClass=groupOfUniqueNames)' else: search_filter = '(cn={0})'.format(group) r = ldap_conn.search( groups_dn, search_filter, search_scope=SUBTREE, attributes=['cn', 'uniqueMember'] ) if not r: raise Exception('LDAP Group search bad formed') groups = ldap_conn.get_response(r)[0] ldap_conn.unbind() async def ldap2json(entry): group_dn = entry['dn'] group_name = entry['attributes']['cn'][0] members_ldap = entry['attributes']['uniqueMember'] members_ldap = filter(lambda x: x != group_dn, members_ldap) # filter self info = await self.get_info_user_or_group(group_dn, scope) info.update({ 'name': group_name, 'members': list(map(self.userdn2id, members_ldap)), }) return info groups = await asyncio.gather(*map(ldap2json, groups)) if group is None: return groups try: return groups[0] except IndexError: return None async def get_all_scopes(self, ldap_conn): """ """ r = ldap_conn.search( self.scopes_filter(), "(objectClass=organizationalUnit)", search_scope=LEVEL, attributes=['ou'] ) if r: scopes = ldap_conn.get_response(r)[0] return [scope['attributes']['ou'][0] for scope in scopes] async def getUserScopes(self, username): """ Aquesta crida retorna tots els scopes als quals pertany un usuari Nota: es pot millorar. Recorre dos cops tots els scopes. Un cop per obtenir-los tots i un altre per filtrar segons si l'usuari pertany o no """ ldap_conn = self.bind_root_readonly() all_scopes = await self.get_all_scopes(ldap_conn) if plone.oauth.is_superuser(username): scopes = all_scopes else: user_dn = self.user_filter.format(username=username) scopes = [] for scope in all_scopes: roles = await self.get_user_roles(ldap_conn, scope, user_dn) if roles: scopes.append(scope) ldap_conn.unbind() return { 'scopes': scopes } async def get_all_users(self, ldap_conn): """ Return all users cn that `username` has in `scope`. Optionally also search by `groups`. """ r = ldap_conn.search( self.users_filter(), "(objectClass=organizationalPerson)", search_scope=LEVEL, attributes=['mail'] ) if r: users = ldap_conn.get_response(r)[0] return [user['attributes']['mail'][0] for user in users] async def getScopeUsers(self, scope): """ Retorna tots els usuaris que pertanyen a un scope Nota: es pot millorar. Recorre dos cops tots els usuaris. Un cop per obtenir-los tots i un altre per filtrar segons si l'usuari pertany o no """ ldap_conn = self.bind_root_readonly() all_users_ids = await self.get_all_users(ldap_conn) users = [] for user_id in all_users_ids: user_dn = self.user_filter.format(username=user_id) roles = await self.get_user_roles(ldap_conn, scope, user_dn) if roles: user = { 'id': user_id, 'roles': roles } users.append(user) ldap_conn.unbind() return { 'users': users }
class LDAP: def __init__(self, domain_controllers): self.domain_controllers = domain_controllers pass def bind(self, ad_username, ad_password): """ Function that can be called to bind to a list of domain controllers as a pool """ logger.debug("Trying to connect to domain controllers: {}".format( self.domain_controllers)) server_pool_list = [] for dc in self.domain_controllers: server_pool_list.append( Server(host=dc, get_info=ALL, port=636, use_ssl=True, connect_timeout=120)) server_pool = ServerPool(server_pool_list, FIRST, active=True, exhaust=120) try: self.conn = Connection(server_pool, auto_bind=True, user=ad_username, password=ad_password, raise_exceptions=True) self.conn.bind() except LDAPTimeLimitExceededResult as e: logger.critical( "Unable to establish a connection with the LDAP server, timed out after 60 seconds" ) exit(1) except Exception as e: logger.critical( "Unable to create an LDAP connection to provision users in Active Directory. The error was: {}" .format(str(e))) raise exit(1) def list_groups(self, ou, attributes=ALL_ATTRIBUTES): if not attributes == ALL_ATTRIBUTES and not "sAMAccountName" in attributes: raise Exception( "The attribute 'sAMAccountName' must be included in the attributes list passed" ) ad_groups = {} try: groups = self.conn.extend.standard.paged_search( search_base=ou, search_filter="(objectClass=Group)", search_scope=SUBTREE, attributes=attributes, paged_size=100, generator=False, ) except Exception as e: raise Exception( "Unable to list groups in AD. The error was: {}".format( str(e))) if not self.conn.result.get("description") == "success": raise Exception( "Unable to list users in AD. The error was: {}".format( self.conn.result)) elif len(groups) == 0: logger.info( "No groups were found in Active Directory, but got a good response. Proceeding." ) return {} for group in groups: # print(json.dumps(group, indent=2, sort_keys=True, default=str)) try: ad_groups[group.get("attributes", {}).get("cn", "")] = dict( group.get("attributes", {})) except Exception as e: logger.warning( "Unable to add group {} to ad_groups list. The error was: {}" .format(group.get("cn", ""), e)) return ad_groups def list_users(self, ou, attributes=ALL_ATTRIBUTES): """ List all users in a given OU or CN :param ou: The DN path where you want the listing to occur :param attributes: a list of attributes that you would like returned in the query (must contain at least userPrincipalName), defaults to ALL :return: a dictionary of returned objects from the specified search DN with the key set to the user's UPN and value their AD record """ if not attributes == ALL_ATTRIBUTES and not "sAMAccountName" in attributes: raise Exception( "The attribute 'sAMAccountName' must be included in the attributes list passed" ) ad_users = {} try: users = self.conn.extend.standard.paged_search( search_base=ou, search_filter="(objectClass=User)", search_scope=SUBTREE, attributes=attributes, paged_size=100, generator=False, ) except Exception as e: raise Exception( "Unable to list users in AD. The error was: {}".format(str(e))) if not self.conn.result.get("description") == "success": raise Exception( "Unable to list users in AD. The error was: {}".format( self.conn.result)) elif len(users) == 0: logger.info( "No users were found in Active Directory, but got a good response. Proceeding." ) return {} for user in users: # print(json.dumps(user, indent=2, sort_keys=True, default=str)) try: # we append each user to a dictionary and set the value to the user record # we are primarily using this as a lookup reference to know if the user was already in AD or not ad_users[user.get("attributes", {}).get("sAMAccountName", "")] = dict( user.get("attributes", {})) except Exception as e: logger.warning( "Unable to add user {} to ad_users list. The error was: {}" .format(user.get("dn", ""), e)) return ad_users def create_user(self, user, password, ou): """ Create a new user in Active Directory with default attributes (including a generated secure password, and account enablement) :param ou: the path in AD for the user to be created :param ldap_user_attributes: the JSON formatted user record :return: bool status of whether the creation succeeded or not """ ldap_user_attributes = { "objectClass": ["top", "person", "organizationalPerson", "user"], "cn": [f"{user['first_name']} {user['last_name']}"], "sAMAccountName": [user['username'][:20] ], # we need to strip the sAMAccountName to 20 chars "displayName": [f"{user['first_name']} {user['last_name']}"], "mail": [user['email']], "userPrincipalName": [user['email']], # mind the extra set of quotes in the uncodePWd value, this is required, do not change # this is very specific to MS AD, so this needs be the encoding "unicodePwd": f'"{password}"'.encode("utf-16-le"), "userAccountControl": "66048", # userAccountControl mappings: https://vaportech.wordpress.com/2007/12/06/useraccountcontrol/ "department": user['team'], "givenName": user['first_name'], "sn": user['last_name'], "title": user['title'], "description": f"{user['title']} for {user['team']}" } try: username = ldap_user_attributes.get( "cn", {})[0] # sAMAccountName is a list, we need the first entry except IndexError as e: logger.error( "Unable to retrieve the sAMAccountName for record {}".format( ldap_user_attributes)) return False logger.debug("Attempting to create AD user {}".format(username)) dn = "CN={},{}".format(username, ou) logger.debug("The DN will be set to {}".format(dn)) logger.debug( f"The ldap_user_attributes will be: {json.dumps(ldap_user_attributes, default=str)}" ) try: resp = self.conn.add(dn, attributes=ldap_user_attributes) except LDAPEntryAlreadyExistsResult as e: logger.warning( "The user {} already exists in Active Directory, skipping". format(username)) ## we return True here since the user does already exist in the domain exit(1) return True except LDAPConstraintViolationResult as e: logger.error( "Unable to update account {} due to a Contstraint Violation". format(username)) return False except Exception as e: logger.error( "An unexpected error occurred while trying to create user {}. The error was: {}" .format( ldap_user_attributes.get("userPrincipalName", [])[0], str(e))) return False if not resp: logger.error("Unable to create user {}. The error was: {}".format( ldap_user_attributes.get("userPrincipalName", ""), self.conn.result)) return False return True def dump_stuff(self, search_base): total_entries = 0 self.conn.search(search_base=search_base, search_filter='(objectClass=OrganizationalUnit)', search_scope=SUBTREE, paged_size=5) total_entries += len(self.conn.response) for entry in self.conn.response: print(entry) print('Total entries retrieved:', total_entries) def add_user_to_group(self, user, group_name, AD_OU): rndUser = f"CN={user},{AD_OU}" rndGroup = f"CN={group_name},{AD_OU}" addUsersInGroups(self.conn, rndUser, rndGroup) def create_group(self, group_name, ou): """ Create a new user in Active Directory with default attributes (including a generated secure password, and account enablement) :param ou: the path in AD for the user to be created :param ldap_user_attributes: the JSON formatted user record :return: bool status of whether the creation succeeded or not """ ldap_group_attributes = { "objectClass": ["top", "group"], "cn": [group_name], "sAMAccountName": [group_name[:20] ], # we need to strip the sAMAccountName to 20 chars "name": [group_name], } logger.debug("Attempting to create AD group {}".format(group_name)) dn = "CN={},{}".format(group_name, ou) logger.debug("The DN will be set to {}".format(dn)) logger.debug( f"The ldap_group_attributes will be: {json.dumps(ldap_group_attributes, default=str)}" ) try: resp = self.conn.add(dn, attributes=ldap_group_attributes) except LDAPEntryAlreadyExistsResult as e: logger.warning( "The group {} already exists in Active Directory, skipping". format(group_name)) return True except LDAPConstraintViolationResult as e: logger.error( "Unable to update account {} due to a Contstraint Violation". format(group_name)) return False except Exception as e: logger.error( "An unexpected error occurred while trying to create group {}. The error was: {}" .format( ldap_user_attributes.get("userPrincipalName", [])[0], str(e))) return False if not resp: logger.error( f"Unable to create group {group_name}. The error was: {self.conn.result}" ) return False return True
#Print server's schema #print (server.schema) #look for all Uniperson and print them before adding conn.search('ou=people,dc=ldap,dc=secuis,dc=fun', '(objectClass=UniPerson)') #print(conn.entries) #look for all attributes of Uniperson from schema, save it in an iterable "ObjectDef" #print(person) #for test in person: # if test['mandatory': True]: print('mandatory') #add a UniPerson user with attributes mail and userPassword conn.add('cn=' + name + ',ou=people,dc=ldap,dc=secuis,dc=fun', 'UniPerson', { 'mail': email, 'userPassword': '******' }) #print(conn.result) #print() #modify a UniPerson's attributes mail and userPassword conn.modify( 'cn=' + name + ',ou=people,dc=ldap,dc=secuis,dc=fun', { 'mail': [(MODIFY_REPLACE, [email])], 'userPassword': [(MODIFY_REPLACE, ['111111'])] }) #print(conn.result) #print() #compare Uniperson's attributes userPassword pwd = '123456'
class ad : """ Simple class for interaction with active Directory using ldap """ def __init__(self): """ Initialize common variables and load config from ? what file """ def connect (self,user,password,server) : """ Create server object and connection with it, latter accessible as \"s\" and \"c\" inside \"ad\" object """ # define an unsecure LDAP server, requesting info on DSE and schema self.s = Server(server, use_ssl=True , get_info = ALL, tls=Tls(validate=ssl.CERT_NONE)) self.c = Connection(self.s, auto_bind = True, client_strategy = SYNC, user=user, \ password=password, authentication=SIMPLE, check_names=True) def disconnect (self) : """ Closes connection with current server """ try : self.c.unbind() return True except: return False def createUser(self,dn,attr) : """ Create user with dn and attr Password is stored in attr['password'] """ unicode_pass='******'+str(attr['password'])+'"' ready_pass=unicode_pass.encode('utf-16-le') del attr['password'] self.c.add(dn,['top','person','organizationalPerson','user'],attr) self.c.modify(dn,{'unicodePwd':(MODIFY_REPLACE,ready_pass)}) self.c.modify(dn,{'userAccountControl':(MODIFY_REPLACE,512)}) return True def resetPassword(self,dn,password) : """ Reset password for user with supplied dn """ unicode_pass='******'+str(password)+'"' ready_pass=unicode_pass.encode('utf-16-le') self.c.modify(dn,{'unicodePwd':(MODIFY_REPLACE,ready_pass)}) return True def searchUsers(self,attr,value,request,baseDn): """ Search users in baseDN, filtered by attr with specific value. Returns list of users and requested atributes. Attributes specified by list of names in \"'request\" """ users=[] ldapFilter='(&(objectCategory=person)(objectClass=user)('+str(attr)+'='+str(value)+'))' self.c.search(search_base=baseDn,search_filter=ldapFilter,search_scope=SUBTREE,attributes=request) for record in self.c.response : if record['type'] == 'searchResEntry' : users.append(record['attributes']) return users def searchOrgUnits(self,attr,value,request,baseDn): """ Search users in baseDN, filtered by attr with specific value. Returns list of users and requested atributes. Attributes specified by list of names in \"'request\" """ ou=[] #print('(&(objectCategory=organizationalUnit)('+attr+'='+value+'))') ldapFilter='(&(objectCategory=organizationalUnit)('+str(attr)+'='+str(value)+'))' self.c.search(search_base=baseDn,search_filter=ldapFilter,search_scope=SUBTREE,attributes=request) for record in self.c.response : #print(record) if record['type'] == 'searchResEntry' : ou.append(record['attributes']) return ou def addToGroup (self,user,group): """ Add user to group. Both specified by distinguished name """ try: self.c.modify(group,{'member':(MODIFY_ADD,user)}) return True except: return False def removeFromGroup (self,user,group): """ Remove user from group. Both specified by distinguished name """ try: self.c.modify(group,{'member':(MODIFY_DELETE,user)}) return True except: return False def modifyAttribute (self,attribute,newValue,adObject,mod=MODIFY_REPLACE): """ Modify any attribute. Replace old attribute by default """ #print(str(attribute)+" "+str(adObject)+" "+str(newValue)) try: self.c.modify(adObject,{attribute:(mod,newValue)}) print(str(attribute)+" "+str(adObject)+" "+str(newValue)) return True except: print("ooops") return False
class Test(unittest.TestCase): def setUp(self): # server = Server(host = test_server, port = test_port, allowed_referral_hosts = ('*', True)) self.connection = Connection(server=None, client_strategy=STRATEGY_LDIF_PRODUCER) self.connection.open() def tearDown(self): self.connection.unbind() self.assertFalse(self.connection.bound) def test_add_request_to_ldif(self): controls = list() controls.append(('2.16.840.1.113719.1.27.103.7', True, 'givenName')) controls.append(('2.16.840.1.113719.1.27.103.7', False, 'sn')) if str != bytes: # python3 controls.append(('2.16.840.1.113719.1.27.103.7', False, bytearray('\u00e0\u00e0', encoding='UTF-8'))) else: controls.append(('2.16.840.1.113719.1.27.103.7', False, bytearray(unicode('\xe0\xe0', encoding='latin1'), encoding='UTF-8'))) # for python2 compatability controls.append(('2.16.840.1.113719.1.27.103.7', False, 'trailingspace ')) self.connection.add(dn_for_test(test_base, 'test-add-operation'), 'iNetOrgPerson', {'objectClass': 'iNetOrgPerson', 'sn': 'test-add', test_name_attr: 'test-add-operation'}, controls=controls) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-add-operation,o=test' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 true: givenName' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 false: sn' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 false:: w6DDoA==' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 false:: dHJhaWxpbmdzcGFjZSA=' in response) self.assertTrue('changetype: add' in response) self.assertTrue('objectClass: inetorgperson' in response) self.assertTrue('sn: test-add' in response) self.assertTrue('cn: test-add-operation' in response) def test_delete_request_to_ldif(self): self.connection.strategy.order = dict(delRequest=['dn:', 'changetype', 'vers']) self.connection.delete(dn_for_test(test_base, 'test-del-operation')) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-del-operation,o=test' in response) self.assertTrue('changetype: delete' in response) def test_modify_dn_request_to_ldif(self): result = self.connection.modify_dn(dn_for_test(test_base, 'test-modify-dn-operation'), test_name_attr + '=test-modified-dn-operation') if not isinstance(result, bool): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-modify-dn-operation,o=test' in response) self.assertTrue('changetype: moddn' in response) self.assertTrue('newrdn: cn=test-modified-dn-operation' in response) self.assertTrue('deleteoldrdn: 1' in response) def test_move_dn_request_to_ldif(self): result = self.connection.modify_dn(dn_for_test(test_base, 'test-move-dn-operation'), test_name_attr + '=test-move-dn-operation', delete_old_dn=False, new_superior=test_moved) if not isinstance(result, bool): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-move-dn-operation,o=test' in response) self.assertTrue('changetype: modrdn' in response) self.assertTrue('newrdn: cn=test-move-dn-operation' in response) self.assertTrue('deleteoldrdn: 0' in response) self.assertTrue('newsuperior: ou=moved,o=test' in response) def test_modify_add_to_ldif(self): result = self.connection.modify(dn_for_test(test_base, 'test-add-for-modify'), {'givenName': (MODIFY_ADD, ['test-modified-added'])}) if not isinstance(result, bool): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-add-for-modify,o=test' in response) self.assertTrue('changetype: modify' in response) self.assertTrue('add: givenName' in response) self.assertTrue('givenName: test-modified-added' in response) self.assertEqual('-', response[-1]) def test_modify_replace_to_ldif(self): result = self.connection.modify(dn_for_test(test_base, 'test-add-for-modify'), {'givenName': (MODIFY_REPLACE, ['test-modified-replace'])}) if not isinstance(result, bool): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-add-for-modify,o=test' in response) self.assertTrue('changetype: modify' in response) self.assertTrue('replace: givenName' in response) self.assertTrue('givenName: test-modified-replace' in response) self.assertEqual('-', response[-1]) def test_modify_delete_to_ldif(self): result = self.connection.modify(dn_for_test(test_base, 'test-add-for-modify'), {'givenName': (MODIFY_DELETE, ['test-modified-added2'])}) if not isinstance(result, bool): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=test-add-for-modify,o=test' in response) self.assertTrue('changetype: modify' in response) self.assertTrue('delete: givenName' in response) self.assertTrue('givenName: test-modified-added2' in response) self.assertEqual('-', response[-1]) def test_multiple_modify_to_ldif(self): # from rfc 2849 example result = self.connection.modify('cn=Paula Jensen, ou=Product Development, dc=airius, dc=com', {'postaladdress': (MODIFY_ADD, ['123 Anystreet $ Sunnyvale, CA $ 94086']), 'description': (MODIFY_DELETE, []), 'telephonenumber': (MODIFY_REPLACE, ['+1 408 555 1234', '+1 408 555 5678']), 'facsimiletelephonenumber': (MODIFY_DELETE, ['+1 408 555 9876'])}) if not isinstance(result, bool): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=Paula Jensen, ou=Product Development, dc=airius, dc=com' in response) self.assertTrue('changetype: modify' in response) self.assertTrue('delete: facsimiletelephonenumber' in response) self.assertTrue('facsimiletelephonenumber: +1 408 555 9876' in response) self.assertTrue('replace: telephonenumber' in response) self.assertTrue('telephonenumber: +1 408 555 1234' in response) self.assertTrue('telephonenumber: +1 408 555 5678' in response) self.assertTrue('add: postaladdress' in response) self.assertTrue('postaladdress: 123 Anystreet $ Sunnyvale, CA $ 94086' in response) self.assertTrue('delete: description' in response) self.assertEqual('-', response[-1])
def ldap_conn_from_new_admin(local_salt_client): """ 1. Create a test user 'DT_test_user' in openldap 2. Add the user to admin group :return: connection to ldap with new created user Finally, delete the user from admin group and openldap """ ldap_password = get_password(local_salt_client, 'openldap:client') # Check that ldap_password is exists, otherwise skip test if not ldap_password: pytest.skip("Openldap service or openldap:client pillar \ are not found on this environment.") ldap_port = local_salt_client.pillar_get( tgt='I@openldap:client and not I@salt:master', param='_param:haproxy_openldap_bind_port', expr_form='compound') ldap_address = local_salt_client.pillar_get( tgt='I@openldap:client and not I@salt:master', param='_param:haproxy_openldap_bind_host', expr_form='compound') ldap_dc = local_salt_client.pillar_get(tgt='openldap:client', param='_param:openldap_dn') ldap_admin_name = local_salt_client.pillar_get( tgt='openldap:client', param='openldap:client:server:auth:user') ldap_admin_password = local_salt_client.pillar_get( tgt='openldap:client', param='openldap:client:server:auth:password') ldap_user_name = 'cn={0},ou=people,{1}'.format(user_name, ldap_dc) # Admins group CN admin_gr_dn = 'cn=admins,ou=groups,{0}'.format(ldap_dc) # List of attributes for test user attrs = { 'cn': user_name, 'sn': user_name, 'uid': user_name, 'userPassword': user_pass, 'objectClass': ['shadowAccount', 'inetOrgPerson'], 'description': 'Test user for CVP DT test' } logging.warning("LOCALS {}".format(locals())) ldap_server = Server(host=ldap_address, port=ldap_port, use_ssl=False, get_info='NO_INFO') admin_conn = Connection(ldap_server, user=ldap_admin_name, password=ldap_admin_password) admin_conn.bind() # Add new user new_user = admin_conn.add(ldap_user_name, 'person', attrs) if not new_user: logging.warning('new_user: {}\n error: {}'.format( new_user, admin_conn.result)) # Add him to admins group modified_user = admin_conn.modify(admin_gr_dn, {'memberUid': (MODIFY_ADD, [user_name])}) if not modified_user: logging.warning("added user to admins: {} \n error: {}".format( modified_user, admin_conn.result)) user_conn = Connection(ldap_server, user=ldap_user_name, password=user_pass) user_conn.bind() # ########################### yield user_conn # ########################### user_conn.unbind() admin_conn.modify(admin_gr_dn, {'memberUid': (MODIFY_DELETE, [user_name])}) admin_conn.delete(ldap_user_name) admin_conn.unbind()
["organization", "top"]): raise LdapError("Unable to create organization : %s" % org_id) def exists(self, org_id): return self.connection.search(search_base=self.base_org_dn, search_filter='(cn=%s)' % org_id) reg = re.compile(r"^cn=([^,]+),%s" % ORGS_DN) conn = Connection(LDAP_URI, LDAP_BINDDN, LDAP_PASSWD, auto_bind=True) conn2 = Connection(LDAP_URI, LDAP_BINDDN, LDAP_PASSWD, auto_bind=True) # Create Orgs organizational unit if needed if not conn.search(search_base=LDAP_DOMAIN, search_filter='(ou=%s)' % ORGS_BRANCH_NAME): if not conn.add(ORGS_DN, ["organizationalUnit", "top"], {"ou" : ORGS_BRANCH_NAME}): raise LdapError("Unable to create orgs organizational unit") print('Successful creation of %s entry' % ORGS_DN) conn.search(search_base=USERS_DN, search_filter='(objectClass=%s)' % USER_OBJECT_CLASS, search_scope=SUBTREE, attributes=['uid', 'cn', 'memberOf', 'o']) orgHelper = OrganizationHelper(USERS_DN, ORGS_DN, conn2) # Browsing Users for user in conn.entries: uid = user.uid.value
class Test(unittest.TestCase): def setUp(self): server = Server(host=test_server, port=test_port, allowed_referral_hosts=('*', True), get_info=test_get_info) self.connection = Connection(server, auto_bind=True, version=3, client_strategy=test_strategy, user=test_user, password=test_password, authentication=test_authentication, lazy=test_lazy_connection, pool_name='pool1', check_names=test_check_names) result = self.connection.add(dn_for_test(test_base, 'test-search-(parentheses)'), [], {'objectClass': 'iNetOrgPerson', 'sn': 'test-search-(parentheses)', 'loginGraceLimit': 10}) if not isinstance(result, bool): self.connection.get_response(result) def tearDown(self): self.connection.unbind() if self.connection.strategy_type == STRATEGY_REUSABLE_THREADED: self.connection.strategy.terminate() self.assertFalse(self.connection.bound) def test_search_exact_match(self): result = self.connection.search(search_base=test_base, search_filter='(' + test_name_attr + '=test-add-operation)', attributes=[test_name_attr, 'givenName', 'jpegPhoto']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), 1) def test_search_extensible_match(self): result = self.connection.search(search_base=test_base, search_filter='(&(o:dn:=test)(objectclass=inetOrgPerson))', attributes=[test_name_attr, 'givenName', 'sn']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 8) def test_search_present(self): result = self.connection.search(search_base=test_base, search_filter='(objectClass=*)', search_scope=SEARCH_SCOPE_WHOLE_SUBTREE, attributes=[test_name_attr, 'givenName', 'jpegPhoto']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 9) def test_search_substring_many(self): result = self.connection.search(search_base=test_base, search_filter='(sn=t*)', attributes=[test_name_attr, 'givenName', 'sn']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 8) def test_search_substring_one(self): result = self.connection.search(search_base=test_base, search_filter='(' + test_name_attr + '=*y)', attributes=[test_name_attr, 'givenName', 'sn']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 1) def test_search_raw(self): result = self.connection.search(search_base=test_base, search_filter='(' + test_name_attr + '=*)', search_scope=SEARCH_SCOPE_WHOLE_SUBTREE, attributes=[test_name_attr, 'givenName', 'photo']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertTrue(len(response) > 8) def test_search_with_operational_attributes(self): result = self.connection.search(search_base=test_base, search_filter='(' + test_name_attr + '=test-add-operation)', search_scope=SEARCH_SCOPE_WHOLE_SUBTREE, attributes=[test_name_attr, 'givenName', 'photo'], get_operational_attributes=True) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(response[0]['attributes']['entryDN'][0], dn_for_test(test_base, 'test-add-operation')) def test_search_simple_paged(self): paged_size = 1 total_entries = 0 result = self.connection.search(search_base=test_base, search_filter='(objectClass=*)', search_scope=SEARCH_SCOPE_WHOLE_SUBTREE, attributes=[test_name_attr, 'givenName'], paged_size=paged_size) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), paged_size) total_entries += len(response) cookie = result['controls']['1.2.840.113556.1.4.319']['value']['cookie'] while cookie: paged_size += 1 result = self.connection.search(search_base=test_base, search_filter='(objectClass=*)', search_scope=SEARCH_SCOPE_WHOLE_SUBTREE, attributes=[test_name_attr, 'givenName'], paged_size=paged_size, paged_cookie=cookie) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') total_entries += len(response) self.assertTrue(len(response) <= paged_size) cookie = result['controls']['1.2.840.113556.1.4.319']['value']['cookie'] self.assertTrue(total_entries > 9) def test_search_exact_match_with_parentheses_in_filter(self): result = self.connection.search(search_base=test_base, search_filter='(' + test_name_attr + '=*' + escape_bytes(')') + '*)', attributes=[test_name_attr, 'sn']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), 1) self.assertEqual(response[0]['attributes']['cn'][0], 'test-search-(parentheses)') def test_search_integer_exact_match(self): result = self.connection.search(search_base=test_base, search_filter='(loginGraceLimit=10)', attributes=[test_name_attr, 'loginGraceLimit']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), 2) def test_search_integer_less_than(self): result = self.connection.search(search_base=test_base, search_filter='(loginGraceLimit<=11)', attributes=[test_name_attr, 'loginGraceLimit']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), 2) def test_search_integer_greater_than(self): result = self.connection.search(search_base=test_base, search_filter='(loginGraceLimit>=9)', attributes=[test_name_attr, 'loginGraceLimit']) if not isinstance(result, bool): response, result = self.connection.get_response(result) else: response = self.connection.response result = self.connection.result self.assertEqual(result['description'], 'success') self.assertEqual(len(response), 2)
conn = Connection(server, 'uid=admin, cn=users, cn=accounts, dc=demo1, dc=freeipa, dc=org', 'Secret123', auto_bind=True) print(0, conn.extend.standard.who_am_i()) # print(server.schema) # print(server.info) # print(server.schema.object_classes['inetorgperson']) # print(server.schema.object_classes['organizationalperson']) # print(server.schema.object_classes['person']) # print(server.schema.object_classes['top']) # conn.search('dc=demo1, dc=freeipa, dc=org', '(&(objectclass=person)(uid=admin))', attributes=['sn', 'krbLastPwdChange', 'objectclass']) # entry = conn.entries[0] # print(entry) conn.add('ou=ldap3-tutorial, dc=demo1, dc=freeipa, dc=org', 'organizationalUnit') # conn.delete('cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org') # from ldap3.protocol.rfc4527 import pre_read_control, post_read_control # print(1, conn.last_error) # conn.add('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'Beatrix', 'sn': 'Young', 'departmentNumber': 'DEV', 'telephoneNumber': 1111}) # print(2, conn.modify('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_ADD, ['Smyth'])]}, controls=[pre_read_control('sn'), post_read_control('sn')])) # print(3, conn.result['controls']) # print(4, conn.modify('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_REPLACE, ['Smith'])]})) # print(5, conn.result) # conn.add('cn=j.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'John', 'sn': 'Smith', 'departmentNumber': 'DEV', 'telephoneNumber': 2222}) # conn.add('cn=m.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'Marianne', 'sn': 'Smith', 'departmentNumber': 'QA', 'telephoneNumber': 3333}) # conn.add('cn=quentin.cat,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'Quentin', 'sn': 'Cat', 'departmentNumber': 'CC', 'telephoneNumber': 4444}) # conn.modify_dn('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith') # conn.add('ou=moved, ou=ldap3-tutorial, dc=demo1, dc=freeipa, dc=org', 'organizationalUnit')
class Test(unittest.TestCase): def setUp(self): self.connection = Connection(server=None, client_strategy=LDIF) self.connection.open() def tearDown(self): self.connection.unbind() self.assertFalse(self.connection.bound) def test_add_request_to_ldif(self): controls = list() controls.append(('2.16.840.1.113719.1.27.103.7', True, 'givenName')) controls.append(('2.16.840.1.113719.1.27.103.7', False, 'sn')) if str != bytes: # python3 controls.append(('2.16.840.1.113719.1.27.103.7', False, bytearray('\u00e0\u00e0', encoding='UTF-8'))) else: controls.append(('2.16.840.1.113719.1.27.103.7', False, bytearray(unicode('\xe0\xe0', encoding='latin1'), encoding='UTF-8'))) # for python2 compatability controls.append(('2.16.840.1.113719.1.27.103.7', False, 'trailingspace ')) self.connection.add(generate_dn(test_base, testcase_id, 'ldif-change-1'), 'iNetOrgPerson', {'objectClass': 'iNetOrgPerson', 'sn': 'ldif-change-1', test_name_attr: 'ldif-change-1'}, controls=controls) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-1,' + test_base in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 true: givenName' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 false: sn' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 false:: w6DDoA==' in response) self.assertTrue('control: 2.16.840.1.113719.1.27.103.7 false:: dHJhaWxpbmdzcGFjZSA=' in response) self.assertTrue('changetype: add' in response) self.assertTrue('objectClass: iNetOrgPerson' in response) self.assertTrue('sn: ldif-change-1' in response) self.assertTrue(test_name_attr + ': ldif-change-1' in response) def test_delete_request_to_ldif(self): self.connection.strategy.order = dict(delRequest=['dn:', 'changetype', 'vers']) self.connection.delete(generate_dn(test_base, testcase_id, 'ldif-change-2')) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-2,' + test_base in response) self.assertTrue('changetype: delete' in response) def test_modify_dn_request_to_ldif(self): result = self.connection.modify_dn(generate_dn(test_base, testcase_id, 'ldif-change-3'), test_name_attr + '=' + testcase_id + 'ldif-change-4,' + test_base) if isinstance(result, int): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-3,' + test_base in response) self.assertTrue('changetype: moddn' in response) self.assertTrue('newrdn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-4,' + test_base in response) self.assertTrue('deleteoldrdn: 1' in response) def test_move_dn_request_to_ldif(self): result = self.connection.modify_dn(generate_dn(test_base, testcase_id, 'ldif-change-5'), test_name_attr + '=' + testcase_id + 'ldif-change-5', delete_old_dn=False, new_superior=test_moved) if isinstance(result, int): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-5,' + test_base in response) self.assertTrue('changetype: modrdn' in response) self.assertTrue('newrdn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-5' in response) self.assertTrue('deleteoldrdn: 0' in response) self.assertTrue('newsuperior: ' + test_moved in response) def test_modify_add_to_ldif(self): result = self.connection.modify(generate_dn(test_base, testcase_id, 'ldif-change-6'), {'givenName': (MODIFY_ADD, ['givenname-6-modified'])}) if isinstance(result, int): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-6,' + test_base in response) self.assertTrue('changetype: modify' in response) self.assertTrue('add: givenName' in response) self.assertTrue('givenName: givenname-6-modified' in response) self.assertEqual('-', response[-1]) def test_modify_replace_to_ldif(self): result = self.connection.modify(generate_dn(test_base, testcase_id, 'ldif-change-7'), {'givenName': (MODIFY_REPLACE, ['givenname-7-replaced'])}) if isinstance(result, int): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-7,' + test_base in response) self.assertTrue('changetype: modify' in response) self.assertTrue('replace: givenName' in response) self.assertTrue('givenName: givenname-7-replaced' in response) self.assertEqual('-', response[-1]) def test_modify_delete_to_ldif(self): result = self.connection.modify(generate_dn(test_base, testcase_id, 'ldif-change-8'), {'givenName': (MODIFY_DELETE, ['givenname-8-deleted'])}) if isinstance(result, int): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: ' + test_name_attr + '=' + testcase_id + 'ldif-change-8,' + test_base in response) self.assertTrue('changetype: modify' in response) self.assertTrue('delete: givenName' in response) self.assertTrue('givenName: givenname-8-deleted' in response) self.assertEqual('-', response[-1]) def test_multiple_modify_to_ldif(self): # from rfc 2849 example result = self.connection.modify('cn=Paula Jensen, ou=Product Development, dc=airius, dc=com', {'postaladdress': (MODIFY_ADD, ['123 Anystreet $ Sunnyvale, CA $ 94086']), 'description': (MODIFY_DELETE, []), 'telephonenumber': (MODIFY_REPLACE, ['+1 408 555 1234', '+1 408 555 5678']), 'facsimiletelephonenumber': (MODIFY_DELETE, ['+1 408 555 9876'])}) if isinstance(result, int): self.connection.get_response(result) response = self.connection.response self.assertTrue('version: 1' in response) self.assertTrue('dn: cn=Paula Jensen, ou=Product Development, dc=airius, dc=com' in response) self.assertTrue('changetype: modify' in response) self.assertTrue('delete: facsimiletelephonenumber' in response) self.assertTrue('facsimiletelephonenumber: +1 408 555 9876' in response) self.assertTrue('replace: telephonenumber' in response) self.assertTrue('telephonenumber: +1 408 555 1234' in response) self.assertTrue('telephonenumber: +1 408 555 5678' in response) self.assertTrue('add: postaladdress' in response) self.assertTrue('postaladdress: 123 Anystreet $ Sunnyvale, CA $ 94086' in response) self.assertTrue('delete: description' in response) self.assertEqual('-', response[-1])
from ldap3 import Server, Connection, ObjectDef, AttrDef, Reader, Writer, ALL, MODIFY_ADD, MODIFY_REPLACE, MODIFY_DELETE, OFFLINE_SLAPD_2_4, MOCK_SYNC # server = Server('my_fake_server', get_info=OFFLINE_SLAPD_2_4) # conn = Connection(server, 'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Secret123', client_strategy=MOCK_SYNC) # conn.strategy.add_entry('uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', {'userPassword': '******', 'sn': 'admin_sn', 'revision': 0}) server = Server('ipa.demo1.freeipa.org', get_info=ALL) conn = Connection(server, 'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Secret123') print(conn.bind()) print(1, conn.last_error) print(conn.result) conn.add('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'organizationalUnit') print(1, conn.last_error) conn.add('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'Beatrix', 'sn': 'Young', 'departmentNumber': 'DEV', 'telephoneNumber': 1111}) print(2, conn.last_error) conn.modify_dn('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith') print(3, conn.last_error) conn.add('ou=moved, ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'organizationalUnit') print(4, conn.last_error) conn.modify_dn('cn=b.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith', new_superior='ou=moved, ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org') print(5, conn.last_error) conn.modify('cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_ADD, ['Smyth'])]}) print(6, conn.last_error) conn.modify('cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_DELETE, ['Young'])]}) print(8, conn.last_error) conn.modify('cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_REPLACE, ['Smith'])]}) print(9, conn.last_error) conn.modify('cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_ADD, ['Young', 'Johnson']), (MODIFY_DELETE, ['Smith'])], 'givenname': [(MODIFY_REPLACE, ['Mary', 'Jane'])]}) print(10, conn.last_error) conn.modify_dn('cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith', new_superior='ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org') print(11, conn.last_error)
class Session: def __init__(self): self.config = config self.ldap_config = self.config['ldap'] url = urlparse(self.ldap_config.get('url')) self.server = Server(url.hostname, port=url.port, get_info=ALL) self.connection = Connection(self.server, auto_bind=True, client_strategy=SYNC, user=self.ldap_config.get('bind_dn'), password=self.ldap_config.get('passwd'), authentication=SIMPLE, check_names=True) def close(self): self.connection.unbind() def get_simple(self, search_base, attributes, search_filter=None, flaten=None): search_filter = search_filter or '(objectClass=*)' if flaten is False: flaten = [] else: flaten = flaten or ['cn', 'uid'] entries = self.connection.extend.standard.paged_search(search_base=search_base, search_filter=search_filter, search_scope=SUBTREE, attributes=attributes, paged_size=10, generator=True) results = [] for entry in entries: if len(entry['attributes']) == 0: continue result = entry['attributes'] result['dn'] = entry['dn'] for attribute_name in result: if attribute_name in flaten: result[attribute_name] = result[attribute_name][0] results.append(result) return results def get_groups(self, attributes=None): attributes = attributes or ['cn', 'gidNumber', 'description'] return self.get_simple(search_base=self.ldap_config.get('group_ou'), attributes=attributes) def get_users(self, attributes=None): attributes = attributes or ['uid', 'cn', 'mail'] casual = list(attributes) casual.remove('groups') users = self.get_simple(search_base=self.ldap_config.get('user_ou'), attributes=casual) if 'groups' in attributes: for user in users: user['groups'] = [group['cn'] for group in self.get_user_groups(uid=user['uid'])] return users def get_user_dn(self, uid): return 'uid={uid},{user_ou}'.format(user_ou=self.ldap_config.get('user_ou'), uid=uid) def get_group_dn(self, cn): return 'cn={cn},{group_ou}'.format(group_ou=self.ldap_config.get('group_ou'), cn=cn) def get_user_groups(self, uid, attributes=None): user_dn = self.get_user_dn(uid) groups = self.get_simple( search_base=self.ldap_config.get('group_ou'), search_filter='(|(member={dn})(memberUid={uid}))'.format(dn=user_dn, uid=uid), attributes=attributes or ['cn']) return groups def add_user(self, username, common_name, email=None): attributes = {'cn': common_name, 'sn': common_name.split()[-1], 'mail': email} self.connection.add( dn="uid={username},{user_ou}".format(username=username, user_ou=self.ldap_config.get('user_ou')), object_class=['inetOrgPerson', 'organizationalPerson', 'person', 'top'], attributes=attributes) self.upgrade_user_schema(username=username) def get_top_uid(self): users = self.get_simple(search_base=self.ldap_config.get('user_ou'), attributes=['uidNumber']) return max([user['uidNumber'] for user in users]) def _migration0(self, username): user_dn = self.get_user_dn(uid=username) self.connection.modify( dn=user_dn, changes={ 'objectClass': [(MODIFY_ADD, ['posixAccount'])], 'loginShell': [(MODIFY_ADD, ['/bin/bash'])], 'homeDirectory': [(MODIFY_ADD, ['/home/{}'.format(username)])], 'gidNumber': [(MODIFY_ADD, ['1999'])], 'uidNumber': [(MODIFY_ADD, [self.get_top_uid() + 1])], }) def upgrade_user_schema(self, username): self._migration0(username=username) def delete_user(self, username): user_dn = self.get_user_dn(uid=username) groups = self.get_user_groups(uid=username) for group in groups: self.delete_from_group(username=username, group=group['cn']) self.connection.delete(user_dn) def change_password(self, username, password): self.connection.extend.standard.modify_password(user=self.get_user_dn(uid=username), new_password=password) def add_to_group(self, username, group): user_dn = self.get_user_dn(uid=username) group_dn = self.get_group_dn(cn=group) self.connection.modify(dn=group_dn, changes={'member': [(MODIFY_ADD, [user_dn])]}) def delete_from_group(self, username, group): user_dn = self.get_user_dn(uid=username) group_dn = self.get_group_dn(cn=group) self.connection.modify(dn=group_dn, changes={'member': [(MODIFY_DELETE, [user_dn])]}) def add_to_groups(self, username, groups): for group in groups: self.add_to_group(username=username, group=group) def delete_from_groups(self, username, groups): for group in groups: self.delete_from_group(username=username, group=group) def activate(self, username): self.add_to_group(username=username, group='members') def deactivate(self, username): self.delete_from_group(username=username, group='members')