keystone.identity.backends.ldap.common 模块

class keystone.identity.backends.ldap.common.AsynchronousMessage(message_id, connection, context_manager)[source]

基类: object

用于处理异步 LDAP 响应的容器。

某些 LDAP API,例如 search_ext,是异步的,并在服务器成功启动操作时返回消息 ID。客户端可以使用此消息 ID 和原始连接来使用 result3 请求获取结果。

此对象保存消息 ID、原始连接以及一个可调用弱引用 Finalizer,用于清理与消息 ID 关联的连接特定的上下文管理器。

参数:
  • message_id – 消息标识符 (str)。

  • connection – 与消息标识符关联的连接 (ldappool.StateConnector)。

“clean”属性是一个可调用对象,用于清理用于创建或返回连接对象的上下文管理器 (weakref.finalize)。

class keystone.identity.backends.ldap.common.BaseLdap(conf)[source]

基类: object

DEFAULT_EXTRA_ATTR_MAPPING: list[str] = []
DEFAULT_FILTER: str | None = None
DEFAULT_ID_ATTR: str = 'cn'
DEFAULT_OBJECTCLASS: str
DEFAULT_OU: str
DEFAULT_STRUCTURAL_CLASSES: list[str]
NotFound: Type[Error]
add_member(member_dn, member_list_dn)[source]

将成员添加到成员列表中。

参数:
  • member_dn – 要添加的成员的 DN。

  • member_list_dn – 要将成员添加到的组的 DN。

引发:
affirm_unique(values)[source]
attribute_ignore: list[str] = []
attribute_options_names: dict[str, str] = {}
create(values)[source]
filter_query(hints, query=None)[source]

应用过滤器到查询。

参数:
  • hints – 包含过滤器列表,可能为 None,表示没有要应用的过滤器。如果它不是 None,则满足此处的任何过滤器将被删除,以便调用者知道是否仍有过滤器需要应用。

  • query – 要包含过滤器的 LDAP 查询

返回 query:

已更新任何满足过滤器的 LDAP 查询

get(object_id, ldap_filter=None)[source]
get_all(ldap_filter=None, hints=None)[source]
get_by_name(name, ldap_filter=None)[source]
get_connection(user=None, password=None, end_user_auth=False)[source]
immutable_attrs: list[str] = []
model: Type[Model]
notfound_arg: str | None = None
options_name: str | None = None
tree_dn: str | None = None
update(object_id, values, old_obj=None)[source]
class keystone.identity.backends.ldap.common.EnabledEmuMixIn(conf)[source]

基类: BaseLdap

如果启用,则模拟布尔“enabled”属性。

创建一个包含所有已启用类别的组,所有缺失的类别都被视为禁用的。

选项

  • $name_enabled_emulation - 布尔值,启用/禁用

  • $name_enabled_emulation_dn - 该组的 DN,默认值为 cn=enabled_${name}s,${tree_dn}

  • $name_enabled_emulation_use_group_config - 布尔值,启用/禁用

其中 ${name}s 是 self.options_name 的复数形式(“users”或“tenants”),${tree_dn} 是 self.tree_dn。

DEFAULT_GROUP_MEMBERS_ARE_IDS = False
DEFAULT_GROUP_OBJECTCLASS = 'groupOfNames'
DEFAULT_MEMBER_ATTRIBUTE = 'member'
create(values)[source]
get(object_id, ldap_filter=None)[source]
get_all(ldap_filter=None, hints=None)[source]
update(object_id, values, old_obj=None)[source]
class keystone.identity.backends.ldap.common.KeystoneLDAPHandler(conn=None)[source]

基础: LDAPHandler

转换数据类型并执行日志记录。

此 LDAP 接口包装了基于 python-ldap 的接口。python-ldap 接口需要用 UTF-8 编码的字符串值,除了 [1] 之外。在撰写本文时,OpenStack 日志框架无法接受用 UTF-8 编码的字符串,如果字符串中出现非 ASCII 字符,日志函数将抛出解码错误。

