diff --git a/apps/dav/appinfo/database.xml b/apps/dav/appinfo/database.xml index 9578526a7051..eb8f70e7c951 100644 --- a/apps/dav/appinfo/database.xml +++ b/apps/dav/appinfo/database.xml @@ -703,6 +703,11 @@ CREATE TABLE calendarobjects ( true true + + publicuri + text + 255 + dav_shares_index true @@ -715,6 +720,9 @@ CREATE TABLE calendarobjects ( type + + publicuri + diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index f3f67edec456..fa93248ded56 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ ownCloud WebDAV endpoint AGPL owncloud.org - 0.2.5 + 0.2.6 diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php index ed27ef863da3..0f38d88f37fd 100644 --- a/apps/dav/appinfo/v1/caldav.php +++ b/apps/dav/appinfo/v1/caldav.php @@ -44,7 +44,8 @@ 'principals/' ); $db = \OC::$server->getDatabaseConnection(); -$calDavBackend = new CalDavBackend($db, $principalBackend); +$config = \OC::$server->getConfig(); +$calDavBackend = new CalDavBackend($db, $principalBackend, $config); $debugging = \OC::$server->getConfig()->getSystemValue('debug', false); diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index e72372bcb674..5fc03f1b6314 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -88,11 +88,12 @@ public function __construct (array $urlParams=array()) { $container->registerService('CalDavBackend', function($c) { /** @var IAppContainer $c */ $db = $c->getServer()->getDatabaseConnection(); + $config = $c->getServer()->getConfig(); $principal = new Principal( $c->getServer()->getUserManager(), $c->getServer()->getGroupManager() ); - return new CalDavBackend($db, $principal); + return new CalDavBackend($db, $principal, $config); }); $container->registerService('BirthdayService', function($c) { diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index e3ab11166483..9f322a487006 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -28,6 +28,7 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\Sharing\Backend; +use OCP\IConfig; use OCP\IDBConnection; use Sabre\CalDAV\Backend\AbstractBackend; use Sabre\CalDAV\Backend\SchedulingSupport; @@ -38,6 +39,7 @@ use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; use Sabre\HTTP\URLUtil; use Sabre\VObject\DateTimeParser; @@ -63,6 +65,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ const MAX_DATE = '2038-01-01'; + const ACCESS_PUBLIC = 4; const CLASSIFICATION_PUBLIC = 0; const CLASSIFICATION_PRIVATE = 1; const CLASSIFICATION_CONFIDENTIAL = 2; @@ -107,16 +110,21 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription /** @var Principal */ private $principalBackend; + /** @var IConfig */ + private $config; + /** * CalDavBackend constructor. * * @param IDBConnection $db * @param Principal $principalBackend + * @param IConfig $config */ - public function __construct(IDBConnection $db, Principal $principalBackend) { + public function __construct(IDBConnection $db, Principal $principalBackend, IConfig $config) { $this->db = $db; $this->principalBackend = $principalBackend; $this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar'); + $this->config = $config; } /** @@ -246,6 +254,121 @@ function getCalendarsForUser($principalUri) { return array_values($calendars); } + /** + * @return array + */ + public function getPublicCalendars() { + $fields = array_values($this->propertyMap); + $fields[] = 'a.id'; + $fields[] = 'a.uri'; + $fields[] = 'a.synctoken'; + $fields[] = 'a.components'; + $fields[] = 'a.principaluri'; + $fields[] = 'a.transparent'; + $fields[] = 's.access'; + $fields[] = 's.publicuri'; + $calendars = []; + $query = $this->db->getQueryBuilder(); + $result = $query->select($fields) + ->from('dav_shares', 's') + ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) + ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC))) + ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar'))) + ->execute(); + + while($row = $result->fetch()) { + list(, $name) = URLUtil::splitPath($row['principaluri']); + $row['displayname'] = $row['displayname'] . "($name)"; + $components = []; + if ($row['components']) { + $components = explode(',',$row['components']); + } + $calendar = [ + 'id' => $row['id'], + 'uri' => $row['publicuri'], + 'principaluri' => $row['principaluri'], + '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), + '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', + '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), + '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ, + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, + ]; + + foreach($this->propertyMap as $xmlName=>$dbName) { + $calendar[$xmlName] = $row[$dbName]; + } + + if (!isset($calendars[$calendar['id']])) { + $calendars[$calendar['id']] = $calendar; + } + } + $result->closeCursor(); + + return array_values($calendars); + } + + /** + * @param string $uri + * @return array + * @throws NotFound + */ + public function getPublicCalendar($uri) { + $fields = array_values($this->propertyMap); + $fields[] = 'a.id'; + $fields[] = 'a.uri'; + $fields[] = 'a.synctoken'; + $fields[] = 'a.components'; + $fields[] = 'a.principaluri'; + $fields[] = 'a.transparent'; + $fields[] = 's.access'; + $fields[] = 's.publicuri'; + $query = $this->db->getQueryBuilder(); + $result = $query->select($fields) + ->from('dav_shares', 's') + ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) + ->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC))) + ->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar'))) + ->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri))) + ->execute(); + + $row = $result->fetch(\PDO::FETCH_ASSOC); + + $result->closeCursor(); + + if ($row === false) { + throw new NotFound('Node with name \'' . $uri . '\' could not be found'); + } + + list(, $name) = URLUtil::splitPath($row['principaluri']); + $row['displayname'] = $row['displayname'] . ' ' . "($name)"; + $components = []; + if ($row['components']) { + $components = explode(',',$row['components']); + } + $uri = md5($this->config->getSystemValue('secret', '') . $row['id']); + $calendar = [ + 'id' => $row['id'], + 'uri' => $uri, + 'principaluri' => $row['principaluri'], + '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'), + '{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0', + '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), + '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'), + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $row['principaluri'], + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ, + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC, + ]; + + foreach($this->propertyMap as $xmlName=>$dbName) { + $calendar[$xmlName] = $row[$dbName]; + } + + return $calendar; + + } + /** * @param string $principal * @param string $uri @@ -1415,6 +1538,46 @@ public function getShares($resourceId) { return $this->sharingBackend->getShares($resourceId); } + /** + * @param boolean $value + * @param \OCA\DAV\CalDAV\Calendar $calendar + */ + public function setPublishStatus($value, $calendar) { + $query = $this->db->getQueryBuilder(); + if ($value) { + $query->insert('dav_shares') + ->values([ + 'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()), + 'type' => $query->createNamedParameter('calendar'), + 'access' => $query->createNamedParameter(self::ACCESS_PUBLIC), + 'resourceid' => $query->createNamedParameter($calendar->getResourceId()), + 'publicuri' => $query->createNamedParameter(md5($this->config->getSystemValue('secret', '') . $calendar->getResourceId())) + ]); + } else { + $query->delete('dav_shares') + ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) + ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))); + } + $query->execute(); + } + + /** + * @param \OCA\DAV\CalDAV\Calendar $calendar + * @return boolean + */ + public function getPublishStatus($calendar) { + $query = $this->db->getQueryBuilder(); + $result = $query->select($query->createFunction('COUNT(*)')) + ->from('dav_shares') + ->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId()))) + ->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC))) + ->execute(); + + $row = $result->fetch(); + $result->closeCursor(); + return reset($row) > 0; + } + /** * @param int $resourceId * @param array $acl diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index 3162a4ccf86b..8c67db222ce5 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -88,6 +88,13 @@ public function getResourceId() { return $this->calendarInfo['id']; } + /** + * @return string + */ + public function getPrincipalURI() { + return $this->calendarInfo['principaluri']; + } + function getACL() { $acl = [ [ @@ -116,6 +123,13 @@ function getACL() { ]; } } + if ($this->isPublic()) { + $acl[] = [ + 'privilege' => '{DAV:}read', + 'principal' => 'principals/system/public', + 'protected' => true, + ]; + } /** @var CalDavBackend $calDavBackend */ $calDavBackend = $this->caldavBackend; @@ -235,6 +249,20 @@ function calendarQuery(array $filters) { return $uris; } + /** + * @param boolean $value + */ + function setPublishStatus($value) { + $this->caldavBackend->setPublishStatus($value, $this); + } + + /** + * @return boolean $value + */ + function getPublishStatus() { + return $this->caldavBackend->getPublishStatus($this); + } + private function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; @@ -242,8 +270,16 @@ private function canWrite() { return true; } + private function isPublic() { + return isset($this->calendarInfo['{http://owncloud.org/ns}public']); + } + private function isShared() { return isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']); } + public function isSubscription() { + return isset($this->calendarInfo['{http://calendarserver.org/ns/}source']); + } + } diff --git a/apps/dav/lib/CalDAV/PublicCalendarRoot.php b/apps/dav/lib/CalDAV/PublicCalendarRoot.php new file mode 100644 index 000000000000..6d74b97f96ea --- /dev/null +++ b/apps/dav/lib/CalDAV/PublicCalendarRoot.php @@ -0,0 +1,67 @@ + + * + * @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\CalDAV; + +use Sabre\DAV\Collection; +use Sabre\DAV\Exception\NotFound; + +class PublicCalendarRoot extends Collection { + + /** @var CalDavBackend */ + protected $caldavBackend; + + /** @var \OCP\IL10N */ + protected $l10n; + + function __construct(CalDavBackend $caldavBackend) { + $this->caldavBackend = $caldavBackend; + $this->l10n = \OC::$server->getL10N('dav'); + } + + /** + * @inheritdoc + */ + function getName() { + return 'public-calendars'; + } + + /** + * @inheritdoc + */ + function getChild($name) { + $calendar = $this->caldavBackend->getPublicCalendar($name); + return new Calendar($this->caldavBackend, $calendar, $this->l10n); + } + + /** + * @inheritdoc + */ + function getChildren() { + $calendars = $this->caldavBackend->getPublicCalendars(); + $children = []; + foreach ($calendars as $calendar) { + // TODO: maybe implement a new class PublicCalendar ??? + $children[] = new Calendar($this->caldavBackend, $calendar, $this->l10n); + } + + return $children; + } +} diff --git a/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php new file mode 100644 index 000000000000..7434da6b62ed --- /dev/null +++ b/apps/dav/lib/CalDAV/Publishing/PublishPlugin.php @@ -0,0 +1,215 @@ +config = $config; + $this->urlGenerator = $urlGenerator; + } + + /** + * 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[] + */ + public function getFeatures() { + // May have to be changed to be detected + return ['oc-calendar-publishing', 'calendarserver-sharing']; + } + + /** + * Returns a plugin name. + * + * Using this name other plugins will be able to access other plugins + * using Sabre\DAV\Server::getPlugin + * + * @return string + */ + public function getPluginName() { + return 'oc-calendar-publishing'; + } + + /** + * 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 + */ + public function initialize(Server $server) { + $this->server = $server; + + $this->server->on('method:POST', [$this, 'httpPost']); + $this->server->on('propFind', [$this, 'propFind']); + } + + public function propFind(PropFind $propFind, INode $node) { + if ($node instanceof Calendar) { + $token = md5($this->config->getSystemValue('secret', '').$node->getResourceId()); + + $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token; + + $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node, $publishUrl) { + if ($node->getPublishStatus()) { + // We return the publish-url only if the calendar is published. + return new Publisher($publishUrl, true); + } + }); + + $propFind->handle('{'.self::NS_CALENDARSERVER.'}pre-publish-url', function () use ($node, $publishUrl) { + // The pre-publish-url is always returned + return new Publisher($publishUrl, false); + }); + + $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function() use ($node) { + return new AllowedSharingModes(!$node->isSubscription(), !$node->isSubscription()); + }); + } + } + + /** + * We intercept this to handle POST requests on calendars. + * + * @param RequestInterface $request + * @param ResponseInterface $response + * + * @return null|bool + */ + public 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; + } + + $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); + + $this->server->xml->parse($requestBody, $request->getUrl(), $documentType); + + switch ($documentType) { + + case '{'.self::NS_CALENDARSERVER.'}publish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof Calendar) { + return; + } + $this->server->transactionType = 'post-publish-calendar'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $node->setPublishStatus(true); + + // iCloud sends back the 202, so we will too. + $response->setStatus(202); + + // 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; + + case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' : + + // We can only deal with IShareableCalendar objects + if (!$node instanceof Calendar) { + return; + } + $this->server->transactionType = 'post-unpublish-calendar'; + + // Getting ACL info + $acl = $this->server->getPlugin('acl'); + + // If there's no ACL support, we allow everything + if ($acl) { + $acl->checkPrivileges($path, '{DAV:}write'); + } + + $node->setPublishStatus(false); + + $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; + + } + } +} diff --git a/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php b/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php new file mode 100644 index 000000000000..375954ffaf3b --- /dev/null +++ b/apps/dav/lib/CalDAV/Publishing/Xml/Publisher.php @@ -0,0 +1,65 @@ +publishUrl = $publishUrl; + $this->isPublished = $isPublished; + } + + /** + * @return string + */ + function getValue() { + return $this->publishUrl; + } + + /** + * The xmlSerialize metod is called during xml writing. + * + * Use the $writer argument to write its own xml serialization. + * + * An important note: do _not_ create a parent element. Any element + * implementing XmlSerializble should only ever write what's considered + * its 'inner xml'. + * + * The parent of the current element is responsible for writing a + * containing element. + * + * This allows serializers to be re-used for different element names. + * + * If you are opening new elements, you must also close them again. + * + * @param Writer $writer + * @return void + */ + function xmlSerialize(Writer $writer) { + if (!$this->isPublished) { + // for pre-publish-url + $writer->write($this->publishUrl); + } else { + // for publish-url + $writer->writeElement('{DAV:}href', $this->publishUrl); + } + } +} diff --git a/apps/dav/lib/Command/CreateCalendar.php b/apps/dav/lib/Command/CreateCalendar.php index 207843883286..7d68cf8730c5 100644 --- a/apps/dav/lib/Command/CreateCalendar.php +++ b/apps/dav/lib/Command/CreateCalendar.php @@ -43,6 +43,7 @@ class CreateCalendar extends Command { /** * @param IUserManager $userManager + * @param IGroupManager $groupManager * @param IDBConnection $dbConnection */ function __construct(IUserManager $userManager, IGroupManager $groupManager, IDBConnection $dbConnection) { @@ -73,9 +74,10 @@ protected function execute(InputInterface $input, OutputInterface $output) { $this->userManager, $this->groupManager ); + $config = \OC::$server->getConfig(); $name = $input->getArgument('name'); - $caldav = new CalDavBackend($this->dbConnection, $principalBackend); + $caldav = new CalDavBackend($this->dbConnection, $principalBackend, $config); $caldav->createCalendar("principals/users/$user", $name, []); } } diff --git a/apps/dav/lib/DAV/PublicAuth.php b/apps/dav/lib/DAV/PublicAuth.php new file mode 100644 index 000000000000..3f5d37f1a69a --- /dev/null +++ b/apps/dav/lib/DAV/PublicAuth.php @@ -0,0 +1,99 @@ + + * + * @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; + +use Sabre\DAV\Auth\Backend\BackendInterface; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; + +class PublicAuth implements BackendInterface { + + /** @var string[] */ + private $publicURLs; + + /** + * @param string[] $publicURLs + */ + public function __construct() { + $this->publicURLs = [ + 'public-calendars', + 'principals/system/public' + ]; + } + + /** + * When this method is called, the backend must check if authentication was + * successful. + * + * The returned value must be one of the following + * + * [true, "principals/username"] + * [false, "reason for failure"] + * + * If authentication was successful, it's expected that the authentication + * backend returns a so-called principal url. + * + * Examples of a principal url: + * + * principals/admin + * principals/user1 + * principals/users/joe + * principals/uid/123457 + * + * If you don't use WebDAV ACL (RFC3744) we recommend that you simply + * return a string such as: + * + * principals/users/[username] + * + * @param RequestInterface $request + * @param ResponseInterface $response + * @return array + */ + function check(RequestInterface $request, ResponseInterface $response) { + + if ($this->isRequestPublic($request)) { + return [true, "principals/system/public"]; + } + return [false, "No public access to this resource."]; + } + + /** + * @inheritdoc + */ + function challenge(RequestInterface $request, ResponseInterface $response) { + } + + /** + * @param RequestInterface $request + * @return bool + */ + private function isRequestPublic(RequestInterface $request) { + $params = $request->getQueryParameters(); + if (isset($params['sabreAction']) && $params['sabreAction'] == 'asset') { + return true; + } + $url = $request->getPath(); + $matchingUrls = array_filter($this->publicURLs, function ($publicUrl) use ($url) { + return strpos($url, $publicUrl, 0) === 0; + }); + return !empty($matchingUrls); + } +} diff --git a/apps/dav/lib/DAV/SystemPrincipalBackend.php b/apps/dav/lib/DAV/SystemPrincipalBackend.php index b8e4a76f6f66..c7b45682419e 100644 --- a/apps/dav/lib/DAV/SystemPrincipalBackend.php +++ b/apps/dav/lib/DAV/SystemPrincipalBackend.php @@ -50,6 +50,10 @@ function getPrincipalsByPrefix($prefixPath) { 'uri' => 'principals/system/system', '{DAV:}displayname' => 'system', ]; + $principals[] = [ + 'uri' => 'principals/system/public', + '{DAV:}displayname' => 'public', + ]; } return $principals; @@ -72,6 +76,13 @@ function getPrincipalByPath($path) { ]; return $principal; } + if ($path === 'principals/system/public') { + $principal = [ + 'uri' => 'principals/system/public', + '{DAV:}displayname' => 'public', + ]; + return $principal; + } return null; } diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index bed75c135414..069ed2183223 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -25,6 +25,7 @@ use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\CalendarRoot; +use OCA\DAV\CalDAV\PublicCalendarRoot; use OCA\DAV\CardDAV\AddressBookRoot; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\Connector\Sabre\Principal; @@ -58,9 +59,11 @@ public function __construct() { $systemPrincipals->disableListing = $disableListing; $filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users'); $filesCollection->disableListing = $disableListing; - $caldavBackend = new CalDavBackend($db, $userPrincipalBackend); + $caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $config); $calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users'); $calendarRoot->disableListing = $disableListing; + $publicCalendarRoot = new PublicCalendarRoot($caldavBackend); + $publicCalendarRoot->disableListing = $disableListing; $systemTagCollection = new SystemTag\SystemTagsByIdCollection( \OC::$server->getSystemTagManager(), @@ -100,6 +103,7 @@ public function __construct() { $systemPrincipals]), $filesCollection, $calendarRoot, + $publicCalendarRoot, new SimpleCollection('addressbooks', [ $usersAddressBookRoot, $systemAddressBookRoot]), diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 9c6775530397..9ede4b400efb 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -33,6 +33,7 @@ use OCA\DAV\Connector\Sabre\DavAclPlugin; use OCA\DAV\Connector\Sabre\DummyGetResponsePlugin; use OCA\DAV\Connector\Sabre\FilesPlugin; +use OCA\DAV\DAV\PublicAuth; use OCA\DAV\Files\BrowserErrorPagePlugin; use OCA\DAV\Files\CustomPropertiesBackend; use OCP\IRequest; @@ -69,6 +70,8 @@ public function __construct(IRequest $request, $baseUri) { $this->server->addPlugin(new BlockLegacyClientPlugin(\OC::$server->getConfig())); $authPlugin = new Plugin(); + $authPlugin->addBackend($authBackend); + $authPlugin->addBackend(new PublicAuth()); $this->server->addPlugin($authPlugin); // allow setup of additional auth backends @@ -105,6 +108,10 @@ public function __construct(IRequest $request, $baseUri) { $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); $this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); + $this->server->addPlugin(new \OCA\DAV\CalDAV\Publishing\PublishPlugin( + \OC::$server->getConfig(), + \OC::$server->getUrlGenerator() + )); // addressbook plugins $this->server->addPlugin(new \OCA\DAV\CardDAV\Plugin()); diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php index 59793992d2e4..4600682e4872 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php @@ -23,6 +23,8 @@ use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Connector\Sabre\Principal; +use OCP\IL10N; +use OCP\IConfig; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Test\TestCase; @@ -41,6 +43,9 @@ abstract class AbstractCalDavBackendTest extends TestCase { /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ protected $principal; + /** var OCP\IConfig */ + protected $config; + const UNIT_TEST_USER = 'principals/users/caldav-unit-test'; const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1'; const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group'; @@ -61,7 +66,8 @@ public function setUp() { ->willReturn([self::UNIT_TEST_GROUP]); $db = \OC::$server->getDatabaseConnection(); - $this->backend = new CalDavBackend($db, $this->principal); + $this->config = \OC::$server->getConfig(); + $this->backend = new CalDavBackend($db, $this->principal, $this->config); $this->tearDown(); } diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index 1a32b01e1ea3..43553e0cc38a 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -332,6 +332,35 @@ public function testSyncSupport() { $this->assertEquals($event, $changes['added'][0]); } + public function testPublications() { + $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []); + + $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0]; + + $l10n = $this->getMockBuilder('\OCP\IL10N') + ->disableOriginalConstructor()->getMock(); + + $calendar = new Calendar($this->backend, $calendarInfo, $l10n); + $calendar->setPublishStatus(true); + $this->assertEquals(true, $calendar->getPublishStatus()); + + $publicCalendars = $this->backend->getPublicCalendars(); + $this->assertEquals(1, count($publicCalendars)); + $this->assertEquals(true, $publicCalendars[0]['{http://owncloud.org/ns}public']); + + $publicCalendarURI = md5($this->config->getSystemValue('secret', '') . $calendar->getResourceId()); + $publicCalendar = $this->backend->getPublicCalendar($publicCalendarURI); + $this->assertEquals(true, $publicCalendar['{http://owncloud.org/ns}public']); + + $calendar->setPublishStatus(false); + $this->assertEquals(false, $calendar->getPublishStatus()); + + $publicCalendarURI = md5($this->config->getSystemValue('secret', '') . $calendar->getResourceId()); + $this->setExpectedException('Sabre\DAV\Exception\NotFound'); + $publicCalendar = $this->backend->getPublicCalendar($publicCalendarURI); + + } + public function testSubscriptions() { $id = $this->backend->createSubscription(self::UNIT_TEST_USER, 'Subscription', [ '{http://calendarserver.org/ns/}source' => new Href('test-source'), diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php new file mode 100644 index 000000000000..9ce558ad34bc --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php @@ -0,0 +1,103 @@ +getDatabaseConnection(); + $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') + ->disableOriginalConstructor() + ->getMock(); + $this->config = \OC::$server->getConfig(); + + $this->backend = new CalDavBackend($db, $this->principal, $this->config); + + $this->publicCalendarRoot = new PublicCalendarRoot($this->backend); + + $this->l10n = $this->getMockBuilder('\OCP\IL10N') + ->disableOriginalConstructor()->getMock(); + } + + public function testGetName() { + $name = $this->publicCalendarRoot->getName(); + $this->assertEquals('public-calendars', $name); + } + + public function testGetChild() { + + $calendar = $this->createPublicCalendar(); + + $publicCalendarURI = md5($this->config->getSystemValue('secret', '') . $calendar->getResourceId()); + + $calendarResult = $this->publicCalendarRoot->getChild($publicCalendarURI); + $this->assertEquals($calendar, $calendarResult); + } + + public function testGetChildren() { + + $publicCalendars = $this->backend->getPublicCalendars(); + + $calendarResults = $this->publicCalendarRoot->getChildren(); + + $this->assertEquals(1, count($calendarResults)); + $this->assertEquals(new Calendar($this->backend, $publicCalendars[0], $this->l10n), $calendarResults[0]); + + } + + /** + * @return Calendar + */ + protected function createPublicCalendar() { + $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []); + + $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0]; + + $calendarInfo['uri'] = md5($this->config->getSystemValue('secret', '') . $calendarInfo['id']); + list(, $name) = Uri\split($calendarInfo['principaluri']); + $calendarInfo['{DAV:}displayname'] = $calendarInfo['{DAV:}displayname'] . ' (' . $name . ')'; + $calendarInfo['{http://owncloud.org/ns}owner-principal'] = $calendarInfo['principaluri']; + $calendarInfo['{http://owncloud.org/ns}read-only'] = false; + $calendarInfo['{http://owncloud.org/ns}public'] = true; + + $calendar = new Calendar($this->backend, $calendarInfo, $this->l10n); + $calendar->setPublishStatus(true); + + return $calendar; + } + + +} diff --git a/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php b/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php new file mode 100644 index 000000000000..b6b1e4381b56 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php @@ -0,0 +1,56 @@ +write([ + '{' . self::NS_CALENDARSERVER . '}publish-url' => $publish, + ]); + + $this->assertEquals('urltopublish', $publish->getValue()); + + $this->assertXmlStringEqualsXmlString( + ' + + urltopublish + ', $xml); + } + + public function testSerializeNotPublished() { + $publish = new Publisher('urltopublish', false); + + $xml = $this->write([ + '{' . self::NS_CALENDARSERVER . '}pre-publish-url' => $publish, + ]); + + $this->assertEquals('urltopublish', $publish->getValue()); + + $this->assertXmlStringEqualsXmlString( + ' + urltopublish', $xml); + } + + + protected $elementMap = []; + protected $namespaceMap = ['DAV:' => 'd']; + protected $contextUri = '/'; + + private function write($input) { + $writer = new Writer(); + $writer->contextUri = $this->contextUri; + $writer->namespaceMap = $this->namespaceMap; + $writer->openMemory(); + $writer->setIndent(true); + $writer->write($input); + return $writer->outputMemory(); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php b/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php new file mode 100644 index 000000000000..69de507dac57 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php @@ -0,0 +1,82 @@ +config = $this->getMockBuilder('\OCP\IConfig')-> + disableOriginalConstructor()-> + getMock(); + $this->config->expects($this->any())->method('getSystemValue') + ->with($this->equalTo('secret')) + ->willReturn('mysecret'); + + $this->urlGenerator = $this->getMockBuilder('OCP\IURLGenerator')-> + disableOriginalConstructor()-> + getMock(); + + /** @var IRequest $request */ + $this->plugin = new PublishPlugin($this->config, $this->urlGenerator); + + $root = new SimpleCollection('calendars'); + $this->server = new Server($root); + /** @var SimpleCollection $node */ + $this->book = $this->getMockBuilder('OCA\DAV\CalDAV\Calendar')-> + disableOriginalConstructor()-> + getMock(); + $this->book->method('getName')->willReturn('cal1'); + $root->addChild($this->book); + $this->plugin->initialize($this->server); + } + + public function testPublishing() { + + $this->book->expects($this->once())->method('setPublishStatus')->with(true); + + // setup request + $request = new Request(); + $request->addHeader('Content-Type', 'application/xml'); + $request->setUrl('cal1'); + $request->setBody(''); + $response = new Response(); + $this->plugin->httpPost($request, $response); + } + + public function testUnPublishing() { + + $this->book->expects($this->once())->method('setPublishStatus')->with(false); + + // setup request + $request = new Request(); + $request->addHeader('Content-Type', 'application/xml'); + $request->setUrl('cal1'); + $request->setBody(''); + $response = new Response(); + $this->plugin->httpPost($request, $response); + } +} diff --git a/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php b/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php index 66975fbf3054..4caf66ffbb75 100644 --- a/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php +++ b/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php @@ -44,7 +44,12 @@ public function providesPrefix() { [[[ 'uri' => 'principals/system/system', '{DAV:}displayname' => 'system', - ]], 'principals/system'], + ], + [ + 'uri' => 'principals/system/public', + '{DAV:}displayname' => 'public', + ] + ], 'principals/system'], ]; }