diff options
author | Vincent Petry <pvince81@owncloud.com> | 2016-03-02 18:25:31 +0100 |
---|---|---|
committer | Vincent Petry <pvince81@owncloud.com> | 2016-03-17 15:35:21 +0100 |
commit | f778e48ee5c9c80724d631190a1425eabc8d0df1 (patch) | |
tree | d8503b70d419364d1063dad89365d5aaec8bbe94 /apps/dav | |
parent | 8fb3e446107da26ac654ffc25af6814a17cb9022 (diff) | |
download | nextcloud-server-f778e48ee5c9c80724d631190a1425eabc8d0df1.tar.gz nextcloud-server-f778e48ee5c9c80724d631190a1425eabc8d0df1.zip |
Add webdav property for share info in PROPFIND response
Diffstat (limited to 'apps/dav')
-rw-r--r-- | apps/dav/lib/connector/sabre/serverfactory.php | 6 | ||||
-rw-r--r-- | apps/dav/lib/connector/sabre/sharesplugin.php | 197 | ||||
-rw-r--r-- | apps/dav/lib/connector/sabre/sharetypelist.php | 87 | ||||
-rw-r--r-- | apps/dav/tests/unit/connector/sabre/sharesplugin.php | 263 |
4 files changed, 553 insertions, 0 deletions
diff --git a/apps/dav/lib/connector/sabre/serverfactory.php b/apps/dav/lib/connector/sabre/serverfactory.php index 6ccec0bc0e8..080f889ae71 100644 --- a/apps/dav/lib/connector/sabre/serverfactory.php +++ b/apps/dav/lib/connector/sabre/serverfactory.php @@ -137,6 +137,12 @@ class ServerFactory { if($this->userSession->isLoggedIn()) { $server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager)); + $server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin( + $objectTree, + $this->userSession, + $userFolder, + \OC::$server->getShareManager() + )); $server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesReportPlugin( $objectTree, diff --git a/apps/dav/lib/connector/sabre/sharesplugin.php b/apps/dav/lib/connector/sabre/sharesplugin.php new file mode 100644 index 00000000000..df524c11a84 --- /dev/null +++ b/apps/dav/lib/connector/sabre/sharesplugin.php @@ -0,0 +1,197 @@ +<?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; + +/** + * ownCloud + * + * @author Vincent Petry + * @copyright 2016 Vincent Petry <pvince81@owncloud.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>. + * + */ + +use \Sabre\DAV\PropFind; +use \Sabre\DAV\PropPatch; +use OCP\IUserSession; +use OCP\Share\IShare; +use OCA\DAV\Connector\Sabre\ShareTypeList; + +/** + * Sabre Plugin to provide share-related properties + */ +class SharesPlugin extends \Sabre\DAV\ServerPlugin { + + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types'; + + /** + * Reference to main server object + * + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \OCP\Share\IManager + */ + private $shareManager; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var string + */ + private $userId; + + /** + * @var \OCP\Files\Folder + */ + private $userFolder; + + /** + * @var IShare[] + */ + private $cachedShareTypes; + + /** + * @param \Sabre\DAV\Tree $tree tree + * @param IUserSession $userSession user session + * @param \OCP\Files\Folder $userFolder user home folder + * @param \OCP\Share\IManager $shareManager share manager + */ + public function __construct( + \Sabre\DAV\Tree $tree, + IUserSession $userSession, + \OCP\Files\Folder $userFolder, + \OCP\Share\IManager $shareManager + ) { + $this->tree = $tree; + $this->shareManager = $shareManager; + $this->userFolder = $userFolder; + $this->userId = $userSession->getUser()->getUID(); + $this->cachedShareTypes = []; + } + + /** + * 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 + */ + public function initialize(\Sabre\DAV\Server $server) { + $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc'; + $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\ShareTypeList'; + $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME; + + $this->server = $server; + $this->server->on('propFind', array($this, 'handleGetProperties')); + } + + /** + * Return a list of share types for outgoing shares + * + * @param \OCP\Files\Node $node file node + * + * @return int[] array of share types + */ + private function getShareTypes(\OCP\Files\Node $node) { + $shareTypes = []; + $requestedShareTypes = [ + \OCP\Share::SHARE_TYPE_USER, + \OCP\Share::SHARE_TYPE_GROUP, + \OCP\Share::SHARE_TYPE_LINK + ]; + foreach ($requestedShareTypes as $requestedShareType) { + // one of each type is enough to find out about the types + $shares = $this->shareManager->getSharesBy( + $this->userId, + $requestedShareType, + $node, + false, + 1 + ); + if (!empty($shares)) { + $shareTypes[] = $requestedShareType; + } + } + return $shareTypes; + } + + /** + * Adds shares to propfind response + * + * @param PropFind $propFind propfind object + * @param \Sabre\DAV\INode $sabreNode sabre node + */ + public function handleGetProperties( + PropFind $propFind, + \Sabre\DAV\INode $sabreNode + ) { + if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) { + return; + } + + // need prefetch ? + if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory + && $propFind->getDepth() !== 0 + && !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) + ) { + $folderNode = $this->userFolder->get($propFind->getPath()); + $children = $folderNode->getDirectoryListing(); + + $this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode); + foreach ($children as $childNode) { + $this->cachedShareTypes[$childNode->getId()] = $this->getShareTypes($childNode); + } + } + + $propFind->handle(self::SHARETYPES_PROPERTYNAME, function() use ($sabreNode) { + if (isset($this->cachedShareTypes[$sabreNode->getId()])) { + $shareTypes = $this->cachedShareTypes[$sabreNode->getId()]; + } else { + $node = $this->userFolder->get($sabreNode->getPath()); + $shareTypes = $this->getShareTypes($node); + } + + return new ShareTypeList($shareTypes); + }); + } +} diff --git a/apps/dav/lib/connector/sabre/sharetypelist.php b/apps/dav/lib/connector/sabre/sharetypelist.php new file mode 100644 index 00000000000..763586412ad --- /dev/null +++ b/apps/dav/lib/connector/sabre/sharetypelist.php @@ -0,0 +1,87 @@ +<?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 Sabre\Xml\Element; +use Sabre\Xml\Reader; +use Sabre\Xml\Writer; + +/** + * ShareTypeList property + * + * This property contains multiple "share-type" elements, each containing a share type. + */ +class ShareTypeList implements Element { + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + /** + * Share types + * + * @var int[] + */ + private $shareTypes; + + /** + * @param int[] $shareTypes + */ + public function __construct($shareTypes) { + $this->shareTypes = $shareTypes; + } + + /** + * Returns the share types + * + * @return int[] + */ + public function getShareTypes() { + return $this->shareTypes; + } + + /** + * The deserialize method is called during xml parsing. + * + * @param Reader $reader + * @return mixed + */ + static function xmlDeserialize(Reader $reader) { + $shareTypes = []; + + foreach ($reader->parseInnerTree() as $elem) { + if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}share-type') { + $shareTypes[] = (int)$elem['value']; + } + } + return new self($shareTypes); + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + foreach ($this->shareTypes as $shareType) { + $writer->writeElement('{' . self::NS_OWNCLOUD . '}share-type', $shareType); + } + } +} diff --git a/apps/dav/tests/unit/connector/sabre/sharesplugin.php b/apps/dav/tests/unit/connector/sabre/sharesplugin.php new file mode 100644 index 00000000000..b491e95bcb9 --- /dev/null +++ b/apps/dav/tests/unit/connector/sabre/sharesplugin.php @@ -0,0 +1,263 @@ +<?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\Connector\Sabre; + +/** + * Copyright (c) 2016 Vincent Petry <pvince81@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ +class SharesPlugin extends \Test\TestCase { + + const SHARETYPES_PROPERTYNAME = \OCA\DAV\Connector\Sabre\SharesPlugin::SHARETYPES_PROPERTYNAME; + + /** + * @var \Sabre\DAV\Server + */ + private $server; + + /** + * @var \Sabre\DAV\Tree + */ + private $tree; + + /** + * @var \OCP\Share\IManager + */ + private $shareManager; + + /** + * @var \OCP\Files\Folder + */ + private $userFolder; + + /** + * @var \OCA\DAV\Connector\Sabre\SharesPlugin + */ + private $plugin; + + public function setUp() { + parent::setUp(); + $this->server = new \Sabre\DAV\Server(); + $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') + ->disableOriginalConstructor() + ->getMock(); + $this->shareManager = $this->getMock('\OCP\Share\IManager'); + $user = $this->getMock('\OCP\IUser'); + $user->expects($this->once()) + ->method('getUID') + ->will($this->returnValue('user1')); + $userSession = $this->getMock('\OCP\IUserSession'); + $userSession->expects($this->once()) + ->method('getUser') + ->will($this->returnValue($user)); + + $this->userFolder = $this->getMock('\OCP\Files\Folder'); + + $this->plugin = new \OCA\DAV\Connector\Sabre\SharesPlugin( + $this->tree, + $userSession, + $this->userFolder, + $this->shareManager + ); + $this->plugin->initialize($this->server); + } + + /** + * @dataProvider sharesGetPropertiesDataProvider + */ + public function testGetProperties($shareTypes) { + $sabreNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $sabreNode->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('/subdir')); + + // node API nodes + $node = $this->getMock('\OCP\Files\Folder'); + + $this->userFolder->expects($this->once()) + ->method('get') + ->with('/subdir') + ->will($this->returnValue($node)); + + $this->shareManager->expects($this->any()) + ->method('getSharesBy') + ->with( + $this->equalTo('user1'), + $this->anything(), + $this->anything(), + $this->equalTo(false), + $this->equalTo(1) + ) + ->will($this->returnCallback(function($userId, $requestedShareType, $node, $flag, $limit) use ($shareTypes){ + if (in_array($requestedShareType, $shareTypes)) { + return ['dummyshare']; + } + return []; + })); + + $propFind = new \Sabre\DAV\PropFind( + '/dummyPath', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + + $this->plugin->handleGetProperties( + $propFind, + $sabreNode + ); + + $result = $propFind->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes()); + } + + /** + * @dataProvider sharesGetPropertiesDataProvider + */ + public function testPreloadThenGetProperties($shareTypes) { + $sabreNode1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $sabreNode1->expects($this->never()) + ->method('getPath'); + $sabreNode2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + $sabreNode2->expects($this->never()) + ->method('getPath'); + + $sabreNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory') + ->disableOriginalConstructor() + ->getMock(); + $sabreNode->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + // never, because we use getDirectoryListing from the Node API instead + $sabreNode->expects($this->never()) + ->method('getChildren'); + $sabreNode->expects($this->any()) + ->method('getPath') + ->will($this->returnValue('/subdir')); + + // node API nodes + $node = $this->getMock('\OCP\Files\Folder'); + $node->expects($this->any()) + ->method('getId') + ->will($this->returnValue(123)); + $node1 = $this->getMock('\OCP\Files\File'); + $node1->expects($this->any()) + ->method('getId') + ->will($this->returnValue(111)); + $node2 = $this->getMock('\OCP\Files\File'); + $node2->expects($this->any()) + ->method('getId') + ->will($this->returnValue(222)); + $node->expects($this->once()) + ->method('getDirectoryListing') + ->will($this->returnValue([$node1, $node2])); + + $this->userFolder->expects($this->once()) + ->method('get') + ->with('/subdir') + ->will($this->returnValue($node)); + + $this->shareManager->expects($this->any()) + ->method('getSharesBy') + ->with( + $this->equalTo('user1'), + $this->anything(), + $this->anything(), + $this->equalTo(false), + $this->equalTo(1) + ) + ->will($this->returnCallback(function($userId, $requestedShareType, $node, $flag, $limit) use ($shareTypes){ + if ($node->getId() === 111 && in_array($requestedShareType, $shareTypes)) { + return ['dummyshare']; + } + + return []; + })); + + // simulate sabre recursive PROPFIND traversal + $propFindRoot = new \Sabre\DAV\PropFind( + '/subdir', + [self::SHARETYPES_PROPERTYNAME], + 1 + ); + $propFind1 = new \Sabre\DAV\PropFind( + '/subdir/test.txt', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + $propFind2 = new \Sabre\DAV\PropFind( + '/subdir/test2.txt', + [self::SHARETYPES_PROPERTYNAME], + 0 + ); + + $this->plugin->handleGetProperties( + $propFindRoot, + $sabreNode + ); + $this->plugin->handleGetProperties( + $propFind1, + $sabreNode1 + ); + $this->plugin->handleGetProperties( + $propFind2, + $sabreNode2 + ); + + $result = $propFind1->getResultForMultiStatus(); + + $this->assertEmpty($result[404]); + unset($result[404]); + $this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes()); + } + + function sharesGetPropertiesDataProvider() { + return [ + [[]], + [[\OCP\Share::SHARE_TYPE_USER]], + [[\OCP\Share::SHARE_TYPE_GROUP]], + [[\OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK]], + [[\OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK]], + ]; + } +} |