Esempio n. 1
0
print(conn.entries[0])
conn.modify('cn=b.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
            {'sn': [(MODIFY_DELETE, ['Young'])]})
conn.search('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
            '(cn=b.smith)',
            attributes=['sn', 'cn'])
print(conn.entries[0])
conn.modify('cn=b.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
            {'sn': [(MODIFY_REPLACE, ['Smith'])]})
conn.search('ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
            '(cn=b.smith)',
            attributes=['sn', 'cn'])
print(conn.entries[0])

print(
    conn.compare('cn=b.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
                 'departmentNumber', 'DEV'))
print(
    conn.compare(
        'cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
        'departmentNumber', 'QA'))

conn.modify(
    'cn=b.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {
        'sn': [(MODIFY_DELETE, ['Johnson'])],
        'givenname': [(MODIFY_REPLACE, ['Beatrix'])]
    })
conn.modify_dn('cn=b.smith,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
               'cn=b.young')
print(
    conn.add(
        'cn=m.johnson,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org',
Esempio n. 2
0
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('查无此人!请检查待修改密码对象格式是否为【姓名工号】')
Esempio n. 3
0
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
Esempio n. 4
0
File: ldap.py Progetto: webkom/lego
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)
Esempio n. 5
0
})
#print(conn.result)
#print()

#modify a UniPerson's attributes mail and userPassword
conn.modify(
    'cn=' + name + ',ou=people,dc=ldap,dc=secuis,dc=fun', {
        'mail': [(MODIFY_REPLACE, [email])],
        'userPassword': [(MODIFY_REPLACE, ['111111'])]
    })
#print(conn.result)
#print()

#compare Uniperson's attributes userPassword
pwd = '123456'
check = conn.compare('cn=' + name + ',ou=people,dc=ldap,dc=secuis,dc=fun',
                     'userPassword', pwd)
#print ('compare with ', pwd)
#print(check)
#print()
pwd = '111111'
check = conn.compare('cn=' + name + ',ou=people,dc=ldap,dc=secuis,dc=fun',
                     'userPassword', pwd)
#print ('compare with ', pwd)
#print(check)
#print()

#look for all Uniperson and print them before deleting
conn.search('ou=people,dc=ldap,dc=secuis,dc=fun',
            '(objectClass=UniPerson)',
            attributes=['*'])
