From ebdf66b70619a30fd3f9172c1b725b8f56ea9358 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 8 Mar 2020 17:33:27 +0100 Subject: [PATCH 1/2] Provide dav setting for user's default calendar And add tests to handle schedule-default-calendar-URL Signed-off-by: Thomas Citharel --- 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 82fddd152e..29733a3a62 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 29c77cad07..c22afa755c 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 e285bbc378..ce8d0542ea 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 3b2f0374b4..9c5968a333 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 b71c16e231..9d120ddbdb 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 859dccbe48..aa245b7141 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); - $this->plugin = new Plugin(); + $response = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $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()); + } } From 0408c37b3de77fd351c1a2fa91f74e47fc7e6cd6 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Sun, 5 Apr 2020 20:57:26 +0200 Subject: [PATCH 2/2] Only delete the default calendar setting when the default calendar itself is deleted Signed-off-by: Thomas Citharel --- apps/dav/lib/AppInfo/Application.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index c22afa755c..d5e3358d96 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -255,7 +255,10 @@ class Application extends App { $principalUri = $event->getArgument('calendarData')['principaluri']; if (strpos($principalUri, 'principals/users') === 0) { list(, $UID) = \Sabre\Uri\split($principalUri); - $config->deleteUserValue($UID, 'dav', 'defaultCalendar'); + $uri = $event->getArgument('calendarData')['uri']; + if ($config->getUserValue($UID, 'dav', 'defaultCalendar') === $uri) { + $config->deleteUserValue($UID, 'dav', 'defaultCalendar'); + } } }); }