summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoeland Jago Douma <rullzer@users.noreply.github.com>2019-12-05 11:04:33 +0100
committerGitHub <noreply@github.com>2019-12-05 11:04:33 +0100
commit63cb31542d9db663f627a17278030b18ed61bc6a (patch)
tree624c5fce9492da4b4f6836a4a1415ba979055af0
parent90401e573e44a4a44d4ce1dc47b534cfb4a9ff1f (diff)
parentbf50342c10dd3093417329865008e7be605135c5 (diff)
downloadnextcloud-server-63cb31542d9db663f627a17278030b18ed61bc6a.tar.gz
nextcloud-server-63cb31542d9db663f627a17278030b18ed61bc6a.zip
Merge pull request #17941 from nextcloud/search-by-owner
Allow filtering the search results to the users home storage
-rw-r--r--apps/dav/lib/Files/FileSearchBackend.php82
-rw-r--r--apps/dav/tests/unit/Files/FileSearchBackendTest.php79
-rw-r--r--lib/private/Files/Cache/Cache.php5
-rw-r--r--lib/private/Files/Cache/QuerySearchHelper.php8
-rw-r--r--lib/private/Files/Node/Folder.php38
-rw-r--r--lib/private/Files/Search/SearchQuery.php16
-rw-r--r--lib/public/Files/Search/ISearchQuery.php8
7 files changed, 210 insertions, 26 deletions
diff --git a/apps/dav/lib/Files/FileSearchBackend.php b/apps/dav/lib/Files/FileSearchBackend.php
index 3eba158c944..6c1f279c6bd 100644
--- a/apps/dav/lib/Files/FileSearchBackend.php
+++ b/apps/dav/lib/Files/FileSearchBackend.php
@@ -119,6 +119,7 @@ class FileSearchBackend implements ISearchBackend {
new SearchPropertyDefinition(FilesPlugin::SIZE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
new SearchPropertyDefinition(TagsPlugin::FAVORITE_PROPERTYNAME, true, true, true, SearchPropertyDefinition::DATATYPE_BOOLEAN),
new SearchPropertyDefinition(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, true, true, false, SearchPropertyDefinition::DATATYPE_NONNEGATIVE_INTEGER),
+ new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, false),
// select only properties
new SearchPropertyDefinition('{DAV:}resourcetype', false, true, false),
@@ -126,7 +127,6 @@ class FileSearchBackend implements ISearchBackend {
new SearchPropertyDefinition(FilesPlugin::CHECKSUMS_PROPERTYNAME, false, true, false),
new SearchPropertyDefinition(FilesPlugin::PERMISSIONS_PROPERTYNAME, false, true, false),
new SearchPropertyDefinition(FilesPlugin::GETETAG_PROPERTYNAME, false, true, false),
- new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, false, true, false),
new SearchPropertyDefinition(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, false, true, false),
new SearchPropertyDefinition(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, false, true, false),
new SearchPropertyDefinition(FilesPlugin::HAS_PREVIEW_PROPERTYNAME, false, true, false, SearchPropertyDefinition::DATATYPE_BOOLEAN),
@@ -169,10 +169,12 @@ class FileSearchBackend implements ISearchBackend {
return new SearchResult($davNode, $path);
}, $results);
- // Sort again, since the result from multiple storages is appended and not sorted
- usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
- return $this->sort($a, $b, $search->orderBy);
- });
+ if (!$query->limitToHome()) {
+ // Sort again, since the result from multiple storages is appended and not sorted
+ usort($nodes, function (SearchResult $a, SearchResult $b) use ($search) {
+ return $this->sort($a, $b, $search->orderBy);
+ });
+ }
// If a limit is provided use only return that number of files
if ($search->limit->maxResults !== 0) {
@@ -267,11 +269,29 @@ class FileSearchBackend implements ISearchBackend {
* @param Query $query
* @return ISearchQuery
*/
- private function transformQuery(Query $query) {
+ private function transformQuery(Query $query): ISearchQuery {
// TODO offset
$limit = $query->limit;
$orders = array_map([$this, 'mapSearchOrder'], $query->orderBy);
- return new SearchQuery($this->transformSearchOperation($query->where), (int)$limit->maxResults, 0, $orders, $this->user);
+
+ $limitHome = false;
+ $ownerProp = $this->extractWhereValue($query->where, FilesPlugin::OWNER_ID_PROPERTYNAME, Operator::OPERATION_EQUAL);
+ if ($ownerProp !== null) {
+ if ($ownerProp === $this->user->getUID()) {
+ $limitHome = true;
+ } else {
+ throw new \InvalidArgumentException("Invalid search value for '{http://owncloud.org/ns}owner-id', only the current user id is allowed");
+ }
+ }
+
+ return new SearchQuery(
+ $this->transformSearchOperation($query->where),
+ (int)$limit->maxResults,
+ 0,
+ $orders,
+ $this->user,
+ $limitHome
+ );
}
/**
@@ -360,4 +380,52 @@ class FileSearchBackend implements ISearchBackend {
return $value;
}
}
+
+ /**
+ * Get a specific property from the were clause
+ */
+ private function extractWhereValue(Operator &$operator, string $propertyName, string $comparison, bool $acceptableLocation = true): ?string {
+ switch ($operator->type) {
+ case Operator::OPERATION_AND:
+ case Operator::OPERATION_OR:
+ case Operator::OPERATION_NOT:
+ foreach ($operator->arguments as &$argument) {
+ $value = $this->extractWhereValue($argument, $propertyName, $comparison, $acceptableLocation && $operator->type === Operator::OPERATION_AND);
+ if ($value !== null) {
+ return $value;
+ }
+ }
+ return null;
+ case Operator::OPERATION_EQUAL:
+ case Operator::OPERATION_GREATER_OR_EQUAL_THAN:
+ case Operator::OPERATION_GREATER_THAN:
+ case Operator::OPERATION_LESS_OR_EQUAL_THAN:
+ case Operator::OPERATION_LESS_THAN:
+ case Operator::OPERATION_IS_LIKE:
+ if ($operator->arguments[0]->name === $propertyName) {
+ if ($operator->type === $comparison) {
+ if ($acceptableLocation) {
+ if ($operator->arguments[1] instanceof Literal) {
+ $value = $operator->arguments[1]->value;
+
+ // to remove the comparison from the query, we replace it with an empty AND
+ $operator = new Operator(Operator::OPERATION_AND);
+
+ return $value;
+ } else {
+ throw new \InvalidArgumentException("searching by '$propertyName' is only allowed with a literal value");
+ }
+ } else{
+ throw new \InvalidArgumentException("searching by '$propertyName' is not allowed inside a '{DAV:}or' or '{DAV:}not'");
+ }
+ } else {
+ throw new \InvalidArgumentException("searching by '$propertyName' is only allowed inside a '$comparison'");
+ }
+ } else {
+ return null;
+ }
+ default:
+ return null;
+ }
+ }
}
diff --git a/apps/dav/tests/unit/Files/FileSearchBackendTest.php b/apps/dav/tests/unit/Files/FileSearchBackendTest.php
index 723fb9f4049..d4b70d61c6f 100644
--- a/apps/dav/tests/unit/Files/FileSearchBackendTest.php
+++ b/apps/dav/tests/unit/Files/FileSearchBackendTest.php
@@ -35,7 +35,9 @@ use OCA\DAV\Files\FileSearchBackend;
use OCP\Files\FileInfo;
use OCP\Files\Folder;
use OCP\Files\IRootFolder;
+use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
+use OCP\Files\Search\ISearchQuery;
use OCP\IUser;
use OCP\Share\IManager;
use SearchDAV\Backend\SearchPropertyDefinition;
@@ -308,4 +310,81 @@ class FileSearchBackendTest extends TestCase {
$query = $this->getBasicQuery(\SearchDAV\Query\Operator::OPERATION_EQUAL, '{DAV:}displayname', 'foo');
$this->search->search($query);
}
+
+ public function testSearchLimitOwnerBasic() {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ /** @var ISearchQuery|null $receivedQuery */
+ $receivedQuery = null;
+ $this->searchFolder
+ ->method('search')
+ ->will($this->returnCallback(function ($query) use (&$receivedQuery) {
+ $receivedQuery = $query;
+ return [
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
+ ];
+ }));
+
+ $query = $this->getBasicQuery(\SearchDAV\Query\Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
+ $this->search->search($query);
+
+ $this->assertNotNull($receivedQuery);
+ $this->assertTrue($receivedQuery->limitToHome());
+
+ /** @var ISearchBinaryOperator $operator */
+ $operator = $receivedQuery->getSearchOperation();
+ $this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
+ $this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
+ $this->assertEmpty($operator->getArguments());
+ }
+
+ public function testSearchLimitOwnerNested() {
+ $this->tree->expects($this->any())
+ ->method('getNodeForPath')
+ ->willReturn($this->davFolder);
+
+ /** @var ISearchQuery|null $receivedQuery */
+ $receivedQuery = null;
+ $this->searchFolder
+ ->method('search')
+ ->will($this->returnCallback(function ($query) use (&$receivedQuery) {
+ $receivedQuery = $query;
+ return [
+ new \OC\Files\Node\Folder($this->rootFolder, $this->view, '/test/path')
+ ];
+ }));
+
+ $query = $this->getBasicQuery(\SearchDAV\Query\Operator::OPERATION_EQUAL, FilesPlugin::OWNER_ID_PROPERTYNAME, $this->user->getUID());
+ $query->where = new \SearchDAV\Query\Operator(
+ \SearchDAV\Query\Operator::OPERATION_AND,
+ [
+ new \SearchDAV\Query\Operator(
+ \SearchDAV\Query\Operator::OPERATION_EQUAL,
+ [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new \SearchDAV\Query\Literal('image/png')]
+ ),
+ new \SearchDAV\Query\Operator(
+ \SearchDAV\Query\Operator::OPERATION_EQUAL,
+ [new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, true), new \SearchDAV\Query\Literal($this->user->getUID())]
+ )
+ ]
+ );
+ $this->search->search($query);
+
+ $this->assertNotNull($receivedQuery);
+ $this->assertTrue($receivedQuery->limitToHome());
+
+ /** @var ISearchBinaryOperator $operator */
+ $operator = $receivedQuery->getSearchOperation();
+ $this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
+ $this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
+ $this->assertCount(2, $operator->getArguments());
+
+ /** @var ISearchBinaryOperator $operator */
+ $operator = $operator->getArguments()[1];
+ $this->assertInstanceOf(ISearchBinaryOperator::class, $operator);
+ $this->assertEquals(ISearchBinaryOperator::OPERATOR_AND, $operator->getType());
+ $this->assertEmpty($operator->getArguments());
+ }
}
diff --git a/lib/private/Files/Cache/Cache.php b/lib/private/Files/Cache/Cache.php
index a90617f5c53..842704d1471 100644
--- a/lib/private/Files/Cache/Cache.php
+++ b/lib/private/Files/Cache/Cache.php
@@ -793,7 +793,10 @@ class Cache implements ICache {
->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
}
- $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
+ $searchExpr = $this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation());
+ if ($searchExpr) {
+ $query->andWhere($searchExpr);
+ }
$this->querySearchHelper->addSearchOrdersToQuery($query, $searchQuery->getOrder());
diff --git a/lib/private/Files/Cache/QuerySearchHelper.php b/lib/private/Files/Cache/QuerySearchHelper.php
index 2d9d8f374f7..47380b72ffa 100644
--- a/lib/private/Files/Cache/QuerySearchHelper.php
+++ b/lib/private/Files/Cache/QuerySearchHelper.php
@@ -88,14 +88,18 @@ class QuerySearchHelper {
* @param ISearchOperator $operator
*/
public function searchOperatorArrayToDBExprArray(IQueryBuilder $builder, array $operators) {
- return array_map(function ($operator) use ($builder) {
+ return array_filter(array_map(function ($operator) use ($builder) {
return $this->searchOperatorToDBExpr($builder, $operator);
- }, $operators);
+ }, $operators));
}
public function searchOperatorToDBExpr(IQueryBuilder $builder, ISearchOperator $operator) {
$expr = $builder->expr();
if ($operator instanceof ISearchBinaryOperator) {
+ if (count($operator->getArguments()) === 0) {
+ return null;
+ }
+
switch ($operator->getType()) {
case ISearchBinaryOperator::OPERATOR_NOT:
$negativeOperator = $operator->getArguments()[0];
diff --git a/lib/private/Files/Node/Folder.php b/lib/private/Files/Node/Folder.php
index 4a134cdcdbf..56ec39d86a8 100644
--- a/lib/private/Files/Node/Folder.php
+++ b/lib/private/Files/Node/Folder.php
@@ -34,7 +34,7 @@ use OCP\Files\FileInfo;
use OCP\Files\Mount\IMountPoint;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
-use OCP\Files\Search\ISearchOperator;
+use OCP\Files\Search\ISearchQuery;
class Folder extends Node implements \OCP\Files\Folder {
/**
@@ -191,7 +191,7 @@ class Folder extends Node implements \OCP\Files\Folder {
/**
* search for files with the name matching $query
*
- * @param string|ISearchOperator $query
+ * @param string|ISearchQuery $query
* @return \OC\Files\Node\Node[]
*/
public function search($query) {
@@ -229,6 +229,11 @@ class Folder extends Node implements \OCP\Files\Folder {
* @return \OC\Files\Node\Node[]
*/
private function searchCommon($method, $args) {
+ $limitToHome = ($method === 'searchQuery')? $args[0]->limitToHome(): false;
+ if ($limitToHome && count(explode('/', $this->path)) !== 3) {
+ throw new \InvalidArgumentException('searching by owner is only allows on the users home folder');
+ }
+
$files = array();
$rootLength = strlen($this->path);
$mount = $this->root->getMount($this->path);
@@ -252,19 +257,22 @@ class Folder extends Node implements \OCP\Files\Folder {
}
}
- $mounts = $this->root->getMountsIn($this->path);
- foreach ($mounts as $mount) {
- $storage = $mount->getStorage();
- if ($storage) {
- $cache = $storage->getCache('');
-
- $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
- $results = call_user_func_array(array($cache, $method), $args);
- foreach ($results as $result) {
- $result['internalPath'] = $result['path'];
- $result['path'] = $relativeMountPoint . $result['path'];
- $result['storage'] = $storage;
- $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage, $result['internalPath'], $result, $mount);
+ if (!$limitToHome) {
+ $mounts = $this->root->getMountsIn($this->path);
+ foreach ($mounts as $mount) {
+ $storage = $mount->getStorage();
+ if ($storage) {
+ $cache = $storage->getCache('');
+
+ $relativeMountPoint = ltrim(substr($mount->getMountPoint(), $rootLength), '/');
+ $results = call_user_func_array([$cache, $method], $args);
+ foreach ($results as $result) {
+ $result['internalPath'] = $result['path'];
+ $result['path'] = $relativeMountPoint . $result['path'];
+ $result['storage'] = $storage;
+ $files[] = new \OC\Files\FileInfo($this->path . '/' . $result['path'], $storage,
+ $result['internalPath'], $result, $mount);
+ }
}
}
}
diff --git a/lib/private/Files/Search/SearchQuery.php b/lib/private/Files/Search/SearchQuery.php
index a22db2f60b3..ddad1fb49de 100644
--- a/lib/private/Files/Search/SearchQuery.php
+++ b/lib/private/Files/Search/SearchQuery.php
@@ -39,6 +39,7 @@ class SearchQuery implements ISearchQuery {
private $order;
/** @var IUser */
private $user;
+ private $limitToHome;
/**
* SearchQuery constructor.
@@ -48,13 +49,22 @@ class SearchQuery implements ISearchQuery {
* @param int $offset
* @param array $order
* @param IUser $user
+ * @param bool $limitToHome
*/
- public function __construct(ISearchOperator $searchOperation, $limit, $offset, array $order, IUser $user) {
+ public function __construct(
+ ISearchOperator $searchOperation,
+ int $limit,
+ int $offset,
+ array $order,
+ IUser $user,
+ bool $limitToHome = false
+ ) {
$this->searchOperation = $searchOperation;
$this->limit = $limit;
$this->offset = $offset;
$this->order = $order;
$this->user = $user;
+ $this->limitToHome = $limitToHome;
}
/**
@@ -91,4 +101,8 @@ class SearchQuery implements ISearchQuery {
public function getUser() {
return $this->user;
}
+
+ public function limitToHome(): bool {
+ return $this->limitToHome;
+ }
}
diff --git a/lib/public/Files/Search/ISearchQuery.php b/lib/public/Files/Search/ISearchQuery.php
index 0929a190489..35093e340a2 100644
--- a/lib/public/Files/Search/ISearchQuery.php
+++ b/lib/public/Files/Search/ISearchQuery.php
@@ -66,4 +66,12 @@ interface ISearchQuery {
* @since 12.0.0
*/
public function getUser();
+
+ /**
+ * Whether or not the search should be limited to the users home storage
+ *
+ * @return bool
+ * @since 18.0.0
+ */
+ public function limitToHome(): bool;
}