2016-07-06 13:19:46 +03:00
|
|
|
<?php
|
2016-09-23 19:20:04 +03:00
|
|
|
/**
|
2017-11-06 17:56:42 +03:00
|
|
|
* @copyright Copyright (c) 2016 Thomas Citharel <tcit@tcit.fr>
|
|
|
|
*
|
2020-04-29 12:57:22 +03:00
|
|
|
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
2019-12-03 21:57:53 +03:00
|
|
|
* @author Georg Ehrke <oc.list@georgehrke.com>
|
|
|
|
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
2020-03-31 11:49:10 +03:00
|
|
|
* @author Thomas Citharel <nextcloud@tcit.fr>
|
2017-11-06 17:56:42 +03:00
|
|
|
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
2016-09-23 19:20:04 +03:00
|
|
|
*
|
|
|
|
* @license GNU AGPL version 3 or any later version
|
|
|
|
*
|
2017-11-06 17:56:42 +03:00
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
2016-09-23 19:20:04 +03:00
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2017-11-06 17:56:42 +03:00
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2016-09-23 19:20:04 +03:00
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
2017-11-06 17:56:42 +03:00
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
2019-12-03 21:57:53 +03:00
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2016-09-23 19:20:04 +03:00
|
|
|
*
|
|
|
|
*/
|
2019-11-22 22:52:10 +03:00
|
|
|
|
2016-07-06 13:19:46 +03:00
|
|
|
namespace OCA\DAV\CalDAV\Publishing;
|
|
|
|
|
2019-11-22 22:52:10 +03:00
|
|
|
use OCA\DAV\CalDAV\Calendar;
|
|
|
|
use OCA\DAV\CalDAV\Publishing\Xml\Publisher;
|
|
|
|
use OCP\IConfig;
|
|
|
|
use OCP\IURLGenerator;
|
|
|
|
use Sabre\CalDAV\Xml\Property\AllowedSharingModes;
|
|
|
|
use Sabre\DAV\Exception\NotFound;
|
2016-07-06 13:19:46 +03:00
|
|
|
use Sabre\DAV\INode;
|
2019-11-22 22:52:10 +03:00
|
|
|
use Sabre\DAV\PropFind;
|
2016-07-06 13:19:46 +03:00
|
|
|
use Sabre\DAV\Server;
|
|
|
|
use Sabre\DAV\ServerPlugin;
|
|
|
|
use Sabre\HTTP\RequestInterface;
|
|
|
|
use Sabre\HTTP\ResponseInterface;
|
|
|
|
|
2016-08-01 16:10:33 +03:00
|
|
|
class PublishPlugin extends ServerPlugin {
|
2020-04-10 17:54:27 +03:00
|
|
|
public const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
/**
|
2016-08-01 11:51:11 +03:00
|
|
|
* 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;
|
2016-07-07 11:16:56 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
/**
|
|
|
|
* PublishPlugin constructor.
|
|
|
|
*
|
|
|
|
* @param IConfig $config
|
|
|
|
* @param IURLGenerator $urlGenerator
|
|
|
|
*/
|
2016-08-01 16:10:33 +03:00
|
|
|
public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
|
2016-08-01 11:51:11 +03:00
|
|
|
$this->config = $config;
|
|
|
|
$this->urlGenerator = $urlGenerator;
|
|
|
|
}
|
2016-07-07 11:16:56 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
/**
|
2016-08-01 11:51:11 +03:00
|
|
|
* 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[]
|
|
|
|
*/
|
2016-08-01 16:10:33 +03:00
|
|
|
public function getFeatures() {
|
2016-08-01 11:51:11 +03:00
|
|
|
// May have to be changed to be detected
|
2016-08-16 11:03:24 +03:00
|
|
|
return ['oc-calendar-publishing', 'calendarserver-sharing'];
|
2016-08-01 11:51:11 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a plugin name.
|
|
|
|
*
|
|
|
|
* Using this name other plugins will be able to access other plugins
|
|
|
|
* using Sabre\DAV\Server::getPlugin
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2020-04-09 14:53:40 +03:00
|
|
|
public function getPluginName() {
|
2016-08-01 11:51:11 +03:00
|
|
|
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
|
|
|
|
*/
|
2016-08-01 16:10:33 +03:00
|
|
|
public function initialize(Server $server) {
|
2016-08-01 11:51:11 +03:00
|
|
|
$this->server = $server;
|
|
|
|
|
|
|
|
$this->server->on('method:POST', [$this, 'httpPost']);
|
|
|
|
$this->server->on('propFind', [$this, 'propFind']);
|
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-08-01 16:10:33 +03:00
|
|
|
public function propFind(PropFind $propFind, INode $node) {
|
2016-08-01 11:51:11 +03:00
|
|
|
if ($node instanceof Calendar) {
|
2016-09-03 11:52:05 +03:00
|
|
|
$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
|
2016-08-01 11:51:11 +03:00
|
|
|
if ($node->getPublishStatus()) {
|
2016-07-31 21:18:35 +03:00
|
|
|
// We return the publish-url only if the calendar is published.
|
2016-09-15 18:01:35 +03:00
|
|
|
$token = $node->getPublishStatus();
|
2016-09-03 11:52:05 +03:00
|
|
|
$publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
|
|
|
|
|
2016-08-01 11:51:11 +03:00
|
|
|
return new Publisher($publishUrl, true);
|
|
|
|
}
|
|
|
|
});
|
2016-07-06 13:39:07 +03:00
|
|
|
|
2020-04-09 14:53:40 +03:00
|
|
|
$propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function () use ($node) {
|
2019-09-17 12:05:55 +03:00
|
|
|
$canShare = (!$node->isSubscription() && $node->canWrite());
|
|
|
|
$canPublish = (!$node->isSubscription() && $node->canWrite());
|
|
|
|
|
2020-08-11 10:24:08 +03:00
|
|
|
if ($this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes') {
|
|
|
|
$canShare &= ($node->getOwner() === $node->getPrincipalURI());
|
|
|
|
$canPublish &= ($node->getOwner() === $node->getPrincipalURI());
|
|
|
|
}
|
|
|
|
|
|
|
|
return new AllowedSharingModes((bool)$canShare, (bool)$canPublish);
|
2016-08-12 11:25:26 +03:00
|
|
|
});
|
2016-08-01 11:51:11 +03:00
|
|
|
}
|
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
/**
|
|
|
|
* We intercept this to handle POST requests on calendars.
|
|
|
|
*
|
|
|
|
* @param RequestInterface $request
|
|
|
|
* @param ResponseInterface $response
|
|
|
|
*
|
2016-09-23 19:20:04 +03:00
|
|
|
* @return void|bool
|
2016-07-31 21:18:35 +03:00
|
|
|
*/
|
2016-08-01 16:10:33 +03:00
|
|
|
public function httpPost(RequestInterface $request, ResponseInterface $response) {
|
2016-07-31 21:18:35 +03:00
|
|
|
$path = $request->getPath();
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// Only handling xml
|
|
|
|
$contentType = $request->getHeader('Content-Type');
|
|
|
|
if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) {
|
|
|
|
return;
|
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// Making sure the node exists
|
|
|
|
try {
|
|
|
|
$node = $this->server->tree->getNodeForPath($path);
|
|
|
|
} catch (NotFound $e) {
|
|
|
|
return;
|
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
$requestBody = $request->getBodyAsString();
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// 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);
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
$this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
switch ($documentType) {
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2020-04-09 17:17:53 +03:00
|
|
|
case '{'.self::NS_CALENDARSERVER.'}publish-calendar':
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// We can only deal with IShareableCalendar objects
|
|
|
|
if (!$node instanceof Calendar) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$this->server->transactionType = 'post-publish-calendar';
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// Getting ACL info
|
|
|
|
$acl = $this->server->getPlugin('acl');
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// If there's no ACL support, we allow everything
|
|
|
|
if ($acl) {
|
2020-08-11 10:24:08 +03:00
|
|
|
/** @var \Sabre\DAVACL\Plugin $acl */
|
2016-07-31 21:18:35 +03:00
|
|
|
$acl->checkPrivileges($path, '{DAV:}write');
|
2020-08-11 10:24:08 +03:00
|
|
|
|
|
|
|
$limitSharingToOwner = $this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes';
|
|
|
|
$isOwner = $acl->getCurrentUserPrincipal() === $node->getOwner();
|
|
|
|
if ($limitSharingToOwner && !$isOwner) {
|
|
|
|
return;
|
|
|
|
}
|
2016-07-31 21:18:35 +03:00
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
$node->setPublishStatus(true);
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// iCloud sends back the 202, so we will too.
|
|
|
|
$response->setStatus(202);
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// 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');
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// Breaking the event chain
|
|
|
|
return false;
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2020-04-09 17:17:53 +03:00
|
|
|
case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar':
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// We can only deal with IShareableCalendar objects
|
|
|
|
if (!$node instanceof Calendar) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$this->server->transactionType = 'post-unpublish-calendar';
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// Getting ACL info
|
|
|
|
$acl = $this->server->getPlugin('acl');
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// If there's no ACL support, we allow everything
|
|
|
|
if ($acl) {
|
2020-08-11 10:24:08 +03:00
|
|
|
/** @var \Sabre\DAVACL\Plugin $acl */
|
2016-07-31 21:18:35 +03:00
|
|
|
$acl->checkPrivileges($path, '{DAV:}write');
|
2020-08-11 10:24:08 +03:00
|
|
|
|
|
|
|
$limitSharingToOwner = $this->config->getAppValue('dav', 'limitAddressBookAndCalendarSharingToOwner', 'no') === 'yes';
|
|
|
|
$isOwner = $acl->getCurrentUserPrincipal() === $node->getOwner();
|
|
|
|
if ($limitSharingToOwner && !$isOwner) {
|
|
|
|
return;
|
|
|
|
}
|
2016-07-31 21:18:35 +03:00
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
$node->setPublishStatus(false);
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
$response->setStatus(200);
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// 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');
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
// Breaking the event chain
|
|
|
|
return false;
|
2016-07-06 13:19:46 +03:00
|
|
|
|
2016-07-31 21:18:35 +03:00
|
|
|
}
|
|
|
|
}
|
2016-07-06 13:19:46 +03:00
|
|
|
}
|