From 4fd58aa45cdd4a15be0f3eafcd353715c37d4d63 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Tue, 7 Feb 2023 18:37:58 +0100 Subject: [PATCH] fix(caldav): Correctly handle calendar recreation for invitations when the current calendar is in the trashbin Follow-up to #32361, see https://github.com/nextcloud/calendar/issues/4098 for details Signed-off-by: Thomas Citharel --- apps/dav/lib/CalDAV/Schedule/Plugin.php | 37 +++++++++++++++---- .../tests/unit/CalDAV/Schedule/PluginTest.php | 29 ++++++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php index ac8521acfee3a..0751638b697e6 100644 --- a/apps/dav/lib/CalDAV/Schedule/Plugin.php +++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php @@ -48,7 +48,6 @@ use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VEvent; use Sabre\VObject\DateTimeParser; -use Sabre\VObject\Document; use Sabre\VObject\FreeBusyGenerator; use Sabre\VObject\ITip; use Sabre\VObject\Parameter; @@ -329,12 +328,12 @@ public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) { /** @var CalendarHome $calendarHome */ $calendarHome = $this->server->tree->getNodeForPath($calendarHomePath); - if (!$calendarHome->childExists($uri)) { + $currentCalendarDeleted = false; + if (!$calendarHome->childExists($uri) || $currentCalendarDeleted = $this->isCalendarDeleted($calendarHome, $uri)) { // If the default calendar doesn't exist if ($isResourceOrRoom) { - $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [ - '{DAV:}displayname' => $displayName, - ]); + // Resources or rooms can't be in the trashbin, so we're fine + $this->createCalendar($calendarHome, $principalUrl, $uri, $displayName); } else { // And we're not handling scheduling on resource/room booking $userCalendars = []; @@ -359,9 +358,16 @@ public function propFindDefaultCalendarUrl(PropFind $propFind, INode $node) { $uri = $userCalendars[0]->getName(); } else { // Otherwise if we have really nothing, create a new calendar - $calendarHome->getCalDAVBackend()->createCalendar($principalUrl, $uri, [ - '{DAV:}displayname' => $displayName, - ]); + if ($currentCalendarDeleted) { + // If the calendar exists but is deleted, we need to purge it first + // This may cause some issues in a non synchronous database setup + $calendar = $this->getCalendar($calendarHome, $uri); + if ($calendar instanceof Calendar) { + $calendar->disableTrashbin(); + $calendar->delete(); + } + } + $this->createCalendar($calendarHome, $principalUrl, $uri, $displayName); } } } @@ -609,4 +615,19 @@ private function stripOffMailTo(string $email): string { return $email; } + + private function getCalendar(CalendarHome $calendarHome, string $uri): INode { + return $calendarHome->getChild($uri); + } + + private function isCalendarDeleted(CalendarHome $calendarHome, string $uri): bool { + $calendar = $this->getCalendar($calendarHome, $uri); + return $calendar instanceof Calendar && $calendar->isDeleted(); + } + + private function createCalendar(CalendarHome $calendarHome, string $principalUri, string $uri, string $displayName): void { + $calendarHome->getCalDAVBackend()->createCalendar($principalUri, $uri, [ + '{DAV:}displayname' => $displayName, + ]); + } } diff --git a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php index 4845188bc88af..8f315eac0ee52 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php @@ -197,6 +197,16 @@ public function propFindDefaultCalendarUrlProvider(): array { false, CalDavBackend::PERSONAL_CALENDAR_URI, CalDavBackend::PERSONAL_CALENDAR_NAME, + true, + true + ], + [ + 'principals/users/myuser', + 'calendars/myuser', + false, + CalDavBackend::PERSONAL_CALENDAR_URI, + CalDavBackend::PERSONAL_CALENDAR_NAME, + false, false, true ], @@ -225,6 +235,7 @@ public function propFindDefaultCalendarUrlProvider(): array { true, false, false, + false, ], [ 'principals/users/myuser', @@ -263,16 +274,8 @@ public function propFindDefaultCalendarUrlProvider(): array { /** * @dataProvider propFindDefaultCalendarUrlProvider - * @param string $principalUri - * @param string|null $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 $hasExistingCalendars = false, bool $propertiesForPath = true): void { - /** @var PropFind $propFind */ + public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $deleted = false, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void { $propFind = new PropFind( $principalUri, [ @@ -328,6 +331,12 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca ->with($calendarUri) ->willReturn($exists); + if ($exists) { + $calendar = $this->createMock(Calendar::class); + $calendar->expects($this->once())->method('isDeleted')->willReturn($deleted); + $calendarHomeObject->expects($deleted && !$hasExistingCalendars ? $this->exactly(2) : $this->once())->method('getChild')->with($calendarUri)->willReturn($calendar); + } + $calendarBackend = $this->createMock(CalDavBackend::class); $calendarUri = $hasExistingCalendars ? 'custom' : $calendarUri; $displayName = $hasExistingCalendars ? 'Custom Calendar' : $displayName; @@ -349,7 +358,7 @@ public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $ca ) ] : []; - if (!$exists) { + if (!$exists || $deleted) { if (!$hasExistingCalendars) { $calendarBackend->expects($this->once()) ->method('createCalendar')