Merge pull request #1197 from nextcloud/oc-public-sharing

CalDAV calendar public sharing
This commit is contained in:
Lukas Reschke 2016-09-27 18:51:40 +02:00 committed by GitHub
commit 06e969cb74
18 changed files with 1033 additions and 8 deletions

View File

@ -703,6 +703,11 @@ CREATE TABLE calendarobjects (
<notnull>true</notnull>
<unsigned>true</unsigned>
</field>
<field>
<name>publicuri</name>
<type>text</type>
<length>255</length>
</field>
<index>
<name>dav_shares_index</name>
<unique>true</unique>
@ -715,6 +720,9 @@ CREATE TABLE calendarobjects (
<field>
<name>type</name>
</field>
<field>
<name>publicuri</name>
</field>
</index>
</declaration>
</table>

View File

@ -46,7 +46,10 @@ $principalBackend = new Principal(
'principals/'
);
$db = \OC::$server->getDatabaseConnection();
$calDavBackend = new CalDavBackend($db, $principalBackend, \OC::$server->getUserManager());
$config = \OC::$server->getConfig();
$userManager = \OC::$server->getUserManager();
$random = \OC::$server->getSecureRandom();
$calDavBackend = new CalDavBackend($db, $principalBackend, $userManager, $config, $random);
$debugging = \OC::$server->getConfig()->getSystemValue('debug', false);

View File

@ -29,9 +29,11 @@ use OCA\DAV\DAV\Sharing\IShareable;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCA\DAV\Connector\Sabre\Principal;
use OCA\DAV\DAV\Sharing\Backend;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use Sabre\CalDAV\Backend\AbstractBackend;
use Sabre\CalDAV\Backend\SchedulingSupport;
use Sabre\CalDAV\Backend\SubscriptionSupport;
@ -41,6 +43,7 @@ use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
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;
@ -66,6 +69,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;
@ -117,6 +121,12 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
/** @var IUserManager */
private $userManager;
/** @var IConfig */
private $config;
/** @var ISecureRandom */
private $random;
/**
* CalDavBackend constructor.
@ -124,12 +134,20 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
* @param IDBConnection $db
* @param Principal $principalBackend
* @param IUserManager $userManager
* @param IConfig $config
* @param ISecureRandom $random
*/
public function __construct(IDBConnection $db, Principal $principalBackend, IUserManager $userManager) {
public function __construct(IDBConnection $db,
Principal $principalBackend,
IUserManager $userManager,
IConfig $config,
ISecureRandom $random) {
$this->db = $db;
$this->principalBackend = $principalBackend;
$this->userManager = $userManager;
$this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar');
$this->config = $config;
$this->random = $random;
}
/**
@ -295,6 +313,120 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $this->userDisplayNames[$uid];
}
/**
* @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']);
}
$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];
}
return $calendar;
}
/**
* @param string $principal
@ -1472,6 +1604,50 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
return $this->sharingBackend->getShares($resourceId);
}
/**
* @param boolean $value
* @param \OCA\DAV\CalDAV\Calendar $calendar
* @return string|null
*/
public function setPublishStatus($value, $calendar) {
$query = $this->db->getQueryBuilder();
if ($value) {
$publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS);
$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($publicUri)
]);
$query->execute();
return $publicUri;
}
$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();
return null;
}
/**
* @param \OCA\DAV\CalDAV\Calendar $calendar
* @return mixed
*/
public function getPublishStatus($calendar) {
$query = $this->db->getQueryBuilder();
$result = $query->select('publicuri')
->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 $row ? reset($row) : false;
}
/**
* @param int $resourceId
* @param array $acl

View File

@ -89,6 +89,13 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
return $this->calendarInfo['id'];
}
/**
* @return string
*/
public function getPrincipalURI() {
return $this->calendarInfo['principaluri'];
}
function getACL() {
$acl = [
[
@ -117,6 +124,13 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
];
}
}
if ($this->isPublic()) {
$acl[] = [
'privilege' => '{DAV:}read',
'principal' => 'principals/system/public',
'protected' => true,
];
}
/** @var CalDavBackend $calDavBackend */
$calDavBackend = $this->caldavBackend;
@ -236,6 +250,23 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
return $uris;
}
/**
* @param boolean $value
* @return string|null
*/
function setPublishStatus($value) {
$publicUri = $this->caldavBackend->setPublishStatus($value, $this);
$this->calendarInfo['publicuri'] = $publicUri;
return $publicUri;
}
/**
* @return mixed $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'];
@ -243,8 +274,16 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable {
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']);
}
}

View File

@ -0,0 +1,67 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @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\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;
}
}

View File

@ -0,0 +1,227 @@
<?php
/**
* @author Thomas Citharel <tcit@tcit.fr>
*
* @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr>
* @license GNU AGPL version 3 or any later version
*
* 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\CalDAV\Publishing;
use Sabre\DAV\PropFind;
use Sabre\DAV\INode;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\Exception\NotFound;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Sabre\CalDAV\Xml\Property\AllowedSharingModes;
use OCA\DAV\CalDAV\Publishing\Xml\Publisher;
use OCA\DAV\CalDAV\Calendar;
use OCP\IURLGenerator;
use OCP\IConfig;
class PublishPlugin extends ServerPlugin {
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
/**
* Reference to SabreDAV server object.
*
* @var \Sabre\DAV\Server
*/
protected $server;
/**
* Config instance to get instance secret.
*
* @var IConfig
*/
protected $config;
/**
* URL Generator for absolute URLs.
*
* @var IURLGenerator
*/
protected $urlGenerator;
/**
* PublishPlugin constructor.
*
* @param IConfig $config
* @param IURLGenerator $urlGenerator
*/
public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
$this->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) {
$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
if ($node->getPublishStatus()) {
// We return the publish-url only if the calendar is published.
$token = $node->getPublishStatus();
$publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
return new Publisher($publishUrl, true);
}
});
$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 void|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;
}
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* @author Thomas Citharel <tcit@tcit.fr>
*
* @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr>
* @license GNU AGPL version 3 or any later version
*
* 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\CalDAV\Publishing\Xml;
use OCA\DAV\CalDAV\Publishing\PublishPlugin as Plugin;
use Sabre\Xml\Writer;
use Sabre\Xml\XmlSerializable;
class Publisher implements XmlSerializable {
/**
* @var string $publishUrl
*/
protected $publishUrl;
/**
* @var boolean $isPublished
*/
protected $isPublished;
/**
* @param string $publishUrl
* @param boolean $isPublished
*/
function __construct($publishUrl, $isPublished) {
$this->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);
}
}
}

