Applied the (modified) patch to extend configuration possibilities of LDAP address books as suggested in #1488753:

- Add option to specify arbitrary replacements of config options with attributes from the bound user
- Allow mapping of group object class => member attribute used in these objects
- Describe the 'member_filter' property for groups config
pull/146/head
Thomas Bruederli 11 years ago
parent 98b7b548a2
commit 3ce7c56826

@ -687,6 +687,8 @@ $config['ldap_public']['Verisign'] = array(
// DN and password to bind as before searching for bind DN, if anonymous search is not allowed // DN and password to bind as before searching for bind DN, if anonymous search is not allowed
'search_bind_dn' => '', 'search_bind_dn' => '',
'search_bind_pw' => '', 'search_bind_pw' => '',
// Optional map of replacement strings => attributes used when binding for an individual address book
'search_bind_attrib' => array(), // e.g. array('%udc' => 'ou')
// Default for %dn variable if search doesn't return DN value // Default for %dn variable if search doesn't return DN value
'search_dn_default' => '', 'search_dn_default' => '',
// Optional authentication identifier to be used as SASL authorization proxy // Optional authentication identifier to be used as SASL authorization proxy
@ -768,14 +770,19 @@ $config['ldap_public']['Verisign'] = array(
// if the groups base_dn is empty, the contact base_dn is used for the groups as well // if the groups base_dn is empty, the contact base_dn is used for the groups as well
// -> in this case, assure that groups and contacts are separated due to the concernig filters! // -> in this case, assure that groups and contacts are separated due to the concernig filters!
'groups' => array( 'groups' => array(
'base_dn' => '', 'base_dn' => '',
'scope' => 'sub', // Search mode: sub|base|list 'scope' => 'sub', // Search mode: sub|base|list
'filter' => '(objectClass=groupOfNames)', 'filter' => '(objectClass=groupOfNames)',
'object_classes' => array("top", "groupOfNames"), 'object_classes' => array('top', 'groupOfNames'), // Object classes to be assigned to new groups
'member_attr' => 'member', // Name of the member attribute, e.g. uniqueMember 'member_attr' => 'member', // Name of the default member attribute, e.g. uniqueMember
'name_attr' => 'cn', // Attribute to be used as group name 'name_attr' => 'cn', // Attribute to be used as group name
'member_filter' => '(objectclass=*)', // Optional filter to use when querying for group members 'email_attr' => 'mail', // Group email address attribute (e.g. for mailing lists)
'vlv' => false, // Use VLV controls to list groups 'member_filter' => '(objectclass=*)', // Optional filter to use when querying for group members
'vlv' => false, // Use VLV controls to list groups
'class_member_attr' => array( // Mapping of group object class to member attribute used in these objects
'groupofnames' => 'member',
'groupofuniquenames' => 'uniquemember'
),
), ),
// this configuration replaces the regular groups listing in the directory tree with // this configuration replaces the regular groups listing in the directory tree with
// a hard-coded list of groups, each listing entries with the configured base DN and filter. // a hard-coded list of groups, each listing entries with the configured base DN and filter.

@ -52,7 +52,7 @@ class rcube_ldap extends rcube_addressbook
* *
* @var array * @var array
*/ */
private static $group_types = array( private $group_types = array(
'group' => 'member', 'group' => 'member',
'groupofnames' => 'member', 'groupofnames' => 'member',
'kolabgroupofnames' => 'member', 'kolabgroupofnames' => 'member',
@ -94,6 +94,9 @@ class rcube_ldap extends rcube_addressbook
$this->prop['groups']['name_attr'] = 'cn'; $this->prop['groups']['name_attr'] = 'cn';
if (empty($this->prop['groups']['scope'])) if (empty($this->prop['groups']['scope']))
$this->prop['groups']['scope'] = 'sub'; $this->prop['groups']['scope'] = 'sub';
// extend group objectclass => member attribute mapping
if (!empty($this->prop['groups']['class_member_attr']))
$this->group_types = array_merge($this->group_types, $this->prop['groups']['class_member_attr']);
// add group name attrib to the list of attributes to be fetched // add group name attrib to the list of attributes to be fetched
$fetch_attributes[] = $this->prop['groups']['name_attr']; $fetch_attributes[] = $this->prop['groups']['name_attr'];
@ -292,6 +295,14 @@ class rcube_ldap extends rcube_addressbook
if ($this->prop['search_base_dn'] && $this->prop['search_filter'] if ($this->prop['search_base_dn'] && $this->prop['search_filter']
&& (strstr($bind_dn, '%dn') || strstr($this->base_dn, '%dn') || strstr($this->groups_base_dn, '%dn')) && (strstr($bind_dn, '%dn') || strstr($this->base_dn, '%dn') || strstr($this->groups_base_dn, '%dn'))
) { ) {
$search_attribs = array('uid');
if ($search_bind_attrib = (array)$this->prop['search_bind_attrib']) {
foreach ($search_bind_attrib as $r => $attr) {
$search_attribs[] = $attr;
$replaces[$r] = '';
}
}
$search_bind_dn = strtr($this->prop['search_bind_dn'], $replaces); $search_bind_dn = strtr($this->prop['search_bind_dn'], $replaces);
$search_base_dn = strtr($this->prop['search_base_dn'], $replaces); $search_base_dn = strtr($this->prop['search_base_dn'], $replaces);
$search_filter = strtr($this->prop['search_filter'], $replaces); $search_filter = strtr($this->prop['search_filter'], $replaces);
@ -321,10 +332,18 @@ class rcube_ldap extends rcube_addressbook
} }
} }
$res = $ldap->search($search_base_dn, $search_filter, 'sub', array('uid')); $res = $ldap->search($search_base_dn, $search_filter, 'sub', $search_attribs);
if ($res) { if ($res) {
$res->rewind(); $res->rewind();
$replaces['%dn'] = $res->get_dn(); $replaces['%dn'] = $res->get_dn();
// add more replacements from 'search_bind_attrib' config
if ($search_bind_attrib) {
$res = $res->current();
foreach ($search_bind_attrib as $r => $attr) {
$replaces[$r] = $res[$attr][0];
}
}
} }
if ($ldap != $this->ldap) { if ($ldap != $this->ldap) {
@ -355,6 +374,23 @@ class rcube_ldap extends rcube_addressbook
$this->base_dn = strtr($this->base_dn, $replaces); $this->base_dn = strtr($this->base_dn, $replaces);
$this->groups_base_dn = strtr($this->groups_base_dn, $replaces); $this->groups_base_dn = strtr($this->groups_base_dn, $replaces);
// replace placeholders in filter settings
if (!empty($this->prop['filter']))
$this->prop['filter'] = strtr($this->prop['filter'], $replaces);
if (!empty($this->prop['groups']['filter']))
$this->prop['groups']['filter'] = strtr($this->prop['groups']['filter'], $replaces);
if (!empty($this->prop['groups']['member_filter']))
$this->prop['groups']['member_filter'] = strtr($this->prop['groups']['member_filter'], $replaces);
if (!empty($this->prop['group_filters'])) {
foreach ($this->prop['group_filters'] as $i => $gf) {
if (!empty($gf['base_dn']))
$this->prop['group_filters'][$i]['base_dn'] = strtr($gf['base_dn'], $replaces);
if (!empty($gf['filter']))
$this->prop['group_filters'][$i]['filter'] = strtr($gf['filter'], $replaces);
}
}
if (empty($bind_user)) { if (empty($bind_user)) {
$bind_user = $u; $bind_user = $u;
} }
@ -559,9 +595,10 @@ class rcube_ldap extends rcube_addressbook
/** /**
* Get all members of the given group * Get all members of the given group
* *
* @param string Group DN * @param string Group DN
* @param array Group entries (if called recursively) * @param boolean Count only
* @return array Accumulated group members * @param array Group entries (if called recursively)
* @return array Accumulated group members
*/ */
function list_group_members($dn, $count = false, $entries = null) function list_group_members($dn, $count = false, $entries = null)
{ {
@ -569,7 +606,7 @@ class rcube_ldap extends rcube_addressbook
// fetch group object // fetch group object
if (empty($entries)) { if (empty($entries)) {
$attribs = array('dn','objectClass','member','uniqueMember','memberURL'); $attribs = array_merge(array('dn','objectClass','memberURL'), array_values($this->group_types));
$entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs); $entries = $this->ldap->read_entries($dn, '(objectClass=*)', $attribs);
if ($entries === false) { if ($entries === false) {
return $group_members; return $group_members;
@ -581,17 +618,17 @@ class rcube_ldap extends rcube_addressbook
$attrs = array(); $attrs = array();
foreach ((array)$entry['objectclass'] as $objectclass) { foreach ((array)$entry['objectclass'] as $objectclass) {
if (strtolower($objectclass) == 'groupofurls') { if (($member_attr = $this->get_group_member_attr(array($objectclass), ''))
$members = $this->_list_group_memberurl($dn, $entry, $count);
$group_members = array_merge($group_members, $members);
}
else if (($member_attr = $this->get_group_member_attr(array($objectclass), ''))
&& ($member_attr = strtolower($member_attr)) && !in_array($member_attr, $attrs) && ($member_attr = strtolower($member_attr)) && !in_array($member_attr, $attrs)
) { ) {
$members = $this->_list_group_members($dn, $entry, $member_attr, $count); $members = $this->_list_group_members($dn, $entry, $member_attr, $count);
$group_members = array_merge($group_members, $members); $group_members = array_merge($group_members, $members);
$attrs[] = $member_attr; $attrs[] = $member_attr;
} }
else if (!empty($entry['memberurl'])) {
$members = $this->_list_group_memberurl($dn, $entry, $count);
$group_members = array_merge($group_members, $members);
}
if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) { if ($this->prop['sizelimit'] && count($group_members) > $this->prop['sizelimit']) {
break 2; break 2;
@ -608,6 +645,7 @@ class rcube_ldap extends rcube_addressbook
* @param string Group DN * @param string Group DN
* @param array Group entry * @param array Group entry
* @param string Member attribute to use * @param string Member attribute to use
* @param boolean Count only
* @return array Accumulated group members * @return array Accumulated group members
*/ */
private function _list_group_members($dn, $entry, $attr, $count) private function _list_group_members($dn, $entry, $attr, $count)
@ -621,8 +659,7 @@ class rcube_ldap extends rcube_addressbook
// read these attributes for all members // read these attributes for all members
$attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes']; $attrib = $count ? array('dn','objectClass') : $this->prop['list_attributes'];
$attrib[] = 'member'; $attrib = array_merge($attrib, array_values($this->group_types));
$attrib[] = 'uniqueMember';
$attrib[] = 'memberURL'; $attrib[] = 'memberURL';
$filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)'; $filter = $this->prop['groups']['member_filter'] ? $this->prop['groups']['member_filter'] : '(objectclass=*)';
@ -1483,7 +1520,7 @@ class rcube_ldap extends rcube_addressbook
{ {
$classes = array_map('strtolower', (array)$entry['objectclass']); $classes = array_map('strtolower', (array)$entry['objectclass']);
return count(array_intersect(array_keys(self::$group_types), $classes)) > 0; return count(array_intersect(array_keys($this->group_types), $classes)) > 0;
} }
/** /**
@ -1914,7 +1951,7 @@ class rcube_ldap extends rcube_addressbook
if (!empty($object_classes)) { if (!empty($object_classes)) {
foreach ((array)$object_classes as $oc) { foreach ((array)$object_classes as $oc) {
if ($attr = self::$group_types[strtolower($oc)]) { if ($attr = $this->group_types[strtolower($oc)]) {
return $attr; return $attr;
} }
} }

@ -1260,6 +1260,7 @@ function rcmail_settings_tabs($attrib)
array('command' => 'preferences', 'type' => 'link', 'label' => 'preferences', 'title' => 'editpreferences'), array('command' => 'preferences', 'type' => 'link', 'label' => 'preferences', 'title' => 'editpreferences'),
array('command' => 'folders', 'type' => 'link', 'label' => 'folders', 'title' => 'managefolders'), array('command' => 'folders', 'type' => 'link', 'label' => 'folders', 'title' => 'managefolders'),
array('command' => 'identities', 'type' => 'link', 'label' => 'identities', 'title' => 'manageidentities'), array('command' => 'identities', 'type' => 'link', 'label' => 'identities', 'title' => 'manageidentities'),
array('command' => 'responses', 'type' => 'link', 'label' => 'responses', 'title' => 'editresponses'),
); );
// get all identites from DB and define list of cols to be displayed // get all identites from DB and define list of cols to be displayed

Loading…
Cancel
Save