]> source.dussan.org Git - nextcloud-server.git/commitdiff
Add REPORT on files endpoint for filtering
authorVincent Petry <pvince81@owncloud.com>
Wed, 3 Feb 2016 20:55:04 +0000 (21:55 +0100)
committerThomas Müller <thomas.mueller@tmit.eu>
Mon, 8 Feb 2016 20:04:53 +0000 (21:04 +0100)
For now only supports filtering by system tags

apps/dav/lib/connector/sabre/filesreportplugin.php [new file with mode: 0644]
apps/dav/lib/connector/sabre/serverfactory.php
apps/dav/tests/unit/connector/sabre/filesreportplugin.php [new file with mode: 0644]

diff --git a/apps/dav/lib/connector/sabre/filesreportplugin.php b/apps/dav/lib/connector/sabre/filesreportplugin.php
new file mode 100644 (file)
index 0000000..5bdd7a7
--- /dev/null
@@ -0,0 +1,321 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Connector\Sabre;
+
+use OC\Files\View;
+use Sabre\DAV\Exception\NotFound;
+use Sabre\DAV\Exception\PreconditionFailed;
+use Sabre\DAV\Exception\ReportNotSupported;
+use Sabre\DAV\Exception\BadRequest;
+use Sabre\DAV\ServerPlugin;
+use Sabre\DAV\Tree;
+use Sabre\DAV\Xml\Element\Response;
+use Sabre\DAV\Xml\Response\MultiStatus;
+use Sabre\DAV\PropFind;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OCP\IUserSession;
+use OCP\Files\Folder;
+use OCP\IGroupManager;
+use OCP\SystemTag\ISystemTagManager;
+use OCP\SystemTag\TagNotFoundException;
+
+class FilesReportPlugin extends ServerPlugin {
+
+       // namespace
+       const NS_OWNCLOUD = 'http://owncloud.org/ns';
+       const REPORT_NAME            = '{http://owncloud.org/ns}filter-files';
+       const SYSTEMTAG_PROPERTYNAME = '{http://owncloud.org/ns}systemtag';
+
+       /**
+        * Reference to main server object
+        *
+        * @var \Sabre\DAV\Server
+        */
+       private $server;
+
+       /**
+        * @var Tree
+        */
+       private $tree;
+
+       /**
+        * @var View
+        */
+       private $fileView;
+
+       /**
+        * @var ISystemTagManager
+        */
+       private $tagManager;
+
+       /**
+        * @var ISystemTagObjectMapper
+        */
+       private $tagMapper;
+
+       /**
+        * @var IUserSession
+        */
+       private $userSession;
+
+       /**
+        * @var IGroupManager
+        */
+       private $groupManager;
+
+       /**
+        * @var Folder
+        */
+       private $userFolder;
+
+       /**
+        * @param Tree $tree
+        * @param View $view
+        */
+       public function __construct(Tree $tree,
+                                                               View $view,
+                                                               ISystemTagManager $tagManager,
+                                                               ISystemTagObjectMapper $tagMapper,
+                                                               IUserSession $userSession,
+                                                               IGroupManager $groupManager,
+                                                               Folder $userFolder
+       ) {
+               $this->tree = $tree;
+               $this->fileView = $view;
+               $this->tagManager = $tagManager;
+               $this->tagMapper = $tagMapper;
+               $this->userSession = $userSession;
+               $this->groupManager = $groupManager;
+               $this->userFolder = $userFolder;
+       }
+
+       /**
+        * This initializes the plugin.
+        *
+        * This function is called by \Sabre\DAV\Server, after
+        * addPlugin is called.
+        *
+        * This method should set up the required event subscriptions.
+        *
+        * @param \Sabre\DAV\Server $server
+        * @return void
+        */
+       public function initialize(\Sabre\DAV\Server $server) {
+
+               $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
+
+               $this->server = $server;
+               $this->server->on('report', array($this, 'onReport'));
+       }
+
+       /**
+        * Returns a list of reports this plugin supports.
+        *
+        * This will be used in the {DAV:}supported-report-set property.
+        *
+        * @param string $uri
+        * @return array
+        */
+       public function getSupportedReportSet($uri) {
+               return [self::REPORT_NAME];
+       }
+
+       /**
+        * REPORT operations to look for files
+        *
+        * @param string $reportName
+        * @param [] $report
+        * @param string $uri
+        * @return bool
+        * @throws NotFound
+        * @throws ReportNotSupported
+        */
+       public function onReport($reportName, $report, $uri) {
+               $reportTargetNode = $this->server->tree->getNodeForPath($uri);
+               if (!$reportTargetNode instanceof Directory || $reportName !== self::REPORT_NAME) {
+                       throw new ReportNotSupported();
+               }
+
+               $ns = '{' . $this::NS_OWNCLOUD . '}';
+               $requestedProps = [];
+               $filterRules = [];
+
+               // parse report properties and gather filter info
+               foreach ($report as $reportProps) {
+                       $name = $reportProps['name'];
+                       if ($name === $ns . 'filter-rules') {
+                               $filterRules = $reportProps['value'];
+                       } else if ($name === '{DAV:}prop') {
+                               // propfind properties
+                               foreach ($reportProps['value'] as $propVal) {
+                                       $requestedProps[] = $propVal['name'];
+                               }
+                       }
+               }
+
+               if (empty($filterRules)) {
+                       // an empty filter would return all existing files which would be slow
+                       throw new BadRequest('Missing filter-rule block in request');
+               }
+
+               // gather all file ids matching filter
+               try {
+                       $resultFileIds = $this->processFilterRules($filterRules);
+               } catch (TagNotFoundException $e) {
+                       throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e);
+               }
+
+               // find sabre nodes by file id, restricted to the root node path
+               $results = $this->findNodesByFileIds($reportTargetNode, $resultFileIds);
+
+               $responses = $this->prepareResponses($requestedProps, $results);
+
+               $xml = $this->server->xml->write(
+                       '{DAV:}multistatus',
+                       new MultiStatus($responses)
+               );
+
+               $this->server->httpResponse->setStatus(207);
+               $this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
+               $this->server->httpResponse->setBody($xml);
+
+               return false;
+       }
+
+       /**
+        * Find file ids matching the given filter rules
+        *
+        * @param array $filterRules
+        * @return array array of unique file id results
+        *
+        * @throws TagNotFoundException whenever a tag was not found
+        */
+       public function processFilterRules($filterRules) {
+               $ns = '{' . $this::NS_OWNCLOUD . '}';
+               $resultFileIds = [];
+               $systemTagIds = [];
+               foreach ($filterRules as $filterRule) {
+                       if ($filterRule['name'] === $ns . 'systemtag') {
+                               $systemTagIds[] = $filterRule['value'];
+                       }
+               }
+
+               // check user permissions, if applicable
+               if (!$this->isAdmin()) {
+                       // check visibility/permission
+                       $tags = $this->tagManager->getTagsByIds($systemTagIds);
+                       $unknownTagIds = [];
+                       foreach ($tags as $tag) {
+                               if (!$tag->isUserVisible()) {
+                                       $unknownTagIds[] = $tag->getId();
+                               }
+                       }
+
+                       if (!empty($unknownTagIds)) {
+                               throw new TagNotFoundException('Tag with ids ' . implode(', ', $unknownTagIds) . ' not found');
+                       }
+               }
+
+               // fetch all file ids and intersect them
+               foreach ($systemTagIds as $systemTagId) {
+                       $fileIds = $this->tagMapper->getObjectIdsForTags($systemTagId, 'files');
+
+                       if (empty($resultFileIds)) {
+                               $resultFileIds = $fileIds;
+                       } else {
+                               $resultFileIds = array_intersect($resultFileIds, $fileIds);
+                       }
+               }
+               return $resultFileIds;
+       }
+
+       /**
+        * Prepare propfind response for the given nodes
+        *
+        * @param string[] $requestedProps requested properties
+        * @param Node[] nodes nodes for which to fetch and prepare responses
+        * @return Response[]
+        */
+       public function prepareResponses($requestedProps, $nodes) {
+               $responses = [];
+               foreach ($nodes as $node) {
+                       $propFind = new PropFind($node->getPath(), $requestedProps);
+
+                       $this->server->getPropertiesByNode($propFind, $node);
+                       // copied from Sabre Server's getPropertiesForPath
+                       $result = $propFind->getResultForMultiStatus();
+                       $result['href'] = $propFind->getPath();
+
+                       $resourceType = $this->server->getResourceTypeForNode($node);
+                       if (in_array('{DAV:}collection', $resourceType) || in_array('{DAV:}principal', $resourceType)) {
+                               $result['href'] .= '/';
+                       }
+
+                       $responses[] = new Response(
+                               rtrim($this->server->getBaseUri(), '/') . $node->getPath(),
+                               $result,
+                               200
+                       );
+               }
+               return $responses;
+       }
+
+       /**
+        * Find Sabre nodes by file ids
+        *
+        * @param Node $rootNode root node for search
+        * @param array $fileIds file ids
+        * @return Node[] array of Sabre nodes
+        */
+       public function findNodesByFileIds($rootNode, $fileIds) {
+               $folder = $this->userFolder;
+               if (trim($rootNode->getPath(), '/') !== '') {
+                       $folder = $folder->get($rootNode->getPath());
+               }
+
+               $results = [];
+               foreach ($fileIds as $fileId) {
+                       $entry = $folder->getById($fileId);
+                       if ($entry) {
+                               $entry = current($entry);
+                               if ($entry instanceof \OCP\Files\File) {
+                                       $results[] = new File($this->fileView, $entry);
+                               } else if ($entry instanceof \OCP\Files\Folder) {
+                                       $results[] = new Directory($this->fileView, $entry);
+                               }
+                       }
+               }
+
+               return $results;
+       }
+
+       /**
+        * Returns whether the currently logged in user is an administrator
+        */
+       private function isAdmin() {
+               $user = $this->userSession->getUser();
+               if ($user !== null) {
+                       return $this->groupManager->isAdmin($user->getUID());
+               }
+               return false;
+       }
+}
index fa4fda4687023fb414f82c66af9cd1d9e9f976ea..9a828787a0d2e4efb9f36b94919ec17d1c1eca44 100644 (file)
@@ -115,7 +115,7 @@ class ServerFactory {
                // wait with registering these until auth is handled and the filesystem is setup
                $server->on('beforeMethod', function () use ($server, $objectTree, $viewCallBack) {
                        // ensure the skeleton is copied
-                       \OC::$server->getUserFolder();
+                       $userFolder = \OC::$server->getUserFolder();
                        
                        /** @var \OC\Files\View $view */
                        $view = $viewCallBack();
@@ -135,6 +135,15 @@ class ServerFactory {
                        if($this->userSession->isLoggedIn()) {
                                $server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager));
                                $server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession));
+                               $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesReportPlugin(
+                                       $objectTree,
+                                       $view,
+                                       \OC::$server->getSystemTagManager(),
+                                       \OC::$server->getSystemTagObjectMapper(),
+                                       $this->userSession,
+                                       \OC::$server->getGroupManager(),
+                                       $userFolder
+                               ));
                                // custom properties plugin must be the last one
                                $server->addPlugin(
                                        new \Sabre\DAV\PropertyStorage\Plugin(
diff --git a/apps/dav/tests/unit/connector/sabre/filesreportplugin.php b/apps/dav/tests/unit/connector/sabre/filesreportplugin.php
new file mode 100644 (file)
index 0000000..853e52f
--- /dev/null
@@ -0,0 +1,519 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2016, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * 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, version 3,
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Tests\Unit\Sabre\Connector;
+
+use OCA\DAV\Connector\Sabre\FilesReportPlugin as FilesReportPluginImplementation;
+use Sabre\DAV\Exception\NotFound;
+use OCP\SystemTag\ISystemTagObjectMapper;
+use OC\Files\View;
+use OCP\Files\Folder;
+use OCP\IGroupManager;
+use OCP\SystemTag\ISystemTagManager;
+
+class FilesReportPlugin extends \Test\TestCase {
+       /** @var \Sabre\DAV\Server */
+       private $server;
+
+       /** @var \Sabre\DAV\Tree */
+       private $tree;
+
+       /** @var ISystemTagObjectMapper */
+       private $tagMapper;
+
+       /** @var ISystemTagManager */
+       private $tagManager;
+
+       /** @var  \OCP\IUserSession */
+       private $userSession;
+
+       /** @var FilesReportPluginImplementation */
+       private $plugin;
+
+       /** @var View **/
+       private $view;
+
+       /** @var IGroupManager **/
+       private $groupManager;
+
+       /** @var Folder **/
+       private $userFolder;
+
+       public function setUp() {
+               parent::setUp();
+               $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $this->view = $this->getMockBuilder('\OC\Files\View')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $this->server = $this->getMockBuilder('\Sabre\DAV\Server')
+                       ->setConstructorArgs([$this->tree])
+                       ->setMethods(['getRequestUri'])
+                       ->getMock();
+
+               $this->groupManager = $this->getMockBuilder('\OCP\IGroupManager')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $this->userFolder = $this->getMockBuilder('\OCP\Files\Folder')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $this->tagManager = $this->getMock('\OCP\SystemTag\ISystemTagManager');
+               $this->tagMapper = $this->getMock('\OCP\SystemTag\ISystemTagObjectMapper');
+               $this->userSession = $this->getMock('\OCP\IUserSession');
+
+               $user = $this->getMock('\OCP\IUser');
+               $user->expects($this->any())
+                       ->method('getUID')
+                       ->will($this->returnValue('testuser'));
+               $this->userSession->expects($this->any())
+                       ->method('getUser')
+                       ->will($this->returnValue($user));
+
+               $this->plugin = new FilesReportPluginImplementation(
+                       $this->tree,
+                       $this->view,
+                       $this->tagManager,
+                       $this->tagMapper,
+                       $this->userSession,
+                       $this->groupManager,
+                       $this->userFolder
+               );
+       }
+
+       /**
+        * @expectedException \Sabre\DAV\Exception\ReportNotSupported
+        */
+       public function testOnReportInvalidNode() {
+               $path = 'totally/unrelated/13';
+
+               $this->tree->expects($this->any())
+                       ->method('getNodeForPath')
+                       ->with('/' . $path)
+                       ->will($this->returnValue($this->getMock('\Sabre\DAV\INode')));
+
+               $this->server->expects($this->any())
+                       ->method('getRequestUri')
+                       ->will($this->returnValue($path));
+               $this->plugin->initialize($this->server);
+
+               $this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, [], '/' . $path);
+       }
+
+       /**
+        * @expectedException \Sabre\DAV\Exception\ReportNotSupported
+        */
+       public function testOnReportInvalidReportName() {
+               $path = 'test';
+
+               $this->tree->expects($this->any())
+                       ->method('getNodeForPath')
+                       ->with('/' . $path)
+                       ->will($this->returnValue($this->getMock('\Sabre\DAV\INode')));
+
+               $this->server->expects($this->any())
+                       ->method('getRequestUri')
+                       ->will($this->returnValue($path));
+               $this->plugin->initialize($this->server);
+
+               $this->plugin->onReport('{whoever}whatever', [], '/' . $path);
+       }
+
+       public function testOnReport() {
+               $path = 'test';
+
+               $parameters = [
+                       [
+                               'name'  => '{DAV:}prop',
+                               'value' => [
+                                       ['name' => '{DAV:}getcontentlength', 'value' => ''],
+                                       ['name' => '{http://owncloud.org/ns}size', 'value' => ''],
+                               ],
+                       ],
+                       [
+                               'name'  => '{http://owncloud.org/ns}filter-rules',
+                               'value' => [
+                                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+                                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+                               ],
+                       ],
+               ];
+
+               $this->groupManager->expects($this->any())
+                       ->method('isAdmin')
+                       ->will($this->returnValue(true));
+
+               $this->tagMapper->expects($this->at(0))
+                       ->method('getObjectIdsForTags')
+                       ->with('123', 'files')
+                       ->will($this->returnValue(['111', '222']));
+               $this->tagMapper->expects($this->at(1))
+                       ->method('getObjectIdsForTags')
+                       ->with('456', 'files')
+                       ->will($this->returnValue(['111', '222', '333']));
+
+               $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $response->expects($this->once())
+                       ->method('setHeader')
+                       ->with('Content-Type', 'application/xml; charset=utf-8');
+
+               $response->expects($this->once())
+                       ->method('setStatus')
+                       ->with(207);
+
+               $response->expects($this->once())
+                       ->method('setBody');
+
+               $this->tree->expects($this->any())
+                       ->method('getNodeForPath')
+                       ->with('/' . $path)
+                       ->will($this->returnValue($reportTargetNode));
+
+               $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $filesNode2 = $this->getMockBuilder('\OCP\Files\File')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $this->userFolder->expects($this->at(0))
+                       ->method('getById')
+                       ->with('111')
+                       ->will($this->returnValue([$filesNode1]));
+               $this->userFolder->expects($this->at(1))
+                       ->method('getById')
+                       ->with('222')
+                       ->will($this->returnValue([$filesNode2]));
+
+               $this->server->expects($this->any())
+                       ->method('getRequestUri')
+                       ->will($this->returnValue($path));
+               $this->server->httpResponse = $response;
+               $this->plugin->initialize($this->server);
+
+               $this->plugin->onReport(FilesReportPluginImplementation::REPORT_NAME, $parameters, '/' . $path);
+       }
+
+       public function testFindNodesByFileIdsRoot() {
+               $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $filesNode1->expects($this->once())
+                       ->method('getName')
+                       ->will($this->returnValue('first node'));
+
+               $filesNode2 = $this->getMockBuilder('\OCP\Files\File')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $filesNode2->expects($this->once())
+                       ->method('getName')
+                       ->will($this->returnValue('second node'));
+
+               $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $reportTargetNode->expects($this->any())
+                       ->method('getPath')
+                       ->will($this->returnValue('/'));
+
+               $this->userFolder->expects($this->at(0))
+                       ->method('getById')
+                       ->with('111')
+                       ->will($this->returnValue([$filesNode1]));
+               $this->userFolder->expects($this->at(1))
+                       ->method('getById')
+                       ->with('222')
+                       ->will($this->returnValue([$filesNode2]));
+
+               $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
+
+               $this->assertCount(2, $result);
+               $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
+               $this->assertEquals('first node', $result[0]->getName());
+               $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
+               $this->assertEquals('second node', $result[1]->getName());
+       }
+
+       public function testFindNodesByFileIdsSubDir() {
+               $filesNode1 = $this->getMockBuilder('\OCP\Files\Folder')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $filesNode1->expects($this->once())
+                       ->method('getName')
+                       ->will($this->returnValue('first node'));
+
+               $filesNode2 = $this->getMockBuilder('\OCP\Files\File')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $filesNode2->expects($this->once())
+                       ->method('getName')
+                       ->will($this->returnValue('second node'));
+
+               $reportTargetNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $reportTargetNode->expects($this->any())
+                       ->method('getPath')
+                       ->will($this->returnValue('/sub1/sub2'));
+
+
+               $subNode = $this->getMockBuilder('\OCP\Files\Folder')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $this->userFolder->expects($this->at(0))
+                       ->method('get')
+                       ->with('/sub1/sub2')
+                       ->will($this->returnValue($subNode));
+
+               $subNode->expects($this->at(0))
+                       ->method('getById')
+                       ->with('111')
+                       ->will($this->returnValue([$filesNode1]));
+               $subNode->expects($this->at(1))
+                       ->method('getById')
+                       ->with('222')
+                       ->will($this->returnValue([$filesNode2]));
+
+               $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']);
+
+               $this->assertCount(2, $result);
+               $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]);
+               $this->assertEquals('first node', $result[0]->getName());
+               $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]);
+               $this->assertEquals('second node', $result[1]->getName());
+       }
+
+       public function testPrepareResponses() {
+               $requestedProps = ['{DAV:}getcontentlength', '{http://owncloud.org/ns}fileid', '{DAV:}resourcetype'];
+
+               $node1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $node2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File')
+                       ->disableOriginalConstructor()
+                       ->getMock();
+
+               $node1->expects($this->once())
+                       ->method('getInternalFileId')
+                       ->will($this->returnValue('111'));
+               $node2->expects($this->once())
+                       ->method('getInternalFileId')
+                       ->will($this->returnValue('222'));
+               $node2->expects($this->once())
+                       ->method('getSize')
+                       ->will($this->returnValue(1024));
+
+               $this->server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesPlugin($this->tree, $this->view));
+               $this->plugin->initialize($this->server);
+               $responses = $this->plugin->prepareResponses($requestedProps, [$node1, $node2]);
+
+               $this->assertCount(2, $responses);
+
+               $this->assertEquals(200, $responses[0]->getHttpStatus());
+               $this->assertEquals(200, $responses[1]->getHttpStatus());
+
+               $props1 = $responses[0]->getResponseProperties();
+               $this->assertEquals('111', $props1[200]['{http://owncloud.org/ns}fileid']);
+               $this->assertNull($props1[404]['{DAV:}getcontentlength']);
+               $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props1[200]['{DAV:}resourcetype']);
+               $resourceType1 = $props1[200]['{DAV:}resourcetype']->getValue();
+               $this->assertEquals('{DAV:}collection', $resourceType1[0]);
+
+               $props2 = $responses[1]->getResponseProperties();
+               $this->assertEquals('1024', $props2[200]['{DAV:}getcontentlength']);
+               $this->assertEquals('222', $props2[200]['{http://owncloud.org/ns}fileid']);
+               $this->assertInstanceOf('\Sabre\DAV\Xml\Property\ResourceType', $props2[200]['{DAV:}resourcetype']);
+               $this->assertCount(0, $props2[200]['{DAV:}resourcetype']->getValue());
+       }
+
+       public function testProcessFilterRulesSingle() {
+               $this->groupManager->expects($this->any())
+                       ->method('isAdmin')
+                       ->will($this->returnValue(true));
+
+               $this->tagMapper->expects($this->once())
+                       ->method('getObjectIdsForTags')
+                       ->with('123')
+                       ->will($this->returnValue(['111', '222']));
+
+               $rules = [
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+               ];
+
+               $this->assertEquals(['111', '222'], $this->plugin->processFilterRules($rules));
+       }
+
+       public function testProcessFilterRulesAndCondition() {
+               $this->groupManager->expects($this->any())
+                       ->method('isAdmin')
+                       ->will($this->returnValue(true));
+
+               $this->tagMapper->expects($this->at(0))
+                       ->method('getObjectIdsForTags')
+                       ->with('123')
+                       ->will($this->returnValue(['111', '222']));
+               $this->tagMapper->expects($this->at(1))
+                       ->method('getObjectIdsForTags')
+                       ->with('456')
+                       ->will($this->returnValue(['222', '333']));
+
+               $rules = [
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+               ];
+
+               $this->assertEquals(['222'], array_values($this->plugin->processFilterRules($rules)));
+       }
+
+       public function testProcessFilterRulesInvisibleTagAsAdmin() {
+               $this->groupManager->expects($this->any())
+                       ->method('isAdmin')
+                       ->will($this->returnValue(true));
+
+               $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag');
+               $tag1->expects($this->any())
+                       ->method('getId')
+                       ->will($this->returnValue('123'));
+               $tag1->expects($this->any())
+                       ->method('isUserVisible')
+                       ->will($this->returnValue(true));
+
+               $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag');
+               $tag2->expects($this->any())
+                       ->method('getId')
+                       ->will($this->returnValue('123'));
+               $tag2->expects($this->any())
+                       ->method('isUserVisible')
+                       ->will($this->returnValue(false));
+
+               // no need to fetch tags to check permissions
+               $this->tagManager->expects($this->never())
+                       ->method('getTagsByIds');
+
+               $this->tagMapper->expects($this->at(0))
+                       ->method('getObjectIdsForTags')
+                       ->with('123')
+                       ->will($this->returnValue(['111', '222']));
+               $this->tagMapper->expects($this->at(1))
+                       ->method('getObjectIdsForTags')
+                       ->with('456')
+                       ->will($this->returnValue(['222', '333']));
+
+               $rules = [
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+               ];
+
+               $this->assertEquals(['222'], array_values($this->plugin->processFilterRules($rules)));
+       }
+
+       /**
+        * @expectedException \OCP\SystemTag\TagNotFoundException
+        */
+       public function testProcessFilterRulesInvisibleTagAsUser() {
+               $this->groupManager->expects($this->any())
+                       ->method('isAdmin')
+                       ->will($this->returnValue(false));
+
+               $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag');
+               $tag1->expects($this->any())
+                       ->method('getId')
+                       ->will($this->returnValue('123'));
+               $tag1->expects($this->any())
+                       ->method('isUserVisible')
+                       ->will($this->returnValue(true));
+
+               $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag');
+               $tag2->expects($this->any())
+                       ->method('getId')
+                       ->will($this->returnValue('123'));
+               $tag2->expects($this->any())
+                       ->method('isUserVisible')
+                       ->will($this->returnValue(false)); // invisible
+
+               $this->tagManager->expects($this->once())
+                       ->method('getTagsByIds')
+                       ->with(['123', '456'])
+                       ->will($this->returnValue([$tag1, $tag2]));
+
+               $rules = [
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+               ];
+
+               $this->plugin->processFilterRules($rules);
+       }
+
+       public function testProcessFilterRulesVisibleTagAsUser() {
+               $this->groupManager->expects($this->any())
+                       ->method('isAdmin')
+                       ->will($this->returnValue(false));
+
+               $tag1 = $this->getMock('\OCP\SystemTag\ISystemTag');
+               $tag1->expects($this->any())
+                       ->method('getId')
+                       ->will($this->returnValue('123'));
+               $tag1->expects($this->any())
+                       ->method('isUserVisible')
+                       ->will($this->returnValue(true));
+
+               $tag2 = $this->getMock('\OCP\SystemTag\ISystemTag');
+               $tag2->expects($this->any())
+                       ->method('getId')
+                       ->will($this->returnValue('123'));
+               $tag2->expects($this->any())
+                       ->method('isUserVisible')
+                       ->will($this->returnValue(true));
+
+               $this->tagManager->expects($this->once())
+                       ->method('getTagsByIds')
+                       ->with(['123', '456'])
+                       ->will($this->returnValue([$tag1, $tag2]));
+
+               $this->tagMapper->expects($this->at(0))
+                       ->method('getObjectIdsForTags')
+                       ->with('123')
+                       ->will($this->returnValue(['111', '222']));
+               $this->tagMapper->expects($this->at(1))
+                       ->method('getObjectIdsForTags')
+                       ->with('456')
+                       ->will($this->returnValue(['222', '333']));
+
+               $rules = [
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'],
+                       ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'],
+               ];
+
+               $this->assertEquals(['222'], array_values($this->plugin->processFilterRules($rules)));
+       }
+}