]> source.dussan.org Git - nextcloud-server.git/commitdiff
use serverControls directly with LDAP calls, fixes 19127 20037/head
authorArthur Schiwon <blizzz@arthur-schiwon.de>
Wed, 18 Mar 2020 16:40:23 +0000 (17:40 +0100)
committerArthur Schiwon <blizzz@arthur-schiwon.de>
Tue, 14 Apr 2020 10:58:28 +0000 (12:58 +0200)
- adapters for PHP API version to Support PHP < 7.3
- switch to pass only one base per search
- cookie logic is moved from Access to API adapters

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
16 files changed:
.drone.yml
apps/user_ldap/composer/composer/autoload_classmap.php
apps/user_ldap/composer/composer/autoload_static.php
apps/user_ldap/lib/Access.php
apps/user_ldap/lib/Command/CheckUser.php
apps/user_ldap/lib/ILDAPWrapper.php
apps/user_ldap/lib/LDAP.php
apps/user_ldap/lib/PagedResults/IAdapter.php [new file with mode: 0644]
apps/user_ldap/lib/PagedResults/Php54.php [new file with mode: 0644]
apps/user_ldap/lib/PagedResults/Php73.php [new file with mode: 0644]
apps/user_ldap/lib/PagedResults/TLinkId.php [new file with mode: 0644]
apps/user_ldap/lib/User/User.php
apps/user_ldap/tests/AccessTest.php
apps/user_ldap/tests/LDAPTest.php
apps/user_ldap/tests/User/UserTest.php
build/integration/ldap_features/openldap-uid-username.feature

index a4a38d3f778275c9c098162f292008da20da3daf..e84918ef6a01f88ba8199cd98316767f89ad4bf2 100644 (file)
@@ -1501,7 +1501,7 @@ trigger:
 
 ---
 kind: pipeline
-name: integration-ldap-openldap-uid-features
+name: integration-ldap-openldap-uid-features-php54-api
 
 steps:
 - name: submodules
@@ -1509,7 +1509,7 @@ steps:
   commands:
     - git submodule update --init
 - name: integration-ldap-openldap-uid-features
-  image: nextcloudci/integration-php7.3:integration-php7.3-2
+  image: nextcloudci/integration-php7.2:integration-php7.2-1
   commands:
     - bash tests/drone-run-integration-tests.sh || exit 0
     - ./occ maintenance:install --admin-pass=admin --data-dir=/dev/shm/nc_int
@@ -1539,6 +1539,49 @@ trigger:
   event:
     - pull_request
     - push
+type: docker
+
+---
+kind: pipeline
+name: integration-ldap-openldap-uid-features
+
+steps:
+    - name: submodules
+      image: docker:git
+      commands:
+          - git submodule update --init
+    - name: integration-ldap-openldap-uid-features
+      image: nextcloudci/integration-php7.3:integration-php7.3-2
+      commands:
+          - bash tests/drone-run-integration-tests.sh || exit 0
+          - ./occ maintenance:install --admin-pass=admin --data-dir=/dev/shm/nc_int
+          - ./occ config:system:set redis host --value=cache
+          - ./occ config:system:set redis port --value=6379 --type=integer
+          - ./occ config:system:set redis timeout --value=0 --type=integer
+          - ./occ config:system:set --type string --value "\\OC\\Memcache\\Redis" memcache.local
+          - ./occ config:system:set --type string --value "\\OC\\Memcache\\Redis" memcache.distributed
+          - cd build/integration
+          - ./run.sh ldap_features/openldap-uid-username.feature
+
+services:
+    - name: cache
+      image: redis
+    - name: openldap
+      image: nextcloudci/openldap:openldap-7
+      environment:
+          SLAPD_DOMAIN: nextcloud.ci
+          SLAPD_ORGANIZATION: Nextcloud
+          SLAPD_PASSWORD: admin
+          SLAPD_ADDITIONAL_MODULES: memberof
+
+trigger:
+    branch:
+        - master
+        - stable*
+    event:
+        - pull_request
+        - push
+type: docker
 
 ---
 kind: pipeline