[1] 在 python-ldap 中,某些字段(DN、RDN、属性名称、查询)表示为文本(Python 3 上的 str,当 bytes_mode=False 时,Python 2 上的 unicode)。有关更多详细信息,请参见:http://www.python-ldap.org/en/2025.2/bytes_mode.html#bytes-mode

在调用之前,Python 数据类型将转换为 LDAP API 所需的字符串表示形式。

然后执行日志记录,以便我们可以跟踪发送/接收到 LDAP 的内容。日志记录还会过滤掉敏感安全项目(例如密码)。

然后将字符串值编码为 UTF-8。

然后调用 LDAP API 入口点。

从 LDAP 调用返回的数据将从 UTF-8 编码的字符串转换回 OpenStack 内部使用的 Python 数据类型。

add_s(dn, modlist)[source]
connect(url, page_size=0, alias_dereferencing=None, use_tls=False, tls_cacertfile=None, tls_cacertdir=None, tls_req_cert=2, chase_referrals=None, debug_level=None, conn_timeout=None, use_pool=None, pool_size=None, pool_retry_max=None, pool_retry_delay=None, pool_conn_timeout=None, pool_conn_lifetime=None)[source]
get_option(option)[source]
modify_s(dn, modlist)[source]
result3(msgid=-1, all=1, timeout=None, resp_ctrl_classes=None)[source]
search_ext(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0)[source]
search_s(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0)[source]
set_option(option, invalue)[source]
simple_bind_s(who='', cred='', serverctrls=None, clientctrls=None)[source]
unbind_s()[source]
class keystone.identity.backends.ldap.common.LDAPHandler(conn=None)[source]

基类: object

抽象类,定义 LDAP API 提供程序的接口。

本地 Keystone 值不能直接传递到 python-ldap API 中和从中传递。类型转换必须发生在 LDAP API 边界处,类型转换的示例是

  • 布尔值映射到字符串“TRUE”和“FALSE”

  • 整数值映射到其字符串表示形式。

  • unicode 字符串编码为 UTF-8

请注意,在 python-ldap 中,某些字段(DN、RDN、属性名称、查询)表示为文本(Python 3 上的 str,当 bytes_mode=False 时,Python 2 上的 unicode)。有关更多详细信息,请参见:http://www.python-ldap.org/en/2025.2/bytes_mode.html#bytes-mode

除了处理 API 边界处的类型转换之外,我们还要求支持多个 LDAP API 提供程序。目前我们有

  • python-ldap,这是 Python 的标准 LDAP API,它需要访问实时 LDAP 服务器。

  • Fake LDAP 模拟 python-ldap。这用于测试,无需实时 LDAP 服务器。

为了支持这些要求,我们需要一个执行类型转换然后调用另一个可配置的 LDAP API(例如,python-ldap 或模拟)的层。

由于日志记录模块的限制,我们还有额外的约束。日志记录模块无法接受 UTF-8 编码的字符串,它将抛出编码异常。因此,所有日志记录都必须在 UTF-8 转换之前执行。这意味着不能在实现 python-ldap API 的 LDAP API 中执行任何日志记录,因为这些 API 被定义为仅接受 UTF-8 字符串。因此,执行类型转换的层也必须执行日志记录。我们分两步执行类型转换,首先将所有 Python 类型转换为 unicode 字符串,然后记录,然后将 unicode 字符串转换为 UTF-8。

可以采用多种方法来实现这一点,我们选择使用链式技术,其中此类实例简单地通过“conn”属性调用链中的下一个成员。通过在实例化类时将现有此类实例作为 conn 属性传递来构建链。

以下是为什么不使用其他可能方法的简要说明

子类化

为了以正确的顺序执行包装操作,类型转换类需要继承每个 API 提供者。这样做很笨拙,会将类数量翻倍,并且可扩展性差。它要求类型转换类了解所有可能的 API 提供者。

装饰器

装饰器提供了一种优雅的解决方案来包装方法,并且是执行类型转换的一种理想方式,在调用被包装的函数之前执行类型转换,然后在被包装的函数返回后转换值。但是,装饰器需要了解方法签名,它必须知道需要转换哪些输入参数以及如何转换结果。对于像 python-ldap 这样具有大量不同方法签名的 API,它需要大量的专用装饰器。经验表明,由于固有的复杂性和剪切粘贴代码的倾向,很容易应用错误的装饰器。另一种选择是参数化装饰器使其“智能”。经验表明,这样的装饰器会变得极其复杂且难以理解和调试。此外,装饰器往往会隐藏方法调用时真正发生的事情,在查看装饰方法的实现时,执行的操作不可见,经验也表明这会导致错误。

