From 79adeb8b088c239d20b7d8c4983190d41d26a262 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Mon, 13 Feb 2023 09:27:25 +0100 Subject: [PATCH] feat(dav): Directly access shared calendars by URI Each time we access a shared calendar details (with getChild), this loads every user's calendars and iterates until the correct one is found. Now we lookup from the shared calendars which ones has the correct URI, just iterating on the possible shares (for instance a calendar can be shared directly through user-to-user share or through user-to-group share). Signed-off-by: Thomas Citharel --- apps/dav/lib/CalDAV/CalDavBackend.php | 81 ++++++++++++++++++++++++++- apps/dav/lib/CalDAV/CalendarHome.php | 5 +- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index b60d731b21512..f89565f0be59b 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -389,7 +389,7 @@ public function getCalendarsForUser($principalUri) { ->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri'))) ->andWhere($query->expr()->eq('s.type', $query->createParameter('type'))) ->setParameter('type', 'calendar') - ->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY); + ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY); $result = $query->executeQuery(); @@ -657,6 +657,85 @@ public function getCalendarByUri($principal, $uri) { return $calendar; } + public function getSharedCalendarByUri(string $principalUri, string $calendarSharedUri): ?array { + // query for shared calendars + [$calendarUri, ] = explode('_shared_by_', $calendarSharedUri, 2); + + $principals = $this->principalBackend->getGroupMembership($principalUri, true); + $principals = array_merge($principals, $this->principalBackend->getCircleMembership($principalUri)); + + $principals[] = $principalUri; + + $fields = array_column($this->propertyMap, 0); + $fields[] = 'a.id'; + $fields[] = 'a.uri'; + $fields[] = 'a.synctoken'; + $fields[] = 'a.components'; + $fields[] = 'a.principaluri'; + $fields[] = 'a.transparent'; + $fields[] = 's.access'; + $query = $this->db->getQueryBuilder(); + $query->select($fields) + ->from('dav_shares', 's') + ->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id')) + ->where($query->expr()->eq('a.uri', $query->createParameter('uri'))) + ->andWhere($query->expr()->in('s.principaluri', $query->createParameter('principaluri'))) + ->andWhere($query->expr()->eq('s.type', $query->createParameter('type'))) + ->setParameter('uri', $calendarUri) + ->setParameter('type', 'calendar') + ->setParameter('principaluri', $principals, IQueryBuilder::PARAM_STR_ARRAY); + + $result = $query->executeQuery(); + + $readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only'; + + $calendar = null; + + while ($row = $result->fetch()) { + $row['principaluri'] = (string)$row['principaluri']; + if ($row['principaluri'] === $principalUri) { + continue; + } + + $readOnly = (int)$row['access'] === Backend::ACCESS_READ; + if (isset($calendars[$row['id']])) { + if ($readOnly) { + // New share can not have more permissions then the old one. + continue; + } + if (isset($calendars[$row['id']][$readOnlyPropertyName]) && + $calendars[$row['id']][$readOnlyPropertyName] === 0) { + // Old share is already read-write, no more permissions can be gained + continue; + } + } + + // Fixes for shared calendars + [, $name] = Uri\split($row['principaluri']); + $row['displayname'] = $row['displayname'] . ' (' . ($this->userManager->getDisplayName($name) ?? ($name ?? '')) . ')'; + + $components = []; + if ($row['components']) { + $components = explode(',', $row['components']); + } + + $calendar = [ + 'id' => $row['id'], + 'uri' => $row['uri'] . '_shared_by_' . $name, + 'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint), + '{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken'] ?: '0'), + '{http://sabredav.org/ns}sync-token' => $row['synctoken'] ?: '0', + '{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components), + '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent'] ? 'transparent' : 'opaque'), + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint), + $readOnlyPropertyName => $readOnly, + ]; + } + $calendar = $this->rowToCalendar($row, $calendar); + $calendar = $this->addOwnerPrincipalToCalendar($calendar); + return $this->addResourceTypeToCalendar($row, $calendar); + } + /** * @return array{id: int, uri: string, '{http://calendarserver.org/ns/}getctag': string, '{http://sabredav.org/ns}sync-token': int, '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set': SupportedCalendarComponentSet, '{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp': ScheduleCalendarTransp, '{urn:ietf:params:xml:ns:caldav}calendar-timezone': ?string }|null */ diff --git a/apps/dav/lib/CalDAV/CalendarHome.php b/apps/dav/lib/CalDAV/CalendarHome.php index a59e76d121fb9..ab9669f3a45fb 100644 --- a/apps/dav/lib/CalDAV/CalendarHome.php +++ b/apps/dav/lib/CalDAV/CalendarHome.php @@ -169,8 +169,9 @@ public function getChild($name) { } // Fallback to cover shared calendars - foreach ($this->caldavBackend->getCalendarsForUser($this->principalInfo['uri']) as $calendar) { - if ($calendar['uri'] === $name) { + if ($this->caldavBackend instanceof CalDavBackend) { + $calendar = $this->caldavBackend->getSharedCalendarByUri($this->principalInfo['uri'], $name); + if(!empty($calendar)) { return new Calendar($this->caldavBackend, $calendar, $this->l10n, $this->config, $this->logger); } }