]> source.dussan.org Git - nextcloud-server.git/commitdiff
LDAP: add support for paged searches.
authorArthur Schiwon <blizzz@owncloud.com>
Fri, 26 Oct 2012 19:53:56 +0000 (21:53 +0200)
committerArthur Schiwon <blizzz@owncloud.com>
Fri, 26 Oct 2012 19:54:34 +0000 (21:54 +0200)
apps/user_ldap/lib/access.php

index 6f2f61dc8a353d8b77598349b9f307082343f956..aa108f9840e15e3238b022ae73a1aaf7056db521 100644 (file)
@@ -26,6 +26,9 @@ namespace OCA\user_ldap\lib;
 abstract class Access {
        protected $connection;
 
+       //never ever check this var directly, always use getPagedSearchResultState
+       protected $pagedSearchedSuccessful;
+
        public function setConnector(Connection &$connection) {
                $this->connection = $connection;
        }
@@ -441,12 +444,12 @@ abstract class Access {
                return true;
        }
 
-       public function fetchListOfUsers($filter, $attr) {
-               return $this->fetchList($this->searchUsers($filter, $attr), (count($attr) > 1));
+       public function fetchListOfUsers($filter, $attr, $limit = null, $offset = null) {
+               return $this->fetchList($this->searchUsers($filter, $attr, $limit, $offset), (count($attr) > 1));
        }
 
-       public function fetchListOfGroups($filter, $attr) {
-               return $this->fetchList($this->searchGroups($filter, $attr), (count($attr) > 1));
+       public function fetchListOfGroups($filter, $attr, $limit = null, $offset = null) {
+               return $this->fetchList($this->searchGroups($filter, $attr, $limit, $offset), (count($attr) > 1));
        }
 
        private function fetchList($list, $manyAttributes) {
@@ -470,8 +473,8 @@ abstract class Access {
         *
         * Executes an LDAP search
         */
-       public function searchUsers($filter, $attr = null) {
-               return $this->search($filter, $this->connection->ldapBaseUsers, $attr);
+       public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
+               return $this->search($filter, $this->connection->ldapBaseUsers, $attr, $limit, $offset);
        }
 
        /**
@@ -482,8 +485,8 @@ abstract class Access {
         *
         * Executes an LDAP search
         */
-       public function searchGroups($filter, $attr = null) {
-               return $this->search($filter, $this->connection->ldapBaseGroups, $attr);
+       public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
+               return $this->search($filter, $this->connection->ldapBaseGroups, $attr, $limit, $offset);
        }
 
        /**
@@ -495,29 +498,73 @@ abstract class Access {
         *
         * Executes an LDAP search
         */
-       private function search($filter, $base, $attr = null) {
+       private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
                if(!is_null($attr) && !is_array($attr)) {
                        $attr = array(mb_strtolower($attr, 'UTF-8'));
                }
 
-               // See if we have a resource
+               // See if we have a resource, in case not cancel with message
                $link_resource = $this->connection->getConnectionResource();
-               if(is_resource($link_resource)) {
-                       $sr = ldap_search($link_resource, $base, $filter, $attr);
-                       $findings = ldap_get_entries($link_resource, $sr );
-
-                       // if we're here, probably no connection resource is returned.
-                       // to make ownCloud behave nicely, we simply give back an empty array.
-                       if(is_null($findings)) {
-                               return array();
-                       }
-               } else {
+               if(!is_resource($link_resource)) {
                        // Seems like we didn't find any resource.
                        // Return an empty array just like before.
                        \OCP\Util::writeLog('user_ldap', 'Could not search, because resource is missing.', \OCP\Util::DEBUG);
                        return array();
                }
 
+               //TODO: lines 516:540 into a function of its own. $pagedSearchOK as return
+               //check wether paged query should be attempted
+               $pagedSearchOK = false;
+               if($this->connection->hasPagedResultSupport && !is_null($limit)) {
+                       $offset = intval($offset); //can be null
+                       //get the cookie from the search for the previous search, required by LDAP
+                       $cookie = $this->getPagedResultCookie($filter, $limit, $offset);
+                       if(empty($cookie) && ($offset > 0)) {
+                               //no cookie known, although the offset is not 0. Maybe cache run out. We need to start all over *sigh* (btw, Dear Reader, did you need LDAP paged searching was designed by MSFT?)
+                               $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
+                               //a bit recursive, $offset of 0 is the exit
+                               $this->search($filter, $base, $attr, $limit, $reOffset, true);
+                               $cookie = $this->getPagedResultCookie($filter, $limit, $offset);
+                               //still no cookie? obviously, the server does not like us. Let's skip paging efforts.
+                               //TODO: remember this, probably does not change in the next request...
+                               if(empty($cookie)) {
+                                       $cookie = null;
+                               }
+                       }
+                       if(!is_null($cookie)) {
+                               $pagedSearchOK = ldap_control_paged_result($link_resource, $limit, false, $cookie);
+                               \OCP\Util::writeLog('user_ldap', 'Ready for a paged search', \OCP\Util::DEBUG);
+                       } else {
+                               \OCP\Util::writeLog('user_ldap', 'No paged search for us, Cpt., Limit '.$limit.' Offset '.$offset, \OCP\Util::DEBUG);
+                       }
+               }
+
+               $sr = ldap_search($link_resource, $base, $filter, $attr);
+               $findings = ldap_get_entries($link_resource, $sr );
+               if($pagedSearchOK) {
+                       \OCP\Util::writeLog('user_ldap', 'Paged search successful', \OCP\Util::INFO);
+                       ldap_control_paged_result_response($link_resource, $sr, $cookie);
+                       \OCP\Util::writeLog('user_ldap', 'Set paged search cookie '.$cookie, \OCP\Util::INFO);
+                       $this->setPagedResultCookie($filter, $limit, $offset, $cookie);
+                       //browsing through prior pages to get the cookie for the new one
+                       if($skipHandling) {
+                               return;
+                       }
+                       //if count is bigger, then the server does not support paged search. Instead, he did a normal search. We set a flag here, so the callee knows how to deal with it.
+                       //TODO: Not used, just make a count on the returned values in the callee
+                       if($findings['count'] <= $limit) {
+                               $this->pagedSearchedSuccessful = true;
+                       }
+               } else {
+                       \OCP\Util::writeLog('user_ldap', 'Paged search failed :(', \OCP\Util::INFO);
+               }
+
+               // if we're here, probably no connection resource is returned.
+               // to make ownCloud behave nicely, we simply give back an empty array.
+               if(is_null($findings)) {
+                       return array();
+               }
+
                if(!is_null($attr)) {
                        $selection = array();
                        $multiarray = false;
@@ -557,6 +604,7 @@ abstract class Access {
                                        }
                                }
                        }
+//                     die(var_dump($selection));
                        return $selection;
                }
                return $findings;
@@ -680,4 +728,51 @@ abstract class Access {
                }
                return $uuid;
        }
+
+       /**
+        * @brief get a cookie for the next LDAP paged search
+        * @param $filter the search filter to identify the correct search
+        * @param $limit the limit (or 'pageSize'), to identify the correct search well
+        * @param $offset the offset for the new search to identify the correct search really good
+        * @returns string containing the key or empty if none is cached
+        */
+       private function getPagedResultCookie($filter, $limit, $offset) {
+               if($offset == 0) {
+                       return '';
+               }
+               $offset -= $limit;
+               //we work with cache here
+               $cachekey = 'lc' . dechex(crc32($filter)) . '-' . $limit . '-' . $offset;
+               $cookie = $this->connection->getFromCache($cachekey);
+               if(is_null($cookie)) {
+                       $cookie = '';
+               }
+               return $cookie;
+       }
+
+       /**
+        * @brief set a cookie for LDAP paged search run
+        * @param $filter the search filter to identify the correct search
+        * @param $limit the limit (or 'pageSize'), to identify the correct search well
+        * @param $offset the offset for the run search to identify the correct search really good
+        * @param $cookie string containing the cookie returned by ldap_control_paged_result_response
+        * @return void
+        */
+       private function setPagedResultCookie($filter, $limit, $offset) {
+               if(!empty($cookie)) {
+                       $cachekey = 'lc' . dechex(crc32($filter)) . '-' . $limit . '-' . $offset;
+                       $cookie = $this->connection->writeToCache($cachekey, $cookie);
+               }
+       }
+
+       /**
+        * @brief check wether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
+        * @return true on success, null or false otherwise
+        */
+       public function getPagedSearchResultState() {
+               $result = $this->pagedSearchedSuccessful;
+               $this->pagedSearchedSuccessful = null;
+               return $result;
+       }
+
 }
\ No newline at end of file