链式调用简化了执行类型转换的包装以及替代 API 提供者的替换。只需创建一个 API 接口的新实例,并将其插入到链的前面。类型转换是显式的且明显的。

如果需要向 API 接口添加新方法,则将其添加到抽象类定义中。如果遗漏将新方法添加到抽象类的任何派生类中,代码将无法加载和运行,从而不可能忘记更新所有派生类。

abstract add_s(dn, modlist)[source]
abstract connect(url, page_size=0, alias_dereferencing=None, use_tls=False, tls_cacertfile=None, tls_cacertdir=None, tls_req_cert=2, chase_referrals=None, debug_level=None, conn_timeout=None, use_pool=None, pool_size=None, pool_retry_max=None, pool_retry_delay=None, pool_conn_timeout=None, pool_conn_lifetime=None)[source]
abstract get_option(option)[source]
abstract modify_s(dn, modlist)[source]
abstract result3(msgid=-1, all=1, timeout=None, resp_ctrl_classes=None)[source]
abstract search_ext(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0)[source]
abstract search_s(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0)[source]
abstract set_option(option, invalue)[source]
abstract simple_bind_s(who='', cred='', serverctrls=None, clientctrls=None)[source]
abstract unbind_s()[source]
class keystone.identity.backends.ldap.common.PooledLDAPHandler(conn=None, use_auth_pool=False)[source]

基础: LDAPHandler

使用池连接管理器的 LDAPHandler 实现。

池的特定配置在 [ldap] 部分中定义。所有其他 LDAP 配置仍然从 [ldap] 部分使用

Keystone LDAP 身份验证逻辑使用其 DN 和密码通过 LDAP 绑定来验证最终用户,以建立提供的密码是否正确。这会迅速填满池(因为池基于其绑定数据重用现有连接),并且不会为其他 LDAP 操作留下池中的空间。现在,当启用相关标志“use_auth_pool”时,可以为这些请求建立一个单独的池。该池可以有自己的大小和连接生命周期。其他池属性在这些池之间共享。如果禁用“use_pool”,则“use_auth_pool”无关紧要。如果未启用“use_auth_pool”,则对于这些 LDAP 操作不使用连接池。

请注意,python-ldap API 要求所有字符串属性值都采用 UTF-8 编码。KeystoneLDAPHandler 在调用此类中的方法之前强制执行此操作。

请注意,在 python-ldap 中,某些字段(DN、RDN、属性名称、查询)表示为文本(Python 3 上的 str,当 bytes_mode=False 时,Python 2 上的 unicode)。有关更多详细信息,请参见:http://www.python-ldap.org/en/2025.2/bytes_mode.html#bytes-mode

Connector

别名 StateConnector

add_s(*args, **kwargs)[source]
auth_pool_prefix = 'auth_pool_'
connect(url, page_size=0, alias_dereferencing=None, use_tls=False, tls_cacertfile=None, tls_cacertdir=None, tls_req_cert=2, chase_referrals=None, debug_level=None, conn_timeout=None, use_pool=None, pool_size=None, pool_retry_max=None, pool_retry_delay=None, pool_conn_timeout=None, pool_conn_lifetime=None)[source]
connection_pools: dict = {}
get_option(option)[source]
modify_s(*args, **kwargs)[source]
result3(message, all=1, timeout=None, resp_ctrl_classes=None)[source]

等待并返回异步消息的结果。

此方法返回先前由 LDAP 异步操作例程(例如 search_ext())启动的操作的结果。python-ldap 中的 search_ext() 方法在 LDAP 服务器成功启动操作后返回一个调用标识符或消息 ID。

期望 messageAsynchronousMessage 类的实例,其中包含消息 ID 和用于发出原始请求的连接。

当调用 message.clean() 时,与 search_ext() 关联的连接和上下文管理器将被清理。

