From d1104954aa0c60c7200d401447da4b480293ef23 Mon Sep 17 00:00:00 2001 From: Thomas Müller Date: Mon, 25 Jan 2016 17:50:39 +0100 Subject: Migrate from CardDAV sharing to more generalk DAV sharing --- apps/dav/lib/carddav/addressbook.php | 10 +- apps/dav/lib/carddav/carddavbackend.php | 10 +- .../lib/carddav/sharing/ishareableaddressbook.php | 64 --- apps/dav/lib/carddav/sharing/plugin.php | 197 ------- apps/dav/lib/carddav/sharing/xml/sharerequest.php | 84 --- apps/dav/lib/dav/sharing/ishareable.php | 64 +++ apps/dav/lib/dav/sharing/plugin.php | 191 +++++++ apps/dav/lib/dav/sharing/xml/sharerequest.php | 84 +++ apps/dav/lib/server.php | 1 - .../caldavtest/tests/CalDAV/sharing-calendars.xml | 573 +++++++++++++++++++++ apps/dav/tests/unit/carddav/sharing/plugintest.php | 8 +- apps/dav/tests/unit/dav/sharing/plugintest.php | 83 +++ 12 files changed, 1009 insertions(+), 360 deletions(-) delete mode 100644 apps/dav/lib/carddav/sharing/ishareableaddressbook.php delete mode 100644 apps/dav/lib/carddav/sharing/plugin.php delete mode 100644 apps/dav/lib/carddav/sharing/xml/sharerequest.php create mode 100644 apps/dav/lib/dav/sharing/ishareable.php create mode 100644 apps/dav/lib/dav/sharing/plugin.php create mode 100644 apps/dav/lib/dav/sharing/xml/sharerequest.php create mode 100644 apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml create mode 100644 apps/dav/tests/unit/dav/sharing/plugintest.php (limited to 'apps/dav') diff --git a/apps/dav/lib/carddav/addressbook.php b/apps/dav/lib/carddav/addressbook.php index 2cfaa7b708c..6c3caecddf6 100644 --- a/apps/dav/lib/carddav/addressbook.php +++ b/apps/dav/lib/carddav/addressbook.php @@ -20,10 +20,10 @@ */ namespace OCA\DAV\CardDAV; -use OCA\DAV\CardDAV\Sharing\IShareableAddressBook; +use OCA\DAV\DAV\Sharing\IShareable; use Sabre\DAV\Exception\NotFound; -class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareableAddressBook { +class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { public function __construct(CardDavBackend $carddavBackend, array $addressBookInfo) { parent::__construct($carddavBackend, $addressBookInfo); @@ -82,14 +82,14 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareableAddres } // add the current user - if (isset($this->addressBookInfo['{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal'])) { - $owner = $this->addressBookInfo['{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal']; + if (isset($this->addressBookInfo['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal'])) { + $owner = $this->addressBookInfo['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal']; $acl[] = [ 'privilege' => '{DAV:}read', 'principal' => $owner, 'protected' => true, ]; - if ($this->addressBookInfo['{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only']) { + if ($this->addressBookInfo['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only']) { $acl[] = [ 'privilege' => '{DAV:}write', 'principal' => $owner, diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php index 28f6099b639..81a3ba7c519 100644 --- a/apps/dav/lib/carddav/carddavbackend.php +++ b/apps/dav/lib/carddav/carddavbackend.php @@ -136,8 +136,8 @@ class CardDavBackend implements BackendInterface, SyncSupport { '{' . Plugin::NS_CARDDAV . '}addressbook-description' => $row['description'], '{http://calendarserver.org/ns/}getctag' => $row['synctoken'], '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', - '{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'], - '{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => $row['access'] === self::ACCESS_READ, + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => $row['access'] === self::ACCESS_READ, ]; } $result->closeCursor(); @@ -893,7 +893,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '', 'status' => 1, 'readOnly' => ($row['access'] === self::ACCESS_READ), - '{'.\OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD.'}principal' => $p['uri'] + '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}principal' => $p['uri'] ]; } @@ -1001,13 +1001,13 @@ class CardDavBackend implements BackendInterface, SyncSupport { foreach ($shares as $share) { $acl[] = [ 'privilege' => '{DAV:}read', - 'principal' => $share['{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], + 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], 'protected' => true, ]; if (!$share['readOnly']) { $acl[] = [ 'privilege' => '{DAV:}write', - 'principal' => $share['{' . \OCA\DAV\CardDAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], + 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], 'protected' => true, ]; } diff --git a/apps/dav/lib/carddav/sharing/ishareableaddressbook.php b/apps/dav/lib/carddav/sharing/ishareableaddressbook.php deleted file mode 100644 index 8e348a90991..00000000000 --- a/apps/dav/lib/carddav/sharing/ishareableaddressbook.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * @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 - * - */ -namespace OCA\DAV\CardDAV\Sharing; -use Sabre\CardDAV\IAddressBook; - -/** - * This interface represents a Calendar that can be shared with other users. - * - */ -interface IShareableAddressBook extends IAddressBook { - - /** - * Updates the list of shares. - * - * The first array is a list of people that are to be added to the - * addressbook. - * - * Every element in the add array has the following properties: - * * href - A url. Usually a mailto: address - * * commonName - Usually a first and last name, or false - * * summary - A description of the share, can also be false - * * readOnly - A boolean value - * - * Every element in the remove array is just the address string. - * - * @param array $add - * @param array $remove - * @return void - */ - function updateShares(array $add, array $remove); - - /** - * Returns the list of people whom this addressbook is shared with. - * - * Every element in this array should have the following properties: - * * href - Often a mailto: address - * * commonName - Optional, for example a first + last name - * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. - * * readOnly - boolean - * * summary - Optional, a description for the share - * - * @return array - */ - function getShares(); - -} \ No newline at end of file diff --git a/apps/dav/lib/carddav/sharing/plugin.php b/apps/dav/lib/carddav/sharing/plugin.php deleted file mode 100644 index d25b84d01f3..00000000000 --- a/apps/dav/lib/carddav/sharing/plugin.php +++ /dev/null @@ -1,197 +0,0 @@ - - * @author Thomas Müller - * - * @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 - * - */ -namespace OCA\DAV\CardDAV\Sharing; - -use OCA\DAV\Connector\Sabre\Auth; -use OCP\IRequest; -use Sabre\DAV\Exception\BadRequest; -use Sabre\DAV\Exception\NotFound; -use Sabre\DAV\Server; -use Sabre\DAV\ServerPlugin; -use Sabre\DAV\XMLUtil; -use Sabre\DAVACL\IACL; -use Sabre\HTTP\RequestInterface; -use Sabre\HTTP\ResponseInterface; - -class Plugin extends ServerPlugin { - - const NS_OWNCLOUD = 'http://owncloud.org/ns'; - - /** @var Auth */ - private $auth; - - /** @var IRequest */ - private $request; - - /** - * Plugin constructor. - * - * @param Auth $authBackEnd - * @param IRequest $request - */ - public function __construct(Auth $authBackEnd, IRequest $request) { - $this->auth = $authBackEnd; - $this->request = $request; - } - - /** - * Reference to SabreDAV server object. - * - * @var \Sabre\DAV\Server - */ - protected $server; - - /** - * This method should return a list of server-features. - * - * This is for example 'versioning' and is added to the DAV: header - * in an OPTIONS response. - * - * @return string[] - */ - function getFeatures() { - - return ['oc-addressbook-sharing']; - - } - - /** - * Returns a plugin name. - * - * Using this name other plugins will be able to access other plugins - * using Sabre\DAV\Server::getPlugin - * - * @return string - */ - function getPluginName() { - - return 'carddav-sharing'; - - } - - /** - * 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 Server $server - * @return void - */ - function initialize(Server $server) { - $this->server = $server; - $server->resourceTypeMapping['OCA\\DAV\CardDAV\\ISharedAddressbook'] = '{' . \Sabre\CardDAV\Plugin::NS_CARDDAV . '}shared'; - $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}share'] = 'OCA\\DAV\\CardDAV\\Sharing\\Xml\\ShareRequest'; - - $this->server->on('method:POST', [$this, 'httpPost']); - } - - /** - * We intercept this to handle POST requests on calendars. - * - * @param RequestInterface $request - * @param ResponseInterface $response - * @return null|false - */ - function httpPost(RequestInterface $request, ResponseInterface $response) { - - $path = $request->getPath(); - - // Only handling xml - $contentType = $request->getHeader('Content-Type'); - if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) - return; - - // Making sure the node exists - try { - $node = $this->server->tree->getNodeForPath($path); - } catch (NotFound $e) { - return; - } - - // CSRF protection - $this->protectAgainstCSRF(); - - $requestBody = $request->getBodyAsString(); - - // If this request handler could not deal with this POST request, it - // will return 'null' and other plugins get a chance to handle the - // request. - // - // However, we already requested the full body. This is a problem, - // because a body can only be read once. This is why we preemptively - // re-populated the request body with the existing data. - $request->setBody($requestBody); - - $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); - - switch ($documentType) { - - // Dealing with the 'share' document, which modified invitees on a - // calendar. - case '{' . self::NS_OWNCLOUD . '}share' : - - // We can only deal with IShareableCalendar objects - if (!$node instanceof IShareableAddressBook) { - return; - } - - $this->server->transactionType = 'post-oc-addressbook-share'; - - // Getting ACL info - $acl = $this->server->getPlugin('acl'); - - // If there's no ACL support, we allow everything - if ($acl) { - /** @var \Sabre\DAVACL\Plugin $acl */ - $acl->checkPrivileges($path, '{DAV:}write'); - } - - $node->updateShares($message->set, $message->remove); - - $response->setStatus(200); - // Adding this because sending a response body may cause issues, - // and I wanted some type of indicator the response was handled. - $response->setHeader('X-Sabre-Status', 'everything-went-well'); - - // Breaking the event chain - return false; - } - } - - private function protectAgainstCSRF() { - $user = $this->auth->getCurrentUser(); - if ($this->auth->isDavAuthenticated($user)) { - return true; - } - - if ($this->request->passesCSRFCheck()) { - return true; - } - - throw new BadRequest(); - } - - -} diff --git a/apps/dav/lib/carddav/sharing/xml/sharerequest.php b/apps/dav/lib/carddav/sharing/xml/sharerequest.php deleted file mode 100644 index bd55dd4073e..00000000000 --- a/apps/dav/lib/carddav/sharing/xml/sharerequest.php +++ /dev/null @@ -1,84 +0,0 @@ - - * - * @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 - * - */ -namespace OCA\DAV\CardDAV\Sharing\Xml; - -use OCA\DAV\CardDAV\Sharing\Plugin; -use Sabre\Xml\Reader; -use Sabre\Xml\XmlDeserializable; - -class ShareRequest implements XmlDeserializable { - - public $set = []; - - public $remove = []; - - /** - * Constructor - * - * @param array $set - * @param array $remove - */ - function __construct(array $set, array $remove) { - - $this->set = $set; - $this->remove = $remove; - - } - - static function xmlDeserialize(Reader $reader) { - - $elements = $reader->parseInnerTree([ - '{' . Plugin::NS_OWNCLOUD. '}set' => 'Sabre\\Xml\\Element\\KeyValue', - '{' . Plugin::NS_OWNCLOUD . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', - ]); - - $set = []; - $remove = []; - - foreach ($elements as $elem) { - switch ($elem['name']) { - - case '{' . Plugin::NS_OWNCLOUD . '}set' : - $sharee = $elem['value']; - - $sumElem = '{' . Plugin::NS_OWNCLOUD . '}summary'; - $commonName = '{' . Plugin::NS_OWNCLOUD . '}common-name'; - - $set[] = [ - 'href' => $sharee['{DAV:}href'], - 'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null, - 'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null, - 'readOnly' => !array_key_exists('{' . Plugin::NS_OWNCLOUD . '}read-write', $sharee), - ]; - break; - - case '{' . Plugin::NS_OWNCLOUD . '}remove' : - $remove[] = $elem['value']['{DAV:}href']; - break; - - } - } - - return new self($set, $remove); - - } - -} diff --git a/apps/dav/lib/dav/sharing/ishareable.php b/apps/dav/lib/dav/sharing/ishareable.php new file mode 100644 index 00000000000..e82c03a6af8 --- /dev/null +++ b/apps/dav/lib/dav/sharing/ishareable.php @@ -0,0 +1,64 @@ + + * + * @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 + * + */ +namespace OCA\DAV\DAV\Sharing; +use Sabre\DAV\INode; + +/** + * This interface represents a dav resource that can be shared with other users. + * + */ +interface IShareable extends INode { + + /** + * Updates the list of shares. + * + * The first array is a list of people that are to be added to the + * resource. + * + * Every element in the add array has the following properties: + * * href - A url. Usually a mailto: address + * * commonName - Usually a first and last name, or false + * * summary - A description of the share, can also be false + * * readOnly - A boolean value + * + * Every element in the remove array is just the address string. + * + * @param array $add + * @param array $remove + * @return void + */ + function updateShares(array $add, array $remove); + + /** + * Returns the list of people whom this resource is shared with. + * + * Every element in this array should have the following properties: + * * href - Often a mailto: address + * * commonName - Optional, for example a first + last name + * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. + * * readOnly - boolean + * * summary - Optional, a description for the share + * + * @return array + */ + function getShares(); + +} \ No newline at end of file diff --git a/apps/dav/lib/dav/sharing/plugin.php b/apps/dav/lib/dav/sharing/plugin.php new file mode 100644 index 00000000000..d3fdd8aa969 --- /dev/null +++ b/apps/dav/lib/dav/sharing/plugin.php @@ -0,0 +1,191 @@ + + * @author Thomas Müller + * + * @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 + * + */ + +namespace OCA\DAV\DAV\Sharing; + +use OCA\DAV\Connector\Sabre\Auth; +use OCP\IRequest; +use Sabre\DAV\Exception\BadRequest; +use Sabre\DAV\Exception\NotFound; +use Sabre\DAV\Server; +use Sabre\DAV\ServerPlugin; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +class Plugin extends ServerPlugin { + + const NS_OWNCLOUD = 'http://owncloud.org/ns'; + + /** @var Auth */ + private $auth; + + /** @var IRequest */ + private $request; + + /** + * Plugin constructor. + * + * @param Auth $authBackEnd + * @param IRequest $request + */ + public function __construct(Auth $authBackEnd, IRequest $request) { + $this->auth = $authBackEnd; + $this->request = $request; + } + + /** + * Reference to SabreDAV server object. + * + * @var \Sabre\DAV\Server + */ + protected $server; + + /** + * This method should return a list of server-features. + * + * This is for example 'versioning' and is added to the DAV: header + * in an OPTIONS response. + * + * @return string[] + */ + function getFeatures() { + return ['oc-resource-sharing']; + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + function getPluginName() { + return 'oc-resource-sharing'; + } + + /** + * 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 Server $server + * @return void + */ + function initialize(Server $server) { + $this->server = $server; + $this->server->xml->elementMap['{' . Plugin::NS_OWNCLOUD . '}share'] = 'OCA\\DAV\\DAV\\Sharing\\Xml\\ShareRequest'; + + $this->server->on('method:POST', [$this, 'httpPost']); + } + + /** + * We intercept this to handle POST requests on a dav resource. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return null|false + */ + function httpPost(RequestInterface $request, ResponseInterface $response) { + + $path = $request->getPath(); + + // Only handling xml + $contentType = $request->getHeader('Content-Type'); + if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) + return; + + // Making sure the node exists + try { + $node = $this->server->tree->getNodeForPath($path); + } catch (NotFound $e) { + return; + } + + // CSRF protection + $this->protectAgainstCSRF(); + + $requestBody = $request->getBodyAsString(); + + // If this request handler could not deal with this POST request, it + // will return 'null' and other plugins get a chance to handle the + // request. + // + // However, we already requested the full body. This is a problem, + // because a body can only be read once. This is why we preemptively + // re-populated the request body with the existing data. + $request->setBody($requestBody); + + $message = $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); + + switch ($documentType) { + + // Dealing with the 'share' document, which modified invitees on a + // calendar. + case '{' . self::NS_OWNCLOUD . '}share' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof IShareable) { + return; + } + + $this->server->transactionType = 'post-oc-addressbook-share'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + /** @var \Sabre\DAVACL\Plugin $acl */ + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $node->updateShares($message->set, $message->remove); + + $response->setStatus(200); + // Adding this because sending a response body may cause issues, + // and I wanted some type of indicator the response was handled. + $response->setHeader('X-Sabre-Status', 'everything-went-well'); + + // Breaking the event chain + return false; + } + } + + private function protectAgainstCSRF() { + $user = $this->auth->getCurrentUser(); + if ($this->auth->isDavAuthenticated($user)) { + return true; + } + + if ($this->request->passesCSRFCheck()) { + return true; + } + + throw new BadRequest(); + } + + +} diff --git a/apps/dav/lib/dav/sharing/xml/sharerequest.php b/apps/dav/lib/dav/sharing/xml/sharerequest.php new file mode 100644 index 00000000000..776fb446b6c --- /dev/null +++ b/apps/dav/lib/dav/sharing/xml/sharerequest.php @@ -0,0 +1,84 @@ + + * + * @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 + * + */ +namespace OCA\DAV\DAV\Sharing\Xml; + +use OCA\DAV\DAV\Sharing\Plugin; +use Sabre\Xml\Reader; +use Sabre\Xml\XmlDeserializable; + +class ShareRequest implements XmlDeserializable { + + public $set = []; + + public $remove = []; + + /** + * Constructor + * + * @param array $set + * @param array $remove + */ + function __construct(array $set, array $remove) { + + $this->set = $set; + $this->remove = $remove; + + } + + static function xmlDeserialize(Reader $reader) { + + $elements = $reader->parseInnerTree([ + '{' . Plugin::NS_OWNCLOUD. '}set' => 'Sabre\\Xml\\Element\\KeyValue', + '{' . Plugin::NS_OWNCLOUD . '}remove' => 'Sabre\\Xml\\Element\\KeyValue', + ]); + + $set = []; + $remove = []; + + foreach ($elements as $elem) { + switch ($elem['name']) { + + case '{' . Plugin::NS_OWNCLOUD . '}set' : + $sharee = $elem['value']; + + $sumElem = '{' . Plugin::NS_OWNCLOUD . '}summary'; + $commonName = '{' . Plugin::NS_OWNCLOUD . '}common-name'; + + $set[] = [ + 'href' => $sharee['{DAV:}href'], + 'commonName' => isset($sharee[$commonName]) ? $sharee[$commonName] : null, + 'summary' => isset($sharee[$sumElem]) ? $sharee[$sumElem] : null, + 'readOnly' => !array_key_exists('{' . Plugin::NS_OWNCLOUD . '}read-write', $sharee), + ]; + break; + + case '{' . Plugin::NS_OWNCLOUD . '}remove' : + $remove[] = $elem['value']['{DAV:}href']; + break; + + } + } + + return new self($set, $remove); + + } + +} diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index 7519a631d5a..5af6905208f 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -79,7 +79,6 @@ class Server { $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); $this->server->addPlugin(new \Sabre\CalDAV\Schedule\Plugin()); $this->server->addPlugin(new IMipPlugin($mailer, $logger)); - $this->server->addPlugin(new \Sabre\CalDAV\SharingPlugin()); $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); $this->server->addPlugin(new CardDAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); diff --git a/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml b/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml new file mode 100644 index 00000000000..fa20a6e4862 --- /dev/null +++ b/apps/dav/tests/travis/caldavtest/tests/CalDAV/sharing-calendars.xml @@ -0,0 +1,573 @@ + + + + + + + + Test calendar sharing calendars + + + caldav + shared-calendars + + + + + DELETEALL + $notificationpath1:/ + + + DELETEALL + $notificationpath2:/ + + + MKCALENDAR + $calendarhome1:/shared/ + + statusCode + + + + PROPPATCH + $calendarhome1:/shared/ + + text/xml; charset=utf-8 + Resource/Common/PROPPATCH/calendar-transp-opaque.xml + + + + + + + POST invitation + + POST + $calendarhome1:/shared/ + + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/1.xml + + + statusCode + + + + + Check Sharee notification collection + + WAITCOUNT 1 + $notificationpath2:/ + + + GETNEW + $notificationpath2:/ + + xmlDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-write/2.xml + + + filter + {http://calendarserver.org/ns/}dtstamp + {http://calendarserver.org/ns/}uid + + + + {http://calendarserver.org/ns/}invite-notification/{http://calendarserver.org/ns/}uid + $inviteuid: + + + + + Sharee replies ACCEPTED + + POST + $calendarhome2:/ + + application/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/3.xml + + + statusCode + + + {DAV:}href + $sharedcalendar: + + + + + Shared calendar exists + + PROPFIND + $sharedcalendar:/ +
+ Depth + 0 +
+ + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/4.xml + + + xmlElementMatch + + exists + $verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:] + $verify-property-prefix:/{DAV:}resourcetype/{DAV:}collection + $verify-property-prefix:/{DAV:}resourcetype/{urn:ietf:params:xml:ns:caldav}calendar + $verify-property-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind + $verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent + + + notexists + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}admin + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}all + + +
+
+ + Shared calendar exists Depth:1 + + PROPFIND + $calendarhome2:/ +
+ Depth + 1 +
+ + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/4.xml + + + xmlElementMatch + + parent + $multistatus-response-prefix:[^{DAV:}href=$sharedcalendar:/] + + + exists + $verify-response-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:] + $verify-response-prefix:/{DAV:}resourcetype/{DAV:}collection + $verify-response-prefix:/{DAV:}resourcetype/{urn:ietf:params:xml:ns:caldav}calendar + $verify-response-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared + $verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read + $verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write + $verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind + $verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind + $verify-response-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}transparent + + + notexists + $verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}admin + $verify-response-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}all + + +
+
+ + Shared calendar has invite property + + PROPFIND + $sharedcalendar:/ +
+ Depth + 0 +
+ + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/5.xml + + + propfindItems + + okprops + {http://calendarserver.org/ns/}invite + + + + xmlElementMatch + + exists + $verify-property-prefix:/{http://calendarserver.org/ns/}invite + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}organizer + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}organizer/{DAV:}href[=$principaluri1:] + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}organizer/{http://calendarserver.org/ns/}common-name[=$username1:] + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user/{DAV:}href[=$cuaddrurn2:] + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user/{http://calendarserver.org/ns/}access/{http://calendarserver.org/ns/}read-write + $verify-property-prefix:/{http://calendarserver.org/ns/}invite/{http://calendarserver.org/ns/}user/{http://calendarserver.org/ns/}invite-accepted + + +
+
+ + Original calendar unchanged + + PROPFIND + $calendarhome1:/shared/ +
+ Depth + 0 +
+ + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/4.xml + + + xmlElementMatch + + exists + $verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:] + $verify-property-prefix:/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp/{urn:ietf:params:xml:ns:caldav}opaque + + +
+
+ + Sharee creates event + + PUT + $sharedcalendar:/1.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/5.ics + + + statusCode + + + + + Sharer sees event + + GET + $calendarhome1:/shared/1.ics + + calendarDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-write/5.ics + + + + + + Sharer changes event + + PUT + $calendarhome1:/shared/1.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/6.ics + + + statusCode + + + + + Sharee sees changed event + + GET + $sharedcalendar:/1.ics + + calendarDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-write/6.ics + + + + + + Sharer creates event + + PUT + $calendarhome1:/shared/2.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/7.ics + + + statusCode + + + + + Sharee sees new event + + GET + $sharedcalendar:/2.ics + + calendarDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-write/7.ics + + + + + + Sharee changes event + + PUT + $sharedcalendar:/2.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-write/8.ics + + + statusCode + + + + + Sharer sees changed event + + GET + $calendarhome1:/shared/2.ics + + calendarDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-write/8.ics + + + + +
+ + + + Set property on Inbox + + PROPPATCH + $inboxpath2:/ + + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/defaultcalendar/1.xml + + + propfindItems + + badprops + {urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL + + + + + + Verify property on inbox + + PROPFIND + $inboxpath2:/ +
+ Depth + 0 +
+ + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/defaultcalendar/2.xml + + + propfindItems + + okprops + $calendarpath2:]]> + + +
+
+
+ + + + POST invitation + + POST + $calendarhome1:/shared/ + + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-only/1.xml + + + statusCode + + + + + Check Sharee notification collection + + WAITCOUNT 1 + $notificationpath2:/ + + + GETNEW + $notificationpath2:/ + + xmlDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-only/2.xml + + + filter + {http://calendarserver.org/ns/}dtstamp + {http://calendarserver.org/ns/}uid + + + + {http://calendarserver.org/ns/}invite-notification/{http://calendarserver.org/ns/}uid + $inviteuid: + + + + + Sharee replies ACCEPTED + + POST + $calendarhome2:/ + + application/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-only/3.xml + + + statusCode + + + {DAV:}href + $sharedcalendar: + + + + + Shared calendar exists + + PROPFIND + $sharedcalendar:/ +
+ Depth + 0 +
+ + text/xml; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-only/4.xml + + + xmlElementMatch + + exists + $verify-property-prefix:/{DAV:}owner/{DAV:}href[=$principaluri1:] + $verify-property-prefix:/{DAV:}resourcetype/{http://calendarserver.org/ns/}shared + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}read + + + notexists + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}write + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}bind + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}unbind + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}admin + $verify-property-prefix:/{DAV:}current-user-privilege-set/{DAV:}privilege/{DAV:}all + + +
+
+ + Create event + + PUT + $sharedcalendar:/3.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-only/5.ics + + + statusCode + + status + 403 + + + + + + Sharer creates event + + PUT + $calendarhome1:/shared/4.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-only/6.ics + + + statusCode + + + + + Sharee sees new event + + GET + $sharedcalendar:/4.ics + + calendarDataMatch + + filepath + Resource/CalDAV/sharing/calendars/read-only/6.ics + + + + + + Sharee cannot change event + + PUT + $sharedcalendar:/4.ics + + text/calendar; charset=utf-8 + Resource/CalDAV/sharing/calendars/read-only/7.ics + + + statusCode + + status + 403 + + + + +
+ + + + DELETEALL + $notificationpath1:/ + $notificationpath2:/ + $notificationpath3:/ + $notificationpath4:/ + + + +
diff --git a/apps/dav/tests/unit/carddav/sharing/plugintest.php b/apps/dav/tests/unit/carddav/sharing/plugintest.php index 19ee075fb4f..f7159c2d22d 100644 --- a/apps/dav/tests/unit/carddav/sharing/plugintest.php +++ b/apps/dav/tests/unit/carddav/sharing/plugintest.php @@ -22,8 +22,8 @@ namespace OCA\DAV\Tests\Unit\CardDAV; -use OCA\DAV\CardDAV\Sharing\IShareableAddressBook; -use OCA\DAV\CardDAV\Sharing\Plugin; +use OCA\DAV\DAV\Sharing\IShareable; +use OCA\DAV\DAV\Sharing\Plugin; use OCA\DAV\Connector\Sabre\Auth; use OCP\IRequest; use Sabre\DAV\Server; @@ -38,7 +38,7 @@ class PluginTest extends TestCase { private $plugin; /** @var Server */ private $server; - /** @var IShareableAddressBook | \PHPUnit_Framework_MockObject_MockObject */ + /** @var IShareable | \PHPUnit_Framework_MockObject_MockObject */ private $book; public function setUp() { @@ -55,7 +55,7 @@ class PluginTest extends TestCase { $root = new SimpleCollection('root'); $this->server = new \Sabre\DAV\Server($root); /** @var SimpleCollection $node */ - $this->book = $this->getMockBuilder('OCA\DAV\CardDAV\Sharing\IShareableAddressBook')->disableOriginalConstructor()->getMock(); + $this->book = $this->getMockBuilder('OCA\DAV\DAV\Sharing\IShareable')->disableOriginalConstructor()->getMock(); $this->book->method('getName')->willReturn('addressbook1.vcf'); $root->addChild($this->book); $this->plugin->initialize($this->server); diff --git a/apps/dav/tests/unit/dav/sharing/plugintest.php b/apps/dav/tests/unit/dav/sharing/plugintest.php new file mode 100644 index 00000000000..ce6c96f1bfc --- /dev/null +++ b/apps/dav/tests/unit/dav/sharing/plugintest.php @@ -0,0 +1,83 @@ + + * + * @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 + * + */ + +namespace OCA\DAV\Tests\Unit\DAV; + + +use OCA\DAV\DAV\Sharing\IShareable; +use OCA\DAV\DAV\Sharing\Plugin; +use OCA\DAV\Connector\Sabre\Auth; +use OCP\IRequest; +use Sabre\DAV\Server; +use Sabre\DAV\SimpleCollection; +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; +use Test\TestCase; + +class PluginTest extends TestCase { + + /** @var Plugin */ + private $plugin; + /** @var Server */ + private $server; + /** @var IShareable | \PHPUnit_Framework_MockObject_MockObject */ + private $book; + + public function setUp() { + parent::setUp(); + + /** @var Auth | \PHPUnit_Framework_MockObject_MockObject $authBackend */ + $authBackend = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Auth')->disableOriginalConstructor()->getMock(); + $authBackend->method('isDavAuthenticated')->willReturn(true); + + /** @var IRequest $request */ + $request = $this->getMockBuilder('OCP\IRequest')->disableOriginalConstructor()->getMock(); + $this->plugin = new Plugin($authBackend, $request); + + $root = new SimpleCollection('root'); + $this->server = new \Sabre\DAV\Server($root); + /** @var SimpleCollection $node */ + $this->book = $this->getMockBuilder('OCA\DAV\DAV\Sharing\IShareable')-> + disableOriginalConstructor()-> + getMock(); + $this->book->method('getName')->willReturn('addressbook1.vcf'); + $root->addChild($this->book); + $this->plugin->initialize($this->server); + } + + public function testSharing() { + + $this->book->expects($this->once())->method('updateShares')->with([[ + 'href' => 'principal:principals/admin', + 'commonName' => null, + 'summary' => null, + 'readOnly' => false + ]], ['mailto:wilfredo@example.com']); + + // setup request + $request = new Request(); + $request->addHeader('Content-Type', 'application/xml'); + $request->setUrl('addressbook1.vcf'); + $request->setBody('principal:principals/admin mailto:wilfredo@example.com'); + $response = new Response(); + $this->plugin->httpPost($request, $response); + } +} -- cgit v1.2.3 From 92c7d1573934eb0fef2f901d90d5303e19bc34f1 Mon Sep 17 00:00:00 2001 From: Thomas Müller Date: Mon, 25 Jan 2016 20:23:11 +0100 Subject: Fix server ctor including unit test - this shall never happen again --- apps/dav/lib/server.php | 2 +- apps/dav/tests/unit/servertest.php | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 apps/dav/tests/unit/servertest.php (limited to 'apps/dav') diff --git a/apps/dav/lib/server.php b/apps/dav/lib/server.php index 5af6905208f..3bf8e155082 100644 --- a/apps/dav/lib/server.php +++ b/apps/dav/lib/server.php @@ -81,7 +81,7 @@ class Server { $this->server->addPlugin(new IMipPlugin($mailer, $logger)); $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); - $this->server->addPlugin(new CardDAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); + $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); // addressbook plugins $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); diff --git a/apps/dav/tests/unit/servertest.php b/apps/dav/tests/unit/servertest.php new file mode 100644 index 00000000000..63bab482b45 --- /dev/null +++ b/apps/dav/tests/unit/servertest.php @@ -0,0 +1,24 @@ +getMockBuilder('\OCP\IRequest') + ->disableOriginalConstructor()->getMock(); + $s = new Server($r, '/'); + $this->assertNotNull($s->server); + } +} \ No newline at end of file -- cgit v1.2.3 From ba565edc1e5b24da8c7ec7a4fae7164eac8cac50 Mon Sep 17 00:00:00 2001 From: Thomas Müller Date: Mon, 25 Jan 2016 22:49:26 +0100 Subject: Extract sharing functionality into own backend class for reusability --- apps/dav/lib/carddav/addressbook.php | 11 +- apps/dav/lib/carddav/carddavbackend.php | 96 ++---------- apps/dav/lib/dav/sharing/backend.php | 173 +++++++++++++++++++++ apps/dav/lib/dav/sharing/ishareable.php | 10 ++ apps/dav/lib/dav/sharing/plugin.php | 2 +- apps/dav/tests/unit/carddav/carddavbackendtest.php | 7 +- 6 files changed, 203 insertions(+), 96 deletions(-) create mode 100644 apps/dav/lib/dav/sharing/backend.php (limited to 'apps/dav') diff --git a/apps/dav/lib/carddav/addressbook.php b/apps/dav/lib/carddav/addressbook.php index 6c3caecddf6..513eae4d723 100644 --- a/apps/dav/lib/carddav/addressbook.php +++ b/apps/dav/lib/carddav/addressbook.php @@ -68,7 +68,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { function getShares() { /** @var CardDavBackend $carddavBackend */ $carddavBackend = $this->carddavBackend; - return $carddavBackend->getShares($this->getBookId()); + return $carddavBackend->getShares($this->getResourceId()); } function getACL() { @@ -100,7 +100,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { /** @var CardDavBackend $carddavBackend */ $carddavBackend = $this->carddavBackend; - return $carddavBackend->applyShareAcl($this->getBookId(), $acl); + return $carddavBackend->applyShareAcl($this->getResourceId(), $acl); } function getChildACL() { @@ -115,11 +115,11 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { /** @var CardDavBackend $carddavBackend */ $carddavBackend = $this->carddavBackend; - return $carddavBackend->applyShareAcl($this->getBookId(), $acl); + return $carddavBackend->applyShareAcl($this->getResourceId(), $acl); } function getChild($name) { - $obj = $this->carddavBackend->getCard($this->getBookId(), $name); + $obj = $this->carddavBackend->getCard($this->getResourceId(), $name); if (!$obj) { throw new NotFound('Card not found'); } @@ -129,8 +129,7 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable { /** * @return int */ - public function getBookId() { + public function getResourceId() { return $this->addressBookInfo['id']; } - } diff --git a/apps/dav/lib/carddav/carddavbackend.php b/apps/dav/lib/carddav/carddavbackend.php index 81a3ba7c519..3dc5c00e10b 100644 --- a/apps/dav/lib/carddav/carddavbackend.php +++ b/apps/dav/lib/carddav/carddavbackend.php @@ -24,9 +24,10 @@ namespace OCA\DAV\CardDAV; -use Doctrine\DBAL\Connection; use OCA\DAV\Connector\Sabre\Principal; use OCP\DB\QueryBuilder\IQueryBuilder; +use OCA\DAV\DAV\Sharing\Backend; +use OCA\DAV\DAV\Sharing\IShareable; use OCP\IDBConnection; use Sabre\CardDAV\Backend\BackendInterface; use Sabre\CardDAV\Backend\SyncSupport; @@ -50,6 +51,9 @@ class CardDavBackend implements BackendInterface, SyncSupport { /** @var IDBConnection */ private $db; + /** @var Backend */ + private $sharingBackend; + /** @var array properties to index */ public static $indexProperties = array( 'BDAY', 'UID', 'N', 'FN', 'TITLE', 'ROLE', 'NOTE', 'NICKNAME', @@ -68,6 +72,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { public function __construct(IDBConnection $db, Principal $principalBackend) { $this->db = $db; $this->principalBackend = $principalBackend; + $this->sharingBackend = new Backend($this->db, 'addressbook'); } /** @@ -715,17 +720,12 @@ class CardDavBackend implements BackendInterface, SyncSupport { } /** - * @param AddressBook $book + * @param IShareable $shareable * @param string[] $add * @param string[] $remove */ - public function updateShares($book, $add, $remove) { - foreach($add as $element) { - $this->shareWith($book, $element); - } - foreach($remove as $element) { - $this->unshare($book->getBookId(), $element); - } + public function updateShares(IShareable $shareable, $add, $remove) { + $this->sharingBackend->updateShares($shareable, $add, $remove); } /** @@ -808,63 +808,6 @@ class CardDavBackend implements BackendInterface, SyncSupport { return $result; } - - /** - * @param AddressBook $addressBook - * @param string $element - */ - private function shareWith($addressBook, $element) { - $user = $element['href']; - $parts = explode(':', $user, 2); - if ($parts[0] !== 'principal') { - return; - } - $p = $this->principalBackend->getPrincipalByPath($parts[1]); - if (is_null($p)) { - return; - } - - // remove the share if it already exists - $this->unshare($addressBook->getBookId(), $element['href']); - $access = self::ACCESS_READ; - if (isset($element['readOnly'])) { - $access = $element['readOnly'] ? self::ACCESS_READ : self::ACCESS_READ_WRITE; - } - - $query = $this->db->getQueryBuilder(); - $query->insert('dav_shares') - ->values([ - 'principaluri' => $query->createNamedParameter($parts[1]), - 'type' => $query->createNamedParameter('addressbook'), - 'access' => $query->createNamedParameter($access), - 'resourceid' => $query->createNamedParameter($addressBook->getBookId()) - ]); - $query->execute(); - } - - /** - * @param int $addressBookId - * @param string $element - */ - private function unshare($addressBookId, $element) { - $parts = explode(':', $element, 2); - if ($parts[0] !== 'principal') { - return; - } - $p = $this->principalBackend->getPrincipalByPath($parts[1]); - if (is_null($p)) { - return; - } - - $query = $this->db->getQueryBuilder(); - $query->delete('dav_shares') - ->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId))) - ->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook'))) - ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1]))) - ; - $query->execute(); - } - /** * Returns the list of people whom this address book is shared with. * @@ -878,26 +821,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { * @return array */ public function getShares($addressBookId) { - $query = $this->db->getQueryBuilder(); - $result = $query->select(['principaluri', 'access']) - ->from('dav_shares') - ->where($query->expr()->eq('resourceid', $query->createNamedParameter($addressBookId))) - ->andWhere($query->expr()->eq('type', $query->createNamedParameter('addressbook'))) - ->execute(); - - $shares = []; - while($row = $result->fetch()) { - $p = $this->principalBackend->getPrincipalByPath($row['principaluri']); - $shares[]= [ - 'href' => "principal:${p['uri']}", - 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '', - 'status' => 1, - 'readOnly' => ($row['access'] === self::ACCESS_READ), - '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}principal' => $p['uri'] - ]; - } - - return $shares; + return $this->sharingBackend->getShares($addressBookId); } /** diff --git a/apps/dav/lib/dav/sharing/backend.php b/apps/dav/lib/dav/sharing/backend.php new file mode 100644 index 00000000000..fee864ffe6f --- /dev/null +++ b/apps/dav/lib/dav/sharing/backend.php @@ -0,0 +1,173 @@ + + * @author Björn Schießle + * @author Scrutinizer Auto-Fixer + * @author Thomas Müller + * + * @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 + * + */ + +namespace OCA\DAV\DAV\Sharing; + +use OCP\IDBConnection; + +class Backend { + + /** @var IDBConnection */ + private $db; + + const ACCESS_OWNER = 1; + const ACCESS_READ_WRITE = 2; + const ACCESS_READ = 3; + + /** @var string */ + private $resourceType; + + /** + * CardDavBackend constructor. + * + * @param IDBConnection $db + */ + public function __construct(IDBConnection $db, $resourceType) { + $this->db = $db; + $this->resourceType = $resourceType; + } + + /** + * @param IShareable $shareable + * @param string[] $add + * @param string[] $remove + */ + public function updateShares($shareable, $add, $remove) { + foreach($add as $element) { + $this->shareWith($shareable, $element); + } + foreach($remove as $element) { + $this->unshare($shareable->getResourceId(), $element); + } + } + + /** + * @param IShareable $shareable + * @param string $element + */ + private function shareWith($shareable, $element) { + $user = $element['href']; + $parts = explode(':', $user, 2); + if ($parts[0] !== 'principal') { + return; + } + + // remove the share if it already exists + $this->unshare($shareable->getResourceId(), $element['href']); + $access = self::ACCESS_READ; + if (isset($element['readOnly'])) { + $access = $element['readOnly'] ? self::ACCESS_READ : self::ACCESS_READ_WRITE; + } + + $query = $this->db->getQueryBuilder(); + $query->insert('dav_shares') + ->values([ + 'principaluri' => $query->createNamedParameter($parts[1]), + 'type' => $query->createNamedParameter($this->resourceType), + 'access' => $query->createNamedParameter($access), + 'resourceid' => $query->createNamedParameter($shareable->getResourceId()) + ]); + $query->execute(); + } + + /** + * @param int $resourceId + * @param string $element + */ + private function unshare($resourceId, $element) { + $parts = explode(':', $element, 2); + if ($parts[0] !== 'principal') { + return; + } + + $query = $this->db->getQueryBuilder(); + $query->delete('dav_shares') + ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId))) + ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) + ->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($parts[1]))) + ; + $query->execute(); + } + + /** + * Returns the list of people whom this resource is shared with. + * + * Every element in this array should have the following properties: + * * href - Often a mailto: address + * * commonName - Optional, for example a first + last name + * * status - See the Sabre\CalDAV\SharingPlugin::STATUS_ constants. + * * readOnly - boolean + * * summary - Optional, a description for the share + * + * @return array + */ + public function getShares($resourceId) { + $query = $this->db->getQueryBuilder(); + $result = $query->select(['principaluri', 'access']) + ->from('dav_shares') + ->where($query->expr()->eq('resourceid', $query->createNamedParameter($resourceId))) + ->andWhere($query->expr()->eq('type', $query->createNamedParameter($this->resourceType))) + ->execute(); + + $shares = []; + while($row = $result->fetch()) { + $shares[]= [ + 'href' => "principal:${row['principaluri']}", +// 'commonName' => isset($p['{DAV:}displayname']) ? $p['{DAV:}displayname'] : '', + 'status' => 1, + 'readOnly' => ($row['access'] === self::ACCESS_READ), + '{'.\OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD.'}principal' => $row['principaluri'] + ]; + } + + return $shares; + } + + /** + * For shared resources the sharee is set in the ACL of the resource + * + * @param int $resourceId + * @param array $acl + * @return array + */ + public function applyShareAcl($resourceId, $acl) { + + $shares = $this->getShares($resourceId); + foreach ($shares as $share) { + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], + 'protected' => true, + ]; + if (!$share['readOnly']) { + $acl[] = [ + 'privilege' => '{DAV:}write', + 'principal' => $share['{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}principal'], + 'protected' => true, + ]; + } + } + return $acl; + } +} diff --git a/apps/dav/lib/dav/sharing/ishareable.php b/apps/dav/lib/dav/sharing/ishareable.php index e82c03a6af8..f6b6bfa8862 100644 --- a/apps/dav/lib/dav/sharing/ishareable.php +++ b/apps/dav/lib/dav/sharing/ishareable.php @@ -61,4 +61,14 @@ interface IShareable extends INode { */ function getShares(); + /** + * @return int + */ + public function getResourceId(); + + /** + * @return string + */ + public function getOwner(); + } \ No newline at end of file diff --git a/apps/dav/lib/dav/sharing/plugin.php b/apps/dav/lib/dav/sharing/plugin.php index d3fdd8aa969..cbb4408e29b 100644 --- a/apps/dav/lib/dav/sharing/plugin.php +++ b/apps/dav/lib/dav/sharing/plugin.php @@ -151,7 +151,7 @@ class Plugin extends ServerPlugin { return; } - $this->server->transactionType = 'post-oc-addressbook-share'; + $this->server->transactionType = 'post-oc-resource-share'; // Getting ACL info $acl = $this->server->getPlugin('acl'); diff --git a/apps/dav/tests/unit/carddav/carddavbackendtest.php b/apps/dav/tests/unit/carddav/carddavbackendtest.php index 1a0d32c186c..0158330a194 100644 --- a/apps/dav/tests/unit/carddav/carddavbackendtest.php +++ b/apps/dav/tests/unit/carddav/carddavbackendtest.php @@ -292,13 +292,13 @@ class CardDavBackendTest extends TestCase { $exampleBook = new AddressBook($this->backend, $books[0]); $this->backend->updateShares($exampleBook, [['href' => 'principal:principals/best-friend']], []); - $shares = $this->backend->getShares($exampleBook->getBookId()); + $shares = $this->backend->getShares($exampleBook->getResourceId()); $this->assertEquals(1, count($shares)); // adding the same sharee again has no effect $this->backend->updateShares($exampleBook, [['href' => 'principal:principals/best-friend']], []); - $shares = $this->backend->getShares($exampleBook->getBookId()); + $shares = $this->backend->getShares($exampleBook->getResourceId()); $this->assertEquals(1, count($shares)); $books = $this->backend->getAddressBooksForUser('principals/best-friend'); @@ -306,7 +306,7 @@ class CardDavBackendTest extends TestCase { $this->backend->updateShares($exampleBook, [], ['principal:principals/best-friend']); - $shares = $this->backend->getShares($exampleBook->getBookId()); + $shares = $this->backend->getShares($exampleBook->getResourceId()); $this->assertEquals(0, count($shares)); $books = $this->backend->getAddressBooksForUser('principals/best-friend'); @@ -432,6 +432,7 @@ class CardDavBackendTest extends TestCase { * @dataProvider dataTestSearch * * @param string $pattern + * @param array $properties * @param array $expected */ public function testSearch($pattern, $properties, $expected) { -- cgit v1.2.3