def _checkAndRemoveInLdap(reg, uid): ''' chequea si existe un usuario y lo elimina obtiene el password y lo retorna ''' logging.info('Conectandose al server ldap en {}'.format(reg.get('ldap_server'))) from ldap3 import Server, Connection, ALL_ATTRIBUTES s = Server(reg.get('ldap_server')) conn = Connection(s, user=reg.get('ldap_user'), password=reg.get('ldap_password')) conn.bind() try: logging.info('buscando usuario con uid = {}'.format(uid)) if not conn.search('ou=people,dc=econo', '(uid={})'.format(uid, uid), attributes=ALL_ATTRIBUTES): return None userPassword = None for e in conn.entries: logging.info(e.entry_get_dn()) if 'userPassword' in e: userPassword = e.userPassword logging.info('Elimando : {}'.format(e.entry_get_dn())) conn.delete(e.entry_get_dn()) return userPassword finally: conn.unbind()
def _remove_all_in(connection: Connection, dn: str): try: connection.search(dn, '(objectClass=*)', search_scope=LEVEL) except LDAPNoSuchObjectResult: return for entry in connection.entries: connection.delete(entry.entry_dn)
def delete_user(cn): try: # 连接服务器 c = Connection(server, auto_bind=True, user="******"+username, password=password) c.delete(cn) return c.result except Exception: return None
def ldap_del(self, cnoudel): """ ldap_del ; shan chu ldap shu ju :param cnoudel: :return: """ server = Server(LDAP_IP, port=LDAP_port, get_info=ALL) conn = Connection(server, user=LDAP_user, password=LDAP_pwd) conn.open() conn.bind() conn.delete(cnoudel + "," + ADMIN_DN) conn.unbind()
def create_user(self, AD_SERVER, OU_BASE, DOMAIN, BIND_USERNAME, BIND_PASSWORD, NEW_USER, NEW_USER_PASWORD, DESCRIPTION): tlsconfig = Tls(validate=ssl.CERT_NONE) server = Server(AD_SERVER, port=636, use_ssl=True, tls=tlsconfig, get_info=ALL) print(AD_SERVER, OU_BASE, DOMAIN, BIND_USERNAME, BIND_PASSWORD, NEW_USER) try: conn = Connection(server, BIND_USERNAME, BIND_PASSWORD) conn.start_tls() if not conn.bind(): return 'Bind ERROR {}'.format(conn.result) temp_user_dn = 'cn=' + NEW_USER + ',' + OU_BASE print(temp_user_dn) conn.add( temp_user_dn, 'user', { 'givenName': NEW_USER, 'description': 'Create Time : ' + time.ctime(), 'company': DESCRIPTION, 'samaccountname': NEW_USER, 'userPrincipalName': NEW_USER + '@' + DOMAIN }) if conn.result['result'] != 0: conn.unbind return conn.result print(conn.result) conn.extend.microsoft.modify_password(temp_user_dn, NEW_USER_PASWORD) if conn.result['result'] != 0: conn.delete(temp_user_dn) conn.unbind return conn.result print(conn.result) conn.modify(temp_user_dn, {'userAccountControl': [(MODIFY_REPLACE, ['512'])]}) except Exception as e: return 'Create User Error : {}'.format(e) conn.unbind() return conn.result
def delete_user(username): try: # 连接服务器 c = Connection(server, auto_bind=True, user="******" + ad_admin_username, password=ad_admin_password) c.delete(get_user_info(username).get('dn')) return True except Exception: return False
def ldap_connection(self): total_entries = 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', auto_bind=True) conn.search('dc=demo1,dc=freeipa,dc=org', '(objectclass=person)') search=conn.entries print("data",search) conn.add('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'organizationalUnit') # Add a new user conn.add('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'Beatrix', 'sn': 'Young', 'departmentNumber': 'DEV', 'telephoneNumber': 1111}) detail=server.schema.object_classes['inetOrgPerson'] conn.search('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', '(cn=b.young)', attributes=['objectclass', 'sn', 'cn', 'givenname']) add=conn.entries[0] print(add) conn.modify_dn('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith') conn.search('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', '(cn=b.smith)', attributes=['objectclass', 'sn', 'cn', 'givenname']) modify=conn.entries[0] print(modify) total_entries += len(conn.response) for entry in conn.response: print(entry['dn'], entry['attributes']) # perform a Delete operation message_id = conn.delete('cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org') # perform the Abandon operation delete= conn.abandon(message_id)
def delAll(): try: ldap_uri = "ldaps://{}".format(host) print('Connecting to ', host) server = Server(ldap_uri, use_ssl=True) conn = Connection(server, DN, password) conn.bind() conn.search(search_base = 'o=gluu', search_filter = '(&(cn=*)(!(uid=admin)))', search_scope = SUBTREE, attributes=ALL_ATTRIBUTES) for entry in conn.response: print(entry['dn']) conn.delete(entry['dn']) conn.unbind() except: print("Cannot connect to host.")
def delete(self): result = False dn = Config.LDAP_DN.replace(Config.LDAP_UID, self._uid) server = Server(Config.LDAP, use_ssl=True, get_info=ALL) try: conn = Connection(server=server, user=Config.LDAP_ADMIN, password=Config.LDAP_ADMIN_PASSWORD, auto_bind=True, receive_timeout=30) result = conn.delete(dn) conn.unbind() except Exception as e: logger.error(e) return result
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 OptLdap(): """ AD中的用户与组织单位操作 """ def __init__(self): """ 连接初始化 """ self.connect = Connection( # 配置服务器连接参数 server=AD_SERVER_POOL, auto_bind=True, authentication=NTLM, # 连接Windows AD 使用NTLM方式认证 user=SERVER_USER, password=SERVER_PASSWORD, ) ldap_logger.info("连接AD域服务器 %s", self.connect) self.leaved_base_dn = 'OU=LEAVED,DC=sh,DC=hupu,DC=com' # 离职账户的OU self.active_base_dn = 'OU=HUPU,DC=sh,DC=hupu,DC=com' # 在职账户的OU self.all_base_dn = 'DC=sh,DC=hupu,DC=com' # 所有用户的OU self.user_search_filter = '(objectclass=user)' # 只获取用户对象 self.ou_search_filter = '(objectclass=organizationalUnit)' # 只获取OU对象 self.attributes_ou = ['Name', 'ObjectGUID'] self.attributes_user = ['name', 'memberOf', 'sAMAccountName', 'badPwdCount', 'displayName', 'mail', 'userAccountControl', 'userPrincipalName', 'telephoneNumber'] def get_users(self, get_type='active'): """ 获取用户信息 """ if get_type == 'all': self.connect.search(search_base=self.all_base_dn, search_filter=self.user_search_filter, attributes=self.attributes_user) elif get_type == 'leaved': self.connect.search(search_base=self.leaved_base_dn, search_filter=self.user_search_filter, attributes=self.attributes_user) else: self.connect.search(search_base=self.active_base_dn, search_filter=self.user_search_filter, attributes=self.attributes_user) res = json.loads(self.connect.response_to_json())['entries'] ldap_logger.info("获取所有用户信息 %s - %s", get_type, self.connect.result) return res def get_obj_info(self, filter_key=None, filter_value=None, filter_all=None, attr=None): """ 根据自定义filter获取用户信息 """ if filter_all: search_filter = filter_all else: search_filter = "(" + filter_key + "=" + filter_value + ")" res = [] attr = attr if attr else ALL_ATTRIBUTES try: self.connect.search(search_base=self.all_base_dn, search_filter=search_filter, attributes=attr) res = json.loads(self.connect.response_to_json())['entries'] except exceptions.LDAPException as ept: ldap_logger.error("获取自定义用户信息失败 %s", ept) raise # finally: # self.connect.unbind() ldap_logger.info("获取自定义用户信息 %s - %s", search_filter, self.connect.result) return res def get_ous(self): """ 获取OU信息 """ self.connect.search(search_base=self.active_base_dn, search_filter=self.ou_search_filter, attributes=self.attributes_ou) res = json.loads(self.connect.response_to_json())['entries'] ldap_logger.info("获取所有OU信息 %s - %s", self.active_base_dn, self.connect.result) return res def del_obj(self, dn): # pylint: disable=invalid-name """ 删除用户 or 部门 :param dn: 'CN=张三,OU=IT组,OU=企业信息化部,OU=虎扑,DC=sh,DC=hupu,DC=com' or 'OU=IT组,OU=企业信息化部,OU=虎扑,DC=sh,DC=hupu,DC=com' :return True/False """ res = self.connect.delete(dn=dn) ldap_logger.info("用户信息 %s - %s", dn, res) return res, self.connect.result def create_obj(self, dn, obj_type, pwd="Abcd.1234", attr=None): # pylint: disable=invalid-name """ 新增用户 or OU :param DN: ou - "OU=IT组,OU=企业信息化部,OU=虎扑,DC=sh,DC=hupu,DC=com" or user - "CN=张三,OU=IT组,OU=企业信息化部,OU=虎扑,DC=sh,DC=hupu,DC=com" :param obj_type: user or ou :param attr: user - {"sAMAccountName": "zhangsan", "Sn": "张", "name":"张三", "UserPrincipalName": "*****@*****.**", "Mail": "*****@*****.**", "Displayname": "张三"} ou - {"name": "IT组"} :return : {"status": True/False, "msg": {}} """ res = False ldap_logger.info("创建AD域Object %s - %s_attr:%s", dn, obj_type, attr) object_class = {'user': ['top', 'person', 'organizationalPerson', 'user'], 'ou': ['top', 'organizationalUnit']} try: res = self.connect.add(dn=dn, object_class=object_class[obj_type], attributes=attr) ldap_logger.info("创建Object结果 %s - %s - %s", dn, self.connect.result, res) msg = self.connect.result if obj_type == 'user': # 如果是用户,需要设置密码、激活账号 self.connect.extend.microsoft.modify_password(dn, pwd) # 设置密码 self.connect.modify(dn, {'userAccountControl': [(MODIFY_REPLACE, 512)]}) # 设置账号状态,激活用户 except exceptions.LDAPException as ept: ldap_logger.error("Object信息 %s - %s - %s", dn, self.connect, ept) msg = "Ldap新增Object操作失败,详细信息查看Log" # finally: # self.connect.unbind() return res, msg def update_obj(self, dn, attr=None): # pylint: disable=invalid-name """ 更新user or OU 只允许OU更新name,user不能更新 ["name","sAMAccountName", "userPrincipalName", "displayname"] OU or USER都可以移动 :param dn: 需要修改的完整DN :param attr: 需要更新的属性值,字典形式 :return {"status: True/False, "msg": {'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'modDNResponse'}} """ changes_dic = {} ldap_logger.info("更新Object的信息 %s - %s", dn, attr) for k, v in attr.items(): # pylint: disable=invalid-name if not self.compare_attr(dn=dn, attr=k, value=v): ldap_logger.info("对比Object信息结果 %s - %s:%s - %s", dn, k, v, self.connect.result) if k == "name": # 修改name值只允许OU修改,不允许修改CN的name res = self._rename_obj(dn=dn, newname=attr['name']) if res['description'] == 'success': if dn[:2] == "OU": dn = "OU=%s,%s" % (attr["name"], dn.split(",", 1)[1]) else: return {"status": False, "msg": "不支持的DN " + dn} elif k == "DistinguishedName": res = self._move_object(dn=dn, new_dn=v) # 调用移动User or OU 的方法 if res['description'] == 'success': if dn[:2] == "CN": dn = "%s" % (attr["DistinguishedName"]) if dn[:2] == "OU": dn = "%s" % (attr["DistinguishedName"]) elif k in ["sAMAccountName", "userPrincipalName", "displayname"]: return {"status": False, "msg": "不支持的属性 " + k} else: changes_dic.update({k:[(MODIFY_REPLACE, [v])]}) self.connect.modify(dn=dn, changes=changes_dic) return {"status": True, "msg": self.connect.result} def _rename_obj(self, dn, newname): # pylint: disable=invalid-name """ OU or User 重命名方法 :param dn:需要修改的object的完整dn路径 :param newname: 新的名字 :return:返回中有:'description': 'success', 表示操作成功 {'result': 0, 'description': 'success', 'dn': '', 'message': '', 'referrals': None, 'type': 'modDNResponse'} """ cn_ou = dn.split("=", 1) newname = cn_ou[0] + "=" + newname res = self.connect.modify_dn(dn, newname) ldap_logger.info("Remove-Object-Info %s - %s - %s", dn, self.connect.result, res) return self.connect.result def _move_object(self, dn, new_dn): # pylint: disable=invalid-name """移动员工 or 部门到新部门""" relative_dn, superou = new_dn.split(",", 1) res = self.connect.modify_dn(dn=dn, relative_dn=relative_dn, new_superior=superou) ldap_logger.info("Move-Object-Info %s - %s - %s", dn, self.connect.result, res) return self.connect.result def leaved_user(self, dn): # pylint: disable=invalid-name """ 处理离职的用户,离职用户放置在离职OU里,账号禁用 """ res = self.connect.modify(dn, {'userAccountControl': [(MODIFY_REPLACE, 514)]}) # 设置账号状态,禁用账号 ldap_logger.info("Leaved-User %s - Disable - %s", dn, res) if res: new_dn = dn.split(",")[0] + "," + self.leaved_base_dn res = self._move_object(dn=dn, new_dn=new_dn) return res def compare_attr(self, dn, attr, value): # pylint: disable=invalid-name """比较员工指定的某个属性 """ try: res = self.connect.compare(dn=dn, attribute=attr, value=value) ldap_logger.info("Commpare-Object-Info %s - %s:%s - %s", dn, attr, value, res) except exceptions.LDAPException as ept: ldap_logger.error("Commpare-Object-Info-Expception %s - %s - %s", dn, self.connect, ept) res = False return res def reset_password(self, dn, new_pwd): # pylint: disable=invalid-name """ 重置密码, 不需要原密码 """ res = self.connect.extend.microsoft.modify_password(dn, new_pwd) ldap_logger.info("Reset-Password %s - %s", dn, self.connect.result) return res, self.connect.result
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 }
# define the server s = Server(host='yourhostname.com', port=389, use_ssl=False, get_info='ALL') # define the connection c = Connection(s, user='******', password='******', version=3, authentication='SIMPLE', client_strategy='SYNC', read_only=False, raise_exceptions=True) if not c.bind(): print('error in binding', c.result) else: print('Bind is successful!!') result = c.delete('c=au,dc=yourhostname,dc=com') if not result: print("Failed to delete") else: print("Successfully deleted") c.unbind() print("Unbinded successful!!")
def main(): parser = argparse.ArgumentParser( description= 'Query/modify DNS records for Active Directory integrated DNS via LDAP' ) parser._optionals.title = "Main options" parser._positionals.title = "Required options" #Main parameters #maingroup = parser.add_argument_group("Main options") parser.add_argument( "host", type=str, metavar='HOSTNAME', help="Hostname/ip or ldap://host:port connection string to connect to") parser.add_argument("-u", "--user", type=str, metavar='USERNAME', help="DOMAIN\\username for authentication.") parser.add_argument( "-p", "--password", type=str, metavar='PASSWORD', help="Password or LM:NTLM hash, will prompt if not specified") parser.add_argument( "--forest", action='store_true', help="Search the ForestDnsZones instead of DomainDnsZones") parser.add_argument( "--legacy", action='store_true', help="Search the System partition (legacy DNS storage)") parser.add_argument( "--zone", help="Zone to search in (if different than the current domain)") parser.add_argument( "--print-zones", action='store_true', help= "Only query all zones on the DNS server, no other modifications are made" ) parser.add_argument("--tcp", action='store_true', help="use DNS over TCP") recordopts = parser.add_argument_group("Record options") recordopts.add_argument("-r", "--record", type=str, metavar='TARGETRECORD', help="Record to target (FQDN)") recordopts.add_argument( "-a", "--action", choices=[ 'add', 'modify', 'query', 'remove', 'resurrect', 'ldapdelete' ], default='query', help="Action to perform. Options: add (add a new record), modify (" "modify an existing record), query (show existing), remove (mark record " "for cleanup from DNS cache), delete (delete from LDAP). Default: query" ) recordopts.add_argument( "-t", "--type", choices=['A'], default='A', help="Record type to add (Currently only A records supported)") recordopts.add_argument("-d", "--data", metavar='RECORDDATA', help="Record data (IP address)") recordopts.add_argument("--allow-multiple", action='store_true', help="Allow multiple A records for the same name") recordopts.add_argument("--ttl", type=int, default=180, help="TTL for record (default: 180)") args = parser.parse_args() #Prompt for password if not set authentication = None if args.user is not None: authentication = NTLM if not '\\' in args.user: print_f('Username must include a domain, use: DOMAIN\\username') sys.exit(1) if args.password is None: args.password = getpass.getpass() # define the server and the connection s = Server(args.host, get_info=ALL) print_m('Connecting to host...') c = Connection(s, user=args.user, password=args.password, authentication=authentication) print_m('Binding to host') # perform the Bind operation if not c.bind(): print_f('Could not bind with specified credentials') print_f(c.result) sys.exit(1) print_o('Bind OK') domainroot = s.info.other['defaultNamingContext'][0] forestroot = s.info.other['rootDomainNamingContext'][0] if args.forest: dnsroot = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % forestroot else: if args.legacy: dnsroot = 'CN=MicrosoftDNS,CN=System,%s' % domainroot else: dnsroot = 'CN=MicrosoftDNS,DC=DomainDnsZones,%s' % domainroot if args.print_zones: zones = get_dns_zones(c, dnsroot) if len(zones) > 0: print_m('Found %d domain DNS zones:' % len(zones)) for zone in zones: print(' %s' % zone) forestdns = 'CN=MicrosoftDNS,DC=ForestDnsZones,%s' % s.info.other[ 'rootDomainNamingContext'][0] zones = get_dns_zones(c, forestdns) if len(zones) > 0: print_m('Found %d forest DNS zones:' % len(zones)) for zone in zones: print(' %s' % zone) return target = args.record if args.zone: zone = args.zone else: # Default to current domain zone = ldap2domain(domainroot) if not target: print_f('You need to specify a target record') return if target.lower().endswith(zone.lower()): target = target[:-(len(zone) + 1)] searchtarget = 'DC=%s,%s' % (zone, dnsroot) # print s.info.naming_contexts c.search(searchtarget, '(&(objectClass=dnsNode)(name=%s))' % ldap3.utils.conv.escape_filter_chars(target), attributes=['dnsRecord', 'dNSTombstoned', 'name']) targetentry = None for entry in c.response: if entry['type'] != 'searchResEntry': continue targetentry = entry # Check if we have the required data if args.action in ['add', 'modify', 'remove'] and not args.data: print_f( 'This operation requires you to specify record data with --data') return # Check if we need the target record to exists, and if yes if it does if args.action in ['modify', 'remove', 'ldapdelete', 'resurrect', 'query' ] and not targetentry: print_f('Target record not found!') return if args.action == 'query': print_o('Found record %s' % targetentry['attributes']['name']) for record in targetentry['raw_attributes']['dnsRecord']: dr = DNS_RECORD(record) # dr.dump() print(targetentry['dn']) print_record(dr, targetentry['attributes']['dNSTombstoned']) continue elif args.action == 'add': # Only A records for now addtype = 1 # Entry exists if targetentry: if not args.allow_multiple: for record in targetentry['raw_attributes']['dnsRecord']: dr = DNS_RECORD(record) if dr['Type'] == 1: address = DNS_RPC_RECORD_A(dr['Data']) print_f( 'Record already exists and points to %s. Use --action modify to overwrite or --allow-multiple to override this' % address.formatCanonical()) return False # If we are here, no A records exists yet record = new_record(addtype, get_next_serial(args.host, zone, args.tcp)) record['Data'] = DNS_RPC_RECORD_A() record['Data'].fromCanonical(args.data) print_m('Adding extra record') c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_ADD, record.getData())]}) print_operation_result(c.result) else: node_data = { # Schema is in the root domain (take if from schemaNamingContext to be sure) 'objectCategory': 'CN=Dns-Node,%s' % s.info.other['schemaNamingContext'][0], 'dNSTombstoned': False, 'name': target } record = new_record(addtype, get_next_serial(args.host, zone, args.tcp)) record['Data'] = DNS_RPC_RECORD_A() record['Data'].fromCanonical(args.data) record_dn = 'DC=%s,%s' % (target, searchtarget) node_data['dnsRecord'] = [record.getData()] print_m('Adding new record') c.add(record_dn, ['top', 'dnsNode'], node_data) print_operation_result(c.result) elif args.action == 'modify': # Only A records for now addtype = 1 # We already know the entry exists targetrecord = None records = [] for record in targetentry['raw_attributes']['dnsRecord']: dr = DNS_RECORD(record) if dr['Type'] == 1: targetrecord = dr else: records.append(record) if not targetrecord: print_f('No A record exists yet. Use --action add to add it') targetrecord['Serial'] = get_next_serial(args.host, zone, args.tcp) targetrecord['Data'] = DNS_RPC_RECORD_A() targetrecord['Data'].fromCanonical(args.data) records.append(targetrecord.getData()) print_m('Modifying record') c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_REPLACE, records)]}) print_operation_result(c.result) elif args.action == 'remove': addtype = 0 if len(targetentry['raw_attributes']['dnsRecord']) > 1: print_m('Target has multiple records, removing the one specified') targetrecord = None for record in targetentry['raw_attributes']['dnsRecord']: dr = DNS_RECORD(record) if dr['Type'] == 1: tr = DNS_RPC_RECORD_A(dr['Data']) if tr.formatCanonical() == args.data: targetrecord = record if not targetrecord: print_f('Could not find a record with the specified data') return c.modify(targetentry['dn'], {'dnsRecord': [(MODIFY_DELETE, targetrecord)]}) print_operation_result(c.result) else: print_m('Target has only one record, tombstoning it') diff = datetime.datetime.today() - datetime.datetime(1601, 1, 1) tstime = int(diff.total_seconds() * 10000) # Add a null record record = new_record(addtype, get_next_serial(args.host, zone, args.tcp)) record['Data'] = DNS_RPC_RECORD_TS() record['Data']['entombedTime'] = tstime c.modify( targetentry['dn'], { 'dnsRecord': [(MODIFY_REPLACE, [record.getData()])], 'dNSTombstoned': [(MODIFY_REPLACE, True)] }) print_operation_result(c.result) elif args.action == 'ldapdelete': print_m('Deleting record over LDAP') c.delete(targetentry['dn']) print_operation_result(c.result) elif args.action == 'resurrect': addtype = 0 if len(targetentry['raw_attributes']['dnsRecord']) > 1: print_m( 'Target has multiple records, I dont know how to handle this.' ) return else: print_m('Target has only one record, resurrecting it') diff = datetime.datetime.today() - datetime.datetime(1601, 1, 1) tstime = int(diff.total_seconds() * 10000) # Add a null record record = new_record(addtype, get_next_serial(args.host, zone, args.tcp)) record['Data'] = DNS_RPC_RECORD_TS() record['Data']['entombedTime'] = tstime c.modify( targetentry['dn'], { 'dnsRecord': [(MODIFY_REPLACE, [record.getData()])], 'dNSTombstoned': [(MODIFY_REPLACE, False)] }) print_o( 'Record resurrected. You will need to (re)add the record with the IP address.' )
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 LibLDAP(object): base_dn = "ou=People,dc=gonzalonazareno,dc=org" group_dn = "ou=Group,dc=gonzalonazareno,dc=org" conn = "" isbind = False grupos = { 'asir1': '1º ASIR', 'asir2': '2º ASIR', 'smr1': '1º SMR', 'smr2': '2º SMR', 'antiguosalumnos': 'Otros', 'profesores': 'Profesor', 'antiguosprofesores': 'A.P.', 'openstackusers': 'OpenStack', 'tituladosasir': 'Titulados ASIR', 'tituladossmr': 'Titulados SMR', } def __init__(self, username="", password=""): try: server = Server('ldap.gonzalonazareno.org') except: print("Error al conectar al servidor") try: if username != "": username = "******" % username self.conn = Connection(server, username, password, auto_bind=True) else: self.conn = Connection(server, auto_bind=True) self.isbind = True except: print("Error al realizar la conexión") def buscar(self, filter, attr=[], base_dn=base_dn): self.conn.search(base_dn, filter, search_scope=SUBTREE, attributes=attr) return [e.entry_attributes_as_dict for e in self.conn.entries] def memberOfGroup(self, uid, key=False): lista = [] usuario = "uid=%s,%s" % (uid, self.base_dn) grupos = self.buscar("(cn=*)", ['cn', 'member'], self.group_dn) for grupo in grupos: if usuario in grupo["member"]: if key: lista.append(grupo["cn"][0]) else: lista.append(self.grupos[grupo["cn"][0]]) return lista def isMemberOfGroup(self, uid, grupo): return grupo in self.memberOfGroup(uid, key=True) def isMemberOfGroups(self, uid, grupos=[]): for grupo in grupos: if grupo in self.memberOfGroup(uid, key=True): return True return False def conv_filtro(self, filtro): cadena = "" if len(filtro) > 0: cadena = "(&(objectClass=inetOrgPerson)" for campo, valor in filtro.items(): if campo == "grupo" and valor == 'all': cadena += "(uid=*)" elif campo == "grupo": grupos = [valor] if valor == "alumnos": grupos = [ "asir1", "asir2", "smr1", "smr2", "antiguosalumnos" ] elif valor == "soloalumnos": grupos = ["asir1", "asir2", "smr1", "smr2"] elif valor == "allprofesores": grupos = ["profesores", "antiguosprofesores"] elif valor == "alltitulados": grupos = ["tituladosasir", "tituladossmr"] cadena2 = "(|" for grupos in grupos: cadena2 += "(memberOf=cn=%s,ou=Group,dc=gonzalonazareno,dc=org)" % grupos cadena2 += ")" cadena += cadena2 else: cadena += "(%s=%s*)" % (campo, valor) cadena += ")" return cadena def logout(self): self.conn.unbind() self.isbind = False def add(self, uid, datos): usuario = "uid=%s,%s" % (uid, self.base_dn) self.conn.add(usuario, attributes=datos) def delete(self, uid): usuario = "uid=%s,%s" % (uid, self.base_dn) self.conn.delete(usuario) def modify(self, uid, datos): usuario = "uid=%s,%s" % (uid, self.base_dn) for c, v in datos.items(): datos[c] = [(MODIFY_REPLACE, [v])] self.conn.modify(usuario, datos) def modUserGroup(self, uid, grupo, adddel): modlist = [] usuario = "uid=%s,%s" % (uid, self.base_dn) grupo = "cn=%s,%s" % (grupo, self.group_dn) if adddel == "add": return self.conn.modify(grupo, {'member': [(MODIFY_ADD, [usuario])]}) elif adddel == "del": return self.conn.modify(grupo, {'member': [(MODIFY_DELETE, [usuario])]})
class AD(object): '''AD域的操作 ''' def __init__(self): '''初始化加载日志配置 AD域连接 AD基础信息加载 ''' # 初始化加载日志配置 self.__setup_logging(path=LOG_CONF) SERVER = Server( host=LDAP_IP, port=636, # 636安全端口 use_ssl=True, get_info=ALL, connect_timeout=5) # 连接超时为5秒 try: self.conn = Connection( server=SERVER, user=USER, password=PASSWORD, auto_bind=True, read_only=False, # 禁止修改数据True receive_timeout=10) # 10秒内没返回消息则触发超时异常 logging.info("distinguishedName:%s res: %s" % (USER, self.conn.bind())) except BaseException as e: logging.error("AD域连接失败,请检查IP/账户/密码") finally: self.conn.closed def __setup_logging(self, path=LOG_CONF, default_level=logging.INFO, env_key="LOG_CFG"): value = os.getenv(env_key, None) if value: path = value if os.path.exists(path): with open(path, "r") as f: config = yaml.safe_load(f) logging.config.dictConfig(config) else: logging.basicConfig(level=default_level) def get_users(self, attr=ALL_ATTRIBUTES): ''' @param {type} @return: total_entries 此次查询到的记录数目 @msg: 获取所有用户 ''' entry_list = self.conn.extend.standard.paged_search( search_base=ENABLED_BASE_DN, search_filter=USER_SEARCH_FILTER, search_scope=SUBTREE, attributes=attr, paged_size=5, generator=False) # 关闭生成器,结果为列表 total_entries = 0 for entry in entry_list: total_entries += 1 logging.info("共查询到记录条目: " + str(total_entries)) return entry_list def get_ous(self, attr=None): ''' @param {type} @return: res所有OU @msg: 获取所有OU ''' self.conn.search(search_base=ENABLED_BASE_DN, search_filter=OU_SEARCH_FILTER, attributes=attr) result = self.conn.response_to_json() res_list = json.loads(result)['entries'] return res_list[::-1] def get_level_users(self, SEARCH_BASE, attr=ALL_ATTRIBUTES): ''' @param {type} @return: total_entries 此次查询到的记录数目 @msg: 获取某OU下用户 ''' entry_list = self.conn.extend.standard.paged_search( search_base=SEARCH_BASE, search_filter=USER_SEARCH_FILTER, search_scope=LEVEL, attributes=attr, paged_size=5, generator=False) # 关闭生成器,结果为列表 total_entries = 0 for entry in entry_list: total_entries += 1 logging.info("共查询到记录条目: " + str(total_entries)) return entry_list def handle_excel(self, path): ''' @param path{string} excel文件绝对路径 @return: result: { 'page_flag': True, 'person_list': [[], [], ...] } @msg: 表格文件预处理 1.增加行列数判————行数决定AD的查询是否分页,列数用以判断必须列数据完整性与补充列; 2.判断必须列【工号|姓名|部门】是否存在且是否有空值 3.人员列表的使用sort函数排序key用lambda函数,排序条件(i[2].count('.'), i[2], i[0])为(部门层级、部门名称、工号) ''' try: # 1.开始源文件格式扫描 df = pd.read_excel(path) # 读取源文件 a, b = df.shape # 表格行列数 cols = df.columns.tolist() # 表格列名列表 is_ex_null = df.isnull().any().tolist() # 列是否存在空值 dic = dict(zip(cols, is_ex_null)) # 存在空值的列 if int("工号" in cols) + int("姓名" in cols) + int( "部门" in cols) < 3: # 判断必须列是否都存在 logging.error( "表格缺少必要列【工号|姓名|部门】请选择正确的源文件;或者将相应列列名修改为【工号|姓名|部门】") exit() elif int(dic["工号"]) + int(dic["姓名"]) + int( dic["部门"]) > 0: # 判断必须列是否有空值 logging.error("必要列存在空值记录,请检查补全后重试:" + '\n' + str(df[df.isnull().values == True])) else: df = pd.read_excel(path, usecols=[i for i in range(0, b)]) use_cols = ["工号", "姓名", "部门"] # 使用的必须列 for c in ["邮件", "电话", "岗位"]: # 扩展列的列名在这里添加即可 if c in cols: use_cols.append(c) df = df[use_cols] # 调整df使用列顺序 person_list = df.values.tolist() # df数据框转list person_list.sort(key=lambda i: (i[2].count('.'), i[2], i[0]), reverse=False) # 多条件排序 # 2.开始处理列表 for i, row in enumerate(person_list): job_id, name, depart = row[0:3] # 将部门列替换成DN row[2] = 'CN=' + str( name + str(job_id)) + ',' + 'OU=' + ',OU='.join( row[2].split('.')[::-1]) + ',' + ENABLED_BASE_DN row.append(CUSTOM_SAMA + str(job_id).zfill(6) ) # 增加登录名列,对应AD域user的 sAMAccountname 属性 row.append(name + str(job_id)) # 增加CN列,对应user的 cn 属性 # 3.开始处理返回字典 result_dic = dict() # 返回字典 if a > 1000: result_dic['page_flag'] = True else: result_dic['page_flag'] = False result_dic['person_list'] = person_list return result_dic except Exception as e: logging.error(e) return None def generate_pwd(self, count): ''' @param count{int} 所需密码长度 @return: pwd: 生成的随机密码 @msg: 生成随机密码,必有数字、大小写、特殊字符且数目伪均等; ''' pwd_list = [] a, b = count // 4, count % 4 # 四种类别先均分除数个字符 pwd_list.extend(random.sample(string.digits, a)) pwd_list.extend(random.sample(string.ascii_lowercase, a)) pwd_list.extend(random.sample(string.ascii_uppercase, a)) pwd_list.extend(random.sample('!@#$%^&*()', a)) # 从四种类别中再取余数个字符 pwd_list.extend( random.sample( string.digits + string.ascii_lowercase + string.ascii_uppercase + '!@#$%^&*()', b)) random.shuffle(pwd_list) pwd_str = ''.join(pwd_list) return pwd_str def write2txt(self, path, content): ''' @param path{string} 写入文件路径;content{string} 每行写入内容 @return: @msg: 每行写入文件 ''' try: if os.path.exists(path): with open(path, mode='a', encoding='utf-8') as file: file.write(content + '\n') else: with open(path, mode='a', encoding='utf-8') as file: file.write(content + '\n') return True except Exception as e: logging.error(e) return False def del_ou_right(self, flag): ''' @param cmd_l{list} 待执行的powershell命令列表 @return: True/False @msg: 连接远程windows并批量执行powershell命令 ''' # powershell命令 用于启用/关闭OU 防止对象被意外删除 属性 # 防止对象被意外删除× enable_del = [ "Import-Module ActiveDirectory", "Get-ADOrganizationalUnit -filter * -Properties ProtectedFromAccidentalDeletion | where {" "$_.ProtectedFromAccidentalDeletion -eq $true} |Set-ADOrganizationalUnit " "-ProtectedFromAccidentalDeletion $false" ] # 防止对象被意外删除√ disable_del = [ "Import-Module ActiveDirectory", "Get-ADOrganizationalUnit -filter * -Properties ProtectedFromAccidentalDeletion | where {" "$_.ProtectedFromAccidentalDeletion -eq $false} |Set-ADOrganizationalUnit " "-ProtectedFromAccidentalDeletion $true" ] flag_map = {0: enable_del, 1: disable_del} try: win = winrm.Session('http://' + LDAP_IP + ':5985/wsman', auth=(WINRM_USER, WINRM_PWD)) for cmd in flag_map[flag]: ret = win.run_ps(cmd) if ret.status_code == 0: # 调用成功 减少日志写入 # if flag == 0: # logging.info("防止对象被意外删除×") # elif flag == 1: # logging.info("防止对象被意外删除√") return True else: return False except Exception as e: logging.error(e) return False def create_obj(self, dn=None, type='user', info=None): ''' @param dn{string}, type{string}'user'/'ou' @return: res新建结果, self.conn.result修改结果 @msg:新增对象 ''' object_class = { 'user': ['user', 'posixGroup', 'top'], 'ou': ['organizationalUnit', 'posixGroup', 'top'], } if info is not None: [job_id, name, dn, email, tel, title, sam, cn] = info user_attr = { 'sAMAccountname': sam, # 登录名 'userAccountControl': 544, # 启用账户 'title': title, # 头衔 'givenName': name[0:1], # 姓 'sn': name[1:], # 名 'displayname': name, # 姓名 'mail': email, # 邮箱 'telephoneNumber': tel, # 电话号 } else: user_attr = None # 创建之前需要对dn中的OU部分进行判断,如果没有需要创建 dn_base = dn.split(',', 1)[1] check_ou_res = self.check_ou(dn_base) if not check_ou_res: logging.error('check_ou失败,未知原因!') return False else: self.conn.add(dn=dn, object_class=object_class[type], attributes=user_attr) add_result = self.conn.result if add_result['result'] == 0: logging.info('新增对象【' + dn + '】成功!') if type == 'user': # 若是新增用户对象,则需要一些初始化操作 self.conn.modify( dn, {'userAccountControl': [('MODIFY_REPLACE', 512)]} ) # 激活用户 # 如果是用户时 new_pwd = self.generate_pwd(8) old_pwd = '' self.conn.extend.microsoft.modify_password( dn, new_pwd, old_pwd) # 初始化密码 info = 'SAM: ' + sam + ' PWD: ' + new_pwd + ' DN: ' + dn save_res = self.write2txt(PWD_PATH, info) # 将账户密码写入文件中 if save_res: logging.info('保存初始化账号密码成功!') else: logging.error('保存初始化账号密码失败: ' + info) self.conn.modify(dn, {'pwdLastSet': (2, [0])}) # 设置第一次登录必须修改密码 elif add_result['result'] == 68: logging.error('entryAlreadyExists 用户已经存在') elif add_result['result'] == 32: logging.error('noSuchObject 对象不存在ou错误') else: logging.error('新增对象: ' + dn + ' 失败!其他未知错误') return add_result def del_obj(self, dn, type): ''' @param dn{string} @return: res修改结果 @msg: 删除对象 ''' if type == 'ou': self.del_ou_right(flag=0) res = self.conn.delete(dn=dn) self.del_ou_right(flag=1) else: res = self.conn.delete(dn=dn) if res == True: logging.info('删除对象' + dn + '成功!') return res else: return False def update_obj(self, old_dn, info=None): ''' @param {type} @return: @msg: 更新对象 ''' if info is not None: [job_id, name, dn, email, tel, title, sam, cn] = info # 组成更新属性之前需要对dn中的OU部分进行判断,如果没有需要创建 dn_base = dn.split(',', 1)[1] check_ou_res = self.check_ou(dn_base) if not check_ou_res: logging.error('check_ou失败,未知原因!') return False else: attr = { 'distinguishedName': dn, # dn 'sAMAccountname': sam, # 登录名 'title': title, # 头衔 'givenName': name[0:1], # 姓 'sn': name[1:], # 名 'displayname': name, # 姓名 'mail': email, # 邮箱 'telephoneNumber': tel, # 电话号 } else: attr = None changes_dic = {} for k, v in attr.items(): if not self.conn.compare(dn=old_dn, attribute=k, value=v): # 待修改属性 if k == "distinguishedName": # 若属性有distinguishedName则需要移动user或ou # 若dn修改了需要将密码文件这个人的dn信息更新下 self.update_pwd_file_line(old_dn=old_dn, new_dn=dn) self.move_obj(dn=old_dn, new_dn=v) changes_dic.update({k: [(MODIFY_REPLACE, [v])]}) if len(changes_dic) != 0: # 有修改的属性时 modify_res = self.conn.modify(dn=dn, changes=changes_dic) logging.info('更新对象: ' + dn + ' 更新内容: ' + str(changes_dic)) return self.conn.result def rename_obj(self, dn, newname): ''' @param newname{type}新的名字,User格式:"cn=新名字";OU格式:"OU=新名字" @return: 修改结果 @msg: 重命名对象 ''' res = self.conn.modify_dn(dn, newname) if res == True: return True else: return False def move_obj(self, dn, new_dn): ''' @param {type} @return: @msg: 移动对象到新OU ''' relative_dn, superou = new_dn.split(",", 1) res = self.conn.modify_dn(dn=dn, relative_dn=relative_dn, new_superior=superou) if res == True: return True else: return False def compare_attr(self, dn, attr, value): ''' @param {type} @return: @msg:比较员工指定的某个属性 ''' res = self.conn.compare(dn=dn, attribute=attr, value=value) return res def check_ou(self, ou, ou_list=None): ''' @param {type} @return: @msg: 递归函数 如何判断OU是修改了名字而不是新建的:当一个OU里面没有人就判断此OU被修改了名字,删除此OU; 不管是新建还是修改了名字,都会将人员转移到新的OU下面:需要新建OU则创建OU后再添加/转移人员 check_ou的作用是为人员的变动准备好OU ''' if ou_list is None: ou_list = [] self.conn.search(ou, OU_SEARCH_FILTER) # 判断OU存在性 while self.conn.result['result'] == 0: if ou_list: for ou in ou_list[::-1]: self.conn.add(ou, 'organizationalUnit') return True else: ou_list.append(ou) ou = ",".join(ou.split(",")[1:]) self.check_ou(ou, ou_list) # 递归判断 return True def scan_ou(self): '''扫描的时候,必须保证此OU为叶子节点,否则报notAllowedOnNonLeaf错误, 例如此次空OU——OU=开发部,OU=核心技术部,OU=RAN,OU=上海总部,DC=randolph,DC=com 的倒数第一、二层都是空OU,但是必须得先删除倒数第一层 因此在获取所有OU列表的位置get_ous就将获得的结果倒叙(用切片[::-1]) ''' res = self.get_ous(attr=['distinguishedName']) # 调用ps脚本,防止对象被意外删除× modify_right_res = self.del_ou_right(flag=0) for i, ou in enumerate(res): dn = ou['attributes']['distinguishedName'] # 判断dd下面是否有用户,没有用户的直接删除 self.conn.search(search_base=dn, search_filter=USER_SEARCH_FILTER) if not self.conn.entries: # 没有用户存在的空OU,可以进行清理 try: delete_res = self.conn.delete(dn=dn) if delete_res: logging.info('删除空的OU: ' + dn + ' 成功!') else: logging.error('删除操作处理结果' + str(self.conn.result)) except Exception as e: logging.error(e) else: logging.info("没有空OU,OU扫描完成!") # 防止对象被意外删除√ self.del_ou_right(flag=1) def disable_users(self, path): ''' @param {type} @return: @msg: 将AD域内的用户不在csv表格中的定义为离职员工 ''' result = ad.handle_excel(path) newest_list = [] # 全量员工列表 for person in result['person_list']: job_id, name, dn, email, tel, title, sam, cn = person[0:8] dd = str(dn).split(',', 1)[1] newest_list.append(name) # 查询AD域现有员工 res = self.get_users(attr=[ 'distinguishedName', 'name', 'cn', 'displayName', 'userAccountControl' ]) for i, ou in enumerate(res): ad_user_distinguishedName, ad_user_displayName, ad_user_cn, ad_user_userAccountControl = ou[ 'attributes']['distinguishedName'], ou['attributes'][ 'displayName'], ou['attributes']['cn'], ou['attributes'][ 'userAccountControl'] rela_dn = "cn=" + str(ad_user_cn) # 判断用户不在最新的员工表格中 或者 AD域中某用户为禁用用户 if ad_user_displayName not in newest_list or ad_user_userAccountControl in DISABLED_USER_FLAG: try: # 禁用用户 self.conn.modify( dn=ad_user_distinguishedName, changes={'userAccountControl': (2, [546])}) logging.info("在AD域中发现不在表格中用户,禁用用户:" + ad_user_distinguishedName) # 移动到离职组 判断OU存在性 self.conn.search(DISABLED_BASE_DN, OU_SEARCH_FILTER) # 判断OU存在性 if self.conn.entries == []: # 搜不到离职员工OU则需要创建此OU self.create_obj(dn=DISABLED_BASE_DN, type='ou') # 移动到离职组 self.conn.modify_dn(dn=ad_user_distinguishedName, relative_dn=rela_dn, new_superior=DISABLED_BASE_DN) logging.info('将禁用用户【' + ad_user_distinguishedName + '】转移到【' + DISABLED_BASE_DN + '】') except Exception as e: logging.error(e) def create_user_by_excel(self, path): ''' @param path{string} 用于新增用户的表格 @return: @msg: ''' res_dic = self.handle_excel(path) for person in res_dic['person_list']: user_info = person self.create_obj(info=user_info) def ad_update(self, path): '''AD域的初始化/更新——从表格文件元数据更新AD域: 判断用户是否在AD域中——不在则新增; 在则判断该用户各属性是否与表格中相同,有不同则修改; 完全相同的用户不用作处理; ''' # 准备表格文件 result = ad.handle_excel(path) ori_data = result['person_list'] try: self.del_ou_right(flag=0) # 防止对象被意外删除× with tqdm(iterable=ori_data, ncols=100, total=len(ori_data), desc='处理进度', unit='人') as tqdm_ori_data: # 封装进度条 for person in tqdm_ori_data: dn, cn = person[2], person[7] user_info = person dd = str(dn).split(',', 1)[1] # 根据cn判断用户是否已经存在 filter_phrase_by_cn = "(&(objectclass=person)(cn=" + cn + "))" search_by_cn = self.conn.search( search_base=ENABLED_BASE_DN, search_filter=filter_phrase_by_cn, attributes=['distinguishedName']) search_by_cn_json_list = json.loads( self.conn.response_to_json())['entries'] search_by_cn_res = self.conn.result if search_by_cn == False: # 根据cn搜索失败,查无此人则新增 self.create_obj(info=user_info) else: old_dn = search_by_cn_json_list[0][ 'dn'] # 部门改变的用户的现有部门,从表格拼接出来的是新的dn在user_info中带过去修改 self.update_obj(old_dn=old_dn, info=user_info) # break # 可测试一个例子 self.del_ou_right(flag=1) # 防止对象被意外删除√ except KeyboardInterrupt: tqdm_ori_data.close() raise tqdm_ori_data.close() def handle_pwd_expire(self, attr=None): ''' @param {type} @return: @msg: 处理密码过期 设置密码不过期 需要补全理论和测试 参考理论地址: https://stackoverflow.com/questions/18615958/ldap-pwdlastset-unable-to-change-without-error-showing ''' attr = ['pwdLastSet'] self.conn.search(search_base=ENABLED_BASE_DN, search_filter=USER_SEARCH_FILTER, attributes=attr) result = self.conn.response_to_json() res_list = json.loads(result)['entries'] for l in res_list: pwdLastSet, dn = l['attributes']['pwdLastSet'], l['dn'] modify_res = self.conn.modify(dn, {'pwdLastSet': (2, [-1])}) # pwdLastSet只能给-1 或 0 if modify_res: logging.info('密码不过期-修改用户: ' + dn) def update_pwd_file_line(self, old_dn=None, new_dn=None, new_pwd=None): ''' @param dn{string} @return: 修改结果 @msg: 当用户的dn或密码被程序更新,将会在这里更新对应部分的信息 采用临时文件替换源文件的方式,节省内存,但占硬盘 参考文章: https://www.cnblogs.com/wuzhengzheng/p/9692368.html ''' with open(PWD_PATH, mode='rt', encoding='utf-8') as file, \ open('TEMP.txt', mode='wt', encoding='utf-8') as temp_file: for line in file: if old_dn and new_dn: # dn被修改 if old_dn in line: line = line.replace(old_dn, new_dn) temp_file.write(line) else: temp_file.write(line) elif new_pwd and old_dn: # 密码被修改 if old_dn in line: # 需要正则匹配旧的密码 pattern = "PWD: (.+?)\\n" # 惰性匹配 local = re.findall(pattern, line) old_pwd = local[0] line = line.replace(old_pwd, new_pwd) temp_file.write(line) else: temp_file.write(line) os.remove(PWD_PATH) os.rename('TEMP.txt', PWD_PATH) def modify_pwd(self, cn): ''' @param cn{string} 姓名工号 戴东1325 @return: 修改结果 @msg: 修改密码 ''' # 根据cn判断用户是否已经存在 filter_phrase_by_cn = "(&(objectclass=person)(cn=" + cn + "))" search_by_cn = self.conn.search(search_base=ENABLED_BASE_DN, search_filter=filter_phrase_by_cn, attributes=['distinguishedName']) search_by_cn_json_list = json.loads( self.conn.response_to_json())['entries'] if search_by_cn: new_pwd = self.generate_pwd(8) old_pwd = '' dn = search_by_cn_json_list[0]['dn'] modify_password_res = self.conn.extend.microsoft.modify_password( dn, new_pwd, old_pwd) if modify_password_res: logging.info('更新了对象: ' + dn + ' 的密码') is_exist = os.path.exists(PWD_PATH) if not is_exist: # 校验密码文件存在性 info = 'DN: ' + dn + ' PWD: ' + new_pwd save_res = self.write2txt(PWD_PATH, info) # 将账户密码写入文件中 if save_res: logging.info('保存初始化账号密码成功!') else: logging.error('保存初始化账号密码失败: ' + info) else: # 若密码修改了需要将密码文件这个人的密码信息更新下 with open(PWD_PATH, mode='rt', encoding='utf-8') as file: if dn in file.read(): is_exist_pwd_record = True else: is_exist_pwd_record = False if is_exist_pwd_record: # 若发现此人信息在密码文件里则更新,否则需创建 self.update_pwd_file_line(old_dn=dn, new_pwd=new_pwd) else: info = 'DN: ' + dn + ' PWD: ' + new_pwd # 因为是修改密码,所以dn未修改 self.write2txt(PWD_PATH, info) else: logging.error('更新对象密码失败!: ' + dn) else: logging.error('查无此人!请检查待修改密码对象格式是否为【姓名工号】')
# 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: sure = input("type y or n : > ") if sure == 'n': pass else: conn.delete('cn=' + to_delete + ', ou=students, dc=security, dc=ch') print(conn.result) # If client wants to modify an entry elif operation == '4' or operation == 'm': to_modify, attr_to_modify, val_to_modify = [], [], [] str = 'y' to_modify.append(input("Which user would you like to modify? > ")) print( "We will get the attributes and values you want to modify, once you are done, type \'n\'." ) while str != 'n': attr_to_modify.append(input("Attribute to modify: > ")) val_to_modify.append(input("Value to modify: > ")) str = input("Continue? y/n > ")
class LdapOLC(object): """A wrapper class to operate on the o=gluu DIT of the LDAP. Args: hostname (string): hostname of the server running the LDAP server addr (string): uri of ldap server, such as ldaps://ldp.foo.org:1636 binddn (string): bind dn for ldap server password (string): the password of binddn """ def __init__(self, addr, binddn, passwd): self.addr = addr self.binddn = binddn self.passwd = passwd self.server = None self.conn = None self.hostname = get_host_port(addr)[0] def connect(self): """Makes connection to ldap server and returns result Returns: the ldap connection result """ logger.debug("Making Ldap Connection") self.server = Server(self.addr, use_ssl=True) self.conn = Connection(self.server, user=self.binddn, password=self.passwd) return self.conn.bind() def loadModules(self, *modules): """This function creates ldap entry on server for loading nodules. Args: modules (list): list of modules to be loaded Returns: -1 if modulas were already loaded, else returns modify result """ #Get loaded modules self.conn.search(search_base='cn=module{0},cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcModuleLoad"]) #addList are modules that will be loaded addList = list(modules) if self.conn.response: #if a module is allread loaded, remove it from addList for a in self.conn.response[0]['attributes']['olcModuleLoad']: r = re.split("{\d+}", a) if len(r) == 1: m = r[0] else: m = r[1] mn = m.split('.') if mn[0] in addList: addList.remove(mn[0]) #If there is still modules to be loaded, add them and return #modify results if addList: return self.conn.modify('cn=module{0},cn=config', {'olcModuleLoad': [MODIFY_ADD, addList]}) #If all modules were loaded previously, return -1 return -1 def checkAccesslogDBEntry(self): """Checks if access logdb (cn=accesslog) entry exists Returns: search results of cn=accesslog """ return self.conn.search(search_base='cn=config', search_filter='(olcSuffix=cn=accesslog)', search_scope=SUBTREE, attributes=["*"]) def accesslogDBEntry(self, replicator_dn, log_dir="/opt/gluu/data/accesslog"): """This function creates ldap entry on server for accesslog database. Args: replicator_dn (string): replicator dn for replication log_dir (string, optional): accesslog database directorsy, default to /opt/gluu/data/accesslog Returns: None if accesslogdb entry is already exists else ldap modifcation result for adding accsesslogdb entry. """ attributes = { 'objectClass': ['olcDatabaseConfig', 'olcMdbConfig'], 'olcDatabase': '{2}mdb', 'olcDbDirectory': log_dir, 'OlcDbMaxSize': 1073741824, 'olcSuffix': 'cn=accesslog', 'olcRootDN': 'cn=admin, cn=accesslog', 'olcRootPW': ldap_encode(self.passwd), 'olcDbIndex': [ 'default eq', 'objectClass,entryCSN,entryUUID,reqEnd,reqResult,reqStart,reqDN' ], 'olcLimits': 'dn.exact="{0}" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited' .format(replicator_dn), } #check if accesslogdb entry is allread exists. If not exists, create it. if not self.checkAccesslogDBEntry(): return self.conn.add('olcDatabase={2}mdb,cn=config', attributes=attributes) def checkSyncprovOverlaysDB1(self): """Checks if overlay configuration entry exists on first database Returns: search results of olcOverlay=syncprov """ return self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(olcOverlay=syncprov)', search_scope=SUBTREE, attributes=["*"]) def syncprovOverlaysDB1(self): """This function creates overlay configuration on first database Returns: None if overlay configuration entry is already exists else ldap modifcation result for adding overlay configuration entry. """ attributes = { 'objectClass': ['olcOverlayConfig', 'olcSyncProvConfig'], 'olcOverlay': 'syncprov', 'olcSpReloadHint': 'TRUE', 'olcSpCheckPoint': '100 10', 'olcSpSessionlog': '10000', } #If not overlay configuration on first database is not exists, crtate it if not self.checkSyncprovOverlaysDB1(): self.conn.add('olcOverlay=syncprov,olcDatabase={1}mdb,cn=config', attributes=attributes) if self.conn.result['description'] == 'success': return True def checkSyncprovOverlaysDB2(self): """Checks if overlay configuration entry exists on second database Returns: search results of olcOverlay=syncprov """ return self.conn.search(search_base='olcDatabase={2}mdb,cn=config', search_filter='(olcOverlay=syncprov)', search_scope=SUBTREE, attributes=["*"]) def syncprovOverlaysDB2(self): """This function creates overlay configuration on second database Returns: None if overlay configuration entry is already exists else ldap modifcation result for adding overlay configuration entry. """ attributes = { 'objectClass': ['olcOverlayConfig', 'olcSyncProvConfig'], 'olcOverlay': 'syncprov', 'olcSpNoPresent': 'TRUE', 'olcSpReloadHint': 'TRUE', } #If not overlay configuration on second database #is not exists, crtate it if not self.checkSyncprovOverlaysDB2(): self.conn.add('olcOverlay=syncprov,olcDatabase={2}mdb,cn=config', attributes=attributes) if self.conn.result['description'] == 'success': return True def checkServerID(self): """Checks if Server ID entry exists Returns: search results of olcServerID """ return self.conn.search(search_base='cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcServerID"]) def setServerID(self, sid): """This function sets Server ID for replication Args: sid (int): Server ID for this server Returns: ldap modifcation result for setting server id entry. """ #modification type is add mod_type = MODIFY_ADD #check if server id exists. self.conn.search(search_base='cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcServerID"]) #If server id exists, modfication type id replace if self.checkServerID(): if self.conn.response[0]['attributes']['olcServerID']: mod_type = MODIFY_REPLACE return self.conn.modify('cn=config', {'olcServerID': [mod_type, str(sid)]}) def setDBIndexes(self): """This function sets indexes for accesslog database Returns: ldap modifcation result for setting indexes for accesslog. """ #check if indexes exist self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcDbIndex"]) addList = ["entryCSN eq", "entryUUID eq"] #remove index that is already exist if self.conn.response: for idx in self.conn.response[0]['attributes']['olcDbIndex']: if idx in addList: addList.remove(idx) return self.conn.modify('olcDatabase={1}mdb,cn=config', {'olcDbIndex': [MODIFY_ADD, addList]}) def checkAccesslogPurge(self): """This function checks if accesslog purge entry exists Returns: search result of objectClass=olcAccessLogConfig """ return self.conn.search( search_base='cn=config', search_filter='(objectClass=olcAccessLogConfig)', search_scope=SUBTREE, attributes=["olcAccessLogPurge"]) def accesslogPurge(self, purge='0:24:0 1:0:0'): """This function creates purge interval and age for accessogdb entries Args: purge (string, optional): interval and age representation separeted by a space in the form: "D+H:M:S" where D: day, H: hour, M: min, S:sec Returns: ldap modifcation result for setting accesslog purge entry. """ #split data to interval and age. p, a = purge.split() pl = p.split(':') al = a.split(':') olcAccessLogPurge = '' #all entries except day, should be double in length if not pl[0] == '0': olcAccessLogPurge += pl[0].zfill(2) + '+' olcAccessLogPurge += "{}:{}".format(pl[1].zfill(2), pl[2].zfill(2)) + ' ' if not al[0] == '0': olcAccessLogPurge += al[0].zfill(2) + '+' olcAccessLogPurge += "{}:{}".format(al[1].zfill(2), al[2].zfill(2)) attributes = { 'objectClass': ['olcOverlayConfig', 'olcAccessLogConfig'], 'olcOverlay': 'accesslog', 'olcAccessLogDB': 'cn=accesslog', 'olcAccessLogOps': 'writes', 'olcAccessLogSuccess': 'TRUE', 'olcAccessLogPurge': olcAccessLogPurge, } if not self.checkAccesslogPurge(): return self.conn.add( 'olcOverlay=accesslog,olcDatabase={1}mdb,cn=config', attributes=attributes) def removeMirrorMode(self): """This function removes mirror mode entry Returns: None if server is not in mirror mode else ldap modification result """ self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcMirrorMode"]) if not self.conn.response: return if self.conn.response[0]['attributes']['olcMirrorMode']: return self.conn.modify('olcDatabase={1}mdb,cn=config', {"olcMirrorMode": [MODIFY_REPLACE, []]}) def checkMirroMode(self): """This function checks if server is in mirror mode Returns: False if server is not in mirror mode else search result of olcMirrorMode """ r = self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcMirrorMode"]) if r: if self.conn.response[0]['attributes']: if self.conn.response[0]['attributes']['olcMirrorMode']: return self.conn.response[0]['attributes']['olcMirrorMode'] return False def makeMirroMode(self): """This function makse server in mirror mode Returns: ldap modification result """ return self.conn.modify('olcDatabase={1}mdb,cn=config', {"olcMirrorMode": [MODIFY_ADD, ["TRUE"]]}) def removeProvider(self, raddr): """This function removes provider form server Args: raddr (string): provider uri, for example: ldaps://ldp.foo.org:1636 Returns: -1 if this server has no such provider else ldap modifcation result for removing provider """ rmMirrorMode = False if len(self.getProviders()) <= 1: rmMirrorMode = True #if there is no such privder return -1 if not self.conn.response: return -1 #iterate all attributes to find basedn of olcSyncrepl for pr in self.conn.response: if pr["attributes"]["olcSyncrepl"]: for pri in pr["attributes"]["olcSyncrepl"]: for l in pri.split(): ls = l.split('=') if ls[0] == 'provider': if ls[1] == raddr: baseDn = pr['dn'] r = self.conn.modify( baseDn, {'olcSyncrepl': [MODIFY_DELETE, [pri]]}) if r: if rmMirrorMode: self.removeMirrorMode() return r def add_provider(self, rid, raddr, rbinddn, rcredentials): """Adds provider to server for replication. Args: rid (int): provider server id raddr (string): provider uri, for example: ldaps://ldp.foo.org:1636 rbindn (string): bind dn of replicator user rcredentials (string): password for replicator user (rbinddn) Returns: modification result of adding provider """ #this is rpvider information ridText = ( 'rid={0} provider={1} bindmethod=simple binddn="{2}" ' 'tls_reqcert=never credentials={3} searchbase="o=gluu" ' 'logbase="cn=accesslog" ' #'filter=(&(objectClass=*)(!(ou:dn:=appliances))) ' 'logfilter="(&(objectClass=auditWriteObject)(reqResult=0))" ' 'schemachecking=on type=refreshAndPersist retry="60 +" ' 'syncdata=accesslog sizeLimit=unlimited ' 'timelimit=unlimited'.format(rid, raddr, rbinddn, rcredentials)) #we should delete if such an entry exists, so search it self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcSyncRepl"]) # delete the entry if a syncrepl config exists for the same rid entry = self.conn.entries[0] for rep in entry["olcSyncRepl"]: if 'rid={0}'.format(rid) in rep: lmod = {"olcSyncRepl": [(MODIFY_DELETE, [rep])]} self.conn.modify('olcDatabase={1}mdb,cn=config', lmod) break mod = {"olcSyncRepl": [(MODIFY_ADD, [ridText])]} return self.conn.modify('olcDatabase={1}mdb,cn=config', mod) def checkAccesslogDB(self): """Checks if access logdb (cn=accesslog) entry exists Returns: search results of cn=accesslog """ return self.conn.search(search_base='cn=config', search_filter='(olcSuffix=cn=accesslog)', search_scope=SUBTREE, attributes=["*"]) def addTestUser(self, cn, sn, mail): """Adds test user Args: cn (string): common name for test user sn (string): last name for test user mail (string): mail address for test user Returns: ldap add result """ #get base dn self.checkBaseDN() #check if base for tests user exists 'ou=testusers,o=gluu' self.checkTestUserBase() #create a uid uid = '{0}@{1}'.format(time.time(), self.hostname) #make dn for test user dn = "uid={0},ou=testusers,o=gluu".format(uid) return self.conn.add(dn, attributes={ 'objectClass': ['top', 'inetOrgPerson'], "cn": cn, 'mail': mail, 'sn': sn, 'title': 'gluuClusterMgrTestUser', 'uid': uid }) def checkTestUserBase(self): """Checks if test user base (ou=testusers,o=gluu) exists. If not exists creates it Returns: None if not base dn exists else returns ldap add result ldap add result """ if not self.conn.search(search_base='ou=testusers,o=gluu', search_filter='(objectClass=inetOrgPerson)', search_scope=BASE, attributes='*'): self.conn.add('ou=testusers,o=gluu', attributes={ 'objectClass': ['top', 'organizationalUnit'], 'ou': 'testusers', }) def searchTestUsers(self): """Searches test user Returns: ldap search result for test users """ return self.conn.search(search_base='ou=testusers,o=gluu', search_filter='(title=gluuClusterMgrTestUser)', search_scope=LEVEL, attributes='*') def delDn(self, dn): """Deltes given dn Args: dn (string): dn to be deleted Returns: ldap delete result """ return self.conn.delete(dn) def getProviders(self): """Collects providers for this server Returns: provider dictionary. """ pDict = {} #Search provider entries if self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcSyncRepl"]): #Iterate all providers and parse it for pe in self.conn.response[0]['attributes']['olcSyncrepl']: for e in pe.split(): es = e.split("=") if re.search('(\{\d*\})*rid', es[0]): pid = es[1] elif es[0] == 'provider': host, port = get_host_port(es[1]) dkey = host if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", host): dkey = get_hostname_by_ip(host) pDict[dkey] = (pid, port, host) return pDict def getMMRStatus(self): """Returns multi master replication status for this server Returns: dictionary includes replicator results """ retDict = {} retDict["server_id"] = None if self.checkServerID(): if self.conn.response[0]['attributes']['olcServerID']: retDict["server_id"] = self.conn.response[0]['attributes'][ 'olcServerID'][0] retDict["overlaysDB1"] = self.checkSyncprovOverlaysDB1() retDict["overlaysDB2"] = self.checkSyncprovOverlaysDB2() retDict["mirrorMode"] = self.checkMirroMode() retDict["accesslogDB"] = self.checkAccesslogDBEntry() retDict["accesslogPurge"] = self.checkAccesslogPurge() retDict["providers"] = self.getProviders() return retDict def getMainDbDN(self): """Returns dn of main db Returns: dn of main db """ if self.conn.search( search_base="cn=config", search_scope=LEVEL, search_filter="(olcDbDirectory=/opt/gluu/data/main_db)", attributes='*'): if self.conn.response: return self.conn.response[0]['dn'] def setLimitOnMainDb(self, replicator_dn): """Sets limit for replicator dn Args: replicator_dn (string): dn for replicator user Returns: ldap modification result """ main_db_dn = self.getMainDbDN() return self.conn.modify( main_db_dn, { 'olcLimits': [ MODIFY_ADD, 'dn.exact="{0}" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited' .format(replicator_dn) ] }) def addReplicatorUser(self, replicator_dn, passwd): """Adds replicator user (dn) Args: replicator_dn (string): dn for replicator user passwd (string): password of replicator user Returns: ldap add/modification result """ #Ckech if base dn exists self.checkBaseDN() #get encoded password enc_passwd = ldap_encode(passwd) #check if replicator user exists self.conn.search(replicator_dn, search_filter='(objectClass=*)', search_scope=BASE) if len(self.conn.response): # user dn already exists return self.conn.modify( replicator_dn, {"userPassword": [MODIFY_REPLACE, enc_passwd]}) else: m = re.search('cn=(?P<cn>[a-zA-Z][a-zA-Z ]*[a-zA-Z]),o=gluu', replicator_dn) cn = m.group('cn') attributes = { 'objectClass': ['top', 'inetOrgPerson'], 'cn': cn, 'sn': 'replicator', 'uid': 'replicator', 'userpassword': enc_passwd, } return self.conn.add(replicator_dn, attributes=attributes) def checkBaseDN(self): """Checks id base dn exists. If not creates Returns: ldap add result """ r = self.conn.search(search_base="o=gluu", search_filter='(objectClass=top)', search_scope=BASE) if not self.conn.search(search_base="o=gluu", search_filter='(objectClass=top)', search_scope=BASE): logger.info("Adding base DN") self.conn.add('o=gluu', attributes={ 'objectClass': ['organization'], 'o': 'gluu', }) def configureOxIDPAuthentication(self, servers): """Makes gluu server aware of all ldap servers in the cluster Args: servers (list): list of server to add oxIDPAuthentication Returns: ldap modify result """ if self.conn.search("ou=appliances,o=gluu", search_filter='(objectClass=gluuAppliance)', search_scope=LEVEL, attributes=["oxIDPAuthentication"]): r = self.conn.response if r: oxidp_s = r[0]["attributes"]["oxIDPAuthentication"][0] oxidp = json.loads(oxidp_s) config = json.loads(oxidp["config"]) config["servers"] = servers oxidp["config"] = json.dumps(config) oxidp_s = json.dumps(oxidp) return self.conn.modify( r[0]['dn'], {"oxIDPAuthentication": [MODIFY_REPLACE, oxidp_s]})
# "uid": "*****@*****.**", # "homeDirectory": "/home/mariana", # "UserPassword": "******" + b2a_base64(md5json).decode("utf-8") #} #objectClass = ["top","person","inetOrgPerson","posixaccount","organizationPerson"] #dn = "uid=%s,dc=dexter,dc=com,dc=br"%(user["mail"]) #user_added = ldap_con.add(dn, objectClass, user) #print(user_added) #email = "*****@*****.**" #dn = "uid=%s,dc=dexter,dc=com,dc=br"%(email) #ldap_con.search( # dn,"(objectclass=person)",attributes=["sn", "userPassword"] #) #print(ldap_con.entries) #email = "*****@*****.**" #dn = "uid=%s,dc=dexter,dc=com,dc=br"%(email) #changes = { # "cn": [(MODIFY_REPLACE, ["maria"])], # "sn": [(MODIFY_REPLACE, ["araújo"])] #} #ldap_con.modify( # dn,changes #) #print(ldap_con.result) email = "*****@*****.**" dn = "uid=%s,dc=dexter,dc=com,dc=br" % (email) print(ldap_con.delete(dn))
def compare_to_ldap(sis_users, needs_sis_username=0): server = Server(host='www.xxx.yyy.zzz', port=636, use_ssl=True, get_info=NONE) # logging.info('Please enter your LDAP username: '******'myname' password = '******' attempts = 0 conn = Connection(server, user='******' + login_name + ',o=xyz', password=password) conn.bind() while conn.result['description'] == 'invalidCredentials': logging.info('Incorrect username or password. Please try again.') logging.info('Please enter your LDAP username: '******'CN=' + login_name + ',o=xyz', password=password) conn.bind() logging.info('LDAP Login successful') ldap_un_list = [] logging.info('\n') search_filter = '(objectclass=Person)' for i in range(3, 5): curr_grade = 'Grade-PK' + str(i) search_base = 'ou=' + curr_grade + ',o=xyz' logging.info('Searching ' + curr_grade) conn.search(search_base=search_base, search_filter=search_filter, search_scope=SUBTREE, attributes=['uid']) for entry in conn.entries: uid = entry['uid'].value ldap_un_list.append(uid.lower()) for i in range(0, 13): curr_grade = 'Grade-' + str(i).zfill(2) search_base = 'ou=' + curr_grade + ',o=xyz' logging.info('Searching ' + curr_grade) conn.search(search_base=search_base, search_filter=search_filter, search_scope=SUBTREE, attributes=['uid']) for entry in conn.entries: uid = entry['uid'].value ldap_un_list.append(uid.lower()) ldap_un_list.sort() # *** Add any potential chromebook enrollment accounts here if necessary. e.g., PK3 or PK4 exclusion_list = [ 'PK3', 'PK4', '00', '1st', '2nd', '3rd', '4th', '5th', '6th', '7th', '8th', '9th', '10th', '11th', '12th', 'billybob' ] for name in exclusion_list: if name in ldap_un_list: ldap_un_list.remove(name) if needs_sis_username == 1: return ldap_un_list logging.info('\n' + str(len(ldap_un_list)) + ' total students in LDAP, Grades PK3-12.') with open('ldap_un_list.log', mode='w') as file: for student in ldap_un_list: file.write(student + '\n') needs_deletion = [] for student in ldap_un_list: if student in sis_users.keys(): continue needs_deletion.append(student) logging.info('\nStudents who need to be deleted from LDAP:') logging.info(needs_deletion) logging.info('\n' + str(len(needs_deletion)) + ' accounts recommended to be deleted.') needs_account = OrderedDict() for student in sis_users.keys(): conn.search(search_base='o=xyz', search_filter='(uid=' + student + ')', search_scope=SUBTREE) if len(conn.entries) > 0: continue needs_account[student] = sis_users[student] logging.info('\nStudents who need to be added to LDAP:') logging.info(needs_account.keys()) logging.info('\n' + str(len(needs_account)) + ' accounts to be created in LDAP.') if len(needs_deletion) == 0: logging.info('No accounts need to be deleted.') else: error_count = 0 # User exists in LDAP but not SIS -> we can delete them from LDAP for username in needs_deletion: conn.search(search_base='o=xyz', search_filter='(uid=' + username + ')') user = conn.entries[0].entry_dn conn.delete(user) if str(conn.result['description']) == 'success': logging.info('Success - ' + username + ' deleted.') else: logging.info('Error - ' + username + ' could not be deleted.') error_count += 1 logging.info('\n') logging.info('\nAccount deletion process completed with ' + str(error_count) + ' errors.') pass_list = create_ldap_accounts(needs_account) update_students_in_sis(needs_account, pass_list) conn.unbind()
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'])
class LDAPLib: def __init__(self): self.connection = Connection( server=settings.LDAP_SERVER, user=settings.LDAP_USER, password=settings.LDAP_PASSWORD, auto_bind=True, ) self.user_base = ",".join(("ou=users", settings.LDAP_BASE_DN)) self.group_base = ",".join(("ou=groups", settings.LDAP_BASE_DN)) self.user_attributes = ["uid", "cn", "sn"] self.group_attributes = ["gidNumber", "cn"] def get_all_users(self): search_filter = "(cn=*)" result = self.connection.search(self.user_base, search_filter, attributes=self.user_attributes) if result: return self.connection.entries return [] def get_all_groups(self): search_filter = "(cn=*)" result = self.connection.search(self.group_base, search_filter, attributes=self.group_attributes) if result: return self.connection.entries return [] def search_user(self, query): search_filter = f"(uid={query})" result = self.connection.search(self.user_base, search_filter, attributes=self.user_attributes) if result: return self.connection.entries[0] def add_user(self, uid, first_name, last_name, email, password_hash): dn = ",".join((f"uid={uid}", self.user_base)) return self.connection.add( dn, ["inetOrgPerson", "top"], { "uid": uid, "cn": first_name, "sn": last_name, "mail": email, "userPassword": "******" + password_hash, }, ) def search_group(self, query): search_filter = f"(gidNumber={query})" result = self.connection.search(self.group_base, search_filter, attributes=self.group_attributes) if result: return self.connection.entries[0] def add_group(self, gid, name): dn = ",".join((f"cn={name}", self.group_base)) return self.connection.add(dn, ["posixGroup", "top"], { "gidNumber": gid, "cn": name }) def update_organization_unit(self, name): """ Make sure the organization unit exists in LDAP. """ search_filter = f"(ou={name})" result = self.connection.search(settings.LDAP_BASE_DN, search_filter, attributes=["ou"]) if not result: dn = ",".join((f"ou={name}", settings.LDAP_BASE_DN)) self.connection.add(dn, ["organizationalUnit", "top"], {"ou": name}) def delete_user(self, uid): dn = ",".join((f"uid={uid}", self.user_base)) return self.connection.delete(dn) def delete_group(self, cn): dn = ",".join((f"cn={cn}", self.group_base)) return self.connection.delete(dn) def check_password(self, uid, password_hash): dn = ",".join((f"uid={uid}", self.user_base)) return self.connection.compare(dn, "userPassword", "{CRYPT}" + password_hash) def change_password(self, uid, password_hash): dn = ",".join((f"uid={uid}", self.user_base)) changes = { "userPassword": [(MODIFY_REPLACE, ["{CRYPT}" + password_hash])] } self.connection.modify(dn, changes) def update_group_members(self, cn, members): dn = ",".join((f"cn={cn}", self.group_base)) if members: changes = {"memberUid": [(MODIFY_REPLACE, members)]} else: changes = {"memberUid": [(MODIFY_DELETE, [])]} self.connection.modify(dn, changes)
class LdapOperations: def __init__(self): self.conn = None self.loggedUsers = [] self.connect() def connect(self): server = Server(URL, get_info=ALL) self.conn = Connection(server, USER, PASS, auto_bind=True) self.conn.bind() def add_user(self, commonName, username, password): test = self.conn.add( 'cn={},ou=myUsers,cn=admin,dc=projet,dc=com'.format(commonName), ['inetOrgPerson', 'top'], { 'objectClass': 'person', 'sn': username, 'userPassword': password }) print(test) if test: return self.generate_selfsigned_cert(commonName) else: return None, None def delete_user(self, commonName): dn = "cn={},ou=myUsers,cn=admin,dc=projet,dc=com".format(commonName) res = self.conn.delete(dn) print(res) def get_all_users(self): self.conn.search(SEARCH, FILTER) #print(self.conn.entries) def check_login_infos(self, commonName, password): commonName = commonName.strip() password = password.strip() filterr = '(&(cn={0})(userPassword={1}))'.format(commonName, password) self.conn.search(SEARCH, filterr) #print(self.conn.entries) return len(self.conn.entries) > 0 def check_certificate(self, cn, certif_string): with open('{}tmp/{}.crt'.format(PROJECT_DIRECTORY, cn), 'wb') as f: f.write(str.encode(certif_string)) completedProcess = subprocess.run( 'openssl verify -CAfile {}ca/ca.crt {}tmp/{}.crt'.format( PROJECT_DIRECTORY, PROJECT_DIRECTORY, cn), shell=True) return completedProcess.returncode == 0 def generate_selfsigned_cert(self, cn): key = rsa.generate_private_key( public_exponent=65537, key_size=2048, backend=default_backend(), ) public_key = key.public_key() pem = public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo) #write our public to disk with open("{}rsa_keys/{}public_key.pem".format(PROJECT_DIRECTORY, cn), "wb") as f: f.write(pem) key_pem = key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption(), ) # Write our private key to disk for safe keeping with open("{}rsa_keys/{}_Key.pem".format(PROJECT_DIRECTORY, cn), "wb") as f: f.write(key_pem) # Generate a CSR csr = x509.CertificateSigningRequestBuilder().subject_name( x509.Name([ # Provide various details about who we are. x509.NameAttribute(NameOID.COUNTRY_NAME, u"TN"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"INSAT"), x509.NameAttribute(NameOID.LOCALITY_NAME, u"INSAT"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, cn), x509.NameAttribute(NameOID.COMMON_NAME, cn + '@insat.com'), ])).add_extension( x509.SubjectAlternativeName([ # Describe what sites we want this certificate for. x509.DNSName(cn + '@insat.com') ]), critical=False, # Sign the CSR with our private key. ).sign(key, hashes.SHA256(), default_backend()) # Create the client certificate pem_cert = open('{}ca/ca.crt'.format(PROJECT_DIRECTORY), 'rb').read() ca = x509.load_pem_x509_certificate(pem_cert, default_backend()) #print(ca) pem_key = open('{}ca/ca.key'.format(PROJECT_DIRECTORY), 'rb').read() ca_key = serialization.load_pem_private_key(pem_key, password=None, backend=default_backend()) #print(ca_key) builder = x509.CertificateBuilder() builder = builder.subject_name(csr.subject) builder = builder.issuer_name(ca.subject) builder = builder.not_valid_before(datetime.datetime.now() + datetime.timedelta(-1)) builder = builder.not_valid_after(datetime.datetime.now() + datetime.timedelta(7)) builder = builder.public_key(csr.public_key()) builder = builder.serial_number(int(uuid.uuid4())) for ext in csr.extensions: builder.add_extension(ext.value, ext.critical) certificate = builder.sign(private_key=ca_key, algorithm=hashes.SHA256(), backend=default_backend()) with open('{}certificates/{}.crt'.format(PROJECT_DIRECTORY, cn), 'wb') as f: f.write(certificate.public_bytes(serialization.Encoding.PEM)) return certificate.public_bytes(serialization.Encoding.PEM), key_pem def login(self, commonName, password, certif): if self.check_login_infos(commonName, password) == False: return False, 'Wrong Password' if self.check_certificate(commonName, certif) == False: return False, 'Invalid Certificate' return True, 'you are logged' def get_public_key(self, commonName): public_key = open( "{}rsa_keys/{}public_key.pem".format(PROJECT_DIRECTORY, commonName), "rb").read() # public_key = serialization.load_pem_public_key(pem_key,backend=default_backend()) return public_key
class LdapManager(): dns_node_attrs = ['dn', 'dc', 'name', 'dnsRecord', 'dNSTombstoned'] """ This method allows the use of nt passwords as well as hashes in the format lmhash:nthash """ def __init__(self, server, port=389, ssl=False, kerberos=False, ntuser=None, ntpass=None): self._server = server self._port = port self._ssl = ssl self._kerberos = kerberos self._ntuser = ntuser self._ntpass = ntpass self._delay = 1 self._page_records = None self._query_delay = 1 self._timeout = None self._base = '' def get_dn(self, domain): dn = '' for part in domain.split('.'): dn += 'DC=' + part + ',' return dn[:-1] def connect(self): """ LDAP connection method. Connects to an AD LDAP server via NTLM or KERBEROS authentication. This method supports the use of a plaintext password as well as NTLM hashes. HASH = LMHASH:NTHASH Returns ------- bool Returns the state of the connection. """ logging.info('[*] Creating an LDAP connection.') s = Server(self._server, port=self._port, use_ssl=self._ssl, connect_timeout=self._timeout, get_info=ALL) if self._kerberos: # Use Kerberos logging.info('[*] Using KERBEROS authentication.') try: self._conn = Connection(s, auto_bind=AUTO_BIND_NONE, authentication=SASL, sasl_mechanism=KERBEROS, read_only=False, return_empty_attributes=True) bind_result = self._conn.bind() except Exception as e: msg = e.message.encode('ascii', 'ignore') if msg.lower( ).find('no kerberos credentials available (default cache: file:/tmp/' ) != -1: logging.error('[-] Kerberos binding error.') logging.debug( '[D] You need a valid kerberos TGT ticket or an LDAP TGS ticket for accessing the remote service.' ) logging.debug( '[D] Save the kerberos ticket ccache DB into /tmp/krb5cc_<uid> and try again.' ) elif msg.lower().find('ticket expired') != -1: logging.error('[-] Kerberos binding error.') logging.debug( '[D] The provided Ticket has expired. Please provide a valid one and try again.' ) else: logging.error('[-] Unexpected binding error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False else: # Use NTLM authentication logging.info('[*] Using NTLM authentication.') try: self._conn = Connection(s, auto_bind=AUTO_BIND_NONE, authentication=NTLM, user=self._ntuser, password=self._ntpass, read_only=False) bind_result = self._conn.bind() except: logging.error('[-] Unexpected binding error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False # Takes the root naming context if bind_result: self._base = json.loads( s.info.to_json())['raw']['rootDomainNamingContext'][0] logging.debug('[D] Binding base: %s' % self._base) else: logging.debug('[D] An error ocurred during the binding process.') logging.debug('[D] Ldap API Message: %s' % self._conn.result['description']) return False return True def generic_search(self, base, s_filter, attributes): """ Performs a generic LDAP search. Returns list[] : A JSON formatted list of objected. """ if base is None: base = self._base if attributes is None: attributes = ALL_ATTRIBUTES json_list = [] cookie = None while cookie == None or cookie != '': try: logging.debug('[D] Searching on base: %s' % base) logging.debug('[D] Searching with filter: %s' % s_filter) logging.debug('[D] Searching the attributes: %s' % attributes) self._conn.search(search_base=base, search_filter=s_filter, search_scope=SUBTREE, attributes=attributes, paged_size=self._page_records, paged_cookie=cookie) except ldap3.core.exceptions.LDAPInvalidFilterError as e: logging.error('[-] Search error: invalid LDAP query filter') logging.debug('[D] %s' % e.message) logging.debug('[D] Filter: %s' % s_filter) break except Exception: logging.error('[-] Unexpected search error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) break try: cookie = self._conn.result['controls'][ '1.2.840.113556.1.4.319']['value']['cookie'] except: cookie = '' # Waits for n seconds before querying again time.sleep(self._query_delay) for entry in self._conn.entries: # Adds the new records to the list of json objects json_list.append(json.loads(entry.entry_to_json())) return json_list def get_dns_node_info(self, domain, name): """ Gets DNS nodes. Returns list[] A JSON formatted list of dnsNode class LDAP objects with ceretain attributes. """ # It uses the full dn (including the name) because it requires to identify those nodes for # which the authenticated user does NOT have privileges and their attributes cannot be obtained domain_dn = self.get_dn(domain) node_dn = 'DC=' + name + ',DC=' + domain + ',CN=MicrosoftDNS,DC=DomainDnsZones,' + domain_dn search_filter = '(objectClass=*)' dns_nodes = self.generic_search(node_dn, search_filter, LdapManager.dns_node_attrs[1:]) dns_record_list = [] if len(dns_nodes) != 0: if dns_nodes[0]['attributes']['name'] == []: logging.debug( '[D] Looks like you don\'t have permissions to read the node\'s attributes...' ) else: dns_records = dns_nodes[0]['attributes']['dnsRecord'] for dns_record in dns_records: dns_record = base64.b64decode(dns_record['encoded']) pair = [] pair.append(DNSRecord.getTypeFromDNSRecord(dns_record)) pair.append(DNSRecord.getDataFromDNSRecord(dns_record)) dns_record_list.append(pair) return dns_record_list def delete_dns_node(self, domain, name): """ Removes a DNS node. Returns Result of operation Status """ domain_dn = self.get_dn(domain) node_dn = 'DC=' + name + ',DC=' + domain + ',CN=MicrosoftDNS,DC=DomainDnsZones,' + domain_dn try: logging.debug('[D] About to remove DNS node at dn: %s' % node_dn) self._conn.delete(node_dn) logging.debug('[D] LDAP Result: ' + self._conn.result['description']) except Exception: logging.error('[-] Unexpected search error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False if self._conn.result['description'] == 'noSuchObject': print '|[*] The specified object does not exist.' return False else: print '[*] The object was successfully removed.' return True def add_dns_node(self, domain, dns_server, name, attacker_ip): """ Adds a DNS node. Returns Boolean Status """ domain_dn = self.get_dn(domain) node_dn = 'DC=' + name + ',DC=' + domain + ',CN=MicrosoftDNS,DC=DomainDnsZones,' + domain_dn objectClass = ['top', 'dnsNode'] dnsRecord = DNSRecord(attacker_ip, dns_server, domain) attributes = { 'dNSTombstoned': True, 'dnsRecord': dnsRecord.getRecord() } try: logging.debug('[D] About to add DNS node at dn: %s' % node_dn) self._conn.add(node_dn, objectClass, attributes) logging.debug('[D] LDAP Result: ' + self._conn.result['description']) except Exception: logging.error('[-] Unexpected search error.') logging.debug('[D] Observe the following traceback:') logging.debug(traceback.format_exc()) return False if self._conn.result['description'] == 'noSuchObject': print '[*] The specified object does not exist.' return False else: print '[*] The object was successfully added.' return True
class OpenLDAPSession(object): __slots__ = ['uri', 'basedn', 'manager', 'passwd', 'ou_people', 'ou_groups', 'ou_hosts', 'ou_host_groups', 'ou_commands', 'ou_command_groups', 'ou_services', 'server', 'connection', 'event_handlers'] EVENT_ON_USER_CREATED = 'on_user_created' EVENT_ON_USER_MODIFIED = 'on_user_modified' EVENT_ON_USER_DELETED = 'on_user_deleted' EVENT_ON_GROUP_CREATED = 'on_group_created' EVENT_ON_GROUP_MODIFIED = 'on_group_modified' EVENT_ON_GROUP_DELETED = 'on_group_deleted' EVENT_ON_HOST_CREATED = 'on_host_created' EVENT_ON_HOST_MODIFIED = 'on_host_modified' EVENT_ON_HOST_DELETED = 'on_host_deleted' EVENT_ON_HOSTGROUP_CREATED = 'on_hostgroup_created' EVENT_ON_HOSTGROUP_MODIFIED = 'on_hostgroup_modified' EVENT_ON_HOSTGROUP_DELETED = 'on_hostgroup_deleted' EVENT_ON_COMMAND_CREATED = 'on_command_created' EVENT_ON_COMMAND_MODIFIED = 'on_command_modified' EVENT_ON_COMMAND_DELETED = 'on_command_deleted' EVENT_ON_COMMANDGROUP_CREATED = 'on_commandgroup_created' EVENT_ON_COMMANDGROUP_MODIFIED = 'on_commandgroup_modified' EVENT_ON_COMMANDGROUP_DELETED = 'on_commandgroup_deleted' EVENT_ON_SERVICE_CREATED = 'on_service_created' EVENT_ON_SERVICE_MODIFIED = 'on_service_modified' EVENT_ON_SERVICE_DELETED = 'on_service_deleted' def __init__(self, uri, basedn, manager, passwd, start_tls=False, ou_people='people', ou_groups='groups', ou_hosts='hosts', ou_host_groups='hostGroups', ou_commands='commands', ou_command_groups='commandGroups', ou_services='services', event_handlers=None): self.uri = uri self.basedn = basedn self.manager = manager self.passwd = passwd self.ou_people = ou_people self.ou_groups = ou_groups self.ou_hosts = ou_hosts self.ou_host_groups = ou_host_groups self.ou_commands = ou_commands self.ou_command_groups = ou_command_groups self.ou_services = ou_services self.server = Server(self.uri, get_info=NONE) self.connection = Connection(self.server, user=self.manager, password=self.passwd, auto_bind=AUTO_BIND_TLS_BEFORE_BIND if start_tls else True) self.event_handlers = event_handlers if self.event_handlers is not None: for handler in self.event_handlers: handler.session = self def __enter__(self): self.connection.__enter__() return self def __exit__(self, exc_type, exc_val, exc_tb): self.connection.__exit__(exc_type, exc_val, exc_tb) @property def _placeholder(self): return 'cn=null,' + self.basedn @staticmethod def _get_entry_value(entry, key): if not hasattr(entry, key): return None return entry[key].value @staticmethod def _epoch_days(): """ days from 1970-01-01 :return: int """ return (time.utcnow() - datetime(1970, 1, 1)).days @staticmethod def _get_date_from_epoch_days(epoch): """ get date from epoch days :param epoch: epoch days :return: """ return (datetime(1970, 1, 1) + timedelta(days=epoch)).date() def _event_handler(self, event, *args, **kwargs): if self.event_handlers is not None: for event_handler in self.event_handlers: try: getattr(event_handler, event)(*args, **kwargs) except: logger.error_traceback(LOGGER_NAME) def construct_skeleton(self): """ Build OpenLDAP skeleton :return: """ for ou in self.ou_people, self.ou_groups, self.ou_hosts, self.ou_host_groups, self.ou_commands, self.ou_command_groups, self.ou_services: if not self.connection.search(self.basedn, '(ou=%s)' % ou): self.connection.add(','.join(('ou=' + ou, self.basedn)), ['organizationalUnit'], {'ou': ou}) def assemble_dn(self, cn, ou): return ','.join(('cn=' + cn, 'ou=' + ou, self.basedn)) def assemble_user_dn(self, cn): return self.assemble_dn(cn, self.ou_people) def assemble_group_dn(self, cn): return self.assemble_dn(cn, self.ou_groups) def assemble_host_dn(self, cn): return self.assemble_dn(cn, self.ou_hosts) def assemble_host_group_dn(self, cn): return self.assemble_dn(cn, self.ou_host_groups) def assemble_command_dn(self, cn): return self.assemble_dn(cn, self.ou_commands) def assemble_command_group_dn(self, cn): return self.assemble_dn(cn, self.ou_command_groups) def assemble_service_dn(self, cn): return self.assemble_dn(cn, self.ou_services) @staticmethod def extract_cn(dn): return dn.split(',')[0].split('=')[1] def search(self, search_base, search_filter, search_scope=SUBTREE, attributes=None): return self.connection.search( search_base=search_base, search_filter=search_filter, search_scope=search_scope, attributes=attributes, ), self.connection.entries def search_common(self, search_base, search_filter, search_scope=SUBTREE, attributes=None): success, entries = self.search(search_base, search_filter, search_scope=search_scope, attributes=attributes) ret = [] if success: for entry in entries: obj = {'dn': entry.entry_dn} for k, v in entry.__dict__.items(): try: obj[k] = v.value except AttributeError: pass ret.append(obj) return ret def search_users(self, search_filter, attributes=None): return self.search_common(','.join(('ou=' + self.ou_people, self.basedn)), search_filter, attributes=attributes) def search_groups(self, search_filter, attributes=None): return self.search_common(','.join(('ou=' + self.ou_groups, self.basedn)), search_filter, attributes=attributes) def search_hosts(self, search_filter, attributes=None): return self.search_common(','.join(('ou=' + self.ou_hosts, self.basedn)), search_filter, attributes=attributes) def search_host_groups(self, search_filter, attributes=None): results = self.search_common(','.join(('ou=' + self.ou_host_groups, self.basedn)), search_filter, attributes=attributes) if 'uniqueMember' in attributes: for result in results: if isinstance(result['uniqueMember'], six.string_types): if result['uniqueMember'] == self._placeholder: result['uniqueMember'] = [] else: result['uniqueMember'] = [result['uniqueMember']] elif isinstance(result['uniqueMember'], collections.Iterable): result['uniqueMember'] = list(filter(lambda x: x != self._placeholder, result['uniqueMember'])) return results def search_commands(self, search_filter, attributes=None): results = self.search_common(','.join(('ou=' + self.ou_commands, self.basedn)), search_filter, attributes=attributes) if 'sudoCommand' in attributes: for result in results: if isinstance(result['sudoCommand'], six.string_types): result['sudoCommand'] = [result['sudoCommand']] return results def search_command_groups(self, search_filter, attributes=None): results = self.search_common(','.join(('ou=' + self.ou_command_groups, self.basedn)), search_filter, attributes=attributes) if 'uniqueMember' in attributes: for result in results: if isinstance(result['uniqueMember'], six.string_types): if result['uniqueMember'] == self._placeholder: result['uniqueMember'] = [] else: result['uniqueMember'] = [result['uniqueMember']] elif isinstance(result['uniqueMember'], collections.Iterable): result['uniqueMember'] = list(filter(lambda x: x != self._placeholder, result['uniqueMember'])) return results def search_services(self, search_filter, attributes=None): results = self.search_common(','.join(('ou=' + self.ou_services, self.basedn)), search_filter, attributes=attributes) if 'authorizedService' in attributes: for result in results: if isinstance(result['authorizedService'], six.string_types): result['authorizedService'] = [result['authorizedService']] return results def get(self, dn, attributes=None): objs = self.search_common(dn, '(objectClass=*)', search_scope=BASE, attributes=attributes) return objs[0] if len(objs) > 0 else None def get_user(self, cn, attributes=None): users = self.search_users(search_filter='(&(objectClass=person)(cn=%s))' % cn, attributes=attributes) return users[0] if len(users) > 0 else None def get_group(self, cn, attributes=None): groups = self.search_groups(search_filter='(&(objectClass=posixGroup)(cn=%s))' % cn, attributes=attributes) return groups[0] if len(groups) > 0 else None def get_host(self, cn, attributes=None): hosts = self.search_hosts(search_filter='(&(objectClass=device)(cn=%s))' % cn, attributes=attributes) return hosts[0] if len(hosts) > 0 else None def get_host_group(self, cn, attributes=None): host_groups = self.search_host_groups(search_filter='(&(objectClass=groupOfUniqueNames)(cn=%s))' % cn, attributes=attributes) return host_groups[0] if len(host_groups) > 0 else None def get_command(self, cn, attributes=None): commands = self.search_commands(search_filter='(&(objectClass=sudoRole)(cn=%s))' % cn, attributes=attributes) return commands[0] if len(commands) > 0 else None def get_command_group(self, cn, attributes=None): command_groups = self.search_command_groups(search_filter='(&(objectClass=groupOfUniqueNames)(cn=%s))' % cn, attributes=attributes) return command_groups[0] if len(command_groups) > 0 else None def get_service(self, cn, attributes=None): services = self.search_services(search_filter='(&(objectClass=authorizedServiceObject)(cn=%s))' % cn, attributes=attributes) return services[0] if len(services) > 0 else None def add(self, dn, object_class=None, attributes=None, controls=None, event=None, skip_event_callback=False): result = self.connection.add(dn, object_class=object_class, attributes=attributes, controls=controls) if not skip_event_callback: self._event_handler(event, dn, object_class=object_class, attributes=attributes) return result def add_user(self, cn, sn, uid_number, gid_number=100, gecos=None, mail=None, display_name=None, shadow_min=None, shadow_max=None, shadow_inactive=None, shadow_warning=None, shadow_last_change=None, skip_event_callback=False): # check value if strings.is_blank(cn): raise Exception('cn cannot be blank') if strings.is_blank(sn): raise Exception('sn cannot be blank') if num.safe_int(uid_number) <= 1000: raise Exception('uidNumber should > 1000') attributes = { 'cn': cn, 'uid': cn, 'sn': sn, 'uidNumber': num.safe_int(uid_number), 'gidNumber': num.safe_int(gid_number), 'homeDirectory': '/home/' + cn, 'loginShell': '/bin/bash', 'userPassword': '******', 'sudoUser': cn, 'sudoHost': 'ALL', 'sudoOption': '!authenticate', } if gecos is not None: attributes['gecos'] = gecos if mail is not None: attributes['mail'] = mail if display_name is not None: attributes['displayName'] = display_name if shadow_min is not None: attributes['shadowMin'] = shadow_min if shadow_max is not None: attributes['shadowMax'] = shadow_max if shadow_inactive is not None: attributes['shadowInactive'] = shadow_inactive if shadow_warning is not None: attributes['shadowWarning'] = shadow_warning if shadow_last_change is not None: attributes['shadowLastChange'] = shadow_last_change # set 0 to force change password on the first login else: attributes['shadowLastChange'] = self._epoch_days() return self.add( dn=self.assemble_user_dn(cn), object_class=['top', 'posixAccount', 'shadowAccount', 'person', 'inetOrgPerson', 'hostObject', 'sudoRole', 'authorizedServiceObject'], attributes=attributes, event=self.EVENT_ON_USER_CREATED, skip_event_callback=skip_event_callback, ) def add_group(self, cn, gid_number, skip_event_callback=False): return self.add( dn=self.assemble_group_dn(cn), object_class=['top', 'posixGroup'], attributes={ 'gidNumber': gid_number }, event=self.EVENT_ON_GROUP_CREATED, skip_event_callback=skip_event_callback, ) def add_host(self, cn, cn_list=None, ip_host_number=None, skip_event_callback=False): attributes = {} if strings.is_blank(cn): raise Exception("host cn can't be blank") if cn_list is not None and not isinstance(cn_list, collections.Iterable): raise Exception("host cn_list should be iterable or None") cn_list = set(cn_list).add(cn) attributes['cn'] = cn if cn_list is None else cn_list if ip_host_number is not None: attributes['ipHostNumber'] = ip_host_number return self.add( dn=self.assemble_host_dn(cn), object_class=['top', 'device', 'ipHost'], attributes=attributes, event=self.EVENT_ON_HOST_CREATED, skip_event_callback=skip_event_callback, ) def add_host_group(self, cn, skip_event_callback=False): return self.add( dn=self.assemble_host_group_dn(cn), object_class=['top', 'groupOfUniqueNames'], attributes={ 'cn': cn, 'uniqueMember': [self._placeholder], }, event=self.EVENT_ON_HOSTGROUP_CREATED, skip_event_callback=skip_event_callback, ) def add_command(self, cn, sudo_command=None, skip_event_callback=False): attributes = { 'cn': cn, 'sn': cn, } if sudo_command is not None: attributes['sudoCommand'] = sudo_command return self.add( dn=self.assemble_command_dn(cn), object_class=['top', 'person', 'sudoRole'], attributes=attributes, event=self.EVENT_ON_COMMAND_CREATED, skip_event_callback=skip_event_callback, ) def add_command_group(self, cn, skip_event_callback=False): return self.add( dn=self.assemble_command_group_dn(cn), object_class=['top', 'groupOfUniqueNames'], attributes={ 'cn': cn, 'uniqueMember': [self._placeholder], }, event=self.EVENT_ON_COMMANDGROUP_CREATED, skip_event_callback=skip_event_callback, ) def add_service(self, cn, authorized_service=None, skip_event_callback=False): attributes = { 'cn': cn, 'sn': cn, } if authorized_service is not None: attributes['authorizedService'] = authorized_service return self.add( dn=self.assemble_service_dn(cn), object_class=['top', 'person', 'authorizedServiceObject'], attributes=attributes, event=self.EVENT_ON_SERVICE_CREATED, skip_event_callback=skip_event_callback, ) @staticmethod def _make_changes(legal_attrs, **other_attrs): changes = {} for attribute, v in other_attrs.items(): if not objects.contains(attribute, *legal_attrs): raise Exception('illegal attribute in "other_attrs": %s' % attribute) if v is None: changes[inflection.camelize(attribute, False)] = MODIFY_DELETE, [] else: if isinstance(v, six.string_types): changes[inflection.camelize(attribute, False)] = MODIFY_REPLACE, [v] elif isinstance(v, collections.Iterable): changes[inflection.camelize(attribute, False)] = MODIFY_REPLACE, v else: raise Exception('illegal type found for "%s"' % attribute) return changes def modify(self, dn, changes, controls=None, event=None, skip_event_callback=False): has_event = self.event_handlers is not None and event is not None and not skip_event_callback oldobject = None if has_event: oldobject = self.get(dn, ALL_ATTRIBUTES) result = self.connection.modify(dn, changes, controls=controls) if has_event: self._event_handler(event, dn, changes, oldobject=oldobject) return result def modify_user(self, cn, sn=None, uid_number=None, skip_event_callback=False, **other_attrs): changes = {} legal_attrs = ('gid_number', 'gecos', 'mail', 'display_name', 'shadow_min', 'shadow_max', 'shadow_inactive', 'shadow_warning', 'host', 'sudoCommand', 'authorizedService') if sn is not None: changes['sn'] = MODIFY_REPLACE, [sn] if uid_number is not None: changes['uidNumber'] = MODIFY_REPLACE, [uid_number] changes.update(self._make_changes(legal_attrs, **other_attrs)) return self.modify(self.assemble_user_dn(cn), changes, event=self.EVENT_ON_USER_MODIFIED, skip_event_callback=skip_event_callback) def modify_group(self, cn, gid_number=None, skip_event_callback=False, **other_attrs): changes = {} legal_attrs = ('memberUid', 'description') if gid_number is not None: changes['gidNumber'] = MODIFY_REPLACE, [gid_number] changes.update(self._make_changes(legal_attrs, **other_attrs)) return self.modify(self.assemble_group_dn(cn), changes, event=self.EVENT_ON_GROUP_MODIFIED, skip_event_callback=skip_event_callback) def modify_host(self, cn, skip_event_callback=False, **other_attrs): changes = {} cn_list = other_attrs.get('cn_list') if cn_list is not None: if not isinstance(cn_list, collections.Iterable): raise Exception("host cn_list should be iterable or None") cn_list = set(cn_list).add(cn) changes['cn'] = MODIFY_REPLACE, cn_list legal_attrs = ['ip_host_number'] new_other_attrs = copy.deepcopy(other_attrs) if hasattr(new_other_attrs, 'cn_list'): del new_other_attrs['cn_list'] changes.update(self._make_changes(legal_attrs, **new_other_attrs)) return self.modify(self.assemble_host_dn(cn), changes, event=self.EVENT_ON_HOST_MODIFIED, skip_event_callback=skip_event_callback) def modify_host_group(self, cn, unique_member=None, skip_event_callback=False): changes = {} if unique_member is not None: if not (isinstance(unique_member, collections.Iterable) and isinstance(unique_member, collections.Sized)): raise Exception("host group unique_member should be iterable or None") if len(unique_member) == 0: changes['uniqueMember'] = MODIFY_REPLACE, [self._placeholder] else: changes['uniqueMember'] = MODIFY_REPLACE, list(map(lambda x: self.assemble_host_dn(x), unique_member)) return self.modify(self.assemble_host_group_dn(cn), changes, event=self.EVENT_ON_HOSTGROUP_MODIFIED, skip_event_callback=skip_event_callback) def modify_command(self, cn, skip_event_callback=False, **other_attrs): return self.modify( self.assemble_command_dn(cn), self._make_changes(['sudoCommand'], **other_attrs), event=self.EVENT_ON_COMMAND_MODIFIED, skip_event_callback=skip_event_callback ) def modify_command_group(self, cn, unique_member=None, skip_event_callback=False): changes = {} if unique_member is not None: if not (isinstance(unique_member, collections.Iterable) and isinstance(unique_member, collections.Sized)): raise Exception("command group unique_member should be iterable or None") if len(unique_member) == 0: changes['uniqueMember'] = MODIFY_REPLACE, [self._placeholder] else: changes['uniqueMember'] = MODIFY_REPLACE, list(map(lambda x: self.assemble_command_dn(x), unique_member)) return self.modify(self.assemble_command_group_dn(cn), changes, event=self.EVENT_ON_COMMANDGROUP_MODIFIED, skip_event_callback=skip_event_callback) def modify_service(self, cn, skip_event_callback=False, **other_attrs): return self.modify( self.assemble_service_dn(cn), self._make_changes(['authorizedService'], **other_attrs), event=self.EVENT_ON_SERVICE_MODIFIED, skip_event_callback=skip_event_callback ) def delete(self, dn, controls=None, event=None, skip_event_callback=False): has_event = self.event_handlers is not None and event is not None and not skip_event_callback oldobject = None if has_event: oldobject = self.get(dn, ALL_ATTRIBUTES) result = self.connection.delete(dn, controls=controls) if has_event: self._event_handler(event, dn, oldobject=oldobject) return result def delete_user(self, cn, skip_event_callback=False): return self.delete(self.assemble_user_dn(cn), event=self.EVENT_ON_USER_DELETED, skip_event_callback=skip_event_callback) def delete_group(self, cn, skip_event_callback=False): return self.delete(self.assemble_group_dn(cn), event=self.EVENT_ON_GROUP_DELETED, skip_event_callback=skip_event_callback) def delete_host(self, cn, skip_event_callback=False): dn = self.assemble_host_dn(cn) result = self.delete(dn, event=self.EVENT_ON_HOST_DELETED, skip_event_callback=skip_event_callback) host_groups = self.search_host_groups('(uniqueMember=%s)' % self.assemble_host_dn(cn), attributes=['cn', 'uniqueMember']) for host_group in host_groups: _cn = host_group.get('cn') host_group.get('uniqueMember').remove(dn) self.modify_host_group(_cn, list(map(lambda x: self.extract_cn(x), host_group.get('uniqueMember'))), skip_event_callback=True) return result def delete_host_group(self, cn, skip_event_callback=False): return self.delete(self.assemble_host_group_dn(cn), event=self.EVENT_ON_HOSTGROUP_DELETED, skip_event_callback=skip_event_callback) def delete_command(self, cn, skip_event_callback=False): dn = self.assemble_command_dn(cn) result = self.delete(dn, event=self.EVENT_ON_COMMAND_DELETED, skip_event_callback=skip_event_callback) command_groups = self.search_command_groups('(uniqueMember=%s)' % self.assemble_command_dn(cn), attributes=['cn', 'uniqueMember']) for command_group in command_groups: _cn = command_group.get('cn') command_group.get('uniqueMember').remove(dn) self.modify_command_group(_cn, list(map(lambda x: self.extract_cn(x), command_group.get('uniqueMember')))) return result def delete_command_group(self, cn, skip_event_callback=False): return self.delete(self.assemble_command_group_dn(cn), event=self.EVENT_ON_COMMANDGROUP_DELETED, skip_event_callback=skip_event_callback) def delete_service(self, cn, skip_event_callback=False): return self.delete(self.assemble_service_dn(cn), event=self.EVENT_ON_SERVICE_DELETED, skip_event_callback=skip_event_callback) def reset_password(self, cn, new_password=None, shadow_last_change=None): hashed_password = hashed(HASHED_SALTED_SHA, new_password) self.connection.modify( self.assemble_user_dn(cn), { 'userPassword': [(MODIFY_REPLACE, [hashed_password])], 'shadowLastChange': [(MODIFY_REPLACE, shadow_last_change if shadow_last_change is not None else self._epoch_days())], } )
class LDAP(object): """ldap """ _server_name = '192.168.10.98' _port = 389 _user = '******' _password = '******' def __init__(self): self.s = Server(host=self._server_name, port=self._port, use_ssl=False, get_info='ALL') self.c = Connection(self.s, user=self._user, password=self._password) if not self.c.bind(): raise LDAPBindError('bind() error') def unbind(self): return self.c.unbind() def set_password(self): """随机生成10为的密码 """ return gen_password(10) def _get_current_uid(self): """获取ou=people里面最大的uidnumber 为后面的添加提供uidNumber """ search_base = 'ou=Yuanli,dc=chuangyu,dc=com' search_filter = '(objectClass=*)' attributes = ['uidNumber'] result = self.c.search(search_base=search_base, search_scope='LEVEL', search_filter=search_filter, attributes=attributes) if not result: raise LDAPExceptionError('获取people最大uid失败!') try: return max([x['attributes']['uidNumber'] for x in self.c.response]) except: return 0 def add_people_ou(self, uid, gid, userPassword=None): """给ou=people添加记录 可以提供密码,如果不提供,随机生成一个 """ dn = 'uid={},ou=Yuanli,dc=chuangyu,dc=com'.format(uid) object_class = ['account', 'posixAccount', 'top'] uidNumber = str(self._get_current_uid() + 1) homeDirectory = '{}@chuangyunet.com'.format(uid) if userPassword is None: userPassword = self.set_password() attrs = { 'cn': uid, 'gidNumber': str(gid), 'homeDirectory': homeDirectory, 'uid': uid, 'uidNumber': uidNumber, 'userPassword': userPassword } return self.c.add(dn, object_class, attrs) def get_group_dn_by_gid(self, gid): """通过gid来找到ou=group的dn名称 """ search_base = 'ou=Yuanli,dc=chuangyu,dc=com' search_scope = 'SUBTREE' search_filter = '(objectClass=*)' attributes = ['gidNumber'] result = self.c.search(search_base=search_base, search_scope=search_scope, search_filter=search_filter, attributes=attributes) if not result: search_base = 'ou=People,dc=chuangyu,dc=com' result = self.c.search(search_base=search_base, search_scope=search_scope, search_filter=search_filter, attributes=attributes) if not result: msg = '查找gid{} ou=group的dn失败'.format(gid) raise LDAPExceptionError(msg) for x in self.c.response: gidNumber = x['attributes']['gidNumber'] if gidNumber and gidNumber == gid: return x['dn'] else: msg = '查找gid{} ou=group的dn失败'.format(gid) raise LDAPExceptionError(msg) def get_user_gid(self, dn): """获取一个dn也就是ou=Yuanli的用户的gid """ search_scope = 'SUBTREE' search_filter = '(objectClass=*)' attributes = ['gidNumber'] result = self.c.search(search_base=dn, search_scope=search_scope, search_filter=search_filter, attributes=attributes) if not result: raise LDAPExceptionError('该账号不存在'.format(dn)) response = self.c.response if response: return response[0]['attributes']['gidNumber'] else: raise LDAPExceptionError('记录{}没有gidNumber'.format(dn)) def add_group_ou(self, gid, uid): """给ou=group添加记录 先根据gid找到dn 然后在添加uid uid接收list和字符串的格式 """ if isinstance(uid, list): list_uid = uid else: list_uid = [uid] dn = self.get_group_dn_by_gid(gid) modify_attr = {'memberUid': ('MODIFY_ADD', list_uid)} return self.c.modify(dn, modify_attr) def delete_people_ou(self, uid): """删除ou=people的dn """ dn = 'uid={},ou=People,dc=chuangyu,dc=com'.format(uid) return self.c.delete(dn) def delete_yuanli_ou(self, uid): """删除ou=yuanli的dn """ dn = 'uid={},ou=Yuanli,dc=chuangyu,dc=com'.format(uid) return self.c.delete(dn) def delete_group_ou(self, gid, uid, dn=None): """删除ou=group里面的某个记录 如果有dn,则不用根据gid查找 不然,需要先根据gid查找出dn uid接收list或者单个字符串, 最后全部转化为list形式 """ if isinstance(uid, list): list_uid = uid else: list_uid = [uid] modify_attr = {'memberUid': ('MODIFY_DELETE', list_uid)} if dn is not None: dn = dn else: dn = self.get_group_dn_by_gid(gid) return self.c.modify(dn, modify_attr) def change_user_password(self, uid, new_password): """修改用户密码""" dn = 'uid={},ou=Yuanli,dc=chuangyu,dc=com'.format(uid) result = self.c.modify( dn, {'userPassword': [(MODIFY_REPLACE, [new_password])]}) if not result: dn = 'uid={},ou=People,dc=chuangyu,dc=com'.format(uid) result = self.c.modify( dn, {'userPassword': [(MODIFY_REPLACE, [new_password])]}) return result
class Ldap: def __init__(self): self.ldap_server = Server( app.config["LDAP_CONFIG"].get('host'), get_info=ALL, connect_timeout=20 ) self.connection = Connection( self.ldap_server, user=app.config["LDAP_CONFIG"].get('user'), password=app.config["LDAP_CONFIG"].get('password'), auto_bind=True ) def search(self, search_dn, search_filter, attributes): """ Ldap 服务器数据查询接口 :param search_dn: :param search_filter: :param attributes: :return: """ self.connection.search(search_dn, search_filter, attributes=attributes) response = [] for i in self.connection.entries: data = json.loads(i.entry_to_json()) response.append(data["attributes"]) self.connection.unbind() return response @staticmethod def auth(user_name, password): """ Ldap 用户密码认证接口 :param user_name: :param password: :return: """ ldap_user_dn = "uid=" + user_name + "," + app.config["LDAP_CONFIG"].get('people_dn') ldap_server = Server(app.config["LDAP_CONFIG"].get('host'), get_info=ALL) ldap_connect = Connection(ldap_server, user=ldap_user_dn, password=password) return ldap_connect.bind() def add(self, dn, _class, attributes): """ LDAP 添加数据 :param dn: 唯一DN :param _class: objectClass 对象列表 :param attributes: json格式数据 :return: 布尔值 """ try: self.connection.add(dn, _class, attributes) return True except Exception as Error: logging.error(Error) return False def delete(self, dn): """ LDAP 删除数据 :param dn: 唯一DN :return: """ try: self.connection.delete(dn) return True except Exception as Error: logging.error(Error) return False def modify(self, dn, data): """ LDAP 数据DN数据,可新增/删除/修改 :param dn: 唯一DN :param data: json格式数据 {'host': [(MODIFY_DELETE, [host])]} ; MODIFY_DELETE/MODIFY_ADD/MODIFY_REPLACE :return: """ try: self.connection.modify(dn, data) return True except Exception as Error: logging.error(Error) return False
# -*- coding: utf-8 -*- from ldap3 import Connection, Server, ALL, MODIFY_REPLACE # define the server s = Server('172.30.1.197', port=636, use_ssl=True, get_info=ALL) admin_username = raw_input('Enter Admin Username....') admin_password = raw_input('Enter Admin Password....') #admin_username = '******' #admin_password = '******' # define the connection c = Connection(s, user=admin_username, password=admin_password) c.bind() c.start_tls() if not c.bind(): print 'Admin username/password Wrong' else: username = raw_input('Enter Username to delete...') dn = 'cn= %s,ou=Police,dc=naanal,dc=local' % username c.delete(dn) print c.result c.unbind()
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])
# 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: sure = input("type y or n : > ") if sure == 'n': pass else: conn.delete('cn='+to_delete+', ou=students, dc=security, dc=ch') print(conn.result) # If client wants to modify an entry elif operation == '4' or operation == 'm': to_modify, attr_to_modify, val_to_modify = [], [], [] str = 'y' to_modify.append(input("Which user would you like to modify? > ")) print("We will get the attributes and values you want to modify, once you are done, type \'n\'.") while str != 'n': attr_to_modify.append(input("Attribute to modify: > ")) val_to_modify.append(input("Value to modify: > ")) str = input("Continue? y/n > ") for i in range(len(attr_to_modify)):
class LdapFunctions: def __init__(self): self.server_ip = '54.36.182.216' self.ldap_username = '******' self.ldap_password = '******' self.ldap_server = Server(self.server_ip, get_info=ALL) self.conn = Connection(self.server_ip, self.ldap_username, self.ldap_password, auto_bind=True) def connect(self): self.conn.bind() def disconnect(self): self.conn.unbind() def get_users(self): try: self.connect() self.conn.search('ou=People,dc=insat,dc=chat,dc=com', '(&(objectclass=inetOrgPerson)(!(uid=0001)))', attributes=['*']) users = [] for entry in self.conn.entries: user = json.loads(entry.entry_to_json()) if('userSMIMECertificate' in user['attributes']): L = ast.literal_eval(user['attributes']['userSMIMECertificate'][0]) user['attributes']['userCertificate'] = str(np.array(L, dtype='int8').tobytes()) users.append(user) self.disconnect() return users except ldap3.LDAPError: self.conn.unbind() return 'authentication error' def get_user(self, username=None): if(username == None): return {} try: self.connect() #attributes=['cn', 'sn'] self.conn.search('ou=People,dc=insat,dc=chat,dc=com', '(&(objectclass=inetOrgPerson)(sn=' + username + ')(!(uid=0001)))', attributes=['displayName', 'uid', 'givenName', 'userPKCS12', 'sn', 'userSMIMECertificate']) if(not self.conn.entries): return "User doesn't exist" else: user = json.loads(self.conn.entries[0].entry_to_json()) if('userSMIMECertificate' in user['attributes']): L = ast.literal_eval(user['attributes']['userSMIMECertificate'][0]) user['attributes']['userCertificate'] = str(np.array(L, dtype='int8').tobytes()) self.disconnect() return user except ldap3.LDAPError: self.conn.unbind() return 'authentication error' def login(self, username, password): try: self.connect() m = hashlib.sha256(str(password).encode('utf-8')) hashed_pass = m.hexdigest() self.conn.search('ou=People,dc=insat,dc=chat,dc=com', '(&(objectclass=inetOrgPerson)(sn=' + username + ')(userPassword='******'))', attributes=['sn', 'displayName', 'givenName', 'uid', 'userPKCS12', 'userSMIMECertificate']) if(not self.conn.entries): return None else: user = json.loads(self.conn.entries[0].entry_to_json()) print(user) if('userSMIMECertificate' in user['attributes']): L = ast.literal_eval(user['attributes']['userSMIMECertificate'][0]) user['attributes']['userCertificate'] = str(np.array(L, dtype='int8').tobytes()) self.disconnect() return user except ldap3.LDAPError: self.conn.unbind() return 'authentication error' def add_user(self): try: self.connect() user = json.loads(request.data) #print(user) #before adding i need to select by username to make sure it's unique username = user['sn'] self.conn.search('ou=People,dc=insat,dc=chat,dc=com', '(&(objectclass=inetOrgPerson)(sn=' + username + '))', attributes=['sn']) if(self.conn.entries): self.conn.unbind() return "User already exists" else: m = hashlib.sha256(str(user['userPassword']).encode('utf-8')) hashed_pass = m.hexdigest() res = self.conn.add( 'cn=' + user['sn'] + ',ou=People,dc=insat,dc=chat,dc=com', attributes={ "objectClass": "inetOrgPerson", "sn": user['sn'], "uid": user['uid'], "givenName": user['givenName'], "displayName": user['displayName'], "userPassword": hashed_pass, "userPKCS12": user['userPKCS12'], #"userCertificate": }, ) self.conn.unbind() if(res): return 'User added successfully' else: return 'User already exists' except ldap3.LDAPError: self.conn.unbind() return 'Authentication error' def modify_user(self, username): try: self.connect() user = json.loads(request.data) #print(user) m = hashlib.sha256(str(user['userPassword']).encode('utf-8')) hashed_pass = m.hexdigest() edits = {} if 'displayName' in user: edits['displayName'] = [(MODIFY_REPLACE, user['displayName'])] if 'givenName' in user: edits['givenName'] = [(MODIFY_REPLACE, user['givenName'])] if 'uid' in user: edits['uid'] = [(MODIFY_REPLACE, user['uid'])] if 'userPKCS12' in user: edits['userPKCS12'] = [(MODIFY_REPLACE, user['userPKCS12'])] if 'userPassword' in user: edits['userPassword'] = [(MODIFY_REPLACE, hashed_pass)] res = self.conn.modify( dn='cn=' + username + ',ou=People,dc=insat,dc=chat,dc=com', changes=edits, ) self.conn.unbind() if(res): return 'User edited succefully' else: return 'An error has occured' except ldap3.LDAPError: self.conn.unbind() return 'Authentication error' def delete_user(self, username=None): if(username == None): return 'An error has occured' try: self.connect() res = self.conn.delete('cn=' + username + ',ou=People,dc=insat,dc=chat,dc=com') self.conn.unbind() if(res): return 'User deleted succefully' else: return 'An error has occured' except ldap3.LDAPError: self.conn.unbind() return 'Authentication error'
class Test(unittest.TestCase): def setUp(self): global testcase_id testcase_id = random_id() 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 compatibility 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.replace('\r\n', '').replace(' ', '') 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.replace('\r\n', '').replace(' ', '') 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.replace('\r\n', '').replace(' ', '') 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.replace('\r\n', '').replace(' ', '') 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.replace('\r\n', '').replace(' ', '') 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.replace('\r\n', '').replace(' ', '') 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])
class LdapOLC(object): def __init__(self, addr, binddn, passwd): self.addr = addr self.binddn = binddn self.passwd = passwd self.server = None self.conn = None self.hostname = get_host_port(addr)[0] def connect(self): logger.debug("Making Ldap Connection") self.server = Server(self.addr, use_ssl=True) self.conn = Connection(self.server, user=self.binddn, password=self.passwd) return self.conn.bind() def loadModules(self, *modules): """If modules are loaded, returns status, If modules are already loaded returns -1""" self.conn.search(search_base='cn=module{0},cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcModuleLoad"]) addList = list(modules) if self.conn.response: for a in self.conn.response[0]['attributes']['olcModuleLoad']: r = re.split("{\d+}", a) if len(r) == 1: m = r[0] else: m = r[1] mn = m.split('.') if mn[0] in addList: addList.remove(mn[0]) if addList: return self.conn.modify('cn=module{0},cn=config', {'olcModuleLoad': [MODIFY_ADD, addList]}) return -1 def checkAccesslogDBEntry(self): return self.conn.search(search_base='cn=config', search_filter='(olcSuffix=cn=accesslog)', search_scope=SUBTREE, attributes=["*"]) def accesslogDBEntry(self, replicator_dn, log_dir="/opt/gluu/data/accesslog"): attributes = { 'objectClass': ['olcDatabaseConfig', 'olcMdbConfig'], 'olcDatabase': '{2}mdb', 'olcDbDirectory': log_dir, 'OlcDbMaxSize': 1073741824, 'olcSuffix': 'cn=accesslog', 'olcRootDN': 'cn=admin, cn=accesslog', 'olcRootPW': ldap_encode(self.passwd), 'olcDbIndex': [ 'default eq', 'objectClass,entryCSN,entryUUID,reqEnd,reqResult,reqStart' ], 'olcLimits': 'dn.exact="{0}" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited' .format(replicator_dn), } if not self.checkAccesslogDBEntry(): return self.conn.add('olcDatabase={2}mdb,cn=config', attributes=attributes) def checkSyncprovOverlaysDB1(self): return self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(olcOverlay=syncprov)', search_scope=SUBTREE, attributes=["*"]) def syncprovOverlaysDB1(self): attributes = { 'objectClass': ['olcOverlayConfig', 'olcSyncProvConfig'], 'olcOverlay': 'syncprov', # 'olcSpNoPresent': 'TRUE', ??? 'olcSpReloadHint': 'TRUE', 'olcSpCheckPoint': '100 10', 'olcSpSessionlog': '10000', } if not self.checkSyncprovOverlaysDB1(): self.conn.add('olcOverlay=syncprov,olcDatabase={1}mdb,cn=config', attributes=attributes) if self.conn.result['description'] == 'success': return True def checkSyncprovOverlaysDB2(self): return self.conn.search(search_base='olcDatabase={2}mdb,cn=config', search_filter='(olcOverlay=syncprov)', search_scope=SUBTREE, attributes=["*"]) def syncprovOverlaysDB2(self): attributes = { 'objectClass': ['olcOverlayConfig', 'olcSyncProvConfig'], # 'structuralObjectClass': ['olcSyncProvConfig'], 'olcOverlay': 'syncprov', 'olcSpNoPresent': 'TRUE', 'olcSpReloadHint': 'TRUE', # 'olcSpCheckPoint': '100 10', # 'olcSpSessionlog': '10000', # 'olcLimits': 'dn.exact="cn=directory manager,o=gluu" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited', } if not self.checkSyncprovOverlaysDB2(): self.conn.add('olcOverlay=syncprov,olcDatabase={2}mdb,cn=config', attributes=attributes) if self.conn.result['description'] == 'success': return True def checkServerID(self): return self.conn.search(search_base='cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcServerID"]) def setServerID(self, sid): mod_type = MODIFY_ADD self.conn.search(search_base='cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcServerID"]) if self.checkServerID(): if self.conn.response[0]['attributes']['olcServerID']: mod_type = MODIFY_REPLACE return self.conn.modify('cn=config', {'olcServerID': [mod_type, str(sid)]}) def setDBIndexes(self): self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcDbIndex"]) addList = ["entryCSN eq", "entryUUID eq"] if self.conn.response: for idx in self.conn.response[0]['attributes']['olcDbIndex']: if idx in addList: addList.remove(idx) return self.conn.modify('olcDatabase={1}mdb,cn=config', {'olcDbIndex': [MODIFY_ADD, addList]}) def checkAccesslogPurge(self): return self.conn.search( search_base='cn=config', search_filter='(objectClass=olcAccessLogConfig)', search_scope=SUBTREE, attributes=["olcAccessLogPurge"]) def accesslogPurge(self): attributes = { 'objectClass': ['olcOverlayConfig', 'olcAccessLogConfig'], 'olcOverlay': 'accesslog', 'olcAccessLogDB': 'cn=accesslog', 'olcAccessLogOps': 'writes', 'olcAccessLogSuccess': 'TRUE', 'olcAccessLogPurge': '07+00:00 01+00:00', } if not self.checkAccesslogPurge(): return self.conn.add( 'olcOverlay=accesslog,olcDatabase={1}mdb,cn=config', attributes=attributes) def removeMirrorMode(self): self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcMirrorMode"]) if not self.conn.response: return if self.conn.response[0]['attributes']['olcMirrorMode']: return self.conn.modify('olcDatabase={1}mdb,cn=config', {"olcMirrorMode": [MODIFY_REPLACE, []]}) def checkMirroMode(self): r = self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcMirrorMode"]) if r: if self.conn.response[0]['attributes']: if self.conn.response[0]['attributes']['olcMirrorMode']: return self.conn.response[0]['attributes']['olcMirrorMode'] return False def makeMirroMode(self): return self.conn.modify('olcDatabase={1}mdb,cn=config', {"olcMirrorMode": [MODIFY_ADD, ["TRUE"]]}) def removeProvider(self, raddr): rmMirrorMode = False if len(self.getProviders()) <= 1: rmMirrorMode = True if not self.conn.response: return -1 for pr in self.conn.response: if pr["attributes"]["olcSyncrepl"]: for pri in pr["attributes"]["olcSyncrepl"]: for l in pri.split(): ls = l.split('=') if ls[0] == 'provider': if ls[1] == raddr: baseDn = pr['dn'] r = self.conn.modify( baseDn, {'olcSyncrepl': [MODIFY_DELETE, [pri]]}) if r: if rmMirrorMode: self.removeMirrorMode() return r def add_provider(self, rid, raddr, rbinddn, rcredentials): ridText = ( 'rid={0} provider={1} bindmethod=simple binddn="{2}" ' 'tls_reqcert=never credentials={3} searchbase="o=gluu" ' 'logbase="cn=accesslog" ' #'filter=(&(objectClass=*)(!(ou:dn:=appliances))) ' 'logfilter="(&(objectClass=auditWriteObject)(reqResult=0))" ' 'schemachecking=on type=refreshAndPersist retry="60 +" ' 'syncdata=accesslog sizeLimit=unlimited ' 'timelimit=unlimited'.format(rid, raddr, rbinddn, rcredentials)) self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcSyncRepl"]) # delete the entry if a syncrepl config exists for the same rid entry = self.conn.entries[0] for rep in entry["olcSyncRepl"]: if 'rid={0}'.format(rid) in rep: lmod = {"olcSyncRepl": [(MODIFY_DELETE, [rep])]} self.conn.modify('olcDatabase={1}mdb,cn=config', lmod) break mod = {"olcSyncRepl": [(MODIFY_ADD, [ridText])]} return self.conn.modify('olcDatabase={1}mdb,cn=config', mod) def checkAccesslogDB(self): return self.conn.search(search_base='cn=config', search_filter='(olcSuffix=cn=accesslog)', search_scope=SUBTREE, attributes=["*"]) def addTestUser(self, cn, sn, mail): self.checkBaseDN() self.checkTestUserBase() uid = '{0}@{1}'.format(time.time(), self.hostname) dn = "uid={0},ou=testusers,o=gluu".format(uid) return self.conn.add(dn, attributes={ 'objectClass': ['top', 'inetOrgPerson'], "cn": cn, 'mail': mail, 'sn': sn, 'title': 'gluuClusterMgrTestUser', 'uid': uid }) def checkTestUserBase(self): if not self.conn.search(search_base='ou=testusers,o=gluu', search_filter='(objectClass=inetOrgPerson)', search_scope=BASE, attributes='*'): self.conn.add('ou=testusers,o=gluu', attributes={ 'objectClass': ['top', 'organizationalUnit'], 'ou': 'testusers', }) def searchTestUsers(self): return self.conn.search(search_base='ou=testusers,o=gluu', search_filter='(title=gluuClusterMgrTestUser)', search_scope=LEVEL, attributes='*') def delDn(self, dn): return self.conn.delete(dn) def getProviders(self): pDict = {} if self.conn.search(search_base='olcDatabase={1}mdb,cn=config', search_filter='(objectClass=*)', search_scope=BASE, attributes=["olcSyncRepl"]): for pe in self.conn.response[0]['attributes']['olcSyncrepl']: for e in pe.split(): es = e.split("=") if re.search('(\{\d*\})*rid', es[0]): pid = es[1] elif es[0] == 'provider': host, port = get_host_port(es[1]) dkey = host if re.match(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$", host): dkey = get_hostname_by_ip(host) pDict[dkey] = (pid, port, host) return pDict def getMMRStatus(self): retDict = {} retDict["server_id"] = None if self.checkServerID(): if self.conn.response[0]['attributes']['olcServerID']: retDict["server_id"] = self.conn.response[0]['attributes'][ 'olcServerID'][0] retDict["overlaysDB1"] = self.checkSyncprovOverlaysDB1() retDict["overlaysDB2"] = self.checkSyncprovOverlaysDB2() retDict["mirrorMode"] = self.checkMirroMode() retDict["accesslogDB"] = self.checkAccesslogDBEntry() retDict["accesslogPurge"] = self.checkAccesslogPurge() retDict["providers"] = self.getProviders() return retDict def getMainDbDN(self): if self.conn.search( search_base="cn=config", search_scope=LEVEL, search_filter="(olcDbDirectory=/opt/gluu/data/main_db)", attributes='*'): if self.conn.response: return self.conn.response[0]['dn'] def setLimitOnMainDb(self, replicator_dn): main_db_dn = self.getMainDbDN() return self.conn.modify( main_db_dn, { 'olcLimits': [ MODIFY_ADD, 'dn.exact="{0}" time.soft=unlimited time.hard=unlimited size.soft=unlimited size.hard=unlimited' .format(replicator_dn) ] }) def addReplicatorUser(self, replicator_dn, passwd): self.checkBaseDN() enc_passwd = ldap_encode(passwd) self.conn.search(replicator_dn, search_filter='(objectClass=*)', search_scope=BASE) if len(self.conn.response): # user dn already exists return self.conn.modify( replicator_dn, {"userPassword": [MODIFY_REPLACE, enc_passwd]}) else: m = re.search('cn=(?P<cn>[a-zA-Z][a-zA-Z ]*[a-zA-Z]),o=gluu', replicator_dn) cn = m.group('cn') attributes = { 'objectClass': ['top', 'inetOrgPerson'], 'cn': cn, 'sn': 'replicator', 'uid': 'replicator', 'userpassword': enc_passwd, } return self.conn.add(replicator_dn, attributes=attributes) def checkBaseDN(self): r = self.conn.search(search_base="o=gluu", search_filter='(objectClass=top)', search_scope=BASE) if not self.conn.search(search_base="o=gluu", search_filter='(objectClass=top)', search_scope=BASE): logger.info("Adding base DN") self.conn.add('o=gluu', attributes={ 'objectClass': ['organization'], 'o': 'gluu', }) def configureOxIDPAuthentication(self, servers): if self.conn.search("ou=appliances,o=gluu", search_filter='(objectClass=gluuAppliance)', search_scope=LEVEL, attributes=["oxIDPAuthentication"]): r = self.conn.response if r: oxidp_s = r[0]["attributes"]["oxIDPAuthentication"][0] oxidp = json.loads(oxidp_s) config = json.loads(oxidp["config"]) config["servers"] = servers oxidp["config"] = json.dumps(config) oxidp_s = json.dumps(oxidp) return self.conn.modify( r[0]['dn'], {"oxIDPAuthentication": [MODIFY_REPLACE, oxidp_s]})
class LDAP: def __init__(self): self.ldap_conn = Connection(server=Server('127.0.0.1'), user=f'cn={admin_user},{ldap_suffix}', password=admin_cred, auto_bind=True) self.num_hits = 0 self.num_search_hits = 0 self.num_change_hits = 0 self.group_dict = {} self.invalid_users = [] self.invalid_group_members = {} self.users_dict_uid = {} self.users_dict_eid = {} self.user_eips = [] self.user_md5s = [] self.__user_populate() self.number_of_users = len(self.user_eips) # Private helpers def __user_populate(self): for userEntry in self.__user_search(['*']): dn = userEntry.entry_dn try: user_eid = userEntry.employeeNumber.value user_md5 = userEntry.carLicense.value user_uid = userEntry.uid.value self.users_dict_uid[user_uid] = { 'm5': user_md5, 'eid': user_eid, 'curr_groups': [], 'dn': dn } self.users_dict_eid[user_eid] = { 'm5': user_md5, 'uid': user_uid, 'curr_groups': [], 'dn': dn } self.user_eips.append(user_eid) self.user_md5s.append(user_md5) except LDAPCursorAttributeError as e: log("WARNING", f"Invalid configured user - {userEntry.entry_dn}") self.invalid_users.append(userEntry) for g in self.get_all_groups(): self.group_dict[g] = [] for e in self.__group_search(['cn', 'memberUid']): memberUids = e.memberUid.value group = e.cn.value if memberUids == None: members = [] elif type(memberUids) == str: members = [memberUids] elif type(memberUids) == list: members = memberUids else: log("CRITICAL", f"Unknown MemberUid State {group} - {memberUids}") for user_uid in members: self.group_dict[group].append(user_uid) if user_uid not in self.users_dict_uid.keys(): if e.cn.value not in self.invalid_group_members.keys(): self.invalid_group_members[e.cn.value] = [] log("WARNING", f"Unknown user {user_uid} in group {e.cn.value}.") self.invalid_group_members[e.cn.value].append(user_uid) else: self.users_dict_uid[user_uid]['curr_groups'].append( e.cn.value) def __check_result(self, result, val='unknown', acceptable=[]): self.num_hits += 1 if result != True and self.ldap_conn.result['result'] != 0: error = self.ldap_conn.result['description'] if error in acceptable: log("WARNING", f"{error}: {val}") else: log("CRITICAL", f"{val} {self.ldap_conn.result}") def __sambaDomainName_unique(self, attribute): self.num_search_hits += 1 result = self.ldap_conn.search( search_base=f'sambaDomainName=sambaDomain,{ldap_suffix}', search_filter='(objectClass=*)', search_scope='SUBTREE', attributes=[attribute]) self.__check_result(result) number = self.ldap_conn.entries[0][attribute].values[0] + 1 self.num_change_hits += 1 result = self.ldap_conn.modify( dn=f'sambaDomainName=sambaDomain,{ldap_suffix}', changes={attribute: [(MODIFY_REPLACE, number)]}) self.__check_result(result) return number def __group_search(self, attributes): self.num_search_hits += 1 result = self.ldap_conn.search( search_base=f'ou=group,{ldap_suffix}', search_filter='(objectClass=posixGroup)', search_scope='SUBTREE', attributes=attributes) self.__check_result(result) return self.ldap_conn.entries def __user_search(self, attributes, uid='*'): self.num_search_hits += 1 result = self.ldap_conn.search( search_base=f'ou=people,{ldap_suffix}', search_filter=f'(&(uid={uid})(objectClass=inetOrgPerson))', search_scope='SUBTREE', attributes=attributes) return self.ldap_conn.entries # Groups def group_create(self, group): dn = f'cn={group},ou=group,{ldap_suffix}' log("CREATE", f"create group {dn}") self.num_change_hits += 1 result = self.ldap_conn.add( dn=dn, object_class=[ 'top', 'posixGroup', 'SambaGroupMapping', 'extensibleObject' ], attributes={ 'gidNumber': self.__sambaDomainName_unique('gidNumber'), 'sambaSID': sambaSID, 'sambaGroupType': 2 }) self.__check_result(result) def group_delete(self, group): dn = f'cn={group},ou=group,{ldap_suffix}' log( 'DELETE', f"delete group {dn} which included {', '.join(self.group_dict[group]) if self.group_dict[group] != [] else 'no members.'}" ) self.num_change_hits += 1 self.__check_result(self.ldap_conn.delete(dn=dn)) def groups_enforce(self, uid, config_groups, cfg_user_md5): current_groups = set(self.user_membership_preloaded(uid)) should_be_groups = set(config_groups) remove = current_groups.difference(should_be_groups) add = should_be_groups.difference(current_groups) if len(add) != 0: log("ENFORCE", f"{uid:<15} {'member of':<15} {', '.join(should_be_groups)}") for g in add: self.groups_add_user(uid, g) if len(remove) != 0: log("ENFORCE", f"{uid:<15} {'NOT member of':<15} {', '.join(remove)}") for g in remove: self.groups_remove_user(uid, g) if (len(add) + len(remove)) != 0: self.num_change_hits += 1 result = self.ldap_conn.modify( dn=f'uid={uid},ou=people,{ldap_suffix}', changes={'carLicense': [(MODIFY_REPLACE, cfg_user_md5)]}) self.__check_result( result, f"[USER: {uid} MD5: {cfg_user_md5} ACTION: modify ]") def groups_add_user(self, uid, group): log("ADD", f" * add {uid} to {group}") self.num_change_hits += 1 result = self.ldap_conn.modify( dn=f'cn={group},ou=group,{ldap_suffix}', changes={'memberUid': [(MODIFY_ADD, uid)]}) self.__check_result(result, f"[GROUP: '{group}' USER: '******', ACTION: add]", ['attributeOrValueExists']) def groups_remove_user(self, uid, group): log("REMOVE", f" * remove {uid} from {group}") self.num_change_hits += 1 result = self.ldap_conn.modify( dn=f'cn={group},ou=group,{ldap_suffix}', changes={'memberUid': [(MODIFY_DELETE, uid)]}) self.__check_result( result, f"[GROUP: '{group}' USER: '******', ACTION: remove]", ['noSuchAttribute']) # Users def user_create(self, uid, eip, mail, m5d, init_groups): dn = f'uid={uid},ou=people,{ldap_suffix}' log("CREATE", f"create user {dn} and to {', '.join(init_groups)}") self.num_change_hits += 1 result = self.ldap_conn.add( dn=dn, object_class=[ 'top', 'person', 'organizationalPerson', 'posixAccount', 'shadowAccount', 'inetOrgPerson' ], attributes={ 'sn': "TODO", 'homeDirectory': f"home/{uid}", 'cn': f"TODO", 'employeeNumber': eip, 'uidNumber': self.__sambaDomainName_unique('uidnumber'), 'gidNumber': 512, 'mail': mail, 'carLicense': m5d, }) self.__check_result(result, uid, ['entryAlreadyExists', 'attributeOrValueExists']) for g in init_groups: self.groups_add_user(uid, g) def user_rename(self, old_uid, new_uid, cfg_user_md5): log("RENAME", f"uid rename from {old_uid} to {new_uid}") self.num_change_hits += 1 result = self.ldap_conn.modify_dn( dn=f'uid={old_uid},ou=people,{ldap_suffix}', relative_dn=f'uid={new_uid}') self.__check_result(result) for group in self.user_membership_preloaded(old_uid): self.groups_remove_user(old_uid, group) self.groups_add_user(new_uid, group) self.users_dict_uid[new_uid] = self.users_dict_uid[old_uid] del self.users_dict_uid[old_uid] self.num_change_hits += 1 result = self.ldap_conn.modify( dn=f'uid={new_uid},ou=people,{ldap_suffix}', changes={'carLicense': [(MODIFY_REPLACE, cfg_user_md5)]}) self.__check_result( result, f"[USER: {old_uid}->{new_uid} MD5: {cfg_user_md5} ACTION: modify ]" ) def user_delete(self, uid, dn): log("DELETE", f"delete user {dn}") for group in self.user_membership_preloaded(uid): self.groups_remove_user(uid, group) self.num_change_hits += 1 result = self.ldap_conn.delete(dn) self.__check_result(result, uid, ['noSuchAttribute']) def user_get_members_of_real(self, uid): self.num_search_hits += 1 result = self.ldap_conn.search( search_base=f'{ldap_suffix}', search_filter=f'(&(cn=*)(memberUid={uid}))', attributes=['cn']) self.__check_result(result) return [g.cn.value for g in self.ldap_conn.entries] def user_membership_preloaded(self, uid): if uid not in self.users_dict_uid.keys(): return [] return self.users_dict_uid[uid]['curr_groups'] def get_users_employeenumber(self): return self.user_eips def get_users_m5d(self): return self.user_md5s def get_all_groups(self): return [e.cn.value for e in self.__group_search(['cn'])]
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 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])
data, auto_bind=True) except core.exceptions.LDAPBindError as e: print("Error connecting to LDAP server: %s" % e) sys.exit(1) # Check and see if obj exists user_search = ldap_connection.search(base_dn, '(&(name=' + objname + ')(objectClass=' + objtype + '))', attributes=['distinguishedName']) # Check the results if not user_search: print(objtype, objname, "does not exist in AD") sys.exit(1) # Add the new user account ldap_connection.delete(obj_dn) if ldap_connection.result['result'] != 0: print("Error adding new " + objtype + ": %s" % ldap_connection.result['description']) sys.exit(1) # LDAP unbind ldap_connection.unbind() # All is good print('Successfully deleted ' + obj_dn)
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')