search_ext(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0)[source]

返回一个 AsynchronousMessage 实例,它是异步 API。

AsynchronousMessage 实例可以安全地在 result3() 中调用。

为了以可预测的方式使用 result3() API,需要使用最初提供 msgid 的相同 LDAP 连接。因此,此方法将现有的连接和 msgid 包装在一个新的 AsynchronousMessage 实例中。在 result3() 获取与 msgid 关联的数据后,与 search_ext() 关联的连接将被释放。

search_s(*args, **kwargs)[source]
set_option(option, invalue)[source]
simple_bind_s(who='', cred='', serverctrls=None, clientctrls=None)[source]
unbind_s()[source]
class keystone.identity.backends.ldap.common.PythonLDAPHandler(conn=None)[source]

基础: LDAPHandler

LDAPHandler 实现,它调用 python-ldap API。

注意,python-ldap API 要求所有字符串属性值都必须使用 UTF-8 编码。

请注意,在 python-ldap 中,某些字段(DN、RDN、属性名称、查询)表示为文本(Python 3 上的 str,当 bytes_mode=False 时,Python 2 上的 unicode)。有关更多详细信息,请参见:http://www.python-ldap.org/en/2025.2/bytes_mode.html#bytes-mode

KeystoneLDAPHandler 在调用此类的这些方法之前强制执行此操作。

add_s(dn, modlist)[source]
connect(url, page_size=0, alias_dereferencing=None, use_tls=False, tls_cacertfile=None, tls_cacertdir=None, tls_req_cert=2, chase_referrals=None, debug_level=None, conn_timeout=None, use_pool=None, pool_size=None, pool_retry_max=None, pool_retry_delay=None, pool_conn_timeout=None, pool_conn_lifetime=None)[source]
get_option(option)[source]
modify_s(dn, modlist)[source]
result3(msgid=-1, all=1, timeout=None, resp_ctrl_classes=None)[source]
search_ext(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0, serverctrls=None, clientctrls=None, timeout=-1, sizelimit=0)[source]
search_s(base, scope, filterstr='(objectClass=*)', attrlist=None, attrsonly=0)[source]
set_option(option, invalue)[source]
simple_bind_s(who='', cred='', serverctrls=None, clientctrls=None)[source]
unbind_s()[source]
convert_ldap_result(ldap_result)[source]

将 LDAP 搜索结果转换为 OpenStack 使用的 Python 类型。

每个结果元组的形式为 (dn, attrs),其中 dn 是包含条目区分名称 (DN) 的字符串,attrs 是一个字典,其中包含与条目关联的属性。attrs 的键是字符串,关联的值是字符串列表。

OpenStack 希望使用它选择的 Python 类型。字符串将是 unicode,真值是布尔值,整数是 int,等等。DN 在 python-ldap 中默认情况下以文本形式表示,适用于 Python 3,并且当 Python 2 的 bytes_mode=False 时,因此不需要解码。

参数:

ldap_result – LDAP 搜索结果

返回值:

包含 (dn, attrs) 的 2 元组列表,其中 dn 是 unicode,attrs 是值类型转换为 OpenStack 首选类型的字典。

keystone.identity.backends.ldap.common.dn_startswith(descendant_dn, dn)[source]

如果且仅当 descendant_dn 在 dn 下方时,返回 True。

参数:
  • descendant_dn – 可能是字符串 DN 或由 ldap.dn.str2dn 解析的 DN。

  • dn – 可能是字符串 DN 或由 ldap.dn.str2dn 解析的 DN。

keystone.identity.backends.ldap.common.enabled2py(val)[source]

类似于 ldap2py,仅对 enabled 属性有用。

keystone.identity.backends.ldap.common.filter_entity(entity_ref)[source]

过滤实体字典中的私有项。

参数:

entity_ref – 实体字典。将移除 ‘dn’ 字段。‘dn’ 用于 LDAP,但不应返回给用户。此值可能会被修改。

返回值:

entity_ref

keystone.identity.backends.ldap.common.is_ava_value_equal(attribute_type, val1, val2)[source]

仅当 AVAs 相等时返回 True。