index fadbc701ec0366b0c03dc4ca0dc281df03091b89..3b08f3f7f3e8f0207c5996a1f77d26d1886f57d5 100644 (file)
@@ -53,6 +53,10 @@ return array(
     'OCA\\User_LDAP\\Migration\\UUIDFixInsert' => $baseDir . '/../lib/Migration/UUIDFixInsert.php',
     'OCA\\User_LDAP\\Migration\\UUIDFixUser' => $baseDir . '/../lib/Migration/UUIDFixUser.php',
     'OCA\\User_LDAP\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
+    'OCA\\User_LDAP\\PagedResults\\IAdapter' => $baseDir . '/../lib/PagedResults/IAdapter.php',
+    'OCA\\User_LDAP\\PagedResults\\Php54' => $baseDir . '/../lib/PagedResults/Php54.php',
+    'OCA\\User_LDAP\\PagedResults\\Php73' => $baseDir . '/../lib/PagedResults/Php73.php',
+    'OCA\\User_LDAP\\PagedResults\\TLinkId' => $baseDir . '/../lib/PagedResults/TLinkId.php',
     'OCA\\User_LDAP\\Proxy' => $baseDir . '/../lib/Proxy.php',
     'OCA\\User_LDAP\\Settings\\Admin' => $baseDir . '/../lib/Settings/Admin.php',
     'OCA\\User_LDAP\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php',
index d40df6e4836258839667e248fa57e80585270caa..97080049d5181e3ac661b3fd08e5ec7a900bdca2 100644 (file)
@@ -68,6 +68,10 @@ class ComposerStaticInitUser_LDAP
         'OCA\\User_LDAP\\Migration\\UUIDFixInsert' => __DIR__ . '/..' . '/../lib/Migration/UUIDFixInsert.php',
         'OCA\\User_LDAP\\Migration\\UUIDFixUser' => __DIR__ . '/..' . '/../lib/Migration/UUIDFixUser.php',
         'OCA\\User_LDAP\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
+        'OCA\\User_LDAP\\PagedResults\\IAdapter' => __DIR__ . '/..' . '/../lib/PagedResults/IAdapter.php',
+        'OCA\\User_LDAP\\PagedResults\\Php54' => __DIR__ . '/..' . '/../lib/PagedResults/Php54.php',
+        'OCA\\User_LDAP\\PagedResults\\Php73' => __DIR__ . '/..' . '/../lib/PagedResults/Php73.php',
+        'OCA\\User_LDAP\\PagedResults\\TLinkId' => __DIR__ . '/..' . '/../lib/PagedResults/TLinkId.php',
         'OCA\\User_LDAP\\Proxy' => __DIR__ . '/..' . '/../lib/Proxy.php',
         'OCA\\User_LDAP\\Settings\\Admin' => __DIR__ . '/..' . '/../lib/Settings/Admin.php',
         'OCA\\User_LDAP\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php',
index 71994585c73236c804a0304aaf5c49c3d788e34d..aba1d7a4193822d0c4aea1720a38823fa7ff4d51 100644 (file)
@@ -74,17 +74,7 @@ class Access extends LDAPUtility {
        protected $pagedSearchedSuccessful;
 
        /**
-        * @var string[] $cookies an array of returned Paged Result cookies
-        */
        protected $cookies = [];
-
-       /**
-        * @var string $lastCookie the last cookie returned from a Paged Results
-        * operation, defaults to an empty string
-        */
-       protected $lastCookie = '';
-
-       /**
         * @var AbstractMapping $userMapper
         */
        protected $userMapper;
@@ -102,6 +92,8 @@ class Access extends LDAPUtility {
        private $config;
        /** @var IUserManager */
        private $ncUserManager;
+       /** @var string */
+       private $lastCookie = '';
 
        public function __construct(
                Connection $connection,
@@ -269,7 +261,7 @@ class Access extends LDAPUtility {
         * @throws ServerNotAvailableException
         */
        public function executeRead($cr, $dn, $attribute, $filter, $maxResults) {
-               $this->initPagedSearch($filter, [$dn], [$attribute], $maxResults, 0);
+               $this->initPagedSearch($filter, $dn, [$attribute], $maxResults, 0);
                $dn = $this->helper->DNasBaseParameter($dn);
                $rr = @$this->invokeLDAPMethod('read', $cr, $dn, $filter, [$attribute]);
                if (!$this->ldap->isResource($rr)) {
@@ -1020,7 +1012,7 @@ class Access extends LDAPUtility {
        public function searchUsers($filter, $attr = null, $limit = null, $offset = null) {
                $result = [];
                foreach ($this->connection->ldapBaseUsers as $base) {
-                       $result = array_merge($result, $this->search($filter, [$base], $attr, $limit, $offset));
+                       $result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset));
                }
                return $result;
        }
@@ -1057,7 +1049,7 @@ class Access extends LDAPUtility {
        public function searchGroups($filter, $attr = null, $limit = null, $offset = null) {
                $result = [];
                foreach ($this->connection->ldapBaseGroups as $base) {
-                       $result = array_merge($result, $this->search($filter, [$base], $attr, $limit, $offset));
+                       $result = array_merge($result, $this->search($filter, $base, $attr, $limit, $offset));
                }
                return $result;
        }
@@ -1142,7 +1134,7 @@ class Access extends LDAPUtility {
                                throw $e;
                        }
 
-                       $arguments[0] = array_pad([], count($arguments[0]), $cr);
+                       $arguments[0] = $cr;
                        $ret = $doMethod();
                }
                return $ret;
@@ -1151,20 +1143,22 @@ class Access extends LDAPUtility {
        /**
         * retrieved. Results will according to the order in the array.
         *
-        * @param $filter
-        * @param $base
-        * @param string[]|string|null $attr
-        * @param int $limit optional, maximum results to be counted
-        * @param int $offset optional, a starting point
+        * @param string $filter
+        * @param string $base
+        * @param string[] $attr
+        * @param int|null $limit optional, maximum results to be counted
+        * @param int|null $offset optional, a starting point
         * @return array|false array with the search result as first value and pagedSearchOK as
         * second | false if not successful
         * @throws ServerNotAvailableException
         */
-       private function executeSearch($filter, $base, &$attr = null, $limit = null, $offset = null) {
-               if (!is_null($attr) && !is_array($attr)) {
-                       $attr = [mb_strtolower($attr, 'UTF-8')];
-               }
-
+       private function executeSearch(
+               string $filter,
+               string $base,
+               ?array &$attr,
+               ?int $limit,
+               ?int $offset
+       ) {
                // See if we have a resource, in case not cancel with message
                $cr = $this->connection->getConnectionResource();
                if (!$this->ldap->isResource($cr)) {
@@ -1175,13 +1169,12 @@ class Access extends LDAPUtility {
                }
 
                //check whether paged search should be attempted
-               $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, $offset);
+               $pagedSearchOK = $this->initPagedSearch($filter, $base, $attr, (int)$limit, (int)$offset);
 
-               $linkResources = array_pad([], count($base), $cr);
-               $sr = $this->invokeLDAPMethod('search', $linkResources, $base, $filter, $attr);
+               $sr = $this->invokeLDAPMethod('search', $cr, $base, $filter, $attr);
                // cannot use $cr anymore, might have changed in the previous call!
                $error = $this->ldap->errno($this->connection->getConnectionResource());
-               if (!is_array($sr) || $error !== 0) {
+               if(!$this->ldap->isResource($sr) || $error !== 0) {
                        \OCP\Util::writeLog('user_ldap', 'Attempt for Paging?  '.print_r($pagedSearchOK, true), ILogger::ERROR);
                        return false;
                }
@@ -1192,26 +1185,27 @@ class Access extends LDAPUtility {
        /**
         * processes an LDAP paged search operation
         *
-        * @param array $sr the array containing the LDAP search resources
-        * @param string $filter the LDAP filter for the search
-        * @param array $base an array containing the LDAP subtree(s) that shall be searched
-        * @param int $iFoundItems number of results in the single search operation
+        * @param resource $sr the array containing the LDAP search resources
+        * @param int $foundItems number of results in the single search operation
         * @param int $limit maximum results to be counted
-        * @param int $offset a starting point
         * @param bool $pagedSearchOK whether a paged search has been executed
         * @param bool $skipHandling required for paged search when cookies to
         * prior results need to be gained
         * @return bool cookie validity, true if we have more pages, false otherwise.
         * @throws ServerNotAvailableException
         */
-       private function processPagedSearchStatus($sr, $filter, $base, $iFoundItems, $limit, $offset, $pagedSearchOK, $skipHandling) {
+       private function processPagedSearchStatus(
+               $sr,
+               int $foundItems,
+               int $limit,
+               bool $pagedSearchOK,
+               bool $skipHandling
+       ): bool {
                $cookie = null;
                if ($pagedSearchOK) {
                        $cr = $this->connection->getConnectionResource();
-                       foreach ($sr as $key => $res) {
-                               if ($this->ldap->controlPagedResultResponse($cr, $res, $cookie)) {
-                                       $this->setPagedResultCookie($base[$key], $filter, $limit, $offset, $cookie);
-                               }
+                       if($this->ldap->controlPagedResultResponse($cr, $sr, $cookie)) {
+                               $this->lastCookie = $cookie;
                        }
 
                        //browsing through prior pages to get the cookie for the new one
@@ -1221,7 +1215,7 @@ class Access extends LDAPUtility {
                        // 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.
-                       if ($iFoundItems <= $limit) {
+                       if($foundItems <= $limit) {
                                $this->pagedSearchedSuccessful = true;
                        }
                } else {
@@ -1244,7 +1238,7 @@ class Access extends LDAPUtility {
         * executes an LDAP search, but counts the results only
         *
         * @param string $filter the LDAP filter for the search
-        * @param array $base an array containing the LDAP subtree(s) that shall be searched
+        * @param array $bases an array containing the LDAP subtree(s) that shall be searched
         * @param string|string[] $attr optional, array, one or more attributes that shall be
         * retrieved. Results will according to the order in the array.
         * @param int $limit optional, maximum results to be counted
@@ -1254,8 +1248,22 @@ class Access extends LDAPUtility {
         * @return int|false Integer or false if the search could not be initialized
         * @throws ServerNotAvailableException
         */
-       private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
-               \OCP\Util::writeLog('user_ldap', 'Count filter:  '.print_r($filter, true), ILogger::DEBUG);
+       private function count(
+               string $filter,
+               array $bases,
+               $attr = null,
+               ?int $limit = null,
+               ?int $offset = null,
+               bool $skipHandling = false
+       ) {
+               \OC::$server->getLogger()->debug('Count filter: {filter}', [
+                       'app' => 'user_ldap',
+                       'filter' => $filter
+               ]);
+
+               if(!is_null($attr) && !is_array($attr)) {
+                       $attr = array(mb_strtolower($attr, 'UTF-8'));
+               }
 
                $limitPerPage = (int)$this->connection->ldapPagingSize;
                if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
@@ -1266,66 +1274,64 @@ class Access extends LDAPUtility {
                $count = null;
                $this->connection->getConnectionResource();
 
-               do {
-                       $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
+               foreach($bases as $base) {
+                       do {
+                               $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
                        if ($search === false) {
-                               return $counter > 0 ? $counter : false;
-                       }
-                       list($sr, $pagedSearchOK) = $search;
-
-                       /* ++ Fixing RHDS searches with pages with zero results ++
-                        * countEntriesInSearchResults() method signature changed
-                        * by removing $limit and &$hasHitLimit parameters
-                        */
-                       $count = $this->countEntriesInSearchResults($sr);
-                       $counter += $count;
-
-                       $hasMorePages = $this->processPagedSearchStatus($sr, $filter, $base, $count, $limitPerPage,
-                                                                               $offset, $pagedSearchOK, $skipHandling);
-                       $offset += $limitPerPage;
-                       /* ++ Fixing RHDS searches with pages with zero results ++
-                        * Continue now depends on $hasMorePages value
-                        */
-                       $continue = $pagedSearchOK && $hasMorePages;
+                                       return $counter > 0 ? $counter : false;
+                               }
+                               list($sr, $pagedSearchOK) = $search;
+
+                               /* ++ Fixing RHDS searches with pages with zero results ++
+                                * countEntriesInSearchResults() method signature changed
+                                * by removing $limit and &$hasHitLimit parameters
+                                */
+                               $count = $this->countEntriesInSearchResults($sr);
+                               $counter += $count;
+
+                               $hasMorePages = $this->processPagedSearchStatus($sr, $count, $limitPerPage, $pagedSearchOK, $skipHandling);
+                               $offset += $limitPerPage;
+                               /* ++ Fixing RHDS searches with pages with zero results ++
+                                * Continue now depends on $hasMorePages value
+                                */
+                               $continue = $pagedSearchOK && $hasMorePages;
                } while ($continue && (is_null($limit) || $limit <= 0 || $limit > $counter));
+               }
 
                return $counter;
        }
 
        /**
-        * @param array $searchResults
+        * @param resource $sr
         * @return int
         * @throws ServerNotAvailableException
         */
-       private function countEntriesInSearchResults($searchResults) {
-               $counter = 0;
-
-               foreach ($searchResults as $res) {
-                       $count = (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $res);
-                       $counter += $count;
-               }
-
-               return $counter;
+       private function countEntriesInSearchResults($sr): int {
+               return (int)$this->invokeLDAPMethod('countEntries', $this->connection->getConnectionResource(), $sr);
        }
 
        /**
         * Executes an LDAP search
         *
-        * @param string $filter the LDAP filter for the search
-        * @param array $base an array containing the LDAP subtree(s) that shall be searched
-        * @param string|string[] $attr optional, array, one or more attributes that shall be
-        * @param int $limit
-        * @param int $offset
-        * @param bool $skipHandling
-        * @return array with the search result
         * @throws ServerNotAvailableException
         */
-       public function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
+       public function search(
+               string $filter,
+               string $base,
+               ?array $attr = null,
+               ?int $limit = null,
+               ?int $offset = null,
+               bool $skipHandling = false
+       ): array {
                $limitPerPage = (int)$this->connection->ldapPagingSize;
                if (!is_null($limit) && $limit < $limitPerPage && $limit > 0) {
                        $limitPerPage = $limit;
                }
 
+               if(!is_null($attr) && !is_array($attr)) {
+                       $attr = [mb_strtolower($attr, 'UTF-8')];
+               }
+
                /* ++ Fixing RHDS searches with pages with zero results ++
                 * As we can have pages with zero results and/or pages with less
                 * than $limit results but with a still valid server 'cookie',
@@ -1334,6 +1340,8 @@ class Access extends LDAPUtility {
                 */
                $findings = [];
                $savedoffset = $offset;
+               $iFoundItems = 0;
+
                do {
                        $search = $this->executeSearch($filter, $base, $attr, $limitPerPage, $offset);
                        if ($search === false) {
@@ -1346,25 +1354,19 @@ class Access extends LDAPUtility {
                                //i.e. result do not need to be fetched, we just need the cookie
                                //thus pass 1 or any other value as $iFoundItems because it is not
                                //used
-                               $this->processPagedSearchStatus($sr, $filter, $base, 1, $limitPerPage,
-                                                               $offset, $pagedSearchOK,
-                                                               $skipHandling);
+                               $this->processPagedSearchStatus($sr, 1, $limitPerPage, $pagedSearchOK, $skipHandling);
                                return [];
                        }
 
-                       $iFoundItems = 0;
-                       foreach ($sr as $res) {
-                               $findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $res));
-                               $iFoundItems = max($iFoundItems, $findings['count']);
-                               unset($findings['count']);
-                       }
+                       $findings = array_merge($findings, $this->invokeLDAPMethod('getEntries', $cr, $sr));
+                       $iFoundItems = max($iFoundItems, $findings['count']);
+                       unset($findings['count']);
 
-                       $continue = $this->processPagedSearchStatus($sr, $filter, $base, $iFoundItems,
-                               $limitPerPage, $offset, $pagedSearchOK,
-                                                                               $skipHandling);
+                       $continue = $this->processPagedSearchStatus($sr, $iFoundItems, $limitPerPage, $pagedSearchOK, $skipHandling);
                        $offset += $limitPerPage;
                } while ($continue && $pagedSearchOK && ($limit === null || count($findings) < $limit));
-               // reseting offset
+
+               // resetting offset
                $offset = $savedoffset;
 
                // if we're here, probably no connection resource is returned.
@@ -1654,17 +1656,22 @@ class Access extends LDAPUtility {
        public function getUserDnByUuid($uuid) {
                $uuidOverride = $this->connection->ldapExpertUUIDUserAttr;
                $filter       = $this->connection->ldapUserFilter;
-               $base         = $this->connection->ldapBaseUsers;
+               $bases        = $this->connection->ldapBaseUsers;
 
                if ($this->connection->ldapUuidUserAttribute === 'auto' && $uuidOverride === '') {
                        // Sacrebleu! The UUID attribute is unknown :( We need first an
                        // existing DN to be able to reliably detect it.
-                       $result = $this->search($filter, $base, ['dn'], 1);
-                       if (!isset($result[0]) || !isset($result[0]['dn'])) {
-                               throw new \Exception('Cannot determine UUID attribute');
+                       foreach ($bases as $base) {
+                               $result = $this->search($filter, $base, ['dn'], 1);
+                               if (!isset($result[0]) || !isset($result[0]['dn'])) {
+                                       continue;
+                               }
+                               $dn = $result[0]['dn'][0];
+                               if ($hasFound = $this->detectUuidAttribute($dn, true)) {
+                                       break;
+                               }
                        }
-                       $dn = $result[0]['dn'][0];
-                       if (!$this->detectUuidAttribute($dn, true)) {
+                       if(!isset($hasFound) || !$hasFound) {
                                throw new \Exception('Cannot determine UUID attribute');
                        }
                } else {
@@ -1955,36 +1962,13 @@ class Access extends LDAPUtility {
         * @throws ServerNotAvailableException
         */
        private function abandonPagedSearch() {
+               if($this->lastCookie === '') {
+                       return;
+               }
                $cr = $this->connection->getConnectionResource();
-               $this->invokeLDAPMethod('controlPagedResult', $cr, 0, false, $this->lastCookie);
+               $this->invokeLDAPMethod('controlPagedResult', $cr, 0, false);
                $this->getPagedSearchResultState();
                $this->lastCookie = '';
-               $this->cookies = [];
-       }
-
-       /**
-        * get a cookie for the next LDAP paged search
-        * @param string $base a string with the base DN for the search
-        * @param string $filter the search filter to identify the correct search
-        * @param int $limit the limit (or 'pageSize'), to identify the correct search well
-        * @param int $offset the offset for the new search to identify the correct search really good
-        * @return string containing the key or empty if none is cached
-        */
-       private function getPagedResultCookie($base, $filter, $limit, $offset) {
-               if ($offset === 0) {
-                       return '';
-               }
-               $offset -= $limit;
-               //we work with cache here
-               $cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
-               $cookie = '';
-               if (isset($this->cookies[$cacheKey])) {
-                       $cookie = $this->cookies[$cacheKey];
-                       if (is_null($cookie)) {
-                               $cookie = '';
-                       }
-               }
-               return $cookie;
        }
 
        /**
@@ -2007,24 +1991,6 @@ class Access extends LDAPUtility {
                return true;
        }
 
-       /**
-        * set a cookie for LDAP paged search run
-        * @param string $base a string with the base DN for the search
-        * @param string $filter the search filter to identify the correct search
-        * @param int $limit the limit (or 'pageSize'), to identify the correct search well
-        * @param int $offset the offset for the run search to identify the correct search really good
-        * @param string $cookie string containing the cookie returned by ldap_control_paged_result_response
-        * @return void
-        */
-       private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
-               // allow '0' for 389ds
-               if (!empty($cookie) || $cookie === '0') {
-                       $cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' . (int)$limit . '-' . (int)$offset;
-                       $this->cookies[$cacheKey] = $cookie;
-                       $this->lastCookie = $cookie;
-               }
-       }
-
        /**
         * Check whether the most recent paged search was successful. It flushed the state var. Use it always after a possible paged search.
         * @return boolean|null true on success, null or false otherwise
@@ -2046,45 +2012,43 @@ class Access extends LDAPUtility {
         * @return bool|true
         * @throws ServerNotAvailableException
         */
-       private function initPagedSearch($filter, $bases, $attr, $limit, $offset) {
+       private function initPagedSearch(
+               string $filter,
+               string $base,
+               ?array $attr,
+               int $limit,
+               int $offset
+       ): bool {
                $pagedSearchOK = false;
                if ($limit !== 0) {
-                       $offset = (int)$offset; //can be null
-                       \OCP\Util::writeLog('user_ldap',
-                               'initializing paged search for  Filter '.$filter.' base '.print_r($bases, true)
-                               .' attr '.print_r($attr, true). ' limit ' .$limit.' offset '.$offset,
-                               ILogger::DEBUG);
+                       \OC::$server->getLogger()->debug(
+                               'initializing paged search for filter {filter}, base {base}, attr {attr}, limit {limit}, offset {offset}',
+                               [
+                                       'app' => 'user_ldap',
+                                       'filter' => $filter,
+                                       'base' => $base,
+                                       'attr' => $attr,
+                                       'limit' => $limit,
+                                       'offset' => $offset
+                               ]
+                       );
                        //get the cookie from the search for the previous search, required by LDAP
-                       foreach ($bases as $base) {
-                               $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
-                               if (empty($cookie) && $cookie !== "0" && ($offset > 0)) {
-                                       // no cookie known from a potential previous search. We need
-                                       // to start from 0 to come to the desired page. cookie value
-                                       // of '0' is valid, because 389ds
-                                       $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
-                                       $this->search($filter, [$base], $attr, $limit, $reOffset, true);
-                                       $cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
-                                       //still no cookie? obviously, the server does not like us. Let's skip paging efforts.
-                                       // '0' is valid, because 389ds
-                                       //TODO: remember this, probably does not change in the next request...
-                                       if (empty($cookie) && $cookie !== '0') {
-                                               $cookie = null;
-                                       }
-                               }
-                               if (!is_null($cookie)) {
-                                       //since offset = 0, this is a new search. We abandon other searches that might be ongoing.
-                                       $this->abandonPagedSearch();
-                                       $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
-                                               $this->connection->getConnectionResource(), $limit,
-                                               false, $cookie);
-                                       if (!$pagedSearchOK) {
-                                               return false;
-                                       }
-                                       \OCP\Util::writeLog('user_ldap', 'Ready for a paged search', ILogger::DEBUG);
-                               } else {
-                                       $e = new \Exception('No paged search possible, Limit '.$limit.' Offset '.$offset);
-                                       \OC::$server->getLogger()->logException($e, ['level' => ILogger::DEBUG]);
-                               }
+                       if(empty($this->lastCookie) && $this->lastCookie !== "0" && ($offset > 0)) {
+                               // no cookie known from a potential previous search. We need
+                               // to start from 0 to come to the desired page. cookie value
+                               // of '0' is valid, because 389ds
+                               $reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
+                               $this->search($filter, $base, $attr, $limit, $reOffset, true);
+                       }
+                       if($this->lastCookie !== '' && $offset === 0) {
+                               //since offset = 0, this is a new search. We abandon other searches that might be ongoing.
+                               $this->abandonPagedSearch();
+                       }
+                       $pagedSearchOK = true === $this->invokeLDAPMethod(
+                               'controlPagedResult', $this->connection->getConnectionResource(), $limit, false
+                       );
+                       if ($pagedSearchOK) {
+                               \OC::$server->getLogger()->debug('Ready for a paged search',['app' => 'user_ldap']);
                        }
                        /* ++ Fixing RHDS searches with pages with zero results ++
                         * We coudn't get paged searches working with our RHDS for login ($limit = 0),
@@ -2102,7 +2066,7 @@ class Access extends LDAPUtility {
                        $pageSize = (int)$this->connection->ldapPagingSize > 0 ? (int)$this->connection->ldapPagingSize : 500;
                        $pagedSearchOK = $this->invokeLDAPMethod('controlPagedResult',
                                $this->connection->getConnectionResource(),
-                               $pageSize, false, '');
+                               $pageSize, false);
                }
 
                return $pagedSearchOK;
index 430e9c359607a23eca96a341b73eea781ceee47c..c34b396291c5d77ceb7944817e0d5da7d3e8b2f6 100644 (file)
@@ -148,7 +148,7 @@ class CheckUser extends Command {
                        $attrs = $access->userManager->getAttributes();
                        $user = $access->userManager->get($uid);
                        $avatarAttributes = $access->getConnection()->resolveRule('avatar');
-                       $result = $access->search('objectclass=*', [$user->getDN()], $attrs, 1, 0);
+                       $result = $access->search('objectclass=*', $user->getDN(), $attrs, 1, 0);
                        foreach ($result[0] as $attribute => $valueSet) {
                                $output->writeln('  ' . $attribute . ': ');
                                foreach ($valueSet as $value) {
index aa67dd596f11ff35cd245c2f893beda4ff24494e..a96c2b52c4ef16004a8a2deeaa8417ea77e8107f 100644 (file)
@@ -60,7 +60,7 @@ interface ILDAPWrapper {
         * @param string $cookie structure sent by LDAP server
         * @return bool true on success, false otherwise
         */
-       public function controlPagedResult($link, $pageSize, $isCritical, $cookie);
+       public function controlPagedResult($link, $pageSize, $isCritical);
 
        /**
         * Retrieve the LDAP pagination cookie
index 409c6ab2b09cd593d234b6b846b9416fa137ed29..bc91fb0ded944efadfcc85f3ba3d8adc909ec26c 100644 (file)
@@ -33,11 +33,25 @@ namespace OCA\User_LDAP;
 
 use OC\ServerNotAvailableException;
 use OCA\User_LDAP\Exceptions\ConstraintViolationException;
+use OCA\User_LDAP\PagedResults\IAdapter;
+use OCA\User_LDAP\PagedResults\Php54;
+use OCA\User_LDAP\PagedResults\Php73;
 
 class LDAP implements ILDAPWrapper {
        protected $curFunc = '';
        protected $curArgs = [];
 
+       /** @var IAdapter */
+       protected $pagedResultsAdapter;
+
+       public function __construct() {
+               if(version_compare(PHP_VERSION, '7.3', '<') === true) {
+                       $this->pagedResultsAdapter = new Php54();
+               } else {
+                       $this->pagedResultsAdapter = new Php73();
+               }
+       }
+
        /**
         * @param resource $link
         * @param string $dn
@@ -64,17 +78,18 @@ class LDAP implements ILDAPWrapper {
                return $this->invokeLDAPMethod('connect', $host);
        }
 
-       /**
-        * @param resource $link
-        * @param resource $result
-        * @param string $cookie
-        * @return bool|LDAP
-        */
-       public function controlPagedResultResponse($link, $result, &$cookie) {
-               $this->preFunctionCall('ldap_control_paged_result_response',
-                       [$link, $result, $cookie]);
-               $result = ldap_control_paged_result_response($link, $result, $cookie);
-               $this->postFunctionCall();
+       public function controlPagedResultResponse($link, $result, &$cookie): bool {
+               $this->preFunctionCall(
+                       $this->pagedResultsAdapter->getResponseCallFunc(),
+                       $this->pagedResultsAdapter->getResponseCallArgs([$link, $result, &$cookie])
+               );
+
+               $result = $this->pagedResultsAdapter->responseCall($link);
+               $cookie = $this->pagedResultsAdapter->getCookie($link);
+
+               if ($this->isResultFalse($result)) {
+                       $this->postFunctionCall();
+               }
 
                return $result;
        }
@@ -83,12 +98,23 @@ class LDAP implements ILDAPWrapper {
         * @param LDAP $link
         * @param int $pageSize
         * @param bool $isCritical
-        * @param string $cookie
         * @return mixed|true
         */
-       public function controlPagedResult($link, $pageSize, $isCritical, $cookie) {
-               return $this->invokeLDAPMethod('control_paged_result', $link, $pageSize,
-                                                                               $isCritical, $cookie);
+       public function controlPagedResult($link, $pageSize, $isCritical) {
+               $fn = $this->pagedResultsAdapter->getRequestCallFunc();
+               $this->pagedResultsAdapter->setRequestParameters($link, $pageSize, $isCritical);
+               if($fn === null) {
+                       return true;
+               }
+
+               $this->preFunctionCall($fn, $this->pagedResultsAdapter->getRequestCallArgs($link));
+               $result = $this->pagedResultsAdapter->requestCall($link);
+
+               if ($this->isResultFalse($result)) {
+                       $this->postFunctionCall();
+               }
+
+               return $result;
        }
 
        /**
@@ -180,12 +206,13 @@ class LDAP implements ILDAPWrapper {
         * @return mixed
         */
        public function read($link, $baseDN, $filter, $attr) {
-               return $this->invokeLDAPMethod('read', $link, $baseDN, $filter, $attr);
+               $this->pagedResultsAdapter->setReadArgs($link, $baseDN, $filter, $attr);
+               return $this->invokeLDAPMethod('read', ...$this->pagedResultsAdapter->getReadArgs($link));
        }
 
        /**
         * @param LDAP $link
-        * @param string $baseDN
+        * @param string[] $baseDN
         * @param string $filter
         * @param array $attr
         * @param int $attrsOnly
@@ -202,7 +229,9 @@ class LDAP implements ILDAPWrapper {
                        return true;
                });
                try {
-                       $result = $this->invokeLDAPMethod('search', $link, $baseDN, $filter, $attr, $attrsOnly, $limit);
+                       $this->pagedResultsAdapter->setSearchArgs($link, $baseDN, $filter, $attr, $attrsOnly, $limit);
+                       $result = $this->invokeLDAPMethod('search', ...$this->pagedResultsAdapter->getSearchArgs($link));
+
                        restore_error_handler();
                        return $result;
                } catch (\Exception $e) {
diff --git a/apps/user_ldap/lib/PagedResults/IAdapter.php b/apps/user_ldap/lib/PagedResults/IAdapter.php
new file mode 100644 (file)
index 0000000..54f1653
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\PagedResults;
+
+interface IAdapter {
+
+       /**
+        * Methods for initiating Paged Results Control
+        */
+
+       /**
+        * The adapter receives paged result parameters from the client. It may
+        * store the parameters for later use.
+        */
+       public function setRequestParameters($link, int $pageSize, bool $isCritical): void;
+
+       /**
+        * The adapter is asked for an function that is being explicitly called to
+        * send the control parameters to LDAP. If not function has to be called,
+        * null shall be returned.
+        *
+        * It will used by the callee for diagnosis and error handling.
+        */
+       public function getRequestCallFunc(): ?string;
+
+       /**
+        * The adapter is asked to provide the arguments it would pass to the
+        * function returned by getRequestCallFunc(). If none shall be called, an
+        * empty array should be returned.
+        *
+        * It will used by the callee for diagnosis and error handling.
+        */
+       public function getRequestCallArgs($link): array;
+
+       /**
+        * The adapter is asked to do the necessary calls to LDAP, if
+        * getRequestCallFunc returned a function. If none, it will not be called
+        * so the return value is best set to false. Otherwise it shall respond
+        * whether setting the controls was successful.
+        */
+       public function requestCall($link): bool;
+
+       /**
+        * The adapter shall report which PHP function will be called to process
+        * the paged results call
+        *
+        * It will used by the callee for diagnosis and error handling.
+        */
+       public function getResponseCallFunc(): string;
+
+       /**
+        * The adapter shall report with arguments will be provided to the LDAP
+        * function it will call
+        *
+        * It will used by the callee for diagnosis and error handling.
+        */
+       public function getResponseCallArgs(array $originalArgs): array;
+
+       /**
+        * the adapter should do it's LDAP function call and return success state
+        *
+        * @param resource $link LDAP resource
+        * @return bool
+        */
+       public function responseCall($link): bool;
+
+       /**
+        * The adapter receives the parameters that were passed to a search
+        * operation. Typically it wants to save the them for the call proper later
+        * on.
+        */
+       public function setSearchArgs(
+               $link,
+               string $baseDN,
+               string $filter,
+               array $attr,
+               int $attrsOnly,
+               int $limit
+       ): void;
+
+       /**
+        * The adapter shall report which arguments shall be passed to the
+        * ldap_search function.
+        */
+       public function getSearchArgs($link): array;
+
+       /**
+        * The adapter receives the parameters that were passed to a read
+        * operation. Typically it wants to save the them for the call proper later
+        * on.
+        */
+       public function setReadArgs($link, string $baseDN, string $filter, array $attr): void;
+
+       /**
+        * The adapter shall report which arguments shall be passed to the
+        * ldap_read function.
+        */
+       public function getReadArgs($link): array;
+
+       /**
+        * Returns the current paged results cookie
+        *
+        * @param resource $link LDAP resource
+        * @return string
+        */
+       public function getCookie($link): string;
+
+}
diff --git a/apps/user_ldap/lib/PagedResults/Php54.php b/apps/user_ldap/lib/PagedResults/Php54.php
new file mode 100644 (file)
index 0000000..cecaff9
--- /dev/null
@@ -0,0 +1,126 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\PagedResults;
+
+/**
+ * Class Php54
+ *
+ * implements paged results support with PHP APIs available from PHP 5.4
+ *
+ * @package OCA\User_LDAP\PagedResults
+ */
+class Php54 implements IAdapter {
+       use TLinkId;
+
+       /** @var array */
+       protected $linkData = [];
+
+       public function getResponseCallFunc(): string {
+               return 'ldap_control_paged_result_response';
+       }
+
+       public function responseCall($link): bool {
+               $linkId = $this->getLinkId($link);
+               return ldap_control_paged_result_response(...$this->linkData[$linkId]['responseArgs']);
+       }
+
+       public function getResponseCallArgs(array $originalArgs): array {
+               $linkId = $this->getLinkId($originalArgs[0]);
+               if(!isset($this->linkData[$linkId])) {
+                       throw new \LogicException('There should be a request before the response');
+               }
+               $this->linkData[$linkId]['responseArgs'] = &$originalArgs;
+               $this->linkData[$linkId]['cookie'] = &$originalArgs[2];
+               return $originalArgs;
+       }
+
+       public function getCookie($link): string {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['cookie'];
+       }
+
+       public function getRequestCallFunc(): ?string {
+               return 'ldap_control_paged_result';
+       }
+
+       public function setRequestParameters($link, int $pageSize, bool $isCritical): void {
+               $linkId = $this->getLinkId($link);
+
+               if($pageSize === 0 || !isset($this->linkData[$linkId]['cookie'])) {
+                       // abandons a previous paged search
+                       $this->linkData[$linkId]['cookie'] = '';
+               }
+
+               $this->linkData[$linkId]['requestArgs'] = [
+                       $link,
+                       $pageSize,
+                       $isCritical,
+                       &$this->linkData[$linkId]['cookie']
+               ];
+       }
+
+       public function getRequestCallArgs($link): array {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['requestArgs'];
+       }
+
+       public function requestCall($link): bool {
+               $linkId = $this->getLinkId($link);
+               return ldap_control_paged_result(...$this->linkData[$linkId]['requestArgs']);
+       }
+
+       public function setSearchArgs(
+               $link,
+               string $baseDN,
+               string $filter,
+               array $attr,
+               int $attrsOnly,
+               int $limit
+       ): void {
+               $linkId = $this->getLinkId($link);
+               if(!isset($this->linkData[$linkId])) {
+                       $this->linkData[$linkId] = [];
+               }
+               $this->linkData[$linkId]['searchArgs'] = func_get_args();
+       }
+
+       public function getSearchArgs($link): array {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['searchArgs'];
+       }
+
+       public function setReadArgs($link, string $baseDN, string $filter, array $attr): void {
+               $linkId = $this->getLinkId($link);
+               if(!isset($this->linkData[$linkId])) {
+                       $this->linkData[$linkId] = [];
+               }
+               $this->linkData[$linkId]['readArgs'] = func_get_args();
+       }
+
+       public function getReadArgs($link): array {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['readArgs'];
+       }
+}
diff --git a/apps/user_ldap/lib/PagedResults/Php73.php b/apps/user_ldap/lib/PagedResults/Php73.php
new file mode 100644 (file)
index 0000000..a4b7455
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\PagedResults;
+
+/**
+ * Class Php73
+ *
+ * implements paged results support with PHP APIs available from PHP 7.3
+ *
+ * @package OCA\User_LDAP\PagedResults
+ */
+class Php73 implements IAdapter {
+       use TLinkId;
+
+       /** @var array */
+       protected $linkData = [];
+
+       public function getResponseCallFunc(): string {
+               return 'ldap_parse_result';
+       }
+
+       public function responseCall($link): bool {
+               $linkId = $this->getLinkId($link);
+               return ldap_parse_result(...$this->linkData[$linkId]['responseArgs']);
+       }
+
+       public function getResponseCallArgs(array $originalArgs): array {
+               $link = array_shift($originalArgs);
+               $linkId = $this->getLinkId($link);
+
+               if(!isset($this->linkData[$linkId])) {
+                       $this->linkData[$linkId] = [];
+               }
+
+               $this->linkData[$linkId]['responseErrorCode'] = 0;
+               $this->linkData[$linkId]['responseErrorMessage'] = '';
+               $this->linkData[$linkId]['serverControls'] = [];
+               $matchedDn = null;
+               $referrals = [];
+
+               $this->linkData[$linkId]['responseArgs'] = [
+                       $link,
+                       array_shift($originalArgs),
+                       &$this->linkData[$linkId]['responseErrorCode'],
+                       $matchedDn,
+                       &$this->linkData[$linkId]['responseErrorMessage'],
+                       $referrals,
+                       &$this->linkData[$linkId]['serverControls']
+               ];
+
+
+               return $this->linkData[$linkId]['responseArgs'];
+       }
+
+       public function getCookie($link): string {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['serverControls'][LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? '';
+       }
+
+       public function getRequestCallFunc(): ?string {
+               return null;
+       }
+
+       public function setRequestParameters($link, int $pageSize, bool $isCritical): void {
+               $linkId = $this->getLinkId($link);
+               if(!isset($this->linkData[$linkId])) {
+                       $this->linkData[$linkId] = [];
+               }
+               $this->linkData[$linkId]['requestArgs'] = [];
+               $this->linkData[$linkId]['requestArgs']['pageSize'] = $pageSize;
+               $this->linkData[$linkId]['requestArgs']['isCritical'] = $isCritical;
+       }
+
+       public function getRequestCallArgs($link): array {
+               // no separate call
+               return [];
+       }
+
+       public function requestCall($link): bool {
+               // no separate call
+               return false;
+       }
+
+       public function setSearchArgs(
+               $link,
+               string $baseDN,
+               string $filter,
+               array $attr,
+               int $attrsOnly,
+               int $limit
+       ): void {
+               $linkId = $this->getLinkId($link);
+               if(!isset($this->linkData[$linkId])) {
+                       $this->linkData[$linkId] = [];
+               }
+
+               $this->linkData[$linkId]['searchArgs'] = func_get_args();
+               $this->preparePagesResultsArgs($linkId, 'searchArgs');
+       }
+
+       public function getSearchArgs($link): array {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['searchArgs'];
+       }
+
+       public function setReadArgs($link, string $baseDN, string $filter, array $attr): void {
+               $linkId = $this->getLinkId($link);
+               if(!isset($this->linkData[$linkId])) {
+                       $this->linkData[$linkId] = [];
+               }
+
+               $this->linkData[$linkId]['readArgs'] = func_get_args();
+               $this->linkData[$linkId]['readArgs'][] = 0; // $attrsonly default
+               $this->linkData[$linkId]['readArgs'][] = -1; // $sizelimit default
+               $this->preparePagesResultsArgs($linkId, 'readArgs');
+       }
+
+       public function getReadArgs($link): array {
+               $linkId = $this->getLinkId($link);
+               return $this->linkData[$linkId]['readArgs'];
+       }
+
+       protected function preparePagesResultsArgs(int $linkId, string $methodKey): void {
+               if(!isset($this->linkData[$linkId]['requestArgs'])) {
+                       return;
+               }
+
+               $serverControls = [[
+                       'oid' => LDAP_CONTROL_PAGEDRESULTS,
+                       'value' => [
+                               'size' => $this->linkData[$linkId]['requestArgs']['pageSize'],
+                               'cookie' => $this->linkData[$linkId]['serverControls'][LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'] ?? ''
+                       ]
+               ]];
+
+               $this->linkData[$linkId][$methodKey][] = -1; // timelimit
+               $this->linkData[$linkId][$methodKey][] = LDAP_DEREF_NEVER;
+               $this->linkData[$linkId][$methodKey][] = $serverControls;
+       }
+}
diff --git a/apps/user_ldap/lib/PagedResults/TLinkId.php b/apps/user_ldap/lib/PagedResults/TLinkId.php
new file mode 100644 (file)
index 0000000..3128307
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\User_LDAP\PagedResults;
+
+
+trait TLinkId {
+       public function getLinkId($link) {
+               if(is_resource($link)) {
+                       return (int)$link;
+               } else if(is_array($link) && isset($link[0]) && is_resource($link[0])) {
+                       return (int)$link[0];
+               }
+               throw new \RuntimeException('No resource provided');
+       }
+}
index 7d4e6c267de617c5b447a025662adac09b9456be..4ec7b27017bb45b1dad9648643123b65e2f13a85 100644 (file)
@@ -711,7 +711,7 @@ class User {
                $uid = $params['uid'];
                if (isset($uid) && $uid === $this->getUsername()) {
                        //retrieve relevant user attributes
-                       $result = $this->access->search('objectclass=*', [$this->dn], ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
+                       $result = $this->access->search('objectclass=*', $this->dn, ['pwdpolicysubentry', 'pwdgraceusetime', 'pwdreset', 'pwdchangedtime']);
 
                        if (array_key_exists('pwdpolicysubentry', $result[0])) {
                                $pwdPolicySubentry = $result[0]['pwdpolicysubentry'];
@@ -728,7 +728,7 @@ class User {
                        $cacheKey = 'ppolicyAttributes' . $ppolicyDN;
                        $result = $this->connection->getFromCache($cacheKey);
                        if (is_null($result)) {
-                               $result = $this->access->search('objectclass=*', [$ppolicyDN], ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
+                               $result = $this->access->search('objectclass=*', $ppolicyDN, ['pwdgraceauthnlimit', 'pwdmaxage', 'pwdexpirewarning']);
                                $this->connection->writeToCache($cacheKey, $result);
                        }
 
index 73fd35b8960f4ff66ec5651fd7a75ad6c651152d..5aec76eb385e6e2de05a6265507fd56fa4a70a89 100644 (file)
@@ -442,7 +442,7 @@ class AccessTest extends TestCase {
                $this->assertSame($values[0], strtolower($dnFromServer));
        }
 
-       
+
        public function testSetPasswordWithDisabledChanges() {
                $this->expectException(\Exception::class);
                $this->expectExceptionMessage('LDAP password changes are disabled');
@@ -474,7 +474,7 @@ class AccessTest extends TestCase {
                $this->assertFalse($this->access->setPassword('CN=foo', 'MyPassword'));
        }
 
-       
+
        public function testSetPasswordWithRejectedChange() {
                $this->expectException(\OC\HintException::class);
                $this->expectExceptionMessage('Password change rejected.');
@@ -540,7 +540,7 @@ class AccessTest extends TestCase {
                        ->method('__get')
                        ->willReturnCallback(function ($key) use ($base) {
                                if (stripos($key, 'base') !== false) {
-                                       return $base;
+                                       return [$base];
                                }
                                return null;
                        });
@@ -548,8 +548,8 @@ class AccessTest extends TestCase {
                $this->ldap
                        ->expects($this->any())
                        ->method('isResource')
-                       ->willReturnCallback(function ($resource) use ($fakeConnection) {
-                               return $resource === $fakeConnection;
+                       ->willReturnCallback(function ($resource) {
+                               return is_resource($resource);
                        });
                $this->ldap
                        ->expects($this->any())
@@ -558,9 +558,9 @@ class AccessTest extends TestCase {
                $this->ldap
                        ->expects($this->once())
                        ->method('search')
-                       ->willReturn([$fakeSearchResultResource]);
+                       ->willReturn($fakeSearchResultResource);
                $this->ldap
-                       ->expects($this->exactly(count($base)))
+                       ->expects($this->exactly(1))
                        ->method('getEntries')
                        ->willReturn($fakeLdapEntries);
 
@@ -572,17 +572,17 @@ class AccessTest extends TestCase {
        public function testSearchNoPagedSearch() {
                // scenario: no pages search, 1 search base
                $filter = 'objectClass=nextcloudUser';
-               $base = ['ou=zombies,dc=foobar,dc=nextcloud,dc=com'];
+               $base = 'ou=zombies,dc=foobar,dc=nextcloud,dc=com';
 
-               $fakeConnection = new \stdClass();
-               $fakeSearchResultResource = new \stdClass();
+               $fakeConnection = ldap_connect();
+               $fakeSearchResultResource = ldap_connect();
                $fakeLdapEntries = [
                        'count' => 2,
                        [
-                               'dn' => 'uid=sgarth,' . $base[0],
+                               'dn' => 'uid=sgarth,' . $base,
                        ],
                        [
-                               'dn' => 'uid=wwilson,' . $base[0],
+                               'dn' => 'uid=wwilson,' . $base,
                        ]
                ];
 
@@ -598,19 +598,19 @@ class AccessTest extends TestCase {
 
        public function testFetchListOfUsers() {
                $filter = 'objectClass=nextcloudUser';
-               $base = ['ou=zombies,dc=foobar,dc=nextcloud,dc=com'];
+               $base = 'ou=zombies,dc=foobar,dc=nextcloud,dc=com';
                $attrs = ['dn', 'uid'];
 
-               $fakeConnection = new \stdClass();
-               $fakeSearchResultResource = new \stdClass();
+               $fakeConnection = ldap_connect();
+               $fakeSearchResultResource = ldap_connect();
                $fakeLdapEntries = [
                        'count' => 2,
                        [
-                               'dn' => 'uid=sgarth,' . $base[0],
+                               'dn' => 'uid=sgarth,' . $base,
                                'uid' => [ 'sgarth' ],
                        ],
                        [
-                               'dn' => 'uid=wwilson,' . $base[0],
+                               'dn' => 'uid=wwilson,' . $base,
                                'uid' => [ 'wwilson' ],
                        ]
                ];
index 5df6b11848733b8d66f0eae2766e57063a54feb0..de2d7c0d3a7ce889ff030faf989ca3a085c73ca2 100644 (file)
@@ -73,7 +73,8 @@ class LDAPTest extends TestCase {
                                trigger_error($errorMessage);
                        });
 
-               $this->ldap->search('pseudo-resource', 'base', 'filter', []);
+               $fakeResource = ldap_connect();
+               $this->ldap->search($fakeResource, 'base', 'filter', []);
                $this->assertSame($wasErrorHandlerCalled, $passThrough);
 
                restore_error_handler();
index 8de71b182ba93c0235f5335cb001ec027581a7e0..afefaf04c781a0f6be9179061134ab59e781176a 100644 (file)
@@ -1197,7 +1197,7 @@ class UserTest extends \Test\TestCase {
                $this->access->expects($this->any())
                        ->method('search')
                        ->willReturnCallback(function ($filter, $base) {
-                               if ($base === [$this->dn]) {
+                               if($base === $this->dn) {
                                        return [
                                                [
                                                        'pwdchangedtime' => [(new \DateTime())->sub(new \DateInterval('P28D'))->format('Ymdhis').'Z'],
@@ -1205,7 +1205,7 @@ class UserTest extends \Test\TestCase {
                                                ],
                                        ];
                                }
-                               if ($base === ['cn=default,ou=policies,dc=foo,dc=bar']) {
+                               if($base === 'cn=default,ou=policies,dc=foo,dc=bar') {
                                        return [
                                                [
                                                        'pwdmaxage' => ['2592000'],
@@ -1260,7 +1260,7 @@ class UserTest extends \Test\TestCase {
                $this->access->expects($this->any())
                        ->method('search')
                        ->willReturnCallback(function ($filter, $base) {
-                               if ($base === [$this->dn]) {
+                               if($base === $this->dn) {
                                        return [
                                                [
                                                        'pwdpolicysubentry' => ['cn=custom,ou=policies,dc=foo,dc=bar'],
@@ -1269,7 +1269,7 @@ class UserTest extends \Test\TestCase {
                                                ]
                                        ];
                                }
-                               if ($base === ['cn=custom,ou=policies,dc=foo,dc=bar']) {
+                               if ($base === 'cn=custom,ou=policies,dc=foo,dc=bar') {
                                        return [
                                                [
                                                        'pwdmaxage' => ['2592000'],
index 1790106ad561d703dcdbeb2361345990a27e7960..6793273e8c7bedb4290cc06c806344b756e7c4c8 100644 (file)
@@ -109,6 +109,28 @@ Feature: LDAP
       | priscilla |
       | shannah   |
 
+  Scenario: Fetch from second batch of all users, invoking pagination with two bases, third page
+    Given modify LDAP configuration
+      | ldapBaseUsers  | ou=PagingTest,dc=nextcloud,dc=ci;ou=PagingTestSecondBase,dc=nextcloud,dc=ci |
+      | ldapPagingSize | 2                                |
+    And As an "admin"
+    And sending "GET" to "/cloud/users?limit=10&offset=4"
+    Then the OCS status code should be "200"
+    And the "users" result should contain "3" of
+      | ebba    |
+      | eindis  |
+      | fjolnir |
+      | gunna   |
+      | juliana |
+      | leo     |
+      | stigur  |
+    And the "users" result should contain "1" of
+      | allisha   |
+      | dogukan   |
+      | lloyd     |
+      | priscilla |
+      | shannah   |
+
   Scenario: Deleting an unavailable LDAP user
     Given As an "admin"
     And sending "GET" to "/cloud/users"