View File

@ -44,6 +44,7 @@ class CreateCalendar extends Command {
/**
* @param IUserManager $userManager
* @param IGroupManager $groupManager
* @param IDBConnection $dbConnection
*/
function __construct(IUserManager $userManager, IGroupManager $groupManager, IDBConnection $dbConnection) {
@ -74,9 +75,11 @@ class CreateCalendar extends Command {
$this->userManager,
$this->groupManager
);
$config = \OC::$server->getConfig();
$random = \OC::$server->getSecureRandom();
$name = $input->getArgument('name');
$caldav = new CalDavBackend($this->dbConnection, $principalBackend, $this->userManager);
$caldav = new CalDavBackend($this->dbConnection, $principalBackend, $this->userManager, $config, $random);
$caldav->createCalendar("principals/users/$user", $name, []);
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @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\DAV;
use Sabre\DAV\Auth\Backend\BackendInterface;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
class PublicAuth implements BackendInterface {
/** @var string[] */
private $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) {
$url = $request->getPath();
$matchingUrls = array_filter($this->publicURLs, function ($publicUrl) use ($url) {
return strpos($url, $publicUrl, 0) === 0;
});
return !empty($matchingUrls);
}
}

View File

@ -51,6 +51,10 @@ class SystemPrincipalBackend extends AbstractBackend {
'uri' => 'principals/system/system',
'{DAV:}displayname' => 'system',
];
$principals[] = [
'uri' => 'principals/system/public',
'{DAV:}displayname' => 'public',
];
}
return $principals;
@ -73,6 +77,13 @@ class SystemPrincipalBackend extends AbstractBackend {
];
return $principal;
}
if ($path === 'principals/system/public') {
$principal = [
'uri' => 'principals/system/public',
'{DAV:}displayname' => 'public',
];
return $principal;
}
return null;
}

