Skip to content

Commit

Permalink
fix(caldav): stricter default calendar checks
Browse files Browse the repository at this point in the history
Reject calendars that
- are subscriptions
- are not writable
- are shared with a user
- are deleted
- don't support VEVENTs

Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
  • Loading branch information
st3iny committed Jul 22, 2024
1 parent 1768bd6 commit cbea787
Show file tree
Hide file tree
Showing 13 changed files with 326 additions and 19 deletions.
3 changes: 2 additions & 1 deletion apps/dav/appinfo/v1/caldav.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use OC\KnownUser\KnownUserService;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\CalendarRoot;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\CalDAV\Security\RateLimitingPlugin;
use OCA\DAV\CalDAV\Validation\CalDavValidatePlugin;
use OCA\DAV\Connector\LegacyDAVACL;
Expand Down Expand Up @@ -92,7 +93,7 @@

$server->addPlugin(new \Sabre\DAV\Sync\Plugin());
$server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
$server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class)));
$server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), \OC::$server->get(DefaultCalendarValidator::class)));

if ($sendInvitations) {
$server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
'OCA\\DAV\\CalDAV\\CalendarObject' => $baseDir . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarProvider' => $baseDir . '/../lib/CalDAV/CalendarProvider.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => $baseDir . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\DefaultCalendarValidator' => $baseDir . '/../lib/CalDAV/DefaultCalendarValidator.php',
'OCA\\DAV\\CalDAV\\EventComparisonService' => $baseDir . '/../lib/CalDAV/EventComparisonService.php',
'OCA\\DAV\\CalDAV\\EventReader' => $baseDir . '/../lib/CalDAV/EventReader.php',
'OCA\\DAV\\CalDAV\\EventReaderRDate' => $baseDir . '/../lib/CalDAV/EventReaderRDate.php',
Expand Down
1 change: 1 addition & 0 deletions apps/dav/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\CalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarObject.php',
'OCA\\DAV\\CalDAV\\CalendarProvider' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarProvider.php',
'OCA\\DAV\\CalDAV\\CalendarRoot' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarRoot.php',
'OCA\\DAV\\CalDAV\\DefaultCalendarValidator' => __DIR__ . '/..' . '/../lib/CalDAV/DefaultCalendarValidator.php',
'OCA\\DAV\\CalDAV\\EventComparisonService' => __DIR__ . '/..' . '/../lib/CalDAV/EventComparisonService.php',
'OCA\\DAV\\CalDAV\\EventReader' => __DIR__ . '/..' . '/../lib/CalDAV/EventReader.php',
'OCA\\DAV\\CalDAV\\EventReaderRDate' => __DIR__ . '/..' . '/../lib/CalDAV/EventReaderRDate.php',
Expand Down
41 changes: 41 additions & 0 deletions apps/dav/lib/CalDAV/DefaultCalendarValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\DAV\CalDAV;

use Sabre\DAV\Exception as DavException;

class DefaultCalendarValidator {
/**
* Check if a given Calendar node is suitable to be used as the default calendar for scheduling.
*
* @throws DavException If the calendar is not suitable to be used as the default calendar
*/
public function validateScheduleDefaultCalendar(Calendar $calendar): void {
// Sanity checks for a calendar that should handle invitations
if ($calendar->isSubscription()
|| !$calendar->canWrite()
|| $calendar->isShared()
|| $calendar->isDeleted()) {
throw new DavException('Calendar is a subscription, not writable, shared or deleted');
}

// Calendar must support VEVENTs
$sCCS = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
$calendarProperties = $calendar->getProperties([$sCCS]);
if (isset($calendarProperties[$sCCS])) {
$supportedComponents = $calendarProperties[$sCCS]->getValue();
} else {
$supportedComponents = ['VJOURNAL', 'VTODO', 'VEVENT'];
}
if (!in_array('VEVENT', $supportedComponents, true)) {
throw new DavException('Calendar does not support VEVENT components');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin;
use OCA\DAV\CalDAV\Auth\PublicPrincipalPlugin;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin;
use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin;
use OCA\DAV\Connector\Sabre\CachingTree;
Expand Down Expand Up @@ -69,7 +70,7 @@ public function __construct(bool $public = true) {
// calendar plugins
$this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class)));
$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), \OC::$server->get(DefaultCalendarValidator::class)));
$this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin());
//$this->server->addPlugin(new \OCA\DAV\DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest()));
Expand Down
19 changes: 16 additions & 3 deletions apps/dav/lib/CalDAV/Schedule/Plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
use Sabre\CalDAV\ICalendar;
use Sabre\CalDAV\ICalendarObject;
use Sabre\CalDAV\Schedule\ISchedulingObject;
use Sabre\DAV\Exception as DavException;
use Sabre\DAV\INode;
use Sabre\DAV\IProperties;
use Sabre\DAV\PropFind;
Expand Down Expand Up @@ -51,13 +53,15 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
public const CALENDAR_USER_TYPE = '{' . self::NS_CALDAV . '}calendar-user-type';
public const SCHEDULE_DEFAULT_CALENDAR_URL = '{' . Plugin::NS_CALDAV . '}schedule-default-calendar-URL';
private LoggerInterface $logger;
private DefaultCalendarValidator $defaultCalendarValidator;

