基于python-ldap的Node-trees LDAP/AD便捷性
项目描述
node.ext.ldap
概述
node.ext.ldap 是一个基于 python-ldap (版本 2.4 或更高) 和 node 的 LDAP 通信便捷库。
此包包含基本配置和通信对象、一个 LDAP 节点对象以及利用 node.ext.ugm 的基于 LDAP 节点的用户和组管理实现。
此包是 bda.ldap 的后续版本。
与0.9.x相比的API更改
LDAPNode 实例不能再有子树的直接子节点。这是因为可能存在重复的 RDN,这是一个设计缺陷。
LDAPNode.search 默认返回 DN 而不是 RDN。
二级键和备选键属性功能已从 LDAPNode 中完全移除。
LDAPProps.check_duplicates 设置已被移除。
用法
LDAP 属性
要为 LDAP 定义连接属性,请使用 node.ext.ldap.LDAPProps 对象。
>>> from node.ext.ldap import LDAPProps
>>> props = LDAPProps(
... uri='ldap://#:12345/',
... user='cn=Manager,dc=my-domain,dc=com',
... password='secret',
... cache=False
... )
使用 node.ext.ldap.testLDAPConnectivity 测试服务器连接性。
>>> from node.ext.ldap import testLDAPConnectivity
>>> assert testLDAPConnectivity(props=props) == 'success'
LDAP 连接
为了处理 LDAP 连接,使用 node.ext.ldap.LDAPConnector。它期望在构造函数中有一个 LDAPProps 实例。通常不需要直接实例化此对象,这发生在创建更高层次抽象的过程中,请参见下文。
>>> from node.ext.ldap import LDAPConnector
>>> import ldap
>>> connector = LDAPConnector(props=props)
调用 bind 创建并返回 LDAP 连接。
>>> conn = connector.bind()
>>> assert isinstance(conn, ldap.ldapobject.ReconnectLDAPObject)
调用 unbind 销毁连接。
>>> connector.unbind()
LDAP 通信
为了与 LDAP 服务器通信,使用 node.ext.ldap.LDAPCommunicator。它提供了搜索和修改目录所需的所有基本功能。
LDAPCommunicator 期望在创建时有一个 LDAPConnector 实例。
>>> from node.ext.ldap import LDAPCommunicator
>>> communicator = LDAPCommunicator(connector)
绑定到服务器
>>> communicator.bind()
添加目录条目
>>> communicator.add(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... {
... 'cn': 'foo',
... 'sn': 'Mustermann',
... 'userPassword': 'secret',
... 'objectClass': ['person'],
... }
... )
设置默认搜索 DN
>>> communicator.baseDN = 'ou=demo,dc=my-domain,dc=com'
在目录中搜索
>>> import node.ext.ldap
>>> res = communicator.search(
... '(objectClass=person)',
... node.ext.ldap.SUBTREE
... )
>>> assert res == [(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... {
... 'objectClass': ['person'],
... 'userPassword': ['secret'],
... 'cn': ['foo'],
... 'sn': ['Mustermann']
... }
... )]
修改目录条目
>>> from ldap import MOD_REPLACE
>>> communicator.modify(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... [(MOD_REPLACE, 'sn', 'Musterfrau')]
... )
>>> res = communicator.search(
... '(objectClass=person)',
... node.ext.ldap.SUBTREE,
... attrlist=['cn']
... )
>>> assert res == [('cn=foo,ou=demo,dc=my-domain,dc=com', {'cn': ['foo']})]
更改代表用户的目录条目的密码
>>> communicator.passwd(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... 'secret',
... '12345'
... )
>>> res = communicator.search(
... '(objectClass=person)',
... node.ext.ldap.SUBTREE,
... attrlist=['userPassword']
... )
>>> assert res == [(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... {'userPassword': ['{SSHA}...']}
... )]
删除目录条目
>>> communicator.delete('cn=foo,ou=demo,dc=my-domain,dc=com')
>>> res = communicator.search(
... '(objectClass=person)',
... node.ext.ldap.SUBTREE
... )
>>> assert res == []
关闭连接
>>> communicator.unbind()
LDAP 会话
node.ext.ldap.LDAPSession 为处理 LDAP 提供了一种更方便的方式。它基本上提供了与 LDAPCommunicator 相同的功能,但在执行操作之前自动创建连接对象并检查连接状态。
实例化 LDAPSession 对象。期望 LDAPProps 实例。
>>> from node.ext.ldap import LDAPSession
>>> session = LDAPSession(props)
LDAP 会话提供了检查给定属性的特殊便利。
>>> res = session.checkServerProperties()
>>> assert res == (True, 'OK')
为会话设置默认搜索 DN
>>> session.baseDN = 'ou=demo,dc=my-domain,dc=com'
在目录中搜索
>>> res = session.search()
>>> assert res == [
... ('ou=demo,dc=my-domain,dc=com',
... {
... 'objectClass': ['top', 'organizationalUnit'],
... 'ou': ['demo'],
... 'description': ['Demo organizational unit']
... }
... )]
添加目录条目
>>> session.add(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... {
... 'cn': 'foo',
... 'sn': 'Mustermann',
... 'userPassword': 'secret',
... 'objectClass': ['person'],
... }
... )
更改代表用户的目录条目的密码
>>> session.passwd('cn=foo,ou=demo,dc=my-domain,dc=com', 'secret', '12345')
验证特定用户
>>> res = session.authenticate('cn=foo,ou=demo,dc=my-domain,dc=com', '12345')
>>> assert res is True
修改目录条目
>>> session.modify(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... [(MOD_REPLACE, 'sn', 'Musterfrau')]
... )
>>> res = session.search(
... '(objectClass=person)',
... node.ext.ldap.SUBTREE,
... attrlist=['cn']
... )
>>> assert res == [(
... 'cn=foo,ou=demo,dc=my-domain,dc=com',
... {'cn': ['foo']}
... )]
删除目录条目
>>> session.delete('cn=foo,ou=demo,dc=my-domain,dc=com')
>>> res = session.search('(objectClass=person)', node.ext.ldap.SUBTREE)
>>> assert res == []
关闭会话
>>> session.unbind()
LDAP 节点
可以将 LDAP 条目作为节点对象处理。因此使用 node.ext.ldap.LDAPNode。要了解完整的节点 API,请参阅 node 包。
创建一个 LDAP 节点。根节点期望基 DN 和一个 LDAPProps 实例。
>>> from node.ext.ldap import LDAPNode
>>> root = LDAPNode('ou=demo,dc=my-domain,dc=com', props=props)
每个 LDAP 节点都有一个 DN 和一个 RDN。
>>> root.DN
u'ou=demo,dc=my-domain,dc=com'
>>> root.rdn_attr
u'ou'
检查创建的节点是否存在于数据库中
>>> root.exists
True
目录条目还没有子节点
>>> root.keys()
[]
向根节点添加子节点
>>> person = LDAPNode()
>>> person.attrs['objectClass'] = ['person', 'inetOrgPerson']
>>> person.attrs['sn'] = 'Mustermann'
>>> person.attrs['userPassword'] = 'secret'
>>> root['cn=person1'] = person
>>> person = LDAPNode()
>>> person.attrs['objectClass'] = ['person', 'inetOrgPerson']
>>> person.attrs['sn'] = 'Musterfrau'
>>> person.attrs['userPassword'] = 'secret'
>>> root['cn=person2'] = person
如果在节点创建期间没有设置 RDN 属性,它将从节点键计算并自动设置
>>> person.attrs['cn']
u'person2'
从 LDAP 节点按键获取子节点 DN
>>> root.child_dn('cn=person1')
u'cn=person1,ou=demo,dc=my-domain,dc=com'
查看树
>>> root.printtree()
<ou=demo,dc=my-domain,dc=com - True>
<cn=person2,ou=demo,dc=my-domain,dc=com:cn=person2 - True>
<cn=person1,ou=demo,dc=my-domain,dc=com:cn=person1 - True>
条目尚未写入目录。在修改 LDAP 节点树时,所有操作都在内存中完成。通过调用树或其一部分来持久化。您可以使用节点的 changed 标志检查节点的同步状态。如果 changed 是 True,则表示节点属性或节点子节点已更改。
>>> root.changed
True
>>> root()
>>> root.changed
False
修改 LDAP 节点
>>> person = root['cn=person1']
修改现有属性
>>> person.attrs['sn'] = 'Mustermensch'
添加新属性
>>> person.attrs['description'] = 'Mustermensch description'
>>> person()
删除属性
>>> del person.attrs['description']
>>> person()
删除 LDAP 节点
>>> del root['cn=person2']
>>> root()
>>> root.printtree()
<ou=demo,dc=my-domain,dc=com - False>
<cn=person1,ou=demo,dc=my-domain,dc=com:cn=person1 - False>
搜索 LDAP
添加一些我们将要搜索的用户和组
>>> for i in range(2, 6):
... node = LDAPNode()
... node.attrs['objectClass'] = ['person', 'inetOrgPerson']
... node.attrs['sn'] = 'Surname %s' % i
... node.attrs['userPassword'] = 'secret%s' % i
... node.attrs['description'] = 'description%s' % i
... node.attrs['businessCategory'] = 'group1'
... root['cn=person%s' % i] = node
>>> node = LDAPNode()
>>> node.attrs['objectClass'] = ['groupOfNames']
>>> node.attrs['member'] = [
... root.child_dn('cn=person1'),
... root.child_dn('cn=person2'),
... ]
... node.attrs['description'] = 'IT'
>>> root['cn=group1'] = node
>>> node = LDAPNode()
>>> node.attrs['objectClass'] = ['groupOfNames']
>>> node.attrs['member'] = [
... root.child_dn('cn=person4'),
... root.child_dn('cn=person5'),
... ]
>>> root['cn=group2'] = node
>>> root()
>>> root.printtree()
<ou=demo,dc=my-domain,dc=com - False>
<cn=person1,ou=demo,dc=my-domain,dc=com:cn=person1 - False>
<cn=person2,ou=demo,dc=my-domain,dc=com:cn=person2 - False>
<cn=person3,ou=demo,dc=my-domain,dc=com:cn=person3 - False>
<cn=person4,ou=demo,dc=my-domain,dc=com:cn=person4 - False>
<cn=person5,ou=demo,dc=my-domain,dc=com:cn=person5 - False>
<cn=group1,ou=demo,dc=my-domain,dc=com:cn=group1 - False>
<cn=group2,ou=demo,dc=my-domain,dc=com:cn=group2 - False>
为了定义搜索条件,使用 LDAP 过滤器,这些过滤器可以使用 bool 运算符 ‘&’ 和 ‘|’ 组合
>>> from node.ext.ldap import LDAPFilter
>>> filter = LDAPFilter('(objectClass=person)')
>>> filter |= LDAPFilter('(objectClass=groupOfNames)')
>>> res = sorted(root.search(queryFilter=filter))
>>> assert res == [
... u'cn=group1,ou=demo,dc=my-domain,dc=com',
... u'cn=group2,ou=demo,dc=my-domain,dc=com',
... u'cn=person1,ou=demo,dc=my-domain,dc=com',
... u'cn=person2,ou=demo,dc=my-domain,dc=com',
... u'cn=person3,ou=demo,dc=my-domain,dc=com',
... u'cn=person4,ou=demo,dc=my-domain,dc=com',
... u'cn=person5,ou=demo,dc=my-domain,dc=com'
... ]
定义多个条件的 LDAP 过滤器
>>> from node.ext.ldap import LDAPDictFilter
>>> filter = LDAPDictFilter({
... 'objectClass': ['person'],
... 'cn': 'person1'
... })
>>> res = root.search(queryFilter=filter)
>>> assert res == [u'cn=person1,ou=demo,dc=my-domain,dc=com']
定义LDAP过滤器关系。在这种情况下,我们建立组‘cn’与人员‘businessCategory’之间的关系
>>> from node.ext.ldap import LDAPRelationFilter
>>> filter = LDAPRelationFilter(root['cn=group1'], 'cn:businessCategory')
>>> res = root.search(queryFilter=filter)
>>> assert res == [
... u'cn=person2,ou=demo,dc=my-domain,dc=com',
... u'cn=person3,ou=demo,dc=my-domain,dc=com',
... u'cn=person4,ou=demo,dc=my-domain,dc=com',
... u'cn=person5,ou=demo,dc=my-domain,dc=com'
... ]
不同的LDAP过滤器类型可以组合使用
>>> filter &= LDAPFilter('(cn=person2)')
>>> str(filter)
'(&(businessCategory=group1)(cn=person2))'
LDAPNode.search接受以下关键字参数。如果使用了多个关键字,则使用适当的‘&’组合搜索条件。
如果提供了attrlist,结果项由包含请求属性的字典的2元组组成,位置为1
- 查询过滤器
可以是LDAP过滤器实例或字符串。如果给定的参数是字符串类型,则创建一个LDAPFilter实例。
- 条件
包含搜索条件的字典。创建一个LDAPDictFilter实例。
- attrlist
要返回的属性名称列表。允许特殊属性rdn和dn。
- 关系
可以是LDAPRelationFilter实例或定义关系的字符串。如果给定的参数是字符串类型,则创建一个LDAPRelationFilter实例。
- relation_node
与relation参数结合使用,当作为字符串给出时,在创建过滤器时使用relation_node而不是self。
- exact_match
标志是否期望1-length结果。如果结果为空或找到多个条目,则引发错误。
- or_search
与criteria结合使用,此参数传递给LDAPDictFilter的创建。此标志控制是否使用‘&’或‘|’组合条件键和值。
- or_keys
与criteria结合使用,此参数传递给LDAPDictFilter的创建。此标志控制是否使用‘|’而不是‘&’组合条件键。
- or_values
与criteria结合使用,此参数传递给LDAPDictFilter的创建。此标志控制是否使用‘|’而不是‘&’组合条件值。
- page_size
与cookie结合使用,用于查询分页结果。
- cookie
与page_size结合使用,用于查询分页结果。
- get_nodes
如果True,结果包含LDAPNode实例而不是DN。
您可以在节点上定义搜索默认值,这些默认值在调用此节点的search时始终考虑。如果设置,则它们始终与任何(可选)传递的过滤器一起使用‘&’组合。
定义默认搜索范围
>>> from node.ext.ldap import SUBTREE
>>> root.search_scope = SUBTREE
定义默认搜索过滤器,可以是LDAPFilter、LDAPDictFilter、LDAPRelationFilter或字符串类型
>>> root.search_filter = LDAPFilter('objectClass=groupOfNames')
>>> res = root.search()
>>> assert res == [
... u'cn=group1,ou=demo,dc=my-domain,dc=com',
... u'cn=group2,ou=demo,dc=my-domain,dc=com'
... ]
>>> root.search_filter = None
定义默认搜索条件为字典
>>> root.search_criteria = {'objectClass': 'person'}
>>> res = root.search()
>>> assert res == [
... u'cn=person1,ou=demo,dc=my-domain,dc=com',
... u'cn=person2,ou=demo,dc=my-domain,dc=com',
... u'cn=person3,ou=demo,dc=my-domain,dc=com',
... u'cn=person4,ou=demo,dc=my-domain,dc=com',
... u'cn=person5,ou=demo,dc=my-domain,dc=com'
... ]
定义默认搜索关系
>>> root.search_relation = LDAPRelationFilter(
... root['cn=group1'],
... 'cn:businessCategory'
... )
>>> res = root.search()
>>> assert res == [
... u'cn=person2,ou=demo,dc=my-domain,dc=com',
... u'cn=person3,ou=demo,dc=my-domain,dc=com',
... u'cn=person4,ou=demo,dc=my-domain,dc=com',
... u'cn=person5,ou=demo,dc=my-domain,dc=com'
... ]
再次,与关键字参数一样,多个定义的默认值使用‘&’组合
# empty result, there are no groups with group 'cn' as 'description'
>>> root.search_criteria = {'objectClass': 'group'}
>>> res = root.search()
>>> assert res == []
JSON 序列化
序列化和反序列化LDAP节点
>>> root = LDAPNode('ou=demo,dc=my-domain,dc=com', props=props)
序列化子节点
>>> from node.serializer import serialize
>>> json_dump = serialize(root.values())
清除并持久化根节点
>>> root.clear()
>>> root()
反序列化JSON转储
>>> from node.serializer import deserialize
>>> deserialize(json_dump, root=root)
[<cn=person1,ou=demo,dc=my-domain,dc=com:cn=person1 - True>,
<cn=person2,ou=demo,dc=my-domain,dc=com:cn=person2 - True>,
<cn=person3,ou=demo,dc=my-domain,dc=com:cn=person3 - True>,
<cn=person4,ou=demo,dc=my-domain,dc=com:cn=person4 - True>,
<cn=person5,ou=demo,dc=my-domain,dc=com:cn=person5 - True>,
<cn=group1,ou=demo,dc=my-domain,dc=com:cn=group1 - True>,
<cn=group2,ou=demo,dc=my-domain,dc=com:cn=group2 - True>]
由于已给出根节点,因此创建了节点
>>> root()
>>> root.printtree()
<ou=demo,dc=my-domain,dc=com - False>
<cn=person1,ou=demo,dc=my-domain,dc=com:cn=person1 - False>
<cn=person2,ou=demo,dc=my-domain,dc=com:cn=person2 - False>
<cn=person3,ou=demo,dc=my-domain,dc=com:cn=person3 - False>
<cn=person4,ou=demo,dc=my-domain,dc=com:cn=person4 - False>
<cn=person5,ou=demo,dc=my-domain,dc=com:cn=person5 - False>
<cn=group1,ou=demo,dc=my-domain,dc=com:cn=group1 - False>
<cn=group2,ou=demo,dc=my-domain,dc=com:cn=group2 - False>
非简单模式与简单模式。创建带有子节点的容器
>>> container = LDAPNode()
>>> container.attrs['objectClass'] = ['organizationalUnit']
>>> root['ou=container'] = container
>>> person = LDAPNode()
>>> person.attrs['objectClass'] = ['person', 'inetOrgPerson']
>>> person.attrs['sn'] = 'Mustermann'
>>> person.attrs['userPassword'] = 'secret'
>>> container['cn=person1'] = person
>>> root()
默认模式下的序列化包含类型特定信息,因此JSON转储可以在以后反序列化
>>> serialized = serialize(container)
>>> assert serialized == (
... '{'
... '"__node__": {'
... '"attrs": {'
... '"objectClass": ["organizationalUnit"], '
... '"ou": "container"'
... '}, '
... '"children": [{'
... '"__node__": {'
... '"attrs": {'
... '"objectClass": ["person", "inetOrgPerson"], '
... '"userPassword": "secret", '
... '"sn": "Mustermann", '
... '"cn": "person1"'
... '},'
... '"class": "node.ext.ldap._node.LDAPNode", '
... '"name": "cn=person1"'
... '}'
... '}], '
... '"class": "node.ext.ldap._node.LDAPNode", '
... '"name": "ou=container"'
... '}'
... '}'
... )
简单模式下的序列化更易于阅读,但不再可反序列化
>>> serialized = serialize(container, simple_mode=True)
>>> assert serialized == (
... '{'
... '"attrs": {'
... '"objectClass": ["organizationalUnit"], '
... '"ou": "container"'
... '}, '
... '"name": "ou=container", '
... '"children": [{'
... '"name": "cn=person1", '
... '"attrs": {'
... '"objectClass": ["person", "inetOrgPerson"], '
... '"userPassword": "secret", '
... '"sn": "Mustermann", '
... '"cn": "person1"'
... '}'
... '}]'
... '}'
... )
用户和组管理
LDAP通常用于管理认证,因此node.ext.ldap提供了用户和组管理的API。该API遵循node.ext.ugm的契约
>>> from node.ext.ldap import ONELEVEL
>>> from node.ext.ldap.ugm import UsersConfig
>>> from node.ext.ldap.ugm import GroupsConfig
>>> from node.ext.ldap.ugm import RolesConfig
>>> from node.ext.ldap.ugm import Ugm
实例化用户、组和角色配置。它们基于PrincipalsConfig类,并期望这些设置
- baseDN
主体容器的基本DN。
- attrmap
主要属性映射为 odict.odict。此对象必须包含保留键与实际LDAP属性之间的映射,以及在严格模式下实例化时所有可访问属性节点映射,见下文。
- 搜索范围
主要节点的搜索范围。
- 查询过滤器
主要节点的搜索查询过滤器
- objectClasses
用于创建新主要节点的对象类。对于某些 objectClasses 已注册默认值回调,用于在主要节点容器上未设置时生成强制属性的默认值。
- defaults
包含主要创建默认值的字典对象。值可以是静态的,也可以是接受节点和新的主要ID作为参数的可调用对象。这些默认值优先于通过设置对象类检测到的默认值。
- strict
定义是否必须在 attmap 中声明所有可用的主要属性,或者仅保留的属性。默认为 True。
- memberOfSupport
标志是否在适当的组成员资格解析中使用 'memberOf' 属性(AD)或 memberOf 挂载(openldap)。
用户、组和角色的保留 attrmap 键
- id
包含用户ID的属性(强制)。
- rdn
表示节点 RDN 的属性(强制)XXX:消除,应自动检测
用户的保留 attrmap 键
- login
可选的替代登录名属性(可选)
创建配置对象
>>> ucfg = UsersConfig(
... baseDN='ou=demo,dc=my-domain,dc=com',
... attrmap={
... 'id': 'cn',
... 'rdn': 'cn',
... 'login': 'sn',
... },
... scope=ONELEVEL,
... queryFilter='(objectClass=person)',
... objectClasses=['person'],
... defaults={},
... strict=False,
... )
>>> gcfg = GroupsConfig(
... baseDN='ou=demo,dc=my-domain,dc=com',
... attrmap={
... 'id': 'cn',
... 'rdn': 'cn',
... },
... scope=ONELEVEL,
... queryFilter='(objectClass=groupOfNames)',
... objectClasses=['groupOfNames'],
... defaults={},
... strict=False,
... memberOfSupport=False,
... )
角色在 LDAP 中像组一样表示。注意,如果组在同一个容器中与角色混合,请确保查询过滤器适用。在我们的演示中,使用不同的组对象类。无论如何,在现实世界中,可能值得考虑为角色单独的容器
>>> rcfg = GroupsConfig(
... baseDN='ou=demo,dc=my-domain,dc=com',
... attrmap={
... 'id': 'cn',
... 'rdn': 'cn',
... },
... scope=ONELEVEL,
... queryFilter='(objectClass=groupOfUniqueNames)',
... objectClasses=['groupOfUniqueNames'],
... defaults={},
... strict=False,
... )
实例化 Ugm 对象
>>> ugm = Ugm(props=props, ucfg=ucfg, gcfg=gcfg, rcfg=rcfg)
Ugm 对象有 2 个子对象,用户容器和组容器。它们可以通过节点 API 访问,也可以通过 users 分别 groups 属性访问
>>> ugm.keys()
['users', 'groups']
>>> ugm.users
<Users object 'users' at ...>
>>> ugm.groups
<Groups object 'groups' at ...>
获取用户
>>> user = ugm.users['person1']
>>> user
<User object 'person1' at ...>
用户属性。保留键在用户属性上可用
>>> user.attrs['id']
u'person1'
>>> user.attrs['login']
u'Mustermensch'
'login' 映射到 'sn'
>>> user.attrs['sn']
u'Mustermensch'
>>> user.attrs['login'] = u'Mustermensch1'
>>> user.attrs['sn']
u'Mustermensch1'
>>> user.attrs['description'] = 'Some description'
>>> user()
检查用户凭证
>>> user.authenticate('secret')
True
更改用户密码
>>> user.passwd('secret', 'newsecret')
>>> user.authenticate('newsecret')
True
用户所属的组
>>> user.groups
[<Group object 'group1' at ...>]
添加新用户
>>> user = ugm.users.create('person99', sn='Person 99')
>>> user()
>>> res = ugm.users.keys()
>>> assert res == [
... u'person1',
... u'person2',
... u'person3',
... u'person4',
... u'person5',
... u'person99'
... ]
删除用户
>>> del ugm.users['person99']
>>> ugm.users()
>>> res = ugm.users.keys()
>>> assert res == [
... u'person1',
... u'person2',
... u'person3',
... u'person4',
... u'person5'
... ]
获取组
>>> group = ugm.groups['group1']
组成员
>>> res = group.member_ids
>>> assert res == [u'person1', u'person2']
>>> group.users
[<User object 'person1' at ...>, <User object 'person2' at ...>]
添加组成员
>>> group.add('person3')
>>> member_ids = group.member_ids
>>> assert member_ids == [u'person1', u'person2', u'person3']
删除组成员
>>> del group['person3']
>>> member_ids = group.member_ids
>>> assert member_ids == [u'person1', u'person2']
组属性操作与用户对象相同。
管理用户和组的角色。角色可以通过 ugm 或主要对象查询、添加和删除。获取用户
>>> user = ugm.users['person1']
通过 ugm 为用户添加角色
>>> ugm.add_role('viewer', user)
直接为用户添加角色
>>> user.add_role('editor')
通过 ugm 查询用户的角色
>>> roles = sorted(ugm.roles(user))
>>> assert roles == ['editor', 'viewer']
直接查询角色
>>> roles = sorted(user.roles)
>>> assert roles == ['editor', 'viewer']
调用 UGM 持久化角色
>>> ugm()
通过 ugm 删除角色
>>> ugm.remove_role('viewer', user)
>>> roles = user.roles
>>> assert roles == ['editor']
直接删除角色
>>> user.remove_role('editor')
>>> roles = user.roles
>>> assert roles == []
调用 UGM 持久化角色
>>> ugm()
与组相同。获取组
>>> group = ugm.groups['group1']
添加角色
>>> ugm.add_role('viewer', group)
>>> group.add_role('editor')
>>> roles = sorted(ugm.roles(group))
>>> assert roles == ['editor', 'viewer']
>>> roles = sorted(group.roles)
>>> assert roles == ['editor', 'viewer']
>>> ugm()
删除角色
>>> ugm.remove_role('viewer', group)
>>> group.remove_role('editor')
>>> roles = group.roles
>>> assert roles == []
>>> ugm()
字符编码
LDAP(至少是 v3,RFC 2251)仅使用 utf-8 字符编码。 LDAPNode 为您进行编码。如果您从 LDAPNode 接收的任何内容不是 unicode(除配置为二进制属性外),请将其视为错误。 LDAPSession、LDAPConnector 和 LDAPCommunicator 是编码中立的,它们不进行解码或编码。
传递给节点或会话的 Unicode 字符串会自动编码为 utf8 以用于 LDAP,除非配置为二进制。如果您提供普通字符串,它们会被解码为 utf8 并重新编码为 utf8 以确保它们是 utf8 或兼容的,例如 ascii。
如果您有未使用 utf8 的 LDAP 服务器,请修改 node.ext.ldap._node.CHARACTER_ENCODING。
缓存支持
node.ext.ldap 可以使用 bda.cache 缓存 LDAP 搜索。您需要在您的应用程序中提供一个缓存工厂实用工具以启用缓存。如果没有提供,node.ext.ldap 将回退到使用 bda.cache.NullCache,它不缓存任何内容,仅作为 API 占位符。
要提供基于 Memcached 的缓存,请安装 memcached 服务器并对其进行配置。然后,您需要提供工厂实用工具
>>> from zope.interface import registry
>>> components = registry.Components('comps')
>>> from node.ext.ldap.cache import MemcachedProviderFactory
>>> cache_factory = MemcachedProviderFactory()
>>> components.registerUtility(cache_factory)
如果有多个位于不同 IP 和端口的 memcached 后端,则工厂的初始化看起来像这样
>>> components = registry.Components('comps')
>>> cache_factory = MemcachedProviderFactory(servers=[
... '10.0.0.10:22122',
... '10.0.0.11:22322'
... ])
>>> components.registerUtility(cache_factory)
依赖项
python-ldap
passlib
argparse
plumber
node
node.ext.ugm
bda.cache
贡献者
Robert Niederreiter
Florian Friesdorf
Jens Klein
Georg Bernhard
Johannes Raggam
Alexander Pilz
Domen Kožar
Daniel Widerin
Asko Soukka
Alex Milosz Sielicki
Manuel Reinhardt
Philip Bauer
历史
1.2 (2022-12-05)
在 node.ext.ugm 1.1 中引入了在 node.ext.ldap.ugm._api.LDAPUser 上实现 expires 和 expired 属性,如 node.ext.ugm.interfaces.IUser 中所述。[rnix]
引入 node.ext.ldap.ugm.expires.AccountExpiration 并用于账户过期管理。[rnix]
删除 node.ext.ldap.ugm._api.AccountExpired 单例。如果认证失败,LDAPUsers.authenticate 总是返回 False。[rnix]
要支持 node.behaviors.suppress_lifecycle_events,需要 node >= 1.1。[mamico]
与 pas.plugins.ldap <= 1.8.1 的向后兼容性,其中 LdapProps 没有时效属性。[mamico]
1.1 (2022-10-06)
将 conn_timeout 和 op_timeout 属性(默认不设置)添加到配置 ReconnectLDAPObject。[mamico]
采用从 node 1.1 中的生命周期相关更改。[rnix]
将 ensure_connection 从 LDAPSession 移至 LDAPCommunicator,以防止在返回缓存结果的搜索上进行绑定。[enfold-josh]
1.0 (2022-03-19)
在 LDAPSession.delete 中调用 ensure_connection。[rnix]
删除 Nodespaces 行为的使用。[rnix]
用 MappingStorage 替换已弃用的 Storage。[rnix]
用 IMappingStorage 替换已弃用的 IStorage。[rnix]
用 MappingNode 替换已弃用的 Nodify。[rnix]
用 MappingConstraints 替换已弃用的 NodeChildValidate。[rnix]
用 MappingAdopt 替换已弃用的 Adopt。[rnix]
在 PrincipalAliasedAttributes 上将已弃用的 allow_non_node_childs 重命名为 allow_non_node_children。[rnix]
1.0rc2 (2022-03-01)
修复 #61:在 GC 时关闭对 LDAP 的打开连接。[jensens]
1.0rc1 (2021-11-08)
将已弃用的 allow_non_node_childs 重命名为 allow_non_node_children。[rnix]
在 FIPS 启用环境中允许生成 MD5 哈希。[frapell]
修复 LDAPStorage.node_by_dn 中的 DN 比较,以忽略大小写。[rnix]
1.0b12 (2020-05-28)
确保 LDAPPrincipals._login_attr 有一个值。这样,LDAPUsers.id_for_login 总是返回存储在数据库中的主体 ID。[rnix]
在 LDAPAttributesBehavior.__setitem__ 中改进值比较,以避免 Unicode 警告。[rnix]
在 node.ext.ldap.ugm._api.Ugm 上实现 invalidate。[rnix]
支持在 memberOf 属性中配置的组DN,但这些组不在 UGM 的配置组中。[jensens]
1.0b11 (2019-09-08)
当发生 LDAP 错误时返回空搜索结果列表。修复了 问题 #50。[maurits]
跳过在搜索多个属性时在 LDAP 中找到但不包含所需属性的实体。[fredvd, maurits]
1.0b10 (2019-06-30)
修复缓存键生成问题。[rnix, pbauer]
1.0b9 (2019-05-07)
重构从对象类到格式和属性映射以提高可读性。[jensens]
增加异常的详细程度以简化调试。[jensens]
在持久化主体时添加来自主体配置中缺少的对象类。[rnix]
如果设置属性值为 node.utils.UNSET 或空字符串,则从条目中删除该属性。大多数 LDAP 实现不允许设置空值,因此在这种情况下我们删除整个属性。[rnix]
如果搜索因不存在对象而失败,则添加调试级别日志。[jensens]
修复搜索中缺失 LDAP 批处理 cookie 的问题。[jensens, rnix]
删除 smbpasswd 依赖。改用 passlib。[rnix]
在使用 python-ldap 时使用 bytes_mode=False。这是 python 3 的默认行为,并将所有内容处理为 unicode/text,除了条目属性值。有关更多详细信息,请参阅 https://www.python-ldap.org/en/latest/bytes_mode.html。[rnix]
在 node.ext.ldap.base 中添加 ensure_bytes_py2。[rnix]
在 node.ext.ldap.base 中将 decode_utf8 重命名为 ensure_text。[rnix]
在 node.ext.ldap.base 中将 encode_utf8 重命名为 ensure_bytes。[rnix]
支持 Python 3。[rnix, reinhardt]
将 doctests 转换为 unittests。[rnix]
1.0b8 (2018-10-22)
使用 ldap.ldapobject.ReconnectLDAPObject 而不是 SimpleLDAPObject 来创建连接对象。这使连接更健壮。向 node.ext.ldap.properties.LDAPServerProperties 添加属性 retry_max(默认 1)和 retry_delay(默认 10),以配置 ReconnectLDAPObject。[joka]
在 LDAPPrincipals.__getitem__ 中使用 explode_dn 以防止 DN 包含逗号时出现 KeyError。[dmunicio]
1.0b7 (2017-12-15)
不要在 node.ext.ldap._node.LDAPStorage.batched_search 中捕获 ValueError。[rnix]
使用属性装饰器为 node.ext.ldap._node.LDAPStorage.changed 和 node.ext.ldap.session.LDAPSession.baseDN 提供支持。[rnix]
修复 node.ext.ldap.interfaces.ILDAPStorage.search 的签名,以匹配 node.ext.ldap._node.LDAPStorage.search 中的实际实现。[rnix]
根据 node.ext.ugm.interfaces.IPrincipals.search 修复 node.ext.ldap.ugm.LDAPPrincipals.search 的签名。实现公开了与 LDAP 相关的参数,并将其重命名为 raw_search。[rnix]
向 LDAPStorage 添加 exists 属性。[rnix]
将来自 Active Directory 架构的 objectSid 和 objectGUID 添加到 properties.BINARY_DEFAULTS。[rnix]
修复 LDAPStorage._multivalued_attributes 和 LDAPStorage._binary_attributes 的默认值。[rnix]
1.0b6 (2017-10-27)
在测试层中将默认数据库切换为 mdb。[jensens]
修复输出顺序可能随机的测试。[jensens]
1.0b5 (2017-10-27)
在测试层中使 db-type 可配置。[jensens]
1.0b4 (2017-06-07)
关闭引用以修复包含别名的 MS AD 的问题。[alexsielicki]
修复搜索以直接从根节点数据(而不是从属性行为)检查二进制属性列表,以避免仅为了简单搜索而无需初始化属性行为[datakurre]
修复跳过基DN之外的组DN,以允许用户的memberof属性包含组基DN之外的组[datakurre]
1.0b3 (2016-10-18)
添加一个batched_search生成函数,它为我们执行实际的批处理。在内部也使用此函数。[jensens, rnix]
在测试中将slapd.conf中的set_size_limit设置为3,以捕获批处理的问题。[jensens, rnix]
修复UGM组映射方法member_ids中缺失的分页。[jensens]
1.0b2 (2016-09-09)
小的代码清理[jensens]
分页LDAP节点__iter__。[jensens, rnix]
1.0b1 (31.12.2015)
移除ILDAPProps.check_duplicates相应的LDAPProps.check_duplicates。[rnix]
rdn可以通过LDAPNode.search中的attrlist显式查询。[rnix]
在LDAPNode.search中引入get_nodes关键字参数。当设置时,搜索结果包含LDAPNode实例而不是结果中的DN。[rnix]
LDAPNode.search在结果中返回DN而不是RDN。这解决了范围SUBTREE的搜索,其中结果项可能包含重复的RDN。[rnix]
在LDAPNode上引入node_by_dn。[rnix]
移除bbb代码:没有python 2.4支持(现在2.7+),强制使用LDAPProperties。[jensens]
彻底重构LDAP UGM实现。[rnix]
LDAP节点在__iter__中仅返回直接子节点,即使搜索范围为子树。[rnix]
LDAPNode键不能再被别名。已移除_key_attr和_rdn_attr。child。
LDAPNode不再提供次要键。已移除_seckey_attrs。[rnix]
废弃node.ext.ldap._node.AttributesBehavior,改用node.ext.ldap._node.LDAPAttributesBehavior。[rnix]
移除废弃的node.ext.ldap._node.AttributesPart。[rnix]
在认证时不要在UNWILLING_TO_PERFORM异常上失败。如果LDAP服务器不允许我们认证一个admin用户,而我们对本地admin用户感兴趣,则可能会抛出此异常。[thet]
添加ignore_cert选项,用于在使用ldaps uri模式时忽略自签名证书的TLS/SSL证书错误。[thet]
日常维护。[rnix]
0.9.7
通过buildout配置添加通过测试ldap服务器钩子外部LDIF层的可能性。[rnix]
在buildout配置中更新openldap版本。[rnix]
0.9.6
添加新属性以允许禁用check_duplicates。这避免了连接使用作为键的非唯一属性的非ldap服务器时发生的以下异常。[saily]
Traceback (most recent call last): ... RuntimeError: Key not unique: <key>='<value>'.
确保attrlist值是字符串[rnix, 2013-12-03]
0.9.5
向node.ext.ldap.ugm._api.LDAPUser添加expired属性。[rnix, 2012-12-17]
引入node.ext.ldap.ugm._api.calculate_expired辅助函数。[rnix, 2012-12-17]
在node.ext.ldap.ugm._api.LDAPUser.authenticate中查找expired属性。[rnix, 2012-12-17]
0.9.4
在node.ext.ldap._node.LDAPStorage._ldap_modify中编码DN[rnix, 2012-11-08]
在node.ext.ldap._node.LDAPStorage._ldap_delete中编码DN[rnix, 2012-11-08]
在node.ext.ldap.ugm._api.LDAPUsers.passwd中编码DN[rnix, 2012-11-08]
在node.ext.ldap.ugm._api.LDAPUsers.authenticate中编码DN[rnix, 2012-11-07]
在LDAPPrincipal.member_of_attr中编码baseDN[rnix, 2012-11-06]
在AttributesBehavior.load中编码baseDN[rnix, 2012-11-06]
Python 2.7兼容性[rnix, 2012-10-16]
PEP-8. [rnix, 2012-10-16]
正确处理UTF-8 DN的LDAPPrincipals.idbydn。 [rnix, 2012-10-16]
将部分重命名为行为。 [rnix, 2012-07-29]
适配到node 0.9.8。 [rnix, 2012-07-29]
适配到plumber 1.2。 [rnix, 2012-07-29]
在LDAPSession.search中不要将cookie转换为unicode。Cookie值不是utf-8字符串,而是如http://tools.ietf.org/html/rfc2696.html中描述的八位字节字符串。 [rnix, 2012-07-27]
添加User.group_ids。 [rnix, 2012-07-26]
0.9.3
修改架构以防止仅绑定到测试BaseDN并延迟绑定。 [jensens, 2012-05-30]
0.9.2
从node.ext.ldap.properties.LDAPProps中移除escape_queries属性。 [rnix, 2012-05-18]
使用zope.interface.implementer而不是zope.interface.implements。 [rnix, 2012-05-18]
在posix用户和组相关的测试LDIF中使用结构化对象类inetOrgPerson而不是account。 [rnix, 2012-04-23]
会话不再神奇地解码一切,并防止从ldap获取二进制数据。LDAP-Node具有语义知识,可以确定二进制数据,并将所有非二进制数据和所有键转换为unicode。 [jensens, 2012-04-04]
添加or_values和or_keys以更精细地控制筛选条件 [iElectric, chaoflow, 2012-03-24]
支持分页搜索 [iElectric, chaoflow, 2012-03-24]
0.9.1
添加is_multivalued到属性,并修改node使用此列表而不是静态列表。为二进制属性做准备。 [jensens, 2012-03-19]
添加schema_info到node。 [jensens, 2012-03-19]
shadowInactive默认为0。 [rnix, 2012-03-06]
在principals配置中引入expiresAttr和expiresUnit。在Users.authenticate中考虑。 [rnix, 2012-02-11]
如果次要键集但条目中未找到属性,则不要抛出KeyError。在这种情况下,跳过条目。 [rnix, 2012-02-10]
在UGM API中强制使用unicode ids和keys。 [rnix, 2012-01-23]
添加对筛选器的unicode支持。 [rnix, 2012-01-23]
添加LDAPUsers.id_for_login。 [rnix, 2012-01-18]
实现openldap memberof overlay和AD memberOf行为的成员支持。 [rnix, 2011-11-07]
为ActiveDirectory添加LDAPProps.escape_queries。 [rnix, 2011-11-06]
为ActiveDirectory添加成员属性到组对象类映射。 [rnix, 2011-11-06]
使testlayer和testldap更灵活,以便在包外部使用。 [jensens, 2010-09-30]
0.9
重构bda.ldap。 [rnix, chaoflow]
待办事项
考虑具有超时的search_st。
调查ReconnectLDAPObject.set_cache_options。
仅在LDAPNode.sortonkeys上检查/实现静默排序。
交互式配置显示当前配置下找到多少用户/组,以及所选用户/组将如何显示。
为UGM添加配置验证。在Ugm.__init__中添加一些检查,以尝试阻止愚蠢的配置。
支持组内组。
重新设计ldap测试设置以允许有多个服务器,以便使用不同的overlay进行测试。最好启动不同的服务器或有一个包含多个数据库的服务器。 whatever feels better。
重新设计测试和ldif以针对隔离的方面。
可能多值的属性始终作为列表。
许可
版权(c)2006-2021,BlueDynamics Alliance,奥地利,德国,瑞士 版权(c)2021-2022,Node贡献者 所有权利保留。
在满足以下条件的情况下,允许以源代码和二进制形式重新分发和使用,无论是否修改:
源代码的重新分发必须保留上述版权声明、本许可清单以及以下免责声明。
二进制形式的重新分发必须在文档和/或其他与分发一起提供的材料中复制上述版权声明、本许可清单以及以下免责声明。
本软件由版权所有者和贡献者提供,“现状”且不提供任何明示或暗示的保证,包括但不限于适销性和适用于特定目的的隐含保证。在任何情况下,版权所有者或贡献者均不对任何直接、间接、偶然、特殊、示范性或后果性损害(包括但不限于替代货物或服务的采购;使用、数据或利润的损失;或业务中断)承担责任,无论此类损害是由于何种原因造成的,无论基于何种责任理论(合同、严格责任或侵权,包括疏忽或其他),即使被告知此类损害的可能性。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解有关安装包的更多信息。
源代码分发
构建分发
node.ext.ldap-1.2.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 18b4766c31bddbf9d72b8a564377fd1b8a9fdc9115afb6079c7aaf438e501c90 |
|
MD5 | cc7ee1e643dc22b307bb227e59fd42b4 |
|
BLAKE2b-256 | 7df669af7a712bde22d551e66160ce60c2aca220b79371173f1757e57670d0a2 |