View File

@ -26,6 +26,7 @@ namespace OCA\DAV;
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;
@ -38,10 +39,12 @@ class RootCollection extends SimpleCollection {
public function __construct() {
$config = \OC::$server->getConfig();
$random = \OC::$server->getSecureRandom();
$userManager = \OC::$server->getUserManager();
$db = \OC::$server->getDatabaseConnection();
$dispatcher = \OC::$server->getEventDispatcher();
$userPrincipalBackend = new Principal(
\OC::$server->getUserManager(),
$userManager,
\OC::$server->getGroupManager()
);
$groupPrincipalBackend = new GroupPrincipalBackend(
@ -59,9 +62,11 @@ class RootCollection extends SimpleCollection {
$systemPrincipals->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($userPrincipalBackend, 'principals/users');
$filesCollection->disableListing = $disableListing;
$caldavBackend = new CalDavBackend($db, $userPrincipalBackend, \OC::$server->getUserManager());
$caldavBackend = new CalDavBackend($db, $userPrincipalBackend, $userManager, $config, $random);
$calendarRoot = new CalendarRoot($userPrincipalBackend, $caldavBackend, 'principals/users');
$calendarRoot->disableListing = $disableListing;
$publicCalendarRoot = new PublicCalendarRoot($caldavBackend);
$publicCalendarRoot->disableListing = $disableListing;
$systemTagCollection = new SystemTag\SystemTagsByIdCollection(
\OC::$server->getSystemTagManager(),
@ -101,6 +106,7 @@ class RootCollection extends SimpleCollection {
$systemPrincipals]),
$filesCollection,
$calendarRoot,
$publicCalendarRoot,
new SimpleCollection('addressbooks', [
$usersAddressBookRoot,
$systemAddressBookRoot]),

View File

@ -35,6 +35,7 @@ use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
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;
@ -78,6 +79,8 @@ class Server {
$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
@ -114,6 +117,10 @@ class Server {
$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());

View File

@ -28,6 +28,8 @@ use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IL10N;
use OCP\IConfig;
use OCP\Security\ISecureRandom;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV\PropPatch;
use Sabre\DAV\Xml\Property\Href;
@ -51,6 +53,12 @@ abstract class AbstractCalDavBackendTest extends TestCase {
/** @var \OCP\IUserManager|\PHPUnit_Framework_MockObject_MockObject */
protected $userManager;
/** var OCP\IConfig */
protected $config;
/** @var ISecureRandom */
private $random;
const UNIT_TEST_USER = 'principals/users/caldav-unit-test';
const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1';
@ -75,8 +83,9 @@ abstract class AbstractCalDavBackendTest extends TestCase {
->willReturn([self::UNIT_TEST_GROUP]);
$db = \OC::$server->getDatabaseConnection();
$this->backend = new CalDavBackend($db, $this->principal, $this->userManager);
$this->config = \OC::$server->getConfig();
$this->random = \OC::$server->getSecureRandom();
$this->backend = new CalDavBackend($db, $this->principal, $this->userManager, $this->config, $this->random);
$this->tearDown();
}

View File

@ -334,6 +334,35 @@ EOD;
$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->assertNotEquals(false, $calendar->getPublishStatus());
$publicCalendars = $this->backend->getPublicCalendars();
$this->assertEquals(1, count($publicCalendars));
$this->assertEquals(true, $publicCalendars[0]['{http://owncloud.org/ns}public']);
$publicCalendarURI = $publicCalendars[0]['uri'];
$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'),

View File

@ -0,0 +1,122 @@
<?php
namespace OCA\DAV\Tests\unit\CalDAV;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\Connector\Sabre\Principal;
use OCP\IL10N;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\PublicCalendarRoot;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use Test\TestCase;
/**
* Class PublicCalendarRootTest
*
* @group DB
*
* @package OCA\DAV\Tests\unit\CalDAV
*/
class PublicCalendarRootTest extends TestCase {
const UNIT_TEST_USER = 'principals/users/caldav-unit-test';
/** @var CalDavBackend */
private $backend;
/** @var PublicCalendarRoot */
private $publicCalendarRoot;
/** @var IL10N */
private $l10n;
/** @var IUserManager */
private $userManager;
/** @var Principal */
private $principal;
/** var IConfig */
protected $config;
/** @var ISecureRandom */
private $random;
public function setUp() {
parent::setUp();
$db = \OC::$server->getDatabaseConnection();
$this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal')
->disableOriginalConstructor()
->getMock();
$this->config = \OC::$server->getConfig();
$this->userManager = $this->getMockBuilder('\OCP\IUserManager')->getMock();
$this->random = \OC::$server->getSecureRandom();
$this->backend = new CalDavBackend(
$db,
$this->principal,
$this->userManager,
$this->config,
$this->random
);
$this->publicCalendarRoot = new PublicCalendarRoot($this->backend);
$this->l10n = $this->getMockBuilder('\OCP\IL10N')
->disableOriginalConstructor()->getMock();
}
public function tearDown() {
parent::tearDown();
if (is_null($this->backend)) {
return;
}
$books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
foreach ($books as $book) {
$this->backend->deleteCalendar($book['id']);
}
}
public function testGetName() {
$name = $this->publicCalendarRoot->getName();
$this->assertEquals('public-calendars', $name);
}
public function testGetChild() {
$calendar = $this->createPublicCalendar();
$publicCalendars = $this->backend->getPublicCalendars();
$this->assertEquals(1, count($publicCalendars));
$this->assertEquals(true, $publicCalendars[0]['{http://owncloud.org/ns}public']);
$publicCalendarURI = $publicCalendars[0]['uri'];
$calendarResult = $this->publicCalendarRoot->getChild($publicCalendarURI);
$this->assertEquals($calendar, $calendarResult);
}
public function testGetChildren() {
$this->createPublicCalendar();
$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];
$calendar = new Calendar($this->backend, $calendarInfo, $this->l10n);
$publicUri = $calendar->setPublishStatus(true);
$calendarInfo = $this->backend->getPublicCalendar($publicUri);
$calendar = new Calendar($this->backend, $calendarInfo, $this->l10n);
return $calendar;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace OCA\DAV\Tests\unit\CalDAV\Publishing;
use OCA\DAV\CalDAV\Publishing\Xml\Publisher;
use Sabre\Xml\Writer;
class PublisherTest extends \PHPUnit_Framework_TestCase {
const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
public function testSerializePublished() {
$publish = new Publisher('urltopublish', true);
$xml = $this->write([
'{' . self::NS_CALENDARSERVER . '}publish-url' => $publish,
]);
$this->assertEquals('urltopublish', $publish->getValue());
$this->assertXmlStringEqualsXmlString(
'<?xml version="1.0"?>
<x1:publish-url xmlns:d="DAV:" xmlns:x1="' . self::NS_CALENDARSERVER . '">
<d:href>urltopublish</d:href>
</x1:publish-url>', $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(
'<?xml version="1.0"?>
<x1:pre-publish-url xmlns:d="DAV:" xmlns:x1="' . self::NS_CALENDARSERVER . '">urltopublish</x1:pre-publish-url>', $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();
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace OCA\DAV\Tests\unit\CalDAV\Publishing;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\Publishing\PublishPlugin;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IConfig;
use Sabre\DAV\Server;
use Sabre\DAV\SimpleCollection;
use Sabre\HTTP\Request;
use Sabre\HTTP\Response;
use Test\TestCase;
class PluginTest extends TestCase {
/** @var PublishPlugin */
private $plugin;
/** @var Server */
private $server;
/** @var Calendar | \PHPUnit_Framework_MockObject_MockObject */
private $book;
/** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */
private $config;
/** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */
private $urlGenerator;
public function setUp() {
parent::setUp();
$this->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('<o:publish-calendar xmlns:o="http://calendarserver.org/ns/"/>');
$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('<o:unpublish-calendar xmlns:o="http://calendarserver.org/ns/"/>');
$response = new Response();
$this->plugin->httpPost($request, $response);
}
}

View File

@ -45,7 +45,12 @@ class SystemPrincipalBackendTest extends TestCase {
[[[
'uri' => 'principals/system/system',
'{DAV:}displayname' => 'system',
]], 'principals/system'],
],
[
'uri' => 'principals/system/public',
'{DAV:}displayname' => 'public',
]
], 'principals/system'],
];
}