/**
* @param IConfig $config
*/
public function __construct(IConfig $config, LoggerInterface $logger) {
public function __construct(IConfig $config, LoggerInterface $logger, DefaultCalendarValidator $defaultCalendarValidator) {
$this->config = $config;
$this->logger = $logger;
$this->defaultCalendarValidator = $defaultCalendarValidator;
}

/**
Expand Down Expand Up @@ -360,11 +364,20 @@ public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) {
* - isn't a calendar subscription
* - user can write to it (no virtual/3rd-party calendars)
* - calendar isn't a share
* - calendar supports VEVENTs
*/
foreach ($calendarHome->getChildren() as $node) {
if ($node instanceof Calendar && !$node->isSubscription() && $node->canWrite() && !$node->isShared() && !$node->isDeleted()) {
$userCalendars[] = $node;
if (!($node instanceof Calendar)) {
continue;
}

try {
$this->defaultCalendarValidator->validateScheduleDefaultCalendar($node);
} catch (DavException $e) {
continue;
}

$userCalendars[] = $node;
}

if (count($userCalendars) > 0) {
Expand Down
4 changes: 3 additions & 1 deletion apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace OCA\DAV\Connector\Sabre;

use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCP\EventDispatcher\IEventDispatcher;
Expand Down Expand Up @@ -167,7 +168,8 @@ public function createServer(string $baseUri,
$server,
$objectTree,
$this->databaseConnection,
$this->userSession->getUser()
$this->userSession->getUser(),
\OC::$server->get(DefaultCalendarValidator::class),
)
)
);
Expand Down
9 changes: 7 additions & 2 deletions apps/dav/lib/DAV/CustomPropertiesBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
namespace OCA\DAV\DAV;

use Exception;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\Connector\Sabre\Directory;
use OCA\DAV\Connector\Sabre\FilesPlugin;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
use Sabre\CalDAV\ICalendar;
use Sabre\DAV\Exception as DavException;
use Sabre\DAV\PropertyStorage\Backend\BackendInterface;
use Sabre\DAV\PropFind;
Expand Down Expand Up @@ -135,6 +136,7 @@ class CustomPropertiesBackend implements BackendInterface {

private Server $server;
private XmlService $xmlService;
private DefaultCalendarValidator $defaultCalendarValidator;

/**
* @param Tree $tree node tree
Expand All @@ -146,6 +148,7 @@ public function __construct(
Tree $tree,
IDBConnection $connection,
IUser $user,
DefaultCalendarValidator $defaultCalendarValidator,
) {
$this->server = $server;
$this->tree = $tree;
Expand All @@ -156,6 +159,7 @@ public function __construct(
$this->xmlService->elementMap,
self::COMPLEX_XML_ELEMENT_MAP,
);
$this->defaultCalendarValidator = $defaultCalendarValidator;
}

/**
Expand Down Expand Up @@ -319,10 +323,11 @@ private function validateProperty(string $path, string $propName, mixed $propVal

// $path is the principal here as this prop is only set on principals
$node = $this->tree->getNodeForPath($href);
if (!($node instanceof ICalendar) || $node->getOwner() !== $path) {
if (!($node instanceof Calendar) || $node->getOwner() !== $path) {
throw new DavException('No such calendar');
}

$this->defaultCalendarValidator->validateScheduleDefaultCalendar($node);
break;
}
}
Expand Down
6 changes: 4 additions & 2 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use OCA\DAV\AppInfo\PluginManager;
use OCA\DAV\BulkUpload\BulkUploadPlugin;
use OCA\DAV\CalDAV\BirthdayService;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\CalDAV\Schedule\IMipPlugin;
use OCA\DAV\CalDAV\Security\RateLimitingPlugin;
use OCA\DAV\CalDAV\Validation\CalDavValidatePlugin;
Expand Down Expand Up @@ -153,7 +154,7 @@ public function __construct(IRequest $request, string $baseUri) {
$this->server->addPlugin(new DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest(), \OC::$server->getConfig()));
$this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
$this->server->addPlugin(new \OCA\DAV\CalDAV\ICSExportPlugin\ICSExportPlugin(\OC::$server->getConfig(), $logger));
$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class)));
$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig(), \OC::$server->get(LoggerInterface::class), \OC::$server->get(DefaultCalendarValidator::class)));

$this->server->addPlugin(\OC::$server->get(\OCA\DAV\CalDAV\Trashbin\Plugin::class));
$this->server->addPlugin(new \OCA\DAV\CalDAV\WebcalCaching\Plugin($request));
Expand Down Expand Up @@ -254,7 +255,8 @@ public function __construct(IRequest $request, string $baseUri) {
$this->server,
$this->server->tree,
\OC::$server->getDatabaseConnection(),
\OC::$server->getUserSession()->getUser()
\OC::$server->getUserSession()->getUser(),
\OC::$server->get(DefaultCalendarValidator::class),
)
)
);
Expand Down
Loading

0 comments on commit cbea787

Please sign in to comment.