LDAP 设置
From LimeSurvey Manual
常规
要使用此功能,您必须在 config.php 中启用 LDAP 支持,并在 config/ldap.php 中配置 LDAP 参数。
在 config.php 中启用 LDAP
- $enableLdap: 如果您想在 LimeSurvey 中使用 LDAP 功能,必须将此参数设置为 true(默认值为 false):
'config'=>array( 'debug'=>0, 'debugsql'=>0, 'enableLdap'=>true, )
定义 LDAP 服务器
首先在 "application/config/ldap.php" 中定义 LDAP 服务器连接选项。每个服务器都有以下可用选项:
- $serverId: 用于标识此 LDAP 服务器的整数。它在查询定义中使用,用于将服务器绑定到特定查询;
- $ldap_server[$serverId]['server']: LDAP 服务器的 IP 地址或 DNS 名称。如果您使用 SSL 安全连接(LDAPs 或 LDAP+Start-TLS),此名称必须与服务器证书的 CN(或 SubjectAlternativeName)一致;
- $ldap_server[$serverId]['port']: LDAP 服务器监听的 TCP 端口。LDAP 和 LDAP+Start-TLS 通常为 389,LDAPs 通常为 636;
- $ldap_server[$serverId]['protoversion']: 可以是 'ldapv2' 或 'ldapv3',取决于您的服务器支持的协议。推荐使用 'ldapv3' 协议。但是,如果您想使用加密连接,请注意 LDAPs 在 'ldapv2' 模式下受支持,而 Start-TLS 是 'ldapv3' 的加密方式;
- $ldap_server[$serverId]['encrypt']: 定义所使用的加密方式。'ldapv2' 服务器支持 'ldaps','ldapv3' 服务器支持 'start-tls'。关键字 'none' 用于明文 LDAP 通信;
- 请不要忘记,对于 'ldaps' 或 'start-tls' 加密,Web 服务器必须能够验证 LDAP 服务器的证书。因此,您需要在 openldap 库中定义您的证书颁发机构(在 Linux 下通常在 /etc/openldap/ldap.conf 文件中完成)。
- $ldap_server[$serverId]['referrals']: 这是一个布尔参数,用于定义是否必须跟随引用(对于 ActiveDirectory,请使用 false);
- $ldap_server[$serverId]['encoding']: 这是一个可选参数,用于指定 LDAP 目录存储字符串时使用的编码。通常您无需设置此参数,因为默认假定的编码 'utf-8' 是 LDAP 目录的标准编码。但是,如果您使用 Active Directory 并且在导入带重音符号的字符串时遇到问题,请尝试将此参数设置为您所在地区使用的编码(例如西欧使用 'cp850')。您可以参阅从CSV文件导入邀請碼界面中的“文件的字符集”下拉列表,查看受支持编码的完整列表。
接下来,您需要定义访问目录所需的身份验证。如果允许 'anonymous'(匿名)访问,请【不要】设置以下两个参数,否则请相应地设置它们:
- $ldap_server[$serverId]['binddn']: 被允许读取目录的 'LDAP' 用户的 DN;
- $ldap_server[$serverId]['bindpw']: 上述 LDAP 用户的密码。
如果您需要定义其他 LDAP 服务器,请添加以下行以递增 serverID 并定义新的参数:
- $serverId++。
在 config/ldap.php 中定义查询
注意:当这些参数中需要 ldap 属性名称 时,请仅使用小写名称:例如使用 displayname,而不是 displayName。
请参阅 config/ldap.php 文件,其中包含配置示例。
简单查询
让我们从简单查询开始。这些查询仅根据 LDAP 条目自身的属性和位置对其进行筛选。它们通常足以用于查询 ActiveDirectory。
- $query_id: LDAP 查询的 ID;
- $ldap_queries[$query_id]['ldapServerId']: 将查询绑定到特定服务器;
- $ldap_queries[$query_id]['name']: 描述查询的字符串。它将显示在界面中;
- $ldap_queries[$query_id]['userbase']: 用户搜索所使用的根 DN;
- $ldap_queries[$query_id]['userfilter']: 用于选择潜在用户条目的筛选器。它必须用括号括起来;
- $ldap_queries[$query_id]['userscope']: 用户 LDAP 搜索的范围('base'、'one' 或 'sub');
- $ldap_queries[$query_id]['firstname_attr']: 将映射到邀請碼条目“名”字段的 Ldap 属性;
- $ldap_queries[$query_id]['lastname_attr']: 将映射到邀請碼条目“姓”字段的 Ldap 属性;
- $ldap_queries[$query_id]['email_attr']: 将映射到邀請碼条目邮件地址字段的 Ldap 属性。
此外,您还可以选择从目录中获取更多信息:
- $ldap_queries[$query_id]['token_attr']: 将映射到邀請碼代码的 Ldap 属性;
- $ldap_queries[$query_id]['language']: 将映射到用户语言编码的 Ldap 属性;
- $ldap_queries[$query_id]['attr1']: 将映射到 attribute_1 字段的 Ldap 属性;
- $ldap_queries[$query_id]['attr2']: 将映射到 attribute_2 字段的 Ldap 属性。
使用 DN 成员的组合组查询
现在让我们来看看如何定义更复杂的查询。
以下查询首先使用一次 LDAP 搜索来查找 LDAP 组。LDAP 组是一种 LDAP 条目,其中包含对用户条目的引用,形式为:
- 用户 ID(例如 posixGroups) ==> 请参阅下一节
- 或用户 DN(例如 groupofnames 和 groupofuniquenames) ==> 见下文
这里我们处理包含用户 DN 的组:
- 如上所述,定义 $query_id、$ldap_queries[$query_id]['ldapServerId']、$ldap_queries[$query_id]['name']。
然后定义组筛选器参数:
- $ldap_queries[$query_id]['groupbase']: 开始搜索组条目的根 DN;
- $ldap_queries[$query_id]['groupfilter']: 用于选择潜在组条目的 LDAP 筛选器;
- $ldap_queries[$query_id]['groupscope']: 组 LDAP 搜索的范围('one'、'base' 或 'sub');
- $ldap_queries[$query_id]['groupmemberattr']: 组条目中包含用户条目引用的 LDAP 属性的名称;
- $ldap_queries[$query_id]['groupmemberisdn']: TRUE。
至此,所有设置均已完成,第一次 LDAP 搜索可以找到与所选组相对应的用户。不过,您可以通过对这些“候选用户”再应用一个筛选器来限制最终选中的用户。当然,这是可选的:
- $ldap_queries[$query_id]['userbase']: 用户 LDAP 搜索的基础 DN(只有符合此基础的候选用户才会被选中);
- $ldap_queries[$query_id]['userscope']: 用户 LDAP 搜索的范围(只有符合 userbase+scope 的候选用户才会被选中);
- $ldap_queries[$query_id]['userfilter']: 应用于每个候选用户条目(基于其属性)的筛选器,用于增加另一层选择。
使用 UID 成员的组合组查询
现在让我们来看看,当组成员是用户 UID 而不是用户 DN 时,如何定义组合组查询。
与使用 DN 成员的组查询一样,这些查询首先使用一次 LDAP 搜索来查找 LDAP 组条目并获取其成员。然后,这些成员值会被用于用户搜索筛选器,以搜索相应的条目。因此,还必须配置另一个参数,用于定义用户条目中应与组内成员 UID 相匹配的用户属性。
让我们回顾一下所需的参数:
- 如上所述,定义 $query_id、$ldap_queries[$query_id]['ldapServerId']、$ldap_queries[$query_id]['name']
然后定义组筛选器参数:
- $ldap_queries[$query_id]['groupbase']: 开始搜索组条目的根 DN;
- $ldap_queries[$query_id]['groupfilter']: 用于选择潜在组条目的 LDAP 筛选器;
- $ldap_queries[$query_id]['groupscope']: 组 LDAP 搜索的范围('one'、'base' 或 'sub');
- $ldap_queries[$query_id]['groupmemberattr']: 组条目中包含用户条目引用的 LDAP 属性的名称;
- $ldap_queries[$query_id]['groupmemberisdn']: FALSE;
- $ldap_queries[$query_id]['useridattr']: 必须与组成员中找到的 UID 相匹配的用户属性的名称。
至此,所有设置均已完成,第一次 LDAP 搜索可以找到与所选组相对应的用户 UID,并且用户搜索筛选器将自动填充。
不过,您可以通过补全根据成员 UID 自动生成的用户筛选条件,进一步限制选中哪些“候选用户”。当然,这是可选的:
- $ldap_queries[$query_id]['userbase']: 用户 LDAP 搜索的基准 DN(仅选中匹配此基准的候选用户);
- $ldap_queries[$query_id]['userscope']: 用户 LDAP 搜索的范围(仅选中匹配 userbase 和范围的候选用户);
- $ldap_queries[$query_id]['userfilter']: 应用于每个候选用户条目(基于其属性)的筛选条件,用于增加另一层选择。
Active Directory 怎么办?
Active Directory (AD) 是 Microsoft 的注册表,可以使用 LDAP 协议对其进行查询。
因此可以将其内容用于 LimeSurvey 邀請碼查询,但这需要了解 AD 的组织方式。
- LDAP 根基准为 dc=my_windows_domain_name,dc=dns_suffix2,dc=dns_suffix1
==> 例如,如果您的公司拥有 DNS 域名“my-company.com”,且您的 Windows 域为“employees”,则根基准为 dc=employees,dc=my-company,dc=com
- 用户和用户组存储在 cn=Users,dc=my_windows_domain_name,dc=dns_suffix2,dc=dns_suffix1 之下(请注意,这不是 ou=users);
- Active Directory 组:
- 组对象在其“member”属性中包含成员的 DN;
- 组成员关系也存储在每个用户条目的 memberOf 属性中。该属性包含用户所属组的 DN;
- 部分组位于 CN=Builtin,dc=my_windows_domain_name,dc=dns_suffix2,dc=dns_suffix1:
- 例如:cn=Administrator,CN=Builtin,dc=my_windows_domain_name,dc=dns_suffix2,dc=dns_suffix1;
在某些情况下,查询 Active Directory 并不那么容易,因此下面提供一个从 Active Directory 获取部分信息的配置示例:
//Connection to the active directory Server:
$serverId=0;
$ldap_server[$serverId]['server'] = "10.10.10.10";
$ldap_server[$serverId]['port'] = "389";
$ldap_server[$serverId]['protoversion'] = "ldapv2";
$ldap_server[$serverId]['encrypt'] = "none"; // Most AD LDAP servers will not have encryption set by default
$ldap_server[$serverId]['referrals'] = false;
$ldap_server[$serverId]['binddn'] = "domain\\user";
$ldap_server[$serverId]['bindpw'] = "userpassword";
//$ldap_server[$serverId]['binddn'] = "CN=user,OU=user_group,DC=xxx,DC=yyy"; this one will not work with active directory, that´s why you need to use "domain\\user"
//Here is a sample query for getting all active users of an active directory:
$query_id=0;
$ldap_queries[$query_id]['ldapServerId'] = 0;
$ldap_queries[$query_id]['name'] = 'Staff with an enabled account';
$ldap_queries[$query_id]['userbase'] = 'OU=USER_GROUP,DC=xxx,DC=yyy';
$ldap_queries[$query_id]['userfilter'] = '(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))';
//AD has no "account is enabled" attribute, so the filter excludes accounts that have the "disabled" flag (bit 2) set in userAccountControl
$ldap_queries[$query_id]['userscope'] = 'sub';
$ldap_queries[$query_id]['firstname_attr'] = 'givenname';
$ldap_queries[$query_id]['lastname_attr'] = 'sn';
$ldap_queries[$query_id]['email_attr'] = 'mail';
$ldap_queries[$query_id]['token_attr'] = ''; // Leave empty for Auto Token generation by phpsv
$ldap_queries[$query_id]['language'] = '';
$ldap_queries[$query_id]['attr1'] = '';
$ldap_queries[$query_id]['attr2'] = '';
//You can also filter on group membership through the memberOf attribute of the user. Here is a sample query for getting all active users that are member of the group "samplegroup" in active directory:
$query_id++;
$ldap_queries[$query_id]['ldapServerId'] = 0;
$ldap_queries[$query_id]['name'] = 'All members of samplegroup';
$ldap_queries[$query_id]['userbase'] = 'OU=USER_GROUP,DC=xxx,DC=yyy';
$ldap_queries[$query_id]['userfilter'] = '(&(objectClass=user)(memberOf=CN=samplegroup,OU=Group Global,OU=USER_GROUP,DC=xxx,DC=yyy)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))';
$ldap_queries[$query_id]['userscope'] = 'sub';
$ldap_queries[$query_id]['firstname_attr'] = 'givenname';
$ldap_queries[$query_id]['lastname_attr'] = 'sn';
$ldap_queries[$query_id]['email_attr'] = 'mail';
$ldap_queries[$query_id]['token_attr'] = ''; // Leave empty for Auto Token generation by phpsv
$ldap_queries[$query_id]['language'] = '';
$ldap_queries[$query_id]['attr1'] = '';
$ldap_queries[$query_id]['attr2'] = '';
另一个用户查询示例:
$ldap_queries[$query_id]['userfilter'] = '(&(objectCategory=Person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))'; // AD doesn't recognise enabled accounts in the normal way, so instead, we check users are not disabled
- 建议在用户筛选条件中添加 (mail=*),以跳过没有电子邮件地址的用户。
组查询示例:
$ldap_queries[$query_id]['groupfilter'] = '(&(objectClass=group)(cn=Domain Admins))'; // AD doesn't use the standard attribute name for groups, so use this example instead.
自动为每个 Active Directory 组创建一个查询
如果您将调查参与者所在的组集中存放在一个组织单位 (OU) 中,则无需为每个组分别编写查询。以下代码会列出该 OU 中的所有组,并为每个组创建一个查询。该查询包含该组的所有已启用成员,包括嵌套组中的成员。
将代码添加到 config/ldap.php 中,位于服务器和查询定义之后、//DO NOT CHANGE BELOW HERE 这一行之前。然后设置顶部的四个变量:
- $groupServerId: 要使用的服务器的 $serverId;
- $groupContainer: 存放这些组的 OU 的 DN。仅列出直接位于此 OU 中的组,不包括子 OU 中的组;
- $groupUserBase: 用于搜索组成员的根 DN;
- $groupNameAttr: 在界面中用作查询名称的组属性(小写),例如 'info'(“Active Directory 用户和计算机”中的“备注”字段)或 'description'。如果某个组在此属性中没有值,则使用其 cn。
$groupServerId = 0;
$groupContainer = 'OU=Survey groups,DC=xxx,DC=yyy';
$groupUserBase = 'DC=xxx,DC=yyy';
$groupNameAttr = 'info';
$srv = $ldap_server[$groupServerId];
$ds = ldap_connect(($srv['encrypt'] == 'ldaps' ? 'ldaps://' : 'ldap://') . $srv['server'] . ':' . $srv['port']);
if ($ds) {
ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $srv['protoversion'] == 'ldapv3' ? 3 : 2);
ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);
// Never send the bind password if Start-TLS was requested but failed
$secure = $srv['encrypt'] != 'start-tls' || @ldap_start_tls($ds);
$bound = $secure && (empty($srv['binddn']) ? @ldap_bind($ds) : @ldap_bind($ds, $srv['binddn'], $srv['bindpw']));
$result = $bound ? @ldap_list($ds, $groupContainer, '(objectCategory=group)', array('cn', $groupNameAttr)) : false;
$groups = $result ? ldap_get_entries($ds, $result) : array('count' => 0);
for ($i = 0; $i < $groups['count']; $i++) {
$query_id++;
$ldap_queries[$query_id]['ldapServerId'] = $groupServerId;
$ldap_queries[$query_id]['name'] = isset($groups[$i][$groupNameAttr][0]) ? $groups[$i][$groupNameAttr][0] : $groups[$i]['cn'][0];
$ldap_queries[$query_id]['userbase'] = $groupUserBase;
// Enabled users with an email address that are members of the group, directly or through nested groups
$ldap_queries[$query_id]['userfilter'] = '(&(objectCategory=person)(objectClass=user)(mail=*)'
. '(!(userAccountControl:1.2.840.113556.1.4.803:=2))'
. '(memberOf:1.2.840.113556.1.4.1941:=' . ldap_escape($groups[$i]['dn'], '', LDAP_ESCAPE_FILTER) . '))';
$ldap_queries[$query_id]['userscope'] = 'sub';
$ldap_queries[$query_id]['firstname_attr'] = 'givenname';
$ldap_queries[$query_id]['lastname_attr'] = 'sn';
$ldap_queries[$query_id]['email_attr'] = 'mail';
$ldap_queries[$query_id]['token_attr'] = '';
$ldap_queries[$query_id]['language'] = '';
$ldap_queries[$query_id]['attr1'] = '';
$ldap_queries[$query_id]['attr2'] = '';
}
ldap_unbind($ds);
}
注意事项:
- 每次打开或提交“导入参与者来自:LDAP”页面时,代码都会连接到目录,因此无需更改配置即可显示新增的组。如果连接或绑定失败,页面将只显示您手动定义的查询。
- 匹配规则 1.2.840.113556.1.4.1941 (LDAP_MATCHING_RULE_IN_CHAIN) 可解析嵌套组。该规则仅存在于 Active Directory 中,因此本示例不适用于其他 LDAP 服务器。对于其他服务器,请为每个组改用组合组查询。
- 若要同时列出 $groupContainer 的子 OU 中的组,请将 ldap_list 替换为 ldap_search。
- 若要列出多个 OU 中的组,请使用这四个变量的其他取值重复该代码。
有关 Active Directory LDAP 结构的更多信息,请参阅 Active Directory Architecture 和 Active Directory Technical Specification。