From ec2185a1cb2a0d412ab9e916618972bc4c70f68c Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Tue, 5 Feb 2013 16:12:37 +0100 Subject: Add special config attribute 'group_filters' to specify a fixed list of groups to be listed in the leftmost pane, each having individual base-dn/filter parameters for contact listing --- program/lib/Roundcube/rcube_ldap.php | 244 ++++++++++++++++++++++------------- 1 file changed, 152 insertions(+), 92 deletions(-) (limited to 'program/lib/Roundcube/rcube_ldap.php') diff --git a/program/lib/Roundcube/rcube_ldap.php b/program/lib/Roundcube/rcube_ldap.php index 80b85d45b..e54e65984 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -38,7 +38,7 @@ class rcube_ldap extends rcube_addressbook /** private properties */ protected $conn; protected $prop = array(); - protected $fieldmap = array(); + protected $fieldmap = array('_objclass' => 'objectclass'); protected $sub_filter; protected $filter = ''; protected $result = null; @@ -82,6 +82,12 @@ class rcube_ldap extends rcube_addressbook $this->prop['groups']['name_attr'] = 'cn'; if (empty($this->prop['groups']['scope'])) $this->prop['groups']['scope'] = 'sub'; + + // add group name attrib to fieldmap in order to have it fetched + $this->fieldmap['_groupname'] = $this->prop['groups']['name_attr']; + } + else if (is_array($p['group_filters']) && count($p['group_filters'])) { + $this->groups = true; } // fieldmap property is given @@ -549,13 +555,15 @@ class rcube_ldap extends rcube_addressbook } else { + $prop = $this->group_id ? $this->group_data : $this->prop; + // add general filter to query - if (!empty($this->prop['filter']) && empty($this->filter)) - $this->set_search_set($this->prop['filter']); + if (!empty($prop['filter']) && empty($this->filter)) + $this->set_search_set($prop['filter']); // exec LDAP search if no result resource is stored if ($this->conn && !$this->ldap_result) - $this->_exec_search(); + $this->_exec_search($prop); // count contacts for this user $this->result = $this->count(); @@ -564,7 +572,7 @@ class rcube_ldap extends rcube_addressbook if ($this->ldap_result && $this->result->count > 0) { // sorting still on the ldap server - if ($this->sort_col && $this->prop['scope'] !== 'base' && !$this->vlv_active) + if ($this->sort_col && $prop['scope'] !== 'base' && !$this->vlv_active) ldap_sort($this->conn, $this->ldap_result, $this->sort_col); // get all entries from the ldap server @@ -599,6 +607,7 @@ class rcube_ldap extends rcube_addressbook // fetch group object if (empty($entries)) { + $this->_debug("C: Read Group [dn: $dn]"); $result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL')); if ($result === false) { @@ -657,21 +666,6 @@ class rcube_ldap extends rcube_addressbook if (empty($entry[$attr])) return $group_members; - // add group record to cache if it isn't yet there - $group_id = self::dn_encode($dn); - $group_cache = $this->cache->get('groups'); - if (!$group_cache[$group_id]) { - $name_attr = $this->prop['groups']['name_attr']; - $group_name = is_array($ldap_data[$i][$name_attr]) ? $ldap_data[$i][$name_attr][0] : $ldap_data[$i][$name_attr]; - - $group_cache[$group_id]['ID'] = $group_id; - $group_cache[$group_id]['dn'] = $dn; - $group_cache[$group_id]['name'] = $group_name; - $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry[$i]['objectclass']); - - $this->cache->set('groups', $group_cache); - } - // read these attributes for all members $attrib = $count ? array('dn') : array_values($this->fieldmap); $attrib[] = 'objectClass'; @@ -726,26 +720,15 @@ class rcube_ldap extends rcube_addressbook // add search filter if any $filter = $this->filter ? '(&(' . $m[3] . ')(' . $this->filter . '))' : $m[3]; - $func = $m[2] == 'sub' ? 'ldap_search' : ($m[2] == 'base' ? 'ldap_read' : 'ldap_list'); - - $attrib = $count ? array('dn') : array_values($this->fieldmap); - if ($result = @$func($this->conn, $m[1], $filter, - $attrib, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) - ) { - $this->_debug("S: ".ldap_count_entries($this->conn, $result)." record(s) for ".$m[1]); - } - else { - $this->_debug("S: ".ldap_error($this->conn)); - return $group_members; - } - - $entries = @ldap_get_entries($this->conn, $result); - for ($j = 0; $j < $entries['count']; $j++) - { - if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count)) - $group_members = array_merge($group_members, $nested_group_members); - else - $group_members[] = $entries[$j]; + $attrs = $count ? array('dn') : array_values($this->fieldmap); + if ($result = $this->ldap_search($m[1], $filter, $m[2], $attrs, $this->group_data)) { + $entries = @ldap_get_entries($this->conn, $result); + for ($j = 0; $j < $entries['count']; $j++) { + if ($nested_group_members = $this->list_group_members($entries[$j]['dn'], $count)) + $group_members = array_merge($group_members, $nested_group_members); + else + $group_members[] = $entries[$j]; + } } } @@ -910,7 +893,7 @@ class rcube_ldap extends rcube_addressbook // set filter string and execute search $this->set_search_set($filter); - $this->_exec_search(); + $this->_exec_search($this->prop); if ($select) $this->list_records(); @@ -936,13 +919,15 @@ class rcube_ldap extends rcube_addressbook $count = count($this->list_group_members($this->group_data['dn'], true)); } else if ($this->conn) { + $prop = $this->group_id ? $this->group_data : $this->prop; + // We have a connection but no result set, attempt to get one. if (empty($this->filter)) { // The filter is not set, set it. $this->filter = $this->prop['filter']; } - $count = (int) $this->_exec_search(true); + $count = (int) $this->_exec_search($prop, true); } return new rcube_result_set($count, ($this->list_page-1) * $this->page_size); @@ -1434,57 +1419,35 @@ class rcube_ldap extends rcube_addressbook /** * Execute the LDAP search based on the stored credentials */ - private function _exec_search($count = false) + private function _exec_search($prop, $count = false) { if ($this->ready) { - $filter = $this->filter ? $this->filter : '(objectclass=*)'; - $function = $this->_scope2func($this->prop['scope'], $ns_function); - - $this->_debug("C: Search [$filter][dn: $this->base_dn]"); + $function = $this->_scope2func($prop['scope'], $ns_function); + $base_dn = $this->group_id && $prop['base_dn'] ? $prop['base_dn'] : $this->base_dn; // when using VLV, we get the total count by... - if (!$count && $function != 'ldap_read' && $this->prop['vlv'] && !$this->group_id) { + if (!$count && $function != 'ldap_read' && $prop['vlv']) { // ...either reading numSubOrdinates attribute - if ($this->prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $this->base_dn, $this->prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) { + if ($prop['numsub_filter'] && ($result_count = @$ns_function($this->conn, $base_dn, $prop['numsub_filter'], array('numSubOrdinates'), 0, 0, 0))) { $counts = ldap_get_entries($this->conn, $result_count); for ($this->vlv_count = $j = 0; $j < $counts['count']; $j++) $this->vlv_count += $counts[$j]['numsubordinates'][0]; $this->_debug("D: total numsubordinates = " . $this->vlv_count); } else if (!function_exists('ldap_parse_virtuallist_control')) // ...or by fetching all records dn and count them - $this->vlv_count = $this->_exec_search(true); - - $this->vlv_active = $this->_vlv_set_controls($this->prop, $this->list_page, $this->page_size); + $this->vlv_count = $this->_exec_search($prop, true); } - // only fetch dn for count (should keep the payload low) - $attrs = $count ? array('dn') : array_values($this->fieldmap); - if ($this->ldap_result = @$function($this->conn, $this->base_dn, $filter, - $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) - ) { - // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result - if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { - if (ldap_parse_result($this->conn, $this->ldap_result, - $errcode, $matcheddn, $errmsg, $referrals, $serverctrls) - && $serverctrls // can be null e.g. in case of adm. limit error - ) { - ldap_parse_virtuallist_control($this->conn, $serverctrls, - $last_offset, $this->vlv_count, $vresult); - $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count"); - } - else { - $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn))); - } + $filter = $this->filter ? $this->filter : '(objectclass=*)'; + $attrs = $count ? array('dn') : array_values($this->fieldmap); // only fetch dn for count (should keep the payload low) + if ($this->ldap_result = $this->ldap_search($base_dn, $filter, $prop['scope'], $attrs, $prop)) { + if ($count) { + return ldap_count_entries($this->conn, $this->ldap_result); + } + else { + return true; } - - $entries_count = ldap_count_entries($this->conn, $this->ldap_result); - $this->_debug("S: $entries_count record(s)"); - - return $count ? $entries_count : true; - } - else { - $this->_debug("S: ".ldap_error($this->conn)); } } @@ -1540,20 +1503,19 @@ class rcube_ldap extends rcube_addressbook private function _ldap2result($rec) { $out = array('_type' => 'person'); + $fieldmap = $this->fieldmap; if ($rec['dn']) $out[$this->primary_key] = self::dn_encode($rec['dn']); // determine record type - if (array_intersect(array('groupofuniquenames','kolabgroupofuniquenames'), (array)$rec['objectclass'])) { + if (array_intersect(array('groupofuniquenames','kolabgroupofuniquenames'), array_map('strtolower', (array)$rec['objectclass']))) { $out['_type'] = 'group'; $out['readonly'] = true; - if ($this->fieldmap['groupname']) { - $this->fieldmap['name'] = $this->fieldmap['groupname']; - } + $fieldmap['name'] = $fieldmap['_groupname']; } - foreach ($this->fieldmap as $rf => $lf) + foreach ($fieldmap as $rf => $lf) { for ($i=0; $i < $rec[$lf]['count']; $i++) { if (!($value = $rec[$lf][$i])) @@ -1718,20 +1680,14 @@ class rcube_ldap extends rcube_addressbook /** * Setter for the current group - * (empty, has to be re-implemented by extending class) */ function set_group($group_id) { - if ($group_id) - { - if (($group_cache = $this->cache->get('groups')) === null) - $group_cache = $this->_fetch_groups(); - + if ($group_id) { $this->group_id = $group_id; - $this->group_data = $group_cache[$group_id]; + $this->group_data = $this->get_group_entry($group_id); } - else - { + else { $this->group_id = 0; $this->group_data = null; } @@ -1772,6 +1728,23 @@ class rcube_ldap extends rcube_addressbook */ private function _fetch_groups($vlv_page = 0) { + // special case: list groups from 'group_filters' config + if (!empty($this->prop['group_filters'])) { + $groups = array(); + + // list regular groups configuration as special filter + if (!empty($this->prop['groups']['filter'])) { + $id = '__groups__'; + $groups[$id] = array('ID' => $id, 'name' => rcube_label('groups')) + $this->prop['groups']; + } + + foreach ($this->prop['group_filters'] as $id => $prop) { + $groups[$id] = $prop + array('ID' => $id, 'name' => ucfirst($id)); + } + + return $groups; + } + $base_dn = $this->groups_base_dn; $filter = $this->prop['groups']['filter']; $name_attr = $this->prop['groups']['name_attr']; @@ -1843,6 +1816,40 @@ class rcube_ldap extends rcube_addressbook return $groups; } + /** + * Fetch a group entry from LDAP and save in local cache + */ + private function get_group_entry($group_id) + { + if (($group_cache = $this->cache->get('groups')) === null) + $group_cache = $this->_fetch_groups(); + + // add group record to cache if it isn't yet there + if (!isset($group_cache[$group_id])) { + $name_attr = $this->prop['groups']['name_attr']; + $dn = self::dn_decode($group_id); + + $this->_debug("C: Read Group [dn: $dn]"); + if ($result = @ldap_read($this->conn, $dn, '(objectClass=*)', array('dn','objectClass','member','uniqueMember','memberURL',$name_attr))) { + $list = ldap_get_entries($this->conn, $result); + $entry = $list[0]; + $group_name = is_array($entry[$name_attr]) ? $entry[$name_attr][0] : $entry[$name_attr]; + $group_cache[$group_id]['ID'] = $group_id; + $group_cache[$group_id]['dn'] = $dn; + $group_cache[$group_id]['name'] = $group_name; + $group_cache[$group_id]['member_attr'] = $this->get_group_member_attr($entry['objectclass']); + } + else { + $this->_debug("S: ".ldap_error($this->conn)); + $group_cache[$group_id] = false; + } + + $this->cache->set('groups', $group_cache); + } + + return $group_cache[$group_id]; + } + /** * Get group properties such as name and email address(es) * @@ -2245,6 +2252,59 @@ class rcube_ldap extends rcube_addressbook return base64_decode($str); } + /** + * Wrapper for ldap_search or ldap_list depending on the given scope. + * It optionally uses VLV index if configured in $prop + * + * @param string Base DN to read from + * @param string Query filter to use + * @param string Listing scope (sub|list|base) + * @param array List of entry attributes to read + * @param array Hash array with query configuration properties: + * - vlv: true if VLV index should be used + * - sort: array of sort attributes (has to be in sync with the VLV index) + */ + protected function ldap_search($base_dn, $filter = '(objectclass=*)', $scope = 'sub', $attrs = array('dn'), $prop = array()) + { + if ($this->ready) { + $this->_debug("C: LDAP Search [$filter][dn: $base_dn]"); + + // set VLV controls if requested + if ($prop['vlv'] && $scope != 'base') + $this->vlv_active = $this->_vlv_set_controls($prop, $this->list_page, $this->page_size); + + $function = $this->_scope2func($scope); + if ($ldap_result = $function($this->conn, $base_dn, $filter, + $attrs, 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']) + ) { + // when running on a patched PHP we can use the extended functions to retrieve the total count from the LDAP search result + if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) { + if (ldap_parse_result($this->conn, $this->ldap_result, + $errcode, $matcheddn, $errmsg, $referrals, $serverctrls) + && $serverctrls // can be null e.g. in case of adm. limit error + ) { + ldap_parse_virtuallist_control($this->conn, $serverctrls, + $last_offset, $this->vlv_count, $vresult); + $this->_debug("S: VLV result: last_offset=$last_offset; content_count=$this->vlv_count"); + } + else { + $this->_debug("S: ".($errmsg ? $errmsg : ldap_error($this->conn))); + } + } + + $entries_count = ldap_count_entries($this->conn, $ldap_result); + $this->_debug("S: $entries_count record(s)"); + + return $ldap_result; + } + else { + $this->_debug("S: ".ldap_error($this->conn)); + } + } + + return false; + } + /** * Wrapper for ldap_add() */ -- cgit v1.2.3