]> source.dussan.org Git - nextcloud-server.git/commitdiff
Forward port of #12493
authorArthur Schiwon <blizzz@owncloud.com>
Fri, 28 Nov 2014 11:08:33 +0000 (12:08 +0100)
committerArthur Schiwon <blizzz@owncloud.com>
Thu, 4 Dec 2014 18:02:09 +0000 (19:02 +0100)
add ldap-search command to occ

Conflicts:
apps/user_ldap/appinfo/register_command.php

LDAP search filter creation changes:

1. do not prepend * wildcard to search terms. Will result in faster search, but
you don't find "foobar"  when looking for "bar"
2. advanced behaviour when search string contains a space and multiple search
attributes are present. The search string is split into single words. The
resulting filter requires that each word at least appears once in any search
attribute. This is supposed to return better results in big LDAPs.

trim search string before passing it on

apps/user_ldap/appinfo/register_command.php
apps/user_ldap/command/search.php [new file with mode: 0644]
apps/user_ldap/lib/access.php
core/js/share.js

index f65b9773388ff25c8dec789a705e078b09c236ad..1a0227db95eddc64f4daa6da5aa28884939b121e 100644 (file)
@@ -11,3 +11,4 @@ $application->add(new OCA\user_ldap\Command\SetConfig());
 $application->add(new OCA\user_ldap\Command\TestConfig());
 $application->add(new OCA\user_ldap\Command\CreateEmptyConfig());
 $application->add(new OCA\user_ldap\Command\DeleteConfig());
