LDAP API For Python 应用实例
前言
公司内部通过 LDAP 统一管理所有平台用户,并且使用LDAP 目录服务器接入 Linux 服务器实现Linux 用户管理,但是日常管理如:添加用户、删除用户、修改用户属性、新增用户属性等操作,实现方法目前有 2 种,第一种:登陆LDAP Server 端服务器通过*.ldif 文件对 LDAP 进行操作,第二种:通过 ldapphpadmin web 平台进行操作,两种方式都需要首先登陆到平台(Linux 服务器或者 web),再通过手动的方式进行编辑文件和应用,自认为比较浪费时间,所以通过 Python ldap3 的插件库对日常操作进行一系列的封装,达到日常运维快速达到目的。
LDAP 安装(略)
详细安装文档可移步LDAP安装系列
LDAP 简略架构
开发过程及代码实现
公共方法使用
def get_random_number_str(length):
"""
生成随机数字字符串,自动生成 uid
:param length: 字符串长度
:return:
"""
num_str = ''.join(str(random.choice(range(10))) for _ in range(length))
return num_str
def ldap_search(*args, **kwargs):
"""
私有方法,抽离处判断组信息是否存在 LDAP 已有组信息内
:param args: LDAP 组信息--LIST
:param kwargs: 准备创建的组信息--Dict
:return: Boolean
"""
# 判断要创建的是否组名是否已经存在
# search_string = None
# if 'ou' in kwargs:
# search_string = kwargs.get('ou')
# elif 'uid' in kwargs:
# search_string = kwargs.get('uid')
search_string = kwargs.get('search_string')
exist = False
# 将传入的数据进行分解后,每个元素的类型是<class 'ldap3.utils.ciDict.CaseInsensitiveDict'>, 在继续判读要创建的组是否存在.
for i in args:
for k, v in i.items():
# if v[0] == search_string:
# 判断要搜索的字符串是否在列表中即可
if search_string in v:
exist = True
return exist
以下列出当前使用用的功能及代码
- 配置文件生成
_LDAP_HOST = "xxxx" # LDAP 链接地址
_LDAP_PORT = 389
_LDAP_USER = "cn=xx,dc=xx,dc=xx" # LDAP 超级管理员或者具有管理权限的用户
_LDAP_PASSWORD = "xxxx" # 超级管理员或者管理员密码
_LDAP_BASE_DC = "dc=xx,dc=xx" # LDAP Base DC 信息
def get_config_parameter(parameter):
if parameter == 'LDAP_HOST': # String
return _LDAP_HOST
elif parameter == 'LDAP_PORT': # int
return _LDAP_PORT
elif parameter == 'LDAP_USER': # String
return _LDAP_USER
elif parameter == 'LDAP_PASSWORD': # String
return _LDAP_PASSWORD
elif parameter == 'LDAP_BASE_DC': # String
return _LDAP_BASE_DC
- 定义初始化函数
def __init__(self):
self.USER = get_config_parameter('LDAP_USER') # 获取配置文件 USER
self.PASSWORD = get_config_parameter('LDAP_PASSWORD') # 获取配置文件密码
self.SERVER = Server(get_config_parameter('LDAP_HOST'), get_info=ALL) # 生成 LDAP Server 实例
self.conn = Connection(self.SERVER, self.USER, self.PASSWORD, client_strategy=SAFE_SYNC, auto_bind=True) # 生成 连接 LDAP 的 Conn 实例
- 获取所有用户信息
def get_users(self, **kwargs):
"""
:return: LIST: 操作的响应结果,包含 UID
"""
# attributes 限制查询出来的属性包括 status: 操作是否成功 result: 操作的结果 response: 操作的响应结果 request: 原始发送的请求
status, result, response, _ = self.conn.search('ou={},dc=xxx,dc=com'.format(kwargs.get('ou')),
'(objectclass=posixAccount)',
attributes=['uid'])
data = [row['attributes'] for row in response]
return data
每次调用 conn 都会产生如下 4 种结果,也是本文档应用的主要手段;
- status: states if the operation was successful 本次操作是否成功 boole;
- result: the LDAP result of the operation 本次操作的结果 list;
- response: the response of a LDAP Search Operation 本次操作的响应结果;
- request: the original request of the operation 本次操作的原始请求;
- 获取所有 OU(组信息)信息
**此处要进行说明: LDAP 中组可以定义为 CN 与可以定义为 OU,所以根据设计获取组信息区分两种(1: ou信息 2:ou下的 cn 信息)
def get_ou(self):
"""
获取顶级所有 OU信息
:return:
"""
search_filter = '(objectclass=organizationalUnit)'
status, result, response, _ = self.conn.search(get_config_parameter('LDAP_BASE_DC'),
search_filter, attributes=['ou'])
data = [row['attributes'] for row in response]
return data
- 获取 ou 下组信息(2 层嵌套组信息)
此处需要注意:LDAP 的搜索语法'(|(objectclass=posixGroup)(objectclass=groupOfUniqueNames))'
def get_groups(self):
"""
获取 Groups ou 下所有组信息
:return: 返回所有组信息
"""
search_filter = '(|(objectclass=posixGroup)(objectclass=groupOfUniqueNames))'
status, result, response, _ = self.conn.search('ou=Groups,dc=xxx,dc=com', search_filter, attributes=['cn'])
data = [row['attributes'] for row in response]
return data
- 创建组(ou)
def create_group_ou(self, *args, **kwargs):
"""
创建顶层 ou
:param args: 传入的组信息
:param kwargs: 传入要创建组信息,包括组名 角色类 具体参数值
:return:
"""
# 传入*args **kwargs目的,将变量直接处理成 数组和 dict
if not ldap_search(*args, **kwargs):
ou = kwargs.get('ou')
dn = "ou={},{}".format(ou, get_config_parameter('LDAP_BASE_DC'))
object_class = kwargs.get('object_class')
attribute = kwargs.get('attribute')
status, result, response, _ = self.conn.add(dn, object_class, attribute)
# 如果创建成功直接返回 True,如果失败返回失败描述
if status:
return 1
else:
return result['description']
else:
return {"description": "OU is Exist"}
- 创建组(ou–>cn)
def create_group_cn(self, *args, **kwargs):
"""
创建顶层 ou下 cn
:param args: 传入的组信息
:param kwargs: 传入要创建组信息,包括组名 角色类 具体参数值
:return:
"""
# 传入*args **kwargs目的,将变量直接处理成 数组和 dict
if not ldap_search(*args, **kwargs):
ou = kwargs.get('ou')
cn = kwargs.get('cn')
dn = "cn={},ou={},{}".format(cn, ou, get_config_parameter('LDAP_BASE_DC'))
objectclass = kwargs.get('objectclass')
attribute = kwargs.get('attribute')
status, result, response, _ = self.conn.add(dn, objectclass, attribute)
# 如果创建成功直接返回 True,如果失败返回失败描述
if status:
return 1
else:
return result['description']
else:
return {"description": "CN already exists under Group"}
- 删除组
def delete_group(self, *args, **kwargs):
"""
:param args: 传入的组信息
:param kwargs: 传入要创建组信息,包括组名 角色类 具体参数值
:return:
"""
# 传入*args **kwargs目的,将变量直接处理成 数组和 dict
if ldap_search(*args, **kwargs):
row = kwargs.get('row')
dn = "{},{}".format(row, get_config_parameter('LDAP_BASE_DC'))
status, result, response, _ = self.conn.delete(dn)
# 如果创建成功直接返回 True,如果失败返回失败描述
if status:
return 1
else:
return result['description']
else:
return {"description": "Group Is Not Found"}
- 创建用户
def create_user(self, *args, **kwargs):
"""
:param args: 已经存在的所有用户信息
:param kwargs: 要创建的用户信息以及属性信息
:return:
"""
if not ldap_search(*args, **kwargs):
ou = "ou={}".format(kwargs.get('ou'))
uid = "uid={}".format(kwargs.get('uid'))
# 根据定义的用户角色生成不同的dn 信息
if kwargs.get('is_admin'):
cn = "cn={}".format(kwargs.get('attribute').get('cn'))
dn = "{},{},{}".format(cn, ou, get_config_parameter('LDAP_BASE_DC'))
else:
dn = "{},{},{}".format(uid, ou, get_config_parameter('LDAP_BASE_DC'))
object_class = kwargs.get('objectclass')
attribute = kwargs.get('attribute')
status, result, response, _ = self.conn.add(dn, object_class, attribute)
if status:
return 1
else:
return result['description']
else:
return {"description": "User Is Exist"}
- 删除组
def delete_user(self, *args, **kwargs):
"""
删除组内成员
:param args: 所有用户的 LIST
:param kwargs: 准备删除用户的信息
:return:
"""
if ldap_search(*args, **kwargs):
ou = "ou={}".format(kwargs.get('ou'))
uid = "uid={}".format(kwargs.get('uid'))
dn = "{},{},{}".format(uid, ou, get_config_parameter('LDAP_BASE_DC'))
status, result, response, _ = self.conn.delete(dn)
# 如果创建成功直接返回 True,如果失败返回失败描述
if status:
return 1
else:
return result['description']
else:
return {"description": "User Is Not Found"}
- 更改用户 UID 属性
def modify_uid(self, *args, **kwargs):
"""
更改用户名称
:param args: 传入已经存在的用户列表
:param kwargs: 要更改的属性
:return:
"""
if ldap_search(*args, **kwargs):
ou = "ou=Users"
# 待更改的 UID 名称
old_uid = "uid={}".format(kwargs.get('uid'))
# 拼接要更改的 DN
dn = "{},{},{}".format(old_uid, ou, get_config_parameter('LDAP_BASE_DC'))
# 更改后的 UID 名称
new_uid = "uid={}".format(kwargs.get('replace_uid'))
# 要先进行对主属性 UID 更新
status, result, response, _ = self.conn.modify_dn(dn, new_uid)
# 此方法直接进行替换主属性 UID 时不正常,只能进行删除与替换新增的,能替换原有的. 在替换时报"namingViolation"
# 重新赋值 dn, 根据新的 dn 更改相关属性
new_dn = "{},{},{}".format(new_uid, ou, get_config_parameter('LDAP_BASE_DC'))
status, result, response, _ = self.conn.modify(new_dn, {'cn': [(MODIFY_REPLACE, kwargs.get('cn'))],
'displayName': [
(MODIFY_REPLACE, kwargs.get('displayName'))],
'givenName': [
(MODIFY_REPLACE, kwargs.get('givenName'))],
'homeDirectory': [
(MODIFY_REPLACE, kwargs.get('homeDirectory'))],
'mail': [(MODIFY_REPLACE, kwargs.get('mail'))]})
# print(self.conn.search(dn, '({})'.format(new_uid),
# attributes=['uid', 'cn', 'givenName', 'givenName', 'homeDirectory', 'mail']))
if status:
return 1
else:
return result['description']
else:
return {"description": "User Not Found Can't Change"}
- 批量更新用户属性值
def batch_modify_attribute(self, *args, **kwargs):
"""
批量更改属性
:param args: 获取所有用户
:param kwargs: 更改用户的属性名称和值{}
:return:
"""
# 要更改什么组织下
ou = kwargs.get('ou')
ret = {}
for name in args:
dn = "uid={},{},{}".format(name.get('uid')[0], ou, get_config_parameter('LDAP_BASE_DC'))
user_mail = "{}{}".format(name.get('uid')[0], kwargs.get('mail_suffix'))
status, result, response, _ = self.conn.modify(dn, {'mail': [(MODIFY_REPLACE, user_mail)]})
ret[name.get('uid')[0]] = result['description']
return ret
- 添加用到组
def AddUserToGroup(self, *args, **kwargs):
group_name = kwargs.get('cn')
ou_name = kwargs.get('ou')
# 获取操作类型
action = kwargs.get('action')
# 拼接完成的 DN
dn = "cn={},ou={},{}".format(group_name, ou_name, get_config_parameter('LDAP_BASE_DC'))
# 判断组是否存在
if ldap_search(*args, **kwargs):
if action == 'add':
status, result, response, _ = self.conn.modify(dn,
{'memberUid': [(MODIFY_ADD, kwargs.get('memberUid'))]})
if status:
return 1
else:
return result['description']
elif action == 'delete':
status, result, response, _ = self.conn.modify(dn,
{'memberUid': [
(MODIFY_DELETE, kwargs.get('memberUid'))]})
if status:
return 1
else:
return result['description']
else:
return {"description": "Unknown operation"}
else:
return {"description": "Group {} Not Exist".format(group_name)}
后续更新使用文档