ldap3 是一个严格遵守 RFC 4510 规范,完全由纯 Python 代码实现的 LDAPv3 客户端。
ldap3 只依赖于 Python 标准库和pyasn1
,无需使用 C 编译器编译或者安装其他二进制程序,直接使用pip
命令安装即可(pip install ldap3
)。
一、连接服务器
1 | from ldap3 import Server, Connection, ALL |
或者也可以使用更简短的形式:1
2
3'<hostname_or_ip>', auto_bind=True) conn = Connection(
conn
Connection(server=Server(host='xx.xx.xx.xx', port=389, use_ssl=False, allowed_referral_hosts=[('*', True)], get_info='SCHEMA', mode='IP_V6_PREFERRED'), auto_bind='NO_TLS', version=3, authentication='ANONYMOUS', client_strategy='SYNC', auto_referrals=True, check_names=True, read_only=False, lazy=False, raise_exceptions=False, fast_decoder=True, auto_range=True, return_empty_attributes=True, auto_encode=True, auto_escape=True, use_referral_cache=False)
获取服务器信息:1
2
3
4
5
6
7
8
9
10'<hostname_or_ip>', get_info=ALL) server = Server(
True) conn = Connection(server, auto_bind=
server.info
DSA info (from DSE):
Supported LDAP versions: 3, 2
Naming contexts:
DC=example,DC=com
CN=Configuration,DC=example,DC=com
CN=Schema,CN=Configuration,DC=example,DC=com
...
用户绑定
1 | from ldap3 import Server, Connection, ALL |
PS:通常在对域账号进行修改等操作时,需要启用 SSL 连接以符合安全规范。在构造 Server
对象时传入 use_ssl=True
即可:1
server = Server('<hostname_or_ip>', port=636, use_ssl=True, get_info=ALL)
如有需要,TLS 的启用可以参考官方文档 https://ldap3.readthedocs.io/ssltls.html
二、对象检索
使用 FreeIPA 开放的 LDAP 示例服务测试搜索操作:1
2
3
4
5
6
7
8
9
10
11from ldap3 import Server, Connection, ALL
'ipa.demo1.freeipa.org', get_info=ALL) server = Server(
'uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org', 'Secret123', auto_bind=True) conn = Connection(server,
'dc=demo1,dc=freeipa,dc=org', '(objectclass=person)') conn.search(
True
conn.entries
[DN: uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T18:57:39.837356
, DN: uid=manager,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T18:57:39.837594
, DN: uid=employee,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T18:57:39.837722
, DN: uid=helpdesk,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T18:57:39.837845
]
search
方法有两个参数是必需的:
search_base
用于指定搜索的起始位置search_filter
用于指定搜索的筛选条件
filter
的定义语法支持 =
、<=
、>=
等比较运算符和 &
、|
、!
等逻辑运算符。如:(&(givenName=John)(mail=*@example.com))
表示寻找名字为 John
且邮箱以 ``@example.com 结尾的域账号。
以下搜索条件则表示寻找名字为 Fred
或 John
,并且邮箱以 ``@example.com 结尾的域账号:
1
2
3
4
5
6
7(&
(|
(givenName=Fred)
(givenName=John)
)
(mail=*@example.com)
)
attributes
参考如下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16'dc=demo1,dc=freeipa,dc=org', '(&(objectclass=person)(uid=admin))', attributes=['sn', 'krbLastPwdChange', 'objectclass']) conn.search(
True
0] conn.entries[
DN: uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T19:14:20.657761
krbLastPwdChange: 2019-01-25 15:16:11+00:00
objectclass: top
person
posixaccount
krbprincipalaux
krbticketpolicyaux
inetuser
ipaobject
ipasshuser
ipaSshGroupOfPubKeys
ipaNTUserAttrs
sn: Administrator
其中 (&(objectclass=person)(uid=admin)
用于指定查找对象类型为 person
且 uid
为 admin
的用户账号; attributes
参数则用于指定搜索结果中额外包含该账户的 sn
、krbLastPwdChange
和 objectclass
属性。
PS:设置 attributes = ['*']
可以在搜索结果中显示对象的所有属性。
可以使用 Entry
对象的 entry_to_json
方法将该对象的所有属性以 JSON 格式输出:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
240].entry_to_json()) print(conn.entries[
{
"attributes": {
"krbLastPwdChange": [
"2019-01-25 15:16:11+00:00"
],
"objectclass": [
"top",
"person",
"posixaccount",
"krbprincipalaux",
"krbticketpolicyaux",
"inetuser",
"ipaobject",
"ipasshuser",
"ipaSshGroupOfPubKeys",
"ipaNTUserAttrs"
],
"sn": [
"Administrator"
]
},
"dn": "uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org"
}
此外还可以使用 paged_size
参数控制每页显示的结果数量。综合实例如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16'search_base': 'dc=demo1,dc=freeipa,dc=org', searchParameters = {
'search_filter': '(objectClass=Person)',
'attributes': ['cn', 'givenName'],
'paged_size': 3}
conn.search(**searchParameters)
True
conn.entries
[DN: uid=admin,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T19:23:50.396926
cn: Administrator
, DN: uid=manager,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T19:23:50.397214
cn: Test Manager
givenName: Test
, DN: uid=employee,cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-13T19:23:50.397509
cn: Test Employee
givenName: Test
]
三、数据库操作
创建条目
新建 OU:1
2'ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'organizationalUnit') conn.add(
True
新建用户:1
2'cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'inetOrgPerson', {'givenName': 'Beatrix', 'sn': 'Young', 'departmentNumber': 'DEV', 'telephoneNumber': 1111}) conn.add(
True
查看 objectClass 结构:1
2
3
4
5
6
7
8
9
10'person'] server.schema.object_classes[
Object class: 2.5.6.6
Short name: person
Type: Structural
Superior: top
Must contain attributes: sn, cn
May contain attributes: userPassword, telephoneNumber, seeAlso, description
Extensions:
X-ORIGIN: RFC 4519
OidInfo: ('2.5.6.6', 'OBJECT_CLASS', 'person', 'RFC4519')
重命名条目
1 | 'cn=b.young,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'cn=b.smith') conn.modify_dn( |
移动条目
1 | 'ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org','organizationalUnit') conn.add( |
更新条目
添加属性值:1
2
3
4
5
6
7
8
9
10from ldap3 import MODIFY_ADD, MODIFY_REPLACE, MODIFY_DELETE
'cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_ADD, ['Smyth'])]}) conn.modify(
True
'ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', '(cn=b.smith)', attributes=['cn', 'sn']) conn.search(
True
0] conn.entries[
DN: cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-14T13:50:45.461241
cn: b.smith
sn: Young
Smyth
移除属性值:1
2
3
4
5
6
7
8'cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_DELETE, ['Young'])]}) conn.modify(
True
'ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', '(cn=b.smith)', attributes=['cn', 'sn']) conn.search(
True
0] conn.entries[
DN: cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-14T13:52:43.586339
cn: b.smith
sn: Smyth
替换属性值:1
2
3
4
5
6
7
8'cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', {'sn': [(MODIFY_REPLACE, ['Smith'])]}) conn.modify(
True
'ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', '(cn=b.smith)', attributes=['cn', 'sn']) conn.search(
True
0] conn.entries[
DN: cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org - STATUS: Read - READ TIME: 2020-01-14T13:53:28.834019
cn: b.smith
sn: Smith
判断属性是否为某个特定值:1
2
3
4'cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'departmentNumber', 'DEV') conn.compare(
True
'cn=b.smith,ou=moved,ou=ldap3-tutorial,dc=demo1,dc=freeipa,dc=org', 'departmentNumber', 'QA') conn.compare(
False
四、域账号操作
修改账户密码:1
2
3
4
5
6
7
8
9
10from ldap3 import Server, Connection
'<hostname_or_ip', use_ssl=True) server = Server(
'admin@example.com', password='admin@123', auto_bind=True) conn = Connection(server, user=
'ou=Domain Users,dc=example,dc=com', '(&(objectClass=person)(sAMAccountName=admin))') conn.search(
True
0].entry_dn dn = conn.entries[
dn
'CN=admin,OU=Domain Users,DC=example,DC=com'
'new_password') conn.extend.microsoft.modify_password(dn,
True
解锁账户:1
2 conn.extend.microsoft.unlock_account(dn)
True
此外该模块下还有 addMembersToGroups
和 removeMembersFromGroups
等函数。