From 3a975d0a89e4576f60d6b9628c3de902e683fdc0 Mon Sep 17 00:00:00 2001 From: Richard Steinmetz Date: Tue, 11 Jun 2024 09:42:03 +0200 Subject: [PATCH] fix(caldav): event links in shared calendar notifications Signed-off-by: Richard Steinmetz --- .../lib/CalDAV/Activity/Provider/Event.php | 28 ++++++++----- .../dav/lib/CalDAV/Activity/Provider/Todo.php | 12 +++--- .../CalDAV/Activity/Provider/EventTest.php | 39 ++++++++++++++++++- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Event.php b/apps/dav/lib/CalDAV/Activity/Provider/Event.php index 45354faae69c3..45d4f731e20a4 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Event.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Event.php @@ -60,7 +60,7 @@ public function __construct(IFactory $languageFactory, IURLGenerator $url, IMana * @param array $eventData * @return array */ - protected function generateObjectParameter(array $eventData) { + protected function generateObjectParameter(array $eventData, string $affectedUser): array { if (!isset($eventData['id']) || !isset($eventData['name'])) { throw new \InvalidArgumentException(); } @@ -76,7 +76,15 @@ protected function generateObjectParameter(array $eventData) { // The calendar app needs to be manually loaded for the routes to be loaded OC_App::loadApp('calendar'); $linkData = $eventData['link']; - $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $linkData['owner'] . '/' . $linkData['calendar_uri'] . '/' . $linkData['object_uri']); + if ($affectedUser === $linkData['owner']) { + $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $linkData['owner'] . '/' . $linkData['calendar_uri'] . '/' . $linkData['object_uri']); + } else { + // Can't use the "real" owner and calendar names here because we create a custom + // calendar for incoming shares with the name "_shared_by_". + // Hack: Fix the link by generating it for the incoming shared calendar instead, + // as seen from the affected user. + $objectId = base64_encode($this->url->getWebroot() . '/remote.php/dav/calendars/' . $affectedUser . '/' . $linkData['calendar_uri'] . '_shared_by_' . $linkData['owner'] . '/' . $linkData['object_uri']); + } $link = [ 'view' => 'dayGridMonth', 'timeRange' => 'now', @@ -168,7 +176,7 @@ protected function getParameters(IEvent $event) { return [ 'actor' => $this->generateUserParameter($parameters['actor']), 'calendar' => $this->generateCalendarParameter($parameters['calendar'], $this->l), - 'event' => $this->generateClassifiedObjectParameter($parameters['object']), + 'event' => $this->generateClassifiedObjectParameter($parameters['object'], $event->getAffectedUser()), ]; case self::SUBJECT_OBJECT_ADD . '_event_self': case self::SUBJECT_OBJECT_DELETE . '_event_self': @@ -177,7 +185,7 @@ protected function getParameters(IEvent $event) { case self::SUBJECT_OBJECT_RESTORE . '_event_self': return [ 'calendar' => $this->generateCalendarParameter($parameters['calendar'], $this->l), - 'event' => $this->generateClassifiedObjectParameter($parameters['object']), + 'event' => $this->generateClassifiedObjectParameter($parameters['object'], $event->getAffectedUser()), ]; } } @@ -189,13 +197,13 @@ protected function getParameters(IEvent $event) { 'actor' => $this->generateUserParameter($parameters['actor']), 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l), 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l), - 'event' => $this->generateClassifiedObjectParameter($parameters['object']), + 'event' => $this->generateClassifiedObjectParameter($parameters['object'], $event->getAffectedUser()), ]; case self::SUBJECT_OBJECT_MOVE . '_event_self': return [ 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l), 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l), - 'event' => $this->generateClassifiedObjectParameter($parameters['object']), + 'event' => $this->generateClassifiedObjectParameter($parameters['object'], $event->getAffectedUser()), ]; } } @@ -212,22 +220,22 @@ protected function getParameters(IEvent $event) { return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateLegacyCalendarParameter($event->getObjectId(), $parameters[1]), - 'event' => $this->generateObjectParameter($parameters[2]), + 'event' => $this->generateObjectParameter($parameters[2], $event->getAffectedUser()), ]; case self::SUBJECT_OBJECT_ADD . '_event_self': case self::SUBJECT_OBJECT_DELETE . '_event_self': case self::SUBJECT_OBJECT_UPDATE . '_event_self': return [ 'calendar' => $this->generateLegacyCalendarParameter($event->getObjectId(), $parameters[1]), - 'event' => $this->generateObjectParameter($parameters[2]), + 'event' => $this->generateObjectParameter($parameters[2], $event->getAffectedUser()), ]; } throw new \InvalidArgumentException(); } - private function generateClassifiedObjectParameter(array $eventData) { - $parameter = $this->generateObjectParameter($eventData); + private function generateClassifiedObjectParameter(array $eventData, string $affectedUser): array { + $parameter = $this->generateObjectParameter($eventData, $affectedUser); if (!empty($eventData['classified'])) { $parameter['name'] = $this->l->t('Busy'); } diff --git a/apps/dav/lib/CalDAV/Activity/Provider/Todo.php b/apps/dav/lib/CalDAV/Activity/Provider/Todo.php index 052370c7d3aa9..68a95341ff24b 100644 --- a/apps/dav/lib/CalDAV/Activity/Provider/Todo.php +++ b/apps/dav/lib/CalDAV/Activity/Provider/Todo.php @@ -85,7 +85,7 @@ protected function getParameters(IEvent $event) { return [ 'actor' => $this->generateUserParameter($parameters['actor']), 'calendar' => $this->generateCalendarParameter($parameters['calendar'], $this->l), - 'todo' => $this->generateObjectParameter($parameters['object']), + 'todo' => $this->generateObjectParameter($parameters['object'], $event->getAffectedUser()), ]; case self::SUBJECT_OBJECT_ADD . '_todo_self': case self::SUBJECT_OBJECT_DELETE . '_todo_self': @@ -94,7 +94,7 @@ protected function getParameters(IEvent $event) { case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self': return [ 'calendar' => $this->generateCalendarParameter($parameters['calendar'], $this->l), - 'todo' => $this->generateObjectParameter($parameters['object']), + 'todo' => $this->generateObjectParameter($parameters['object'], $event->getAffectedUser()), ]; } } @@ -106,13 +106,13 @@ protected function getParameters(IEvent $event) { 'actor' => $this->generateUserParameter($parameters['actor']), 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l), 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l), - 'todo' => $this->generateObjectParameter($parameters['object']), + 'todo' => $this->generateObjectParameter($parameters['object'], $event->getAffectedUser()), ]; case self::SUBJECT_OBJECT_MOVE . '_todo_self': return [ 'sourceCalendar' => $this->generateCalendarParameter($parameters['sourceCalendar'], $this->l), 'targetCalendar' => $this->generateCalendarParameter($parameters['targetCalendar'], $this->l), - 'todo' => $this->generateObjectParameter($parameters['object']), + 'todo' => $this->generateObjectParameter($parameters['object'], $event->getAffectedUser()), ]; } } @@ -131,7 +131,7 @@ protected function getParameters(IEvent $event) { return [ 'actor' => $this->generateUserParameter($parameters[0]), 'calendar' => $this->generateLegacyCalendarParameter($event->getObjectId(), $parameters[1]), - 'todo' => $this->generateObjectParameter($parameters[2]), + 'todo' => $this->generateObjectParameter($parameters[2], $event->getAffectedUser()), ]; case self::SUBJECT_OBJECT_ADD . '_todo_self': case self::SUBJECT_OBJECT_DELETE . '_todo_self': @@ -140,7 +140,7 @@ protected function getParameters(IEvent $event) { case self::SUBJECT_OBJECT_UPDATE . '_todo_needs_action_self': return [ 'calendar' => $this->generateLegacyCalendarParameter($event->getObjectId(), $parameters[1]), - 'todo' => $this->generateObjectParameter($parameters[2]), + 'todo' => $this->generateObjectParameter($parameters[2], $event->getAffectedUser()), ]; } diff --git a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php index 2760495212495..b0b2cc936a5b2 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php @@ -91,7 +91,9 @@ public function dataGenerateObjectParameter() { * @param bool $calendarAppEnabled */ public function testGenerateObjectParameter(int $id, string $name, ?array $link, bool $calendarAppEnabled = true): void { + $affectedUser = 'otheruser'; if ($link) { + $affectedUser = $link['owner']; $generatedLink = [ 'view' => 'dayGridMonth', 'timeRange' => 'now', @@ -124,7 +126,40 @@ public function testGenerateObjectParameter(int $id, string $name, ?array $link, if ($link && $calendarAppEnabled) { $result['link'] = 'fullLink'; } - $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter])); + $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, $affectedUser])); + } + + public function testGenerateObjectParameterWithSharedCalendar(): void { + $link = [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'personal', + 'owner' => 'sharer' + ]; + $generatedLink = [ + 'view' => 'dayGridMonth', + 'timeRange' => 'now', + 'mode' => 'sidebar', + 'objectId' => base64_encode('/remote.php/dav/calendars/sharee/' . $link['calendar_uri'] . '_shared_by_sharer/' . $link['object_uri']), + 'recurrenceId' => 'next' + ]; + $this->appManager->expects($this->once()) + ->method('isEnabledForUser') + ->with('calendar') + ->willReturn(true); + $this->url->expects($this->once()) + ->method('getWebroot'); + $this->url->expects($this->once()) + ->method('linkToRouteAbsolute') + ->with('calendar.view.indexview.timerange.edit', $generatedLink) + ->willReturn('fullLink'); + $objectParameter = ['id' => 42, 'name' => 'calendar', 'link' => $link]; + $result = [ + 'type' => 'calendar-event', + 'id' => 42, + 'name' => 'calendar', + 'link' => 'fullLink', + ]; + $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, 'sharee'])); } public function dataGenerateObjectParameterThrows() { @@ -143,6 +178,6 @@ public function dataGenerateObjectParameterThrows() { public function testGenerateObjectParameterThrows($eventData, string $exception = InvalidArgumentException::class): void { $this->expectException($exception); - $this->invokePrivate($this->provider, 'generateObjectParameter', [$eventData]); + $this->invokePrivate($this->provider, 'generateObjectParameter', [$eventData, 'no_user']); } }