fix: include invisible tags for admins

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
pull/39202/head
Arthur Schiwon 2 years ago
parent 4ac77f0f7a
commit 66a7064db3
No known key found for this signature in database
GPG Key ID: 7424F1874854DF23
  1. 8
      apps/dav/lib/Connector/Sabre/FilesReportPlugin.php
  2. 104
      apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php
  3. 60
      lib/private/Files/Cache/QuerySearchHelper.php
  4. 17
      lib/private/Files/Cache/SearchBuilder.php
  5. 9
      lib/private/SystemTag/SystemTagManager.php
  6. 5
      lib/public/SystemTag/ISystemTagManager.php

@ -283,8 +283,6 @@ class FilesReportPlugin extends ServerPlugin {
* *
* @param array $filterRules * @param array $filterRules
* @return array array of unique file id results * @return array array of unique file id results
*
* @throws TagNotFoundException whenever a tag was not found
*/ */
protected function processFilterRulesForFileIDs($filterRules) { protected function processFilterRulesForFileIDs($filterRules) {
$ns = '{' . $this::NS_OWNCLOUD . '}'; $ns = '{' . $this::NS_OWNCLOUD . '}';
@ -335,12 +333,8 @@ class FilesReportPlugin extends ServerPlugin {
!empty($systemTagIds) !empty($systemTagIds)
&& (method_exists($this->userFolder, 'searchBySystemTag')) && (method_exists($this->userFolder, 'searchBySystemTag'))
) { ) {
$tags = $this->tagManager->getTagsByIds($systemTagIds); $tags = $this->tagManager->getTagsByIds($systemTagIds, $this->userSession->getUser());
foreach ($tags as $tag) { foreach ($tags as $tag) {
if (!$tag->isUserVisible()) {
// searchBySystemTag() also has the criteria to include only user visible tags. They can be skipped early nevertheless.
continue;
}
$tagName = $tag->getName(); $tagName = $tag->getName();
$tmpNodes = $this->userFolder->searchBySystemTag($tagName, $this->userSession->getUser()->getUID(), $limit ?? 0, $offset ?? 0); $tmpNodes = $this->userFolder->searchBySystemTag($tagName, $this->userSession->getUser()->getUID(), $limit ?? 0, $offset ?? 0);
if (count($nodes) === 0) { if (count($nodes) === 0) {

@ -44,6 +44,7 @@ use OCP\IUserSession;
use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTag;
use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagManager;
use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\ISystemTagObjectMapper;
use OCP\SystemTag\TagNotFoundException;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use Sabre\DAV\INode; use Sabre\DAV\INode;
use Sabre\DAV\Tree; use Sabre\DAV\Tree;
@ -734,7 +735,7 @@ class FilesReportPluginTest extends \Test\TestCase {
->method('getTagsByIds') ->method('getTagsByIds')
->with(['123', '456', '789']) ->with(['123', '456', '789'])
->willReturn([$tag123, $tag456, $tag789]); ->willReturn([$tag123, $tag456, $tag789]);
$this->userFolder->expects($this->exactly(2)) $this->userFolder->expects($this->exactly(2))
->method('searchBySystemTag') ->method('searchBySystemTag')
->withConsecutive(['OneTwoThree'], ['FourFiveSix'], ['SevenEightNein']) ->withConsecutive(['OneTwoThree'], ['FourFiveSix'], ['SevenEightNein'])
@ -758,39 +759,54 @@ class FilesReportPluginTest extends \Test\TestCase {
->method('isAdmin') ->method('isAdmin')
->willReturn(true); ->willReturn(true);
$tag1 = $this->getMockBuilder(ISystemTag::class) $filesNode1 = $this->createMock(File::class);
->disableOriginalConstructor() $filesNode1->expects($this->any())
->getMock(); ->method('getSize')
$tag1->expects($this->any()) ->willReturn(12);
$filesNode1->expects($this->any())
->method('getId') ->method('getId')
->willReturn('123'); ->willReturn(111);
$tag1->expects($this->any()) $filesNode2 = $this->createMock(Folder::class);
$filesNode2->expects($this->any())
->method('getSize')
->willReturn(10);
$filesNode2->expects($this->any())
->method('getId')
->willReturn(222);
$filesNode3 = $this->createMock(Folder::class);
$filesNode3->expects($this->any())
->method('getSize')
->willReturn(13);
$filesNode3->expects($this->any())
->method('getId')
->willReturn(333);
$tag123 = $this->createMock(ISystemTag::class);
$tag123->expects($this->any())
->method('getName')
->willReturn('OneTwoThree');
$tag123->expects($this->any())
->method('isUserVisible') ->method('isUserVisible')
->willReturn(true); ->willReturn(true);
$tag456 = $this->createMock(ISystemTag::class);
$tag2 = $this->getMockBuilder(ISystemTag::class) $tag456->expects($this->any())
->disableOriginalConstructor() ->method('getName')
->getMock(); ->willReturn('FourFiveSix');
$tag2->expects($this->any()) $tag456->expects($this->any())
->method('getId')
->willReturn('123');
$tag2->expects($this->any())
->method('isUserVisible') ->method('isUserVisible')
->willReturn(false); ->willReturn(false);
// no need to fetch tags to check permissions $this->tagManager->expects($this->once())
$this->tagManager->expects($this->never()) ->method('getTagsByIds')
->method('getTagsByIds'); ->with(['123', '456'])
->willReturn([$tag123, $tag456]);
$this->tagMapper->expects($this->exactly(2)) $this->userFolder->expects($this->exactly(2))
->method('getObjectIdsForTags') ->method('searchBySystemTag')
->withConsecutive( ->withConsecutive(['OneTwoThree'], ['FourFiveSix'])
['123'],
['456'],
)
->willReturnOnConsecutiveCalls( ->willReturnOnConsecutiveCalls(
['111', '222'], [$filesNode1, $filesNode2],
['222', '333'], [$filesNode2, $filesNode3],
); );
$rules = [ $rules = [
@ -798,41 +814,39 @@ class FilesReportPluginTest extends \Test\TestCase {
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
]; ];
$this->assertEquals(['222'], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])));
} }
public function testProcessFilterRulesInvisibleTagAsUser(): void { public function testProcessFilterRulesInvisibleTagAsUser(): void {
$this->expectException(\OCP\SystemTag\TagNotFoundException::class); $this->expectException(TagNotFoundException::class);
$this->groupManager->expects($this->any()) $this->groupManager->expects($this->any())
->method('isAdmin') ->method('isAdmin')
->willReturn(false); ->willReturn(false);
$tag1 = $this->getMockBuilder(ISystemTag::class) $tag123 = $this->createMock(ISystemTag::class);
->disableOriginalConstructor() $tag123->expects($this->any())
->getMock(); ->method('getName')
$tag1->expects($this->any()) ->willReturn('OneTwoThree');
->method('getId') $tag123->expects($this->any())
->willReturn('123');
$tag1->expects($this->any())
->method('isUserVisible') ->method('isUserVisible')
->willReturn(true); ->willReturn(true);
$tag456 = $this->createMock(ISystemTag::class);
$tag2 = $this->getMockBuilder(ISystemTag::class) $tag456->expects($this->any())
->disableOriginalConstructor() ->method('getName')
->getMock(); ->willReturn('FourFiveSix');
$tag2->expects($this->any()) $tag456->expects($this->any())
->method('getId')
->willReturn('123');
$tag2->expects($this->any())
->method('isUserVisible') ->method('isUserVisible')
->willReturn(false); // invisible ->willReturn(false);
$this->tagManager->expects($this->once()) $this->tagManager->expects($this->once())
->method('getTagsByIds') ->method('getTagsByIds')
->with(['123', '456']) ->with(['123', '456'])
->willReturn([$tag1, $tag2]); ->willThrowException(new TagNotFoundException());
$this->userFolder->expects($this->never())
->method('searchBySystemTag');
$rules = [ $rules = [
['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],

@ -36,9 +36,9 @@ use OCP\Files\IMimeTypeLoader;
use OCP\Files\IRootFolder; use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountPoint; use OCP\Files\Mount\IMountPoint;
use OCP\Files\Search\ISearchBinaryOperator; use OCP\Files\Search\ISearchBinaryOperator;
use OCP\Files\Search\ISearchComparison;
use OCP\Files\Search\ISearchQuery; use OCP\Files\Search\ISearchQuery;
use OCP\IDBConnection; use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUser; use OCP\IUser;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -54,6 +54,7 @@ class QuerySearchHelper {
private $searchBuilder; private $searchBuilder;
/** @var QueryOptimizer */ /** @var QueryOptimizer */
private $queryOptimizer; private $queryOptimizer;
private IGroupManager $groupManager;
public function __construct( public function __construct(
IMimeTypeLoader $mimetypeLoader, IMimeTypeLoader $mimetypeLoader,
@ -61,7 +62,8 @@ class QuerySearchHelper {
SystemConfig $systemConfig, SystemConfig $systemConfig,
LoggerInterface $logger, LoggerInterface $logger,
SearchBuilder $searchBuilder, SearchBuilder $searchBuilder,
QueryOptimizer $queryOptimizer QueryOptimizer $queryOptimizer,
IGroupManager $groupManager,
) { ) {
$this->mimetypeLoader = $mimetypeLoader; $this->mimetypeLoader = $mimetypeLoader;
$this->connection = $connection; $this->connection = $connection;
@ -69,6 +71,7 @@ class QuerySearchHelper {
$this->logger = $logger; $this->logger = $logger;
$this->searchBuilder = $searchBuilder; $this->searchBuilder = $searchBuilder;
$this->queryOptimizer = $queryOptimizer; $this->queryOptimizer = $queryOptimizer;
$this->groupManager = $groupManager;
} }
protected function getQueryBuilder() { protected function getQueryBuilder() {
@ -118,16 +121,16 @@ class QuerySearchHelper {
return $tags; return $tags;
} }
protected function equipQueryForSystemTags(CacheQueryBuilder $query): void { protected function equipQueryForSystemTags(CacheQueryBuilder $query, IUser $user): void {
$query $query->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $query->expr()->andX(
->leftJoin('file', 'systemtag_object_mapping', 'systemtagmap', $query->expr()->andX( $query->expr()->eq('file.fileid', $query->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)),
$query->expr()->eq('file.fileid', $query->expr()->castColumn('systemtagmap.objectid', IQueryBuilder::PARAM_INT)), $query->expr()->eq('systemtagmap.objecttype', $query->createNamedParameter('files'))
$query->expr()->eq('systemtagmap.objecttype', $query->createNamedParameter('files')) ));
)) $on = $query->expr()->andX($query->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'));
->leftJoin('systemtagmap', 'systemtag', 'systemtag', $query->expr()->andX( if (!$this->groupManager->isAdmin($user->getUID())) {
$query->expr()->eq('systemtag.id', 'systemtagmap.systemtagid'), $on->add($query->expr()->eq('systemtag.visibility', $query->createNamedParameter(true)));
$query->expr()->eq('systemtag.visibility', $query->createNamedParameter(true)) }
)); $query->leftJoin('systemtagmap', 'systemtag', 'systemtag', $on);
} }
protected function equipQueryForDavTags(CacheQueryBuilder $query, IUser $user): void { protected function equipQueryForDavTags(CacheQueryBuilder $query, IUser $user): void {
@ -171,25 +174,12 @@ class QuerySearchHelper {
$query = $builder->selectFileCache('file', false); $query = $builder->selectFileCache('file', false);
if ($this->searchBuilder->shouldJoinTags($searchQuery->getSearchOperation())) { $requestedFields = $this->searchBuilder->extractRequestedFields($searchQuery->getSearchOperation());
$user = $searchQuery->getUser(); if (in_array('systemtag', $requestedFields)) {
if ($user === null) { $this->equipQueryForSystemTags($query, $this->requireUser($searchQuery));
throw new \InvalidArgumentException("Searching by tag requires the user to be set in the query"); }
} if (in_array('tagname', $requestedFields) || in_array('favorite', $requestedFields)) {
if ($searchQuery->getSearchOperation() instanceof ISearchComparison) { $this->equipQueryForDavTags($query, $this->requireUser($searchQuery));
switch ($searchQuery->getSearchOperation()->getField()) {
case 'systemtag':
$this->equipQueryForSystemTags($query);
break;
case 'tagname':
case 'favorite':
$this->equipQueryForDavTags($query, $user);
break;
}
} elseif ($searchQuery->getSearchOperation() instanceof SearchBinaryOperator) {
$this->equipQueryForSystemTags($query);
$this->equipQueryForDavTags($query, $user);
}
} }
$this->applySearchConstraints($query, $searchQuery, $caches); $this->applySearchConstraints($query, $searchQuery, $caches);
@ -217,6 +207,14 @@ class QuerySearchHelper {
return $results; return $results;
} }
protected function requireUser(ISearchQuery $searchQuery): IUser {
$user = $searchQuery->getUser();
if ($user === null) {
throw new \InvalidArgumentException("This search operation requires the user to be set in the query");
}
return $user;
}
/** /**
* @return list{0?: array<array-key, ICache>, 1?: array<array-key, IMountPoint>} * @return list{0?: array<array-key, ICache>, 1?: array<array-key, IMountPoint>}
*/ */

@ -69,20 +69,17 @@ class SearchBuilder {
} }
/** /**
* Whether or not the tag tables should be joined to complete the search * @return string[]
*
* @param ISearchOperator $operator
* @return boolean
*/ */
public function shouldJoinTags(ISearchOperator $operator) { public function extractRequestedFields(ISearchOperator $operator): array {
if ($operator instanceof ISearchBinaryOperator) { if ($operator instanceof ISearchBinaryOperator) {
return array_reduce($operator->getArguments(), function ($shouldJoin, ISearchOperator $operator) { return array_reduce($operator->getArguments(), function (array $fields, ISearchOperator $operator) {
return $shouldJoin || $this->shouldJoinTags($operator); return array_unique(array_merge($fields, $this->extractRequestedFields($operator)));
}, false); }, []);
} elseif ($operator instanceof ISearchComparison) { } elseif ($operator instanceof ISearchComparison) {
return $operator->getField() === 'tagname' || $operator->getField() === 'favorite' || $operator->getField() === 'systemtag'; return [$operator->getField()];
} }
return false; return [];
} }
/** /**

@ -91,7 +91,7 @@ class SystemTagManager implements ISystemTagManager {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getTagsByIds($tagIds): array { public function getTagsByIds($tagIds, ?IUser $user = null): array {
if (!\is_array($tagIds)) { if (!\is_array($tagIds)) {
$tagIds = [$tagIds]; $tagIds = [$tagIds];
} }
@ -116,7 +116,12 @@ class SystemTagManager implements ISystemTagManager {
$result = $query->execute(); $result = $query->execute();
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$tags[$row['id']] = $this->createSystemTagFromRow($row); $tag = $this->createSystemTagFromRow($row);
if ($user && !$this->canUserSeeTag($tag, $user)) {
// if a user is given, hide invisible tags
continue;
}
$tags[$row['id']] = $tag;
} }
$result->closeCursor(); $result->closeCursor();

@ -38,6 +38,7 @@ interface ISystemTagManager {
* Returns the tag objects matching the given tag ids. * Returns the tag objects matching the given tag ids.
* *
* @param array|string $tagIds id or array of unique ids of the tag to retrieve * @param array|string $tagIds id or array of unique ids of the tag to retrieve
* @param ?IUser $user optional user to run a visibility check against for each tag
* *
* @return ISystemTag[] array of system tags with tag id as key * @return ISystemTag[] array of system tags with tag id as key
* *
@ -45,9 +46,9 @@ interface ISystemTagManager {
* @throws TagNotFoundException if at least one given tag ids did no exist * @throws TagNotFoundException if at least one given tag ids did no exist
* The message contains a json_encoded array of the ids that could not be found * The message contains a json_encoded array of the ids that could not be found
* *
* @since 9.0.0 * @since 9.0.0, optional parameter $user added in 28.0.0
*/ */
public function getTagsByIds($tagIds): array; public function getTagsByIds($tagIds, ?IUser $user = null): array;
/** /**
* Returns the tag object matching the given attributes. * Returns the tag object matching the given attributes.

Loading…
Cancel
Save