From ebdf66b70619a30fd3f9172c1b725b8f56ea9358 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Sun, 8 Mar 2020 17:33:27 +0100
Subject: [PATCH] Provide dav setting for user's default calendar

And add tests to handle schedule-default-calendar-URL

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 apps/dav/appinfo/v1/caldav.php                |   2 +-
 apps/dav/lib/AppInfo/Application.php          |  14 ++
 .../InvitationResponseServer.php              |   2 +-
 apps/dav/lib/CalDAV/Schedule/Plugin.php       |  37 ++-
 apps/dav/lib/Server.php                       |   2 +-
 .../tests/unit/CalDAV/Schedule/PluginTest.php | 223 +++++++++++++++++-
 6 files changed, 265 insertions(+), 15 deletions(-)

diff --git a/apps/dav/appinfo/v1/caldav.php b/apps/dav/appinfo/v1/caldav.php
index 82fddd152e6..29733a3a623 100644
--- a/apps/dav/appinfo/v1/caldav.php
+++ b/apps/dav/appinfo/v1/caldav.php
@@ -92,7 +92,7 @@ if ($debugging) {
 
 $server->addPlugin(new \Sabre\DAV\Sync\Plugin());
 $server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin());
-$server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin());
+$server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig()));
 
 if ($sendInvitations) {
 	$server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php
index 29c77cad07f..c22afa755cb 100644
--- a/apps/dav/lib/AppInfo/Application.php
+++ b/apps/dav/lib/AppInfo/Application.php
@@ -49,6 +49,7 @@ use OCA\DAV\HookManager;
 use OCP\AppFramework\App;
 use OCP\Calendar\IManager as ICalendarManager;
 use OCP\Contacts\IManager as IContactsManager;
+use OCP\IConfig;
 use OCP\IUser;
 use Symfony\Component\EventDispatcher\GenericEvent;
 
@@ -244,6 +245,19 @@ class Application extends App {
 		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', $listener);
 		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', $listener);
 		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', $listener);
+
+		/**
+		 * In case the user has set their default calendar to this one
+		 */
+		$dispatcher->addListener('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', function (GenericEvent $event) {
+			/** @var IConfig $config */
+			$config = $this->getContainer()->getServer()->getConfig();
+			$principalUri = $event->getArgument('calendarData')['principaluri'];
+			if (strpos($principalUri, 'principals/users') === 0) {
+				list(, $UID) = \Sabre\Uri\split($principalUri);
+				$config->deleteUserValue($UID, 'dav', 'defaultCalendar');
+			}
+		});
 	}
 
 	public function getSyncService() {
diff --git a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php
index e285bbc378c..ce8d0542eaf 100644
--- a/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php
+++ b/apps/dav/lib/CalDAV/InvitationResponse/InvitationResponseServer.php
@@ -84,7 +84,7 @@ class InvitationResponseServer {
 		// 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());
+		$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig()));
 		$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()));
diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php
index 3b2f0374b46..9c5968a333c 100644
--- a/apps/dav/lib/CalDAV/Schedule/Plugin.php
+++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php
@@ -29,6 +29,7 @@ namespace OCA\DAV\CalDAV\Schedule;
 use DateTimeZone;
 use OCA\DAV\CalDAV\CalDavBackend;
 use OCA\DAV\CalDAV\CalendarHome;
+use OCP\IConfig;
 use Sabre\CalDAV\ICalendar;
 use Sabre\DAV\INode;
 use Sabre\DAV\IProperties;
@@ -47,15 +48,31 @@ use Sabre\VObject\ITip;
 use Sabre\VObject\Parameter;
 use Sabre\VObject\Property;
 use Sabre\VObject\Reader;
+use function \Sabre\Uri\split;
 
 class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
 
+	/**
+	 * @var IConfig
+	 */
+	private $config;
+
 	/** @var ITip\Message[] */
 	private $schedulingResponses = [];
 
 	/** @var string|null */
 	private $pathOfCalendarObjectChange = null;
 
