Skip to content

Commit 0cf7db4

Browse files
committed
feat(federation): Add accept/decline mechanism for federated calendars
Signed-off-by: David Dreschner <david.dreschner@nextcloud.com>
1 parent 09dc693 commit 0cf7db4

19 files changed

Lines changed: 856 additions & 31 deletions

apps/dav/appinfo/info.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<name>WebDAV</name>
1111
<summary>WebDAV endpoint</summary>
1212
<description>WebDAV endpoint</description>
13-
<version>1.34.2</version>
13+
<version>1.34.3</version>
1414
<licence>agpl</licence>
1515
<author>owncloud.org</author>
1616
<namespace>DAV</namespace>

apps/dav/appinfo/routes.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
],
1616
'ocs' => [
1717
['name' => 'direct#getUrl', 'url' => '/api/v1/direct', 'verb' => 'POST'],
18+
['name' => 'federated_calendar#getPending', 'url' => '/api/v1/federated_calendars/pending', 'verb' => 'GET'],
19+
['name' => 'federated_calendar#accept', 'url' => '/api/v1/federated_calendars/pending/{id}', 'verb' => 'POST'],
20+
['name' => 'federated_calendar#decline', 'url' => '/api/v1/federated_calendars/pending/{id}', 'verb' => 'DELETE'],
1821
['name' => 'upcoming_events#getEvents', 'url' => '/api/v1/events/upcoming', 'verb' => 'GET'],
1922
['name' => 'out_of_office#getCurrentOutOfOfficeData', 'url' => '/api/v1/outOfOffice/{userId}/now', 'verb' => 'GET'],
2023
['name' => 'out_of_office#getOutOfOffice', 'url' => '/api/v1/outOfOffice/{userId}', 'verb' => 'GET'],

apps/dav/composer/composer/autoload_classmap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@
7676
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarEntity' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarEntity.php',
7777
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
7878
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
79+
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarInvitationService' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarInvitationService.php',
80+
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarInviteNotifier' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarInviteNotifier.php',
7981
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
8082
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
8183
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => $baseDir . '/../lib/CalDAV/Federation/FederationSharingService.php',
@@ -259,6 +261,7 @@
259261
'OCA\\DAV\\Controller\\BirthdayCalendarController' => $baseDir . '/../lib/Controller/BirthdayCalendarController.php',
260262
'OCA\\DAV\\Controller\\DirectController' => $baseDir . '/../lib/Controller/DirectController.php',
261263
'OCA\\DAV\\Controller\\ExampleContentController' => $baseDir . '/../lib/Controller/ExampleContentController.php',
264+
'OCA\\DAV\\Controller\\FederatedCalendarController' => $baseDir . '/../lib/Controller/FederatedCalendarController.php',
262265
'OCA\\DAV\\Controller\\InvitationResponseController' => $baseDir . '/../lib/Controller/InvitationResponseController.php',
263266
'OCA\\DAV\\Controller\\OutOfOfficeController' => $baseDir . '/../lib/Controller/OutOfOfficeController.php',
264267
'OCA\\DAV\\Controller\\UpcomingEventsController' => $baseDir . '/../lib/Controller/UpcomingEventsController.php',
@@ -388,6 +391,7 @@
388391
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => $baseDir . '/../lib/Migration/Version1031Date20240610134258.php',
389392
'OCA\\DAV\\Migration\\Version1034Date20250605132605' => $baseDir . '/../lib/Migration/Version1034Date20250605132605.php',
390393
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => $baseDir . '/../lib/Migration/Version1034Date20250813093701.php',
394+
'OCA\\DAV\\Migration\\Version1040Date20260805000000' => $baseDir . '/../lib/Migration/Version1040Date20260805000000.php',
391395
'OCA\\DAV\\Model\\ExampleEvent' => $baseDir . '/../lib/Model/ExampleEvent.php',
392396
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => $baseDir . '/../lib/Paginate/LimitedCopyIterator.php',
393397
'OCA\\DAV\\Paginate\\PaginateCache' => $baseDir . '/../lib/Paginate/PaginateCache.php',

apps/dav/composer/composer/autoload_static.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ class ComposerStaticInitDAV
9191
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarEntity' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarEntity.php',
9292
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
9393
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
94+
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarInvitationService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarInvitationService.php',
95+
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarInviteNotifier' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarInviteNotifier.php',
9496
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
9597
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
9698
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederationSharingService.php',
@@ -274,6 +276,7 @@ class ComposerStaticInitDAV
274276
'OCA\\DAV\\Controller\\BirthdayCalendarController' => __DIR__ . '/..' . '/../lib/Controller/BirthdayCalendarController.php',
275277
'OCA\\DAV\\Controller\\DirectController' => __DIR__ . '/..' . '/../lib/Controller/DirectController.php',
276278
'OCA\\DAV\\Controller\\ExampleContentController' => __DIR__ . '/..' . '/../lib/Controller/ExampleContentController.php',
279+
'OCA\\DAV\\Controller\\FederatedCalendarController' => __DIR__ . '/..' . '/../lib/Controller/FederatedCalendarController.php',
277280
'OCA\\DAV\\Controller\\InvitationResponseController' => __DIR__ . '/..' . '/../lib/Controller/InvitationResponseController.php',
278281
'OCA\\DAV\\Controller\\OutOfOfficeController' => __DIR__ . '/..' . '/../lib/Controller/OutOfOfficeController.php',
279282
'OCA\\DAV\\Controller\\UpcomingEventsController' => __DIR__ . '/..' . '/../lib/Controller/UpcomingEventsController.php',
@@ -403,6 +406,7 @@ class ComposerStaticInitDAV
403406
'OCA\\DAV\\Migration\\Version1031Date20240610134258' => __DIR__ . '/..' . '/../lib/Migration/Version1031Date20240610134258.php',
404407
'OCA\\DAV\\Migration\\Version1034Date20250605132605' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250605132605.php',
405408
'OCA\\DAV\\Migration\\Version1034Date20250813093701' => __DIR__ . '/..' . '/../lib/Migration/Version1034Date20250813093701.php',
409+
'OCA\\DAV\\Migration\\Version1040Date20260805000000' => __DIR__ . '/..' . '/../lib/Migration/Version1040Date20260805000000.php',
406410
'OCA\\DAV\\Model\\ExampleEvent' => __DIR__ . '/..' . '/../lib/Model/ExampleEvent.php',
407411
'OCA\\DAV\\Paginate\\LimitedCopyIterator' => __DIR__ . '/..' . '/../lib/Paginate/LimitedCopyIterator.php',
408412
'OCA\\DAV\\Paginate\\PaginateCache' => __DIR__ . '/..' . '/../lib/Paginate/PaginateCache.php',

apps/dav/lib/AppInfo/Application.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use OCA\DAV\CalDAV\CalendarManager;
1515
use OCA\DAV\CalDAV\CalendarProvider;
1616
use OCA\DAV\CalDAV\Federation\CalendarFederationProvider;
17+
use OCA\DAV\CalDAV\Federation\FederatedCalendarInviteNotifier;
1718
use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
1819
use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider;
1920
use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider;
@@ -212,6 +213,7 @@ public function register(IRegistrationContext $context): void {
212213
$context->registerEventListener(CalendarObjectDeletedEvent::class, CalendarFederationNotificationListener::class);
213214

214215
$context->registerNotifierService(Notifier::class);
216+
$context->registerNotifierService(FederatedCalendarInviteNotifier::class);
215217

216218
$context->registerCalendarProvider(CalendarProvider::class);
217219
$context->registerCalendarProvider(CachedSubscriptionProvider::class);

apps/dav/lib/BackgroundJob/FederatedCalendarSyncJob.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace OCA\DAV\BackgroundJob;
1111

1212
use OCA\DAV\CalDAV\Federation\CalendarFederationConfig;
13+
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
1314
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
1415
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
1516
use OCP\AppFramework\Db\DoesNotExistException;
@@ -50,6 +51,11 @@ protected function run($argument): void {
5051
return;
5152
}
5253

54+
// Never fetch data for invitations the sharee did not accept (yet)
55+
if ($calendar->getState() !== FederatedCalendarEntity::STATE_ACCEPTED) {
56+
return;
57+
}
58+
5359
try {
5460
$this->syncService->syncOne($calendar);
5561
} catch (ClientExceptionInterface $e) {

apps/dav/lib/CalDAV/CalDavBackend.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4049,7 +4049,10 @@ public function unshare(IShareable $shareable, string $principal): void {
40494049
* @return array<string, mixed>[]
40504050
*/
40514051
public function getFederatedCalendarsForUser(string $principalUri): array {
4052-
$federatedCalendars = $this->federatedCalendarMapper->findByPrincipalUri($principalUri);
4052+
$federatedCalendars = $this->federatedCalendarMapper->findByPrincipalUri(
4053+
$principalUri,
4054+
FederatedCalendarEntity::STATE_ACCEPTED,
4055+
);
40534056
return array_map(
40544057
static fn (FederatedCalendarEntity $entity) => $entity->toCalendarInfo(),
40554058
$federatedCalendars,
@@ -4058,6 +4061,10 @@ public function getFederatedCalendarsForUser(string $principalUri): array {
40584061

40594062
public function getFederatedCalendarByUri(string $principalUri, string $uri): ?array {
40604063
$federatedCalendar = $this->federatedCalendarMapper->findByUri($principalUri, $uri);
4061-
return $federatedCalendar?->toCalendarInfo();
4064+
if ($federatedCalendar === null
4065+
|| $federatedCalendar->getState() !== FederatedCalendarEntity::STATE_ACCEPTED) {
4066+
return null;
4067+
}
4068+
return $federatedCalendar->toCalendarInfo();
40624069
}
40634070
}

apps/dav/lib/CalDAV/Federation/CalendarFederationConfig.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,11 @@ public function isIncomingServer2serverShareEnabled(): bool {
4141
}
4242
return $this->appConfig->getValueBool('files_sharing', 'incoming_server2server_share_enabled', true);
4343
}
44+
45+
/**
46+
* Check if incoming shares from trusted servers are accepted automatically
47+
*/
48+
public function isTrustedShareAutoAcceptEnabled(): bool {
49+
return $this->appConfig->getValueBool('files_sharing', 'federatedTrustedShareAutoAccept', true);
50+
}
4451
}

apps/dav/lib/CalDAV/Federation/CalendarFederationProvider.php

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public function __construct(
3535
private readonly CalendarFederationConfig $calendarFederationConfig,
3636
private readonly IJobList $jobList,
3737
private readonly ICloudIdManager $cloudIdManager,
38+
private readonly FederatedCalendarInvitationService $invitationService,
3839
) {
3940
}
4041

@@ -122,25 +123,49 @@ public function shareReceived(ICloudFederationShare $share): string {
122123

123124
$sharedWithPrincipal = 'principals/users/' . $share->getShareWith();
124125

125-
// Delete existing incoming federated share first
126-
$this->federatedCalendarMapper->deleteByUri($sharedWithPrincipal, $calendarUri);
127-
128-
$calendar = new FederatedCalendarEntity();
129-
$calendar->setPrincipaluri($sharedWithPrincipal);
130-
$calendar->setUri($calendarUri);
131-
$calendar->setRemoteUrl($calendarUrl);
132-
$calendar->setDisplayName($displayName);
133-
$calendar->setColor($color);
134-
$calendar->setToken($share->getShareSecret());
135-
$calendar->setSharedBy($share->getSharedBy());
136-
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
137-
$calendar->setPermissions($permissions);
138-
$calendar->setComponents($components);
139-
$calendar = $this->federatedCalendarMapper->insert($calendar);
140-
141-
$this->jobList->add(FederatedCalendarSyncJob::class, [
142-
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
143-
]);
126+
$calendar = $this->federatedCalendarMapper->findByUri($sharedWithPrincipal, $calendarUri);
127+
$isNew = $calendar === null;
128+
129+
if ($calendar === null) {
130+
$calendar = new FederatedCalendarEntity();
131+
$calendar->setPrincipaluri($sharedWithPrincipal);
132+
$calendar->setUri($calendarUri);
133+
$calendar->setRemoteUrl($calendarUrl);
134+
$calendar->setDisplayName($displayName);
135+
$calendar->setColor($color);
136+
$calendar->setToken($share->getShareSecret());
137+
$calendar->setSharedBy($share->getSharedBy());
138+
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
139+
$calendar->setPermissions($permissions);
140+
$calendar->setComponents($components);
141+
$calendar->setState(FederatedCalendarEntity::STATE_PENDING);
142+
$calendar = $this->federatedCalendarMapper->insert($calendar);
143+
} else {
144+
$calendar->setToken($share->getShareSecret());
145+
$calendar->setPermissions($permissions);
146+
$calendar->setComponents($components);
147+
if ($calendar->getState() === FederatedCalendarEntity::STATE_PENDING) {
148+
// The open invitation shows the sharer's metadata, keep it
149+
// fresh. Accepted calendars are not touched as the sharee owns
150+
// the display name and color from that point on.
151+
$calendar->setDisplayName($displayName);
152+
$calendar->setColor($color);
153+
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
154+
}
155+
$this->federatedCalendarMapper->update($calendar);
156+
}
157+
158+
if ($calendar->getState() === FederatedCalendarEntity::STATE_ACCEPTED) {
159+
// Re-share of an already accepted calendar: just refresh the data
160+
$this->jobList->add(FederatedCalendarSyncJob::class, [
161+
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
162+
]);
163+
} elseif ($this->invitationService->shouldAutoAccept($share->getOwner(), $calendar->getRemoteUrl())) {
164+
$this->invitationService->accept($calendar);
165+
} elseif ($isNew) {
166+
// A re-shared pending calendar keeps its original invitation
167+
$this->invitationService->notifyAboutNewShare($calendar);
168+
}
144169

145170
return (string)$calendar->getId();
146171
}
@@ -196,6 +221,7 @@ private function handleSyncCalendarNotification(array $notification): array {
196221
$calendarUrl,
197222
'principals/users/' . $shareWith->getUser(),
198223
$sharedSecret,
224+
FederatedCalendarEntity::STATE_ACCEPTED,
199225
);
200226
if (empty($calendars)) {
201227
throw new ShareNotFound('Calendar is not shared with the sharee');

apps/dav/lib/CalDAV/Federation/FederatedCalendarEntity.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,13 @@
3939
* @method void setSharedByDisplayName(string $sharedByDisplayName)
4040
* @method string getComponents()
4141
* @method void setComponents(string $components)
42+
* @method int getState()
43+
* @method void setState(int $state)
4244
*/
4345
class FederatedCalendarEntity extends Entity {
46+
public const STATE_PENDING = 0;
47+
public const STATE_ACCEPTED = 1;
48+
4449
protected string $principaluri = '';
4550
protected string $uri = '';
4651
protected string $displayName = '';
@@ -53,6 +58,7 @@ class FederatedCalendarEntity extends Entity {
5358
protected string $sharedBy = '';
5459
protected string $sharedByDisplayName = '';
5560
protected string $components = '';
61+
protected int $state = self::STATE_PENDING;
5662

5763
public function __construct() {
5864
$this->addType('principaluri', Types::STRING);
@@ -67,6 +73,7 @@ public function __construct() {
6773
$this->addType('sharedBy', Types::STRING);
6874
$this->addType('sharedByDisplayName', Types::STRING);
6975
$this->addType('components', Types::STRING);
76+
$this->addType('state', Types::INTEGER);
7077
}
7178

7279
public function getSyncTokenForSabre(): string {

0 commit comments

Comments
 (0)