跳转到主要内容

基于python-ldap的Node-trees LDAP/AD便捷性

项目描述

node.ext.ldap

Latest PyPI version Number of PyPI downloads Test 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

要返回的属性名称列表。允许特殊属性rdndn

关系

可以是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(除配置为二进制属性外),请将其视为错误。 LDAPSessionLDAPConnectorLDAPCommunicator 是编码中立的,它们不进行解码或编码。

传递给节点或会话的 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 上实现 expiresexpired 属性,如 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_timeoutop_timeout 属性(默认不设置)添加到配置 ReconnectLDAPObject。[mamico]

  • 采用从 node 1.1 中的生命周期相关更改。[rnix]

  • ensure_connectionLDAPSession 移至 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.changednode.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 架构的 objectSidobjectGUID 添加到 properties.BINARY_DEFAULTS。[rnix]

  • 修复 LDAPStorage._multivalued_attributesLDAPStorage._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配置中引入expiresAttrexpiresUnit。在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 (1.9 MB 查看哈希值)

上传时间 源代码

构建分发

node.ext.ldap-1.2-py3-none-any.whl (2.0 MB 查看哈希值)

上传时间 Python 3

由以下支持