+	public const CALENDAR_USER_TYPE = '{' . self::NS_CALDAV . '}calendar-user-type';
+	public const SCHEDULE_DEFAULT_CALENDAR_URL = '{' . Plugin::NS_CALDAV . '}schedule-default-calendar-URL';
+
+	/**
+	 * @param IConfig $config
+	 */
+	public function __construct(IConfig $config) {
+		$this->config = $config;
+	}
+
 	/**
 	 * Initializes the plugin
 	 *
@@ -81,13 +98,12 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
 	public function propFind(PropFind $propFind, INode $node) {
 		if ($node instanceof IPrincipal) {
 			// overwrite Sabre/Dav's implementation
-			$propFind->handle('{' . self::NS_CALDAV . '}calendar-user-type', function () use ($node) {
+			$propFind->handle(self::CALENDAR_USER_TYPE, function () use ($node) {
 				if ($node instanceof IProperties) {
-					$calendarUserType = '{' . self::NS_CALDAV . '}calendar-user-type';
-					$props = $node->getProperties([$calendarUserType]);
+					$props = $node->getProperties([self::CALENDAR_USER_TYPE]);
 
-					if (isset($props[$calendarUserType])) {
-						return $props[$calendarUserType];
+					if (isset($props[self::CALENDAR_USER_TYPE])) {
+						return $props[self::CALENDAR_USER_TYPE];
 					}
 				}
 
@@ -261,7 +277,7 @@ EOF;
 	 */
 	public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) {
 		if ($node instanceof IPrincipal) {
-			$propFind->handle('{' . self::NS_CALDAV . '}schedule-default-calendar-URL', function () use ($node) {
+			$propFind->handle(self::SCHEDULE_DEFAULT_CALENDAR_URL, function () use ($node) {
 				/** @var \OCA\DAV\CalDAV\Plugin $caldavPlugin */
 				$caldavPlugin = $this->server->getPlugin('caldav');
 				$principalUrl = $node->getPrincipalUrl();
@@ -272,12 +288,13 @@ EOF;
 				}
 
 				if (strpos($principalUrl, 'principals/users') === 0) {
-					$uri = CalDavBackend::PERSONAL_CALENDAR_URI;
-					$displayname = CalDavBackend::PERSONAL_CALENDAR_NAME;
+					list(, $userId) = split($principalUrl);
+					$uri = $this->config->getUserValue($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
+					$displayName = CalDavBackend::PERSONAL_CALENDAR_NAME;
 				} elseif (strpos($principalUrl, 'principals/calendar-resources') === 0 ||
 						  strpos($principalUrl, 'principals/calendar-rooms') === 0) {
 					$uri = CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI;
-					$displayname = CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME;
+					$displayName = CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME;
 				} else {
 					// How did we end up here?
 					// TODO - throw exception or just ignore?
@@ -288,7 +305,7 @@ EOF;
 				$calendarHome = $this->server->tree->getNodeForPath($calendarHomePath);
 				if (!$calendarHome->childExists($uri)) {
 					$calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [
-						'{DAV:}displayname' => $displayname,
+						'{DAV:}displayname' => $displayName,
 					]);
 				}
 
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index b71c16e2319..9d120ddbdbf 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -151,7 +151,7 @@ class Server {
 		if ($this->requestIsForSubtree(['calendars', 'public-calendars', 'system-calendars', 'principals'])) {
 			$this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin());
 			$this->server->addPlugin(new \OCA\DAV\CalDAV\ICSExportPlugin\ICSExportPlugin(\OC::$server->getConfig(), \OC::$server->getLogger()));
-			$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin());
+			$this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(\OC::$server->getConfig()));
 			if (\OC::$server->getConfig()->getAppValue('dav', 'sendInvitations', 'yes') === 'yes') {
 				$this->server->addPlugin(\OC::$server->query(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class));
 			}
diff --git a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
index 859dccbe489..aa245b71419 100644
--- a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
+++ b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php
@@ -25,28 +25,68 @@
 
 namespace OCA\DAV\Tests\unit\CalDAV\Schedule;
 
+use OCA\DAV\CalDAV\CalDavBackend;
+use OCA\DAV\CalDAV\CalendarHome;
+use OCA\DAV\CalDAV\Plugin as CalDAVPlugin;
 use OCA\DAV\CalDAV\Schedule\Plugin;
+use OCP\IConfig;
+use PHPUnit\Framework\MockObject\MockObject;
+use Sabre\DAV\PropFind;
 use Sabre\DAV\Server;
+use Sabre\DAV\Tree;
 use Sabre\DAV\Xml\Property\Href;
+use Sabre\DAV\Xml\Property\LocalHref;
+use Sabre\DAVACL\IPrincipal;
+use Sabre\HTTP\ResponseInterface;
 use Sabre\VObject\Parameter;
 use Sabre\VObject\Property\ICalendar\CalAddress;
+use Sabre\Xml\Service;
 use Test\TestCase;
 
 class PluginTest extends TestCase {
 	/** @var Plugin */
 	private $plugin;
-	/** @var Server|\PHPUnit_Framework_MockObject_MockObject */
+	/** @var Server|MockObject */
 	private $server;
 
+	/** @var IConfig|MockObject  */
+	private $config;
+
 	protected function setUp(): void {
 		parent::setUp();
 
 		$this->server = $this->createMock(Server::class);
+		$this->config = $this->createMock(IConfig::class);
+
+		$response = $this->getMockBuilder(ResponseInterface::class)
+			->disableOriginalConstructor()
+			->getMock();
 
-		$this->plugin = new Plugin();
+		$this->server->httpResponse = $response;
+		$this->server->xml = new Service();
+
+		$this->plugin = new Plugin($this->config);
 		$this->plugin->initialize($this->server);
 	}
 
+	public function testInitialize() {
+		$plugin = new Plugin($this->config);
+
+		$this->server->expects($this->at(7))
+			->method('on')
+			->with('propFind', [$plugin, 'propFindDefaultCalendarUrl'], 90);
+
+		$this->server->expects($this->at(8))
+			->method('on')
+			->with('afterWriteContent', [$plugin, 'dispatchSchedulingResponses']);
+
+		$this->server->expects($this->at(9))
+			->method('on')
+			->with('afterCreateFile', [$plugin, 'dispatchSchedulingResponses']);
+
+		$plugin->initialize($this->server);
+	}
+
 	public function testGetAddressesForPrincipal() {
 		$href = $this->createMock(Href::class);
 		$href
@@ -125,4 +165,183 @@ class PluginTest extends TestCase {
 		$this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property2]));
 		$this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property3]));
 	}
+
+	public function propFindDefaultCalendarUrlProvider(): array {
+		return [
+			[
+				'principals/users/myuser',
+				'calendars/myuser',
+				false,
+				CalDavBackend::PERSONAL_CALENDAR_URI,
+				CalDavBackend::PERSONAL_CALENDAR_NAME,
+				true
+			],
+			[
+				'principals/users/myuser',
+				'calendars/myuser',
+				false,
+				CalDavBackend::PERSONAL_CALENDAR_URI,
+				CalDavBackend::PERSONAL_CALENDAR_NAME,
+				false
+			],
+			[
+				'principals/users/myuser',
+				null,
+				false,
+				CalDavBackend::PERSONAL_CALENDAR_URI,
+				CalDavBackend::PERSONAL_CALENDAR_NAME,
+				true
+			],
+			[
+				'principals/users/myuser',
+				'calendars/myuser',
+				false,
+				CalDavBackend::PERSONAL_CALENDAR_URI,
+				CalDavBackend::PERSONAL_CALENDAR_NAME,
+				true,
+				false,
+			],
+			[
+				'principals/users/myuser',
+				'calendars/myuser',
+				false,
+				'my_other_calendar',
+				'My Other Calendar',
+				true
+			],
+			[
+				'principals/calendar-resources',
+				'system-calendars/calendar-resources/myuser',
+				true,
+				CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
+				CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
+				true
+			],
+			[
+				'principals/calendar-resources',
+				'system-calendars/calendar-resources/myuser',
+				true,
+				CalDavBackend::RESOURCE_BOOKING_CALENDAR_URI,
+				CalDavBackend::RESOURCE_BOOKING_CALENDAR_NAME,
+				false
+			],
+			[
+				'principals/something-else',
+				'calendars/whatever',
+				false,
+				CalDavBackend::PERSONAL_CALENDAR_URI,
+				CalDavBackend::PERSONAL_CALENDAR_NAME,
+				true
+			],
+		];
+	}
+
+	/**
+	 * @dataProvider propFindDefaultCalendarUrlProvider
+	 * @param string $principalUri
+	 * @param string $calendarHome
+	 * @param bool $isResource
+	 * @param string $calendarUri
+	 * @param string $displayName
+	 * @param bool $exists
+	 * @param bool $propertiesForPath
+	 */
+	public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $propertiesForPath = true) {
+		/** @var PropFind $propFind */
+		$propFind = new PropFind(
+			$principalUri,
+			[
+				Plugin::SCHEDULE_DEFAULT_CALENDAR_URL
+			],
+			0
+		);
+		/** @var IPrincipal|MockObject $node */
+		$node = $this->getMockBuilder(IPrincipal::class)
+			->disableOriginalConstructor()
+			->getMock();
+
+		$node->expects($this->once())
+			->method('getPrincipalUrl')
+			->with()
+			->willReturn($principalUri);
+
+		$calDAVPlugin = $this->getMockBuilder(CalDAVPlugin::class)
+			->disableOriginalConstructor()
+			->getMock();
+
+		$calDAVPlugin->expects($this->once())
+			->method('getCalendarHomeForPrincipal')
+			->willReturn($calendarHome);
+
+		$this->server->expects($this->once())
+			->method('getPlugin')
+			->with('caldav')
+			->willReturn($calDAVPlugin);
+		if (!$calendarHome) {
+			$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
+
+			$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
+			return;
+		}
+		if ($principalUri === 'principals/something-else') {
+			$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
+
+			$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
+			return;
+		}
+		if (!$isResource) {
+			$this->config->expects($this->once())
+				->method('getUserValue')
+				->with('myuser', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI)
+				->willReturn($calendarUri);
+		}
+
+		$calendarHomeObject = $this->createMock(CalendarHome::class);
+		$calendarHomeObject->expects($this->once())
+			->method('childExists')
+			->with($calendarUri)
+			->willReturn($exists);
+
+		if (!$exists) {
+			$calendarBackend = $this->createMock(CalDavBackend::class);
+			$calendarBackend->expects($this->once())
+				->method('createCalendar')
+				->with($principalUri, $calendarUri, [
+					'{DAV:}displayname' => $displayName,
+				]);
+
+			$calendarHomeObject->expects($this->once())
+				->method('getCalDAVBackend')
+				->with()
+				->willReturn($calendarBackend);
+		}
+
+		/** @var Tree|MockObject $tree */
+		$tree = $this->createMock(Tree::class);
+		$tree->expects($this->once())
+			->method('getNodeForPath')
+			->with($calendarHome)
+			->willReturn($calendarHomeObject);
+		$this->server->tree = $tree;
+
+		$properties = $propertiesForPath ? [
+			['href' => '/remote.php/dav/' . $calendarHome . '/' . $calendarUri]
+		] : [];
+
+		$this->server->expects($this->once())
+			->method('getPropertiesForPath')
+			->with($calendarHome .'/' . $calendarUri, [], 1)
+			->willReturn($properties);
+
+		$this->plugin->propFindDefaultCalendarUrl($propFind, $node);
+
+		if (!$propertiesForPath) {
+			$this->assertNull($propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL));
+			return;
+		}
+
+		/** @var LocalHref $result */
+		$result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL);
+		$this->assertEquals('/remote.php/dav/'. $calendarHome . '/' . $calendarUri, $result->getHref());
+	}
 }
-- 
GitLab