+$application->add(new OCA\user_ldap\Command\Search());
diff --git a/apps/user_ldap/command/search.php b/apps/user_ldap/command/search.php
new file mode 100644 (file)
index 0000000..e202555
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OCA\user_ldap\Command;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+use OCA\user_ldap\User_Proxy;
+use OCA\user_ldap\Group_Proxy;
+use OCA\user_ldap\lib\Helper;
+use OCA\user_ldap\lib\LDAP;
+
+class Search extends Command {
+       protected function configure() {
+               $this
+                       ->setName('ldap:search')
+                       ->setDescription('executes a user or group search')
+                       ->addArgument(
+                                       'search',
+                                       InputArgument::REQUIRED,
+                                       'the search string (can be empty)'
+                                    )
+                       ->addOption(
+                                       'group',
+                                       null,
+                                       InputOption::VALUE_NONE,
+                                       'searches groups instead of users'
+                                    )
+                       ->addOption(
+                                       'offset',
+                                       null,
+                                       InputOption::VALUE_REQUIRED,
+                                       'The offset of the result set. Needs to be a multiple of limit. defaults to 0.',
+                                       0
+                                    )
+                       ->addOption(
+                                       'limit',
+                                       null,
+                                       InputOption::VALUE_REQUIRED,
+                                       'limit the results. 0 means no limit, defaults to 15',
+                                       15
+                                    )
+               ;
+       }
+
+       /**
+        * Tests whether the offset and limit options are valid
+        * @param int $offset
+        * @param int $limit
+        * @throws \InvalidArgumentException
+        */
+       protected function validateOffsetAndLimit($offset, $limit) {
+               if($limit < 0) {
+                       throw new \InvalidArgumentException('limit must be  0 or greater');
+               }
+               if($offset  < 0) {
+                       throw new \InvalidArgumentException('offset must be 0 or greater');
+               }
+               if($limit === 0 && $offset !== 0) {
+                       throw new \InvalidArgumentException('offset must be 0 if limit is also set to 0');
+               }
+               if($offset > 0 && ($offset % $limit !== 0)) {
+                       throw new \InvalidArgumentException('offset must be a multiple of limit');
+               }
+       }
+
+       protected function execute(InputInterface $input, OutputInterface $output) {
+               $configPrefixes = Helper::getServerConfigurationPrefixes(true);
+               $ldapWrapper = new LDAP();
+
+               $offset = intval($input->getOption('offset'));
+               $limit = intval($input->getOption('limit'));
+               $this->validateOffsetAndLimit($offset, $limit);
+
+               if($input->getOption('group')) {
+                       $proxy = new Group_Proxy($configPrefixes, $ldapWrapper);
+                       $getMethod = 'getGroups';
+                       $printID = false;
+               } else {
+                       $proxy = new User_Proxy($configPrefixes, $ldapWrapper);
+                       $getMethod = 'getDisplayNames';
+                       $printID = true;
+               }
+
+               $result = $proxy->$getMethod($input->getArgument('search'), $limit, $offset);
+               foreach($result as $id => $name) {
+                       $line = $name . ($printID ? ' ('.$id.')' : '');
+                       $output->writeln($line);
+               }
+       }
+}
index 6f28a87d30c0cd1d7867454f22d20fd0a9acaf8a..5a4d324fba22b9ad644d73cdf56b798f454c7999 100644 (file)
@@ -403,6 +403,8 @@ class Access extends LDAPUtility implements user\IUserTools {
 
                //a new user/group! Add it only if it doesn't conflict with other backend's users or existing groups
                //disabling Cache is required to avoid that the new user is cached as not-existing in fooExists check
+               //NOTE: mind, disabling cache affects only this instance! Using it
+               // outside of core user management will still cache the user as non-existing.
                $originalTTL = $this->connection->ldapCacheTTL;
                $this->connection->setConfiguration(array('ldapCacheTTL' => 0));
                if(($isUser && !\OCP\User::userExists($intName))
@@ -507,6 +509,7 @@ class Access extends LDAPUtility implements user\IUserTools {
                                if($isUsers) {
                                        //cache the user names so it does not need to be retrieved
                                        //again later (e.g. sharing dialogue).
+                                       $this->cacheUserExists($ocName);
                                        $this->cacheUserDisplayName($ocName, $nameByLDAP);
                                }
                        }
@@ -515,6 +518,14 @@ class Access extends LDAPUtility implements user\IUserTools {
                return $ownCloudNames;
        }
 
+       /**
+        * caches a user as existing
+        * @param string $ocName the internal ownCloud username
+        */
+       public function cacheUserExists($ocName) {
+               $this->connection->writeToCache('userExists'.$ocName, true);
+       }
+
        /**
         * caches the user display name
         * @param string $ocName the internal ownCloud username
@@ -1141,6 +1152,33 @@ class Access extends LDAPUtility implements user\IUserTools {
                        $this->connection->ldapGroupDisplayName);
        }
 
+       /**
+        * creates a filter part for searches by splitting up the given search
+        * string into single words
+        * @param string $search the search term
+        * @param string[] $searchAttributes needs to have at least two attributes,
+        * otherwise it does not make sense :)
+        * @return string the final filter part to use in LDAP searches
+        * @throws \Exception
+        */
+       private function getAdvancedFilterPartForSearch($search, $searchAttributes) {
+               if(!is_array($searchAttributes) || count($searchAttributes) < 2) {
+                       throw new \Exception('searchAttributes must be an array with at least two string');
+               }
+               $searchWords = explode(' ', trim($search));
+               $wordFilters = array();
+               foreach($searchWords as $word) {
+                       $word .= '*';
+                       //every word needs to appear at least once
+                       $wordMatchOneAttrFilters = array();
+                       foreach($searchAttributes as $attr) {
+                               $wordMatchOneAttrFilters[] = $attr . '=' . $word;
+                       }
+                       $wordFilters[] = $this->combineFilterWithOr($wordMatchOneAttrFilters);
+               }
+               return $this->combineFilterWithAnd($wordFilters);
+       }
+
        /**
         * creates a filter part for searches
         * @param string $search the search term
@@ -1151,7 +1189,19 @@ class Access extends LDAPUtility implements user\IUserTools {
         */
        private function getFilterPartForSearch($search, $searchAttributes, $fallbackAttribute) {
                $filter = array();
-               $search = empty($search) ? '*' : '*'.$search.'*';
+               $haveMultiSearchAttributes = (is_array($searchAttributes) && count($searchAttributes) > 0);
+               if($haveMultiSearchAttributes && strpos(trim($search), ' ') !== false) {
+                       try {
+                               return $this->getAdvancedFilterPartForSearch($search, $searchAttributes);
+                       } catch(\Exception $e) {
+                               \OCP\Util::writeLog(
+                                       'user_ldap',
+                                       'Creating advanced filter for search failed, falling back to simple method.',
+                                       \OCP\Util::INFO
+                               );
+                       }
+               }
+               $search = empty($search) ? '*' : $search.'*';
                if(!is_array($searchAttributes) || count($searchAttributes) === 0) {
                        if(empty($fallbackAttribute)) {
                                return '';
index d9ae0168129575265425fb7a99a7583404c5c2d5..b856363d157422d46cb59e0e7c47d803b38d7b80 100644 (file)
@@ -447,7 +447,7 @@ OC.Share={
                        $('#shareWith').autocomplete({minLength: 2, delay: 750, source: function(search, response) {
                                var $loading = $('#dropdown .shareWithLoading');
                                $loading.removeClass('hidden');
-                               $.get(OC.filePath('core', 'ajax', 'share.php'), { fetch: 'getShareWith', search: search.term, itemShares: OC.Share.itemShares }, function(result) {
+                               $.get(OC.filePath('core', 'ajax', 'share.php'), { fetch: 'getShareWith', search: search.term.trim(), itemShares: OC.Share.itemShares }, function(result) {
                                        $loading.addClass('hidden');
                                        if (result.status == 'success' && result.data.length > 0) {
                                                $( "#shareWith" ).autocomplete( "option", "autoFocus", true );