#print(conn.entries)
Esempio n. 6
0
class AD(object):
    '''    AD用户操作    '''
    def __init__(self, server, username, password):
        '''初始化'''
        self.conn = Connection(  # 配置服务器连接参数
            server=server,
            auto_bind=True,
            read_only=False,  # 禁止修改数据:True
            user=username,  # 管理员账户
            password=password,
        )

        self.active_base_dn = 'ou=people,dc=micl,dc=org'  # 账户所在OU
        # self.search_filter = '(objectclass=user)'  # 只获取【用户】对象
        self.search_filter = '(objectclass=*)'  # 只获取【用户】对象
        self.user_search_filter = '(&(objectclass=*)(mail={}))'  # 只获取【用户】对象
        self.ou_search_filter = '(objectclass=organizationalUnit)'  # 只获取【OU】对象

    def password_generator(self):

        s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#\
        $%^&*()?"

        passlen = 8
        p = "".join(random.sample(s, passlen))
        return p

    def get_user_list_json(self):
        '''获取所有的用户 JSON'''
        self.conn.search(search_base=self.active_base_dn,
                         search_filter=self.search_filter,
                         attributes=ALL_ATTRIBUTES)
        res = self.conn.response_to_json()
        res = json.loads(res)['entries']
        return res[1:]

    def get_user_list(self):
        '''获取所有的用户'''
        self.conn.search(search_base=self.active_base_dn,
                         search_filter=self.search_filter,
                         attributes=ALL_ATTRIBUTES)
        return self.conn.entries[1:]

    def get_username_list(self):
        users = self.get_user_list()
        usernames = [x.uid.value for x in users if x]
        return usernames

    def get_email_list(self):
        users = self.get_user_list()
        emails = [x.mail.value for x in users if x]
        return emails

    def generate_uid(self):
        users = self.get_user_list_json()
        uid = []
        for user in users:
            uid.append(user['attributes']['uidNumber'])
        uid.sort()
        return uid[-1] + 1

    def username_exists(self, username):
        usernames = self.get_username_list()
        if username in usernames:
            return True
        else:
            return False

    def email_exists(self, email):
        emails = self.get_email_list()
        if email in emails:
            return True
        else:
            return False

    def get_user(self, email):
        '''获取指定的用户'''
        search_base = 'cn={},{}'.format(email, self.active_base_dn)
        # search_filter = self.user_search_filter.format(email)
        result = self.conn.search(search_base=search_base,
                                  search_filter=self.search_filter,
                                  attributes=ALL_ATTRIBUTES)
        if result:
            return self.conn.entries[0]
        else:
            return result

    def modify_user_password(self, email):
        user = self.get_user(email)
        if not user:
            return False, EMAIL_DOES_NOT_EXISTS
        user_dn = user.entry_dn
        password = self.password_generator()
        username = user.uid.value
        first_name = user.givenName.value
        last_name = user.sn.value
        name = '{} {}'.format(first_name, last_name)
        hashed_password = hashed(HASHED_SALTED_SHA, password)
        changes = {'userPassword': [(MODIFY_REPLACE, [hashed_password])]}
        res = self.conn.modify(user_dn, changes=changes)
        r = send_email(username, password, name, email)
        if not r.success:
            return r.success, RESET_PASSWORD_FAIL
        if not res:
            return res, RESET_PASSWORD_FAIL
        else:
            return res, RESET_PASSWORD

    def create_user(self,
                    username,
                    email,
                    first_name,
                    last_name,
                    mobile,
                    attr=None):
        if self.username_exists(username):
            return False, USERNAME_EXISTS

        if self.email_exists(email):
            return False, EMAIL_EXISTS

        password = self.password_generator()

        hashed_password = hashed(HASHED_SALTED_SHA, password)

        dn = 'cn={},{}'.format(email, self.active_base_dn)
        object_class = [
            'person', 'posixAccount', 'top', 'inetOrgPerson',
            'organizationalPerson'
        ]
        attr = {
            'mail': [email],
            # 'sn': [last_name],
            'sn': [last_name],
            'givenName': [first_name],
            'gidNumber': [2000],
            'userPassword': [hashed_password],
            'loginShell': ['/bin/bash'],
            'uidNumber': [self.generate_uid()],
            'homeDirectory': ['/home/{}'.format(username)],
            'uid': [username],
            'cn': [email]
        }
        res = self.conn.add(dn=dn, object_class=object_class, attributes=attr)

        name = '{} {}'.format(first_name, last_name)
        r = send_email(username, password, name, email)
        if not r.success:
            return r.success, CREATE_FAIL
        if res:
            return res, CREATE_SUCCESS
        else:
            return res, CREATE_FAIL

    def OU_get(self):
        '''获取所有的OU'''
        self.conn.search(search_base=self.active_base_dn,
                         search_filter=self.ou_search_filter,
                         attributes=ALL_ATTRIBUTES)
        res = self.conn.response_to_json()
        res = json.loads(res)['entries']
        return res

    def create_obj(self, dn, type, attr=None):
        '''
        新建用户or 部门,User需要设置密码,激活账户
        :param dn: dn = "ou=人事部3,ou=罗辑实验室,dc=adtest,dc=intra"  
                    # 创建的OU的完整路径
                   dn = "cn=张三,ou=人事部3,ou=罗辑实验室,dc=adtest,dc=intra" 
                    # 创建的User的完整路径
        :param type:选项:ou or user
        :param attr = {#User 属性表,需要设置什么属性,增加对应的键值对
                        "SamAccountName": "zhangsan",  # 账号
                        "EmployeeID":"1",    # 员工编号
                        "Sn": "张",  # 姓
                        "name": "张三",
                        "telephoneNumber": "12345678933",
                        "mobile": "12345678933",
                        "UserPrincipalName":"*****@*****.**",
                        "Mail":"*****@*****.**",
                        "Displayname": "张三",
                        "Manager":"CN=李四,OU=人事部,DC=adtest,DC=com",#需要使用用户的DN路径
                    }
                attr = {#OU属性表
                        'name':'人事部',
                        'managedBy':"CN=张三,OU=IT组,OU=罗辑实验室,DC=adtest,DC=intra",
                         #部分负责人
                        }
        :return:True and success 是创建成功了
        (True, {'result': 0, 'description': 'success', 'dn': '', 'message': '',
         'referrals': None, 'type': 'addResponse'})

        '''
        object_class = {
            'user': ['user', 'posixGroup', 'top'],
            'ou': ['organizationalUnit', 'posixGroup', 'top'],
        }
        res = self.conn.add(dn=dn,
                            object_class=object_class[type],
                            attributes=attr)
        if type == "user":  # 如果是用户时,我们需要给账户设置密码,并把账户激活
            self.conn.extend.microsoft.modify_password(dn,
                                                       "XXXXXXXXX")  # 设置用户密码
            self.conn.modify(
                dn, {'userAccountControl': [('MODIFY_REPLACE', 512)]})  # 激活用户
        return res, self.conn.result

    def del_obj(self, DN):
        '''
        删除用户 or 部门
        :param DN:
        :return:True
        '''
        res = self.conn.delete(dn=DN)
        return res

    def update_obj(self, dn, attr):
        '''更新员工 or 部门属性
        先比较每个属性值,是否和AD中的属性一致,不一样的记录,统一update
        注意:
            1. attr中dn属性写在最后
            2. 如果name属性不一样的话,需要先变更名字(实际是变更原始dn为新name的DN),
            后续继续操作update and move_object
        User 的 attr 照如下格式写:
        dn = "cn=test4,ou=IT组,dc=adtest,dc=com" #需要移动的User的原始路径
        {#updateUser需要更新的属性表
             "Sn": "李",  # 姓
             "telephoneNumber": "12345678944",
             "mobile": "12345678944",
             "Displayname": "张三3",
             "Manager":"CN=李四,OU=人事部,DC=adtest,DC=com",#需要使用用户的DN路径
             'DistinguishedName':"cn=张三,ou=IT组,dc=adtest,dc=com" 
             #用户需要移动部门时,提供此属性,否则不提供
            }

        OU 的 attr 格式如下:
        dn = "ou=人事部,dc=adtest,dc=com" #更新前OU的原始路径
        attr = {
        'name':'人事部',
        'managedBy':"CN=张三,OU=IT组,DC=adtest,DC=com",
        'DistinguishedName': "ou=人事部,dc=adtest,dc=com" # 更新后的部门完整路径
        }
        '''
        changes_dic = {}
        for k, v in attr.items():
            if not self.conn.compare(dn=dn, attribute=k, value=v):
                if k == "name":
                    # 改过名字后,DN就变了,这里调用重命名的方法
                    res = self.__rename_obj(dn=dn, newname=attr['name'])
                    if res['description'] == "success":
                        if "CN" == dn[:2]:
                            dn = "cn=%s,%s" % (attr["name"], dn.split(",",
                                                                      1)[1])
                        if "OU" == dn[:2]:
                            dn = "DN=%s,%s" % (attr["name"], dn.split(",",
                                                                      1)[1])
                # 如果属性里有“DistinguishedName”,表示需要移动User or OU
                if k == "DistinguishedName":
                    self.__move_object(dn=dn, new_dn=v)  # 调用移动User or OU 的方法
                changes_dic.update({k: [(MODIFY_REPLACE, [v])]})
                self.conn.modify(dn=dn, changes=changes_dic)
        return self.conn.result

    def __rename_obj(self, dn, newname):
        '''
        OU or User 重命名方法
        :param dn:需要修改的object的完整dn路径
        :param newname: 新的名字,User格式:"cn=新名字";OU格式:"OU=新名字"
        :return:返回中有:'description': 'success', 表示操作成功
        {'result': 0, 'description': 'success', 'dn': '', 'message': '', 
        'referrals': None, 'type': 'modDNResponse'}
        '''
        self.conn.modify_dn(dn, newname)
        return self.conn.result

    def compare_attr(self, dn, attr, value):
        '''比较员工指定的某个属性
        '''
        res = self.conn.compare(dn=dn, attribute=attr, value=value)
        return res

    def __move_object(self, dn, new_dn):
        '''移动员工 or 部门到新部门'''
        relative_dn, superou = new_dn.split(",", 1)
        res = self.conn.modify_dn(dn=dn,
                                  relative_dn=relative_dn,
                                  new_superior=superou)
        return res

    def check_credentials(self, username, password):
        """
        用户认证接口 #
        """
        users = self.get_user_list()
        dn = None
        for user in users:
            if user['uid'].value == username:
                dn = user.entry_dn
        server = Server('192.168.0.10')

        connection = Connection(server, user=dn, password=password)
        print("username:%s ;res: %s" % (username, connection.bind()))
        result = connection.bind()
        if result:
            authencated = 'isAuthenticated'
        else:
            authencated = 'isNotAuthenticated'
        connection.closed
        return result, authencated
Esempio n. 7
0
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)