aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorblizzz <blizzz@owncloud.com>2014-12-04 18:58:18 +0100
committerblizzz <blizzz@owncloud.com>2014-12-04 18:58:18 +0100
commitaf1c47d1e830e09965582278d5af4cf5ed630ff3 (patch)
tree5b84a6f8e76ec669e3e4052bc0b242136ab728fd
parentd30059d76ce04a9b2a4bd91d0261977bb0c972ce (diff)
parentddd832c2dcfa330fe5335daa8b2165172ce79a2a (diff)
downloadnextcloud-server-af1c47d1e830e09965582278d5af4cf5ed630ff3.tar.gz
nextcloud-server-af1c47d1e830e09965582278d5af4cf5ed630ff3.zip
Merge pull request #12493 from owncloud/ldap_search_perf
LDAP search behaviour modifications
-rw-r--r--apps/user_ldap/appinfo/register_command.php1
-rw-r--r--apps/user_ldap/command/search.php100
-rw-r--r--apps/user_ldap/lib/access.php52
-rw-r--r--core/js/share.js2
4 files changed, 153 insertions, 2 deletions
diff --git a/apps/user_ldap/appinfo/register_command.php b/apps/user_ldap/appinfo/register_command.php
index 10d992531c4..efce2d0b49e 100644
--- a/apps/user_ldap/appinfo/register_command.php
+++ b/apps/user_ldap/appinfo/register_command.php
@@ -9,3 +9,4 @@
$application->add(new OCA\user_ldap\Command\ShowConfig());
$application->add(new OCA\user_ldap\Command\SetConfig());
$application->add(new OCA\user_ldap\Command\TestConfig());
+$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
index 00000000000..e20255510d8
--- /dev/null
+++ b/apps/user_ldap/command/search.php
@@ -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);
+ }
+ }
+}
diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php
index d89029abf17..8a0191e4244 100644
--- a/apps/user_ldap/lib/access.php
+++ b/apps/user_ldap/lib/access.php
@@ -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);
}
}
@@ -516,6 +519,14 @@ class Access extends LDAPUtility implements user\IUserTools {
}
/**
+ * 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
* @param string $displayName the display name
@@ -1141,6 +1152,33 @@ class Access extends LDAPUtility implements user\IUserTools {
}
/**
+ * 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
* @param string[]|null $searchAttributes
@@ -1150,7 +1188,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 '';
diff --git a/core/js/share.js b/core/js/share.js
index 85e92b43be1..c83c3ee08c8 100644
--- a/core/js/share.js
+++ b/core/js/share.js
@@ -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 );