在比较 AVAs 时,应考虑属性类型的相等匹配规则。为简单起见,此实现执行不区分大小写的比较。

请注意,此函数使用 prep_case_insenstive,因此该函数的限制也适用于此处。

keystone.identity.backends.ldap.common.is_dn_equal(dn1, dn2)[source]

仅当 DNs 相等时返回 True。

如果 DN 具有相同数量的 RDN,并且在每个位置的 RDN 相同,则两个 DN 相等。请参阅 RFC4517。

请注意,此函数使用 is_rdn_equal 比较 RDN,因此该函数的限制也适用于此处。

参数:
  • dn1 – 字符串 DN 或由 ldap.dn.str2dn 解析的 DN。

  • dn2 – 字符串 DN 或由 ldap.dn.str2dn 解析的 DN。

keystone.identity.backends.ldap.common.is_rdn_equal(rdn1, rdn2)[source]

仅当 RDNs 相等时返回 True。

  • RDNs 必须具有相同数量的 AVAs。

  • RDNs 的每个 AVA 必须与相同属性类型相等。顺序不重要。请注意,一个属性类型仅在一个 RDN 中的一个 AVA 中,否则 DN 无效。

  • 属性类型不区分大小写。请注意,属性类型比较比此处实现的更复杂。此函数仅比较不区分大小写。代码应处理属性类型的多个名称(例如,cn、commonName 和 2.5.4.3 相同)。

请注意,此函数使用 is_ava_value_equal 比较 AVAs,因此该函数的限制也适用于此处。

keystone.identity.backends.ldap.common.ldap2py(val)[source]

将 LDAP 格式的值转换为 OpenStack 使用的 Python 类型。

几乎所有 LDAP 值都存储为 UTF-8 编码的字符串。OpenStack 倾向于使用 Unicode 友好的值。

参数:

val – LDAP 格式的值

返回值:

val 转换为首选的 Python 类型

keystone.identity.backends.ldap.common.ldap_scope(scope)[source]
keystone.identity.backends.ldap.common.parse_deref(opt)[source]
keystone.identity.backends.ldap.common.parse_tls_cert(opt)[source]
keystone.identity.backends.ldap.common.prep_case_insensitive(value)[source]

准备字符串进行不区分大小写的比较。

这在 RFC4518 中定义。为简单起见,此函数所做的一切都是将所有字符转换为小写,删除前导和尾随空格,并将空格序列压缩为单个空格。

keystone.identity.backends.ldap.common.py2ldap(val)[source]

将 Python 值转换为 LDAP 接受的类型(unicode)。

LDAP API 仅接受值的字符串,因此将该值的类型转换为 Unicode 字符串。后续类型转换将根据 python-ldap API 的要求将 Unicode 编码为 UTF-8,但现在我们只需要该值的字符串表示形式。

参数:

val – 要转换为 LDAP 字符串表示形式的值

返回值:

值的 Unicode 字符串表示形式。

keystone.identity.backends.ldap.common.register_handler(prefix, handler)[source]
keystone.identity.backends.ldap.common.safe_iter(attrs)[source]
keystone.identity.backends.ldap.common.use_conn_pool(func)[source]

仅对连接池特定的 LDAP API 使用此项。

这会将连接对象作为被装饰 API 之后的下一个参数添加到其中。

keystone.identity.backends.ldap.common.utf8_decode(value)[source]

将 UTF-8 解码为 Unicode。

如果该值为二进制字符串,则假定其为 UTF-8 编码并将其解码为 Unicode 字符串。否则,将该值从其类型转换为 Unicode 字符串。

参数:

value – 要返回为 Unicode 的值

返回值:

值为 Unicode

引发:

UnicodeDecodeError – 对于无效的 UTF-8 编码

keystone.identity.backends.ldap.common.utf8_encode(value)[source]

将 basestring 编码为 UTF-8。

如果字符串是 Unicode,则将其编码为 UTF-8;如果字符串是 str,则假定其已编码。否则,引发 TypeError。

参数:

value – 一个 basestring

返回值:

value 的 UTF-8 编码版本

引发:

TypeError – 如果 value 不是 basestring