Skip to content

Commit 7b4c8da

Browse files
committed
fixup! feat: calendar federation
1 parent f2c377c commit 7b4c8da

12 files changed

+1305
-42
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
use OCP\AppFramework\Services\IAppConfig;
1313

14-
final class CalendarFederationConfig {
14+
class CalendarFederationConfig {
1515
public function __construct(
1616
private readonly IAppConfig $appConfig,
1717
) {

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCA\DAV\DAV\Sharing\Backend as DavSharingBackend;
1616
use OCP\AppFramework\Http;
1717
use OCP\BackgroundJob\IJobList;
18+
use OCP\Constants;
1819
use OCP\Federation\Exceptions\ProviderCouldNotAddShareException;
1920
use OCP\Federation\ICloudFederationProvider;
2021
use OCP\Federation\ICloudFederationShare;
@@ -41,7 +42,7 @@ public function shareReceived(ICloudFederationShare $share): string {
4142
if (!$this->calendarFederationConfig->isFederationEnabled()) {
4243
$this->logger->debug('Received a federation invite but federation is disabled');
4344
throw new ProviderCouldNotAddShareException(
44-
'Server does not support talk federation',
45+
'Server does not support calendar federation',
4546
'',
4647
Http::STATUS_SERVICE_UNAVAILABLE,
4748
);
@@ -58,7 +59,6 @@ public function shareReceived(ICloudFederationShare $share): string {
5859
}
5960

6061
$rawProtocol = $share->getProtocol();
61-
// TODO: test what happens if no version in protocol
6262
switch ($rawProtocol[ICalendarFederationProtocol::PROP_VERSION]) {
6363
case CalendarFederationProtocolV1::VERSION:
6464
try {
@@ -92,13 +92,15 @@ public function shareReceived(ICloudFederationShare $share): string {
9292
);
9393
}
9494

95-
if ($access !== DavSharingBackend::ACCESS_READ) {
96-
throw new ProviderCouldNotAddShareException(
95+
// TODO: implement read-write sharing
96+
$permissions = match ($access) {
97+
DavSharingBackend::ACCESS_READ => Constants::PERMISSION_READ,
98+
default => throw new ProviderCouldNotAddShareException(
9799
"Unsupported access value: $access",
98100
'',
99101
Http::STATUS_BAD_REQUEST,
100-
);
101-
}
102+
),
103+
};
102104

103105
// The calendar uri is the local name of the calendar. As such it must not contain slashes.
104106
// Just use the hashed url for simplicity here.
@@ -119,7 +121,7 @@ public function shareReceived(ICloudFederationShare $share): string {
119121
$calendar->setToken($share->getShareSecret());
120122
$calendar->setSharedBy($share->getSharedBy());
121123
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
122-
$calendar->setPermissions($access);
124+
$calendar->setPermissions($permissions);
123125
$calendar->setComponents($components);
124126
$calendar = $this->federatedCalendarMapper->insert($calendar);
125127

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

Lines changed: 7 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@
1010
namespace OCA\DAV\CalDAV\Federation;
1111

1212
use OCA\DAV\DAV\RemoteUserPrincipalBackend;
13-
use OCP\DB\QueryBuilder\IQueryBuilder;
13+
use OCA\DAV\DAV\Sharing\SharingMapper;
1414
use OCP\Defaults;
15-
use OCP\IDBConnection;
1615
use Sabre\DAV\Auth\Backend\BackendInterface;
1716
use Sabre\HTTP\Auth\Basic as BasicAuth;
1817
use Sabre\HTTP\RequestInterface;
@@ -22,7 +21,7 @@ final class FederatedCalendarAuth implements BackendInterface {
2221
private readonly string $realm;
2322

2423
public function __construct(
25-
private readonly IDBConnection $db,
24+
private readonly SharingMapper $sharingMapper,
2625
) {
2726
$defaults = new Defaults();
2827
$this->realm = $defaults->getName();
@@ -39,32 +38,10 @@ private function validateUserPass(
3938
$remoteUserPrincipalUri = RemoteUserPrincipalBackend::PRINCIPAL_PREFIX . "/$username";
4039
[, $remoteUserPrincipalId] = \Sabre\Uri\split($remoteUserPrincipalUri);
4140

42-
$qb = $this->db->getQueryBuilder();
43-
$qb->select('c.uri', 'c.principaluri')
44-
->from('dav_shares', 'ds')
45-
->join('ds', 'calendars', 'c', $qb->expr()->eq(
46-
'ds.resourceid',
47-
'c.id',
48-
IQueryBuilder::PARAM_INT,
49-
))
50-
->where($qb->expr()->eq(
51-
'ds.type',
52-
$qb->createNamedParameter('calendar', IQueryBuilder::PARAM_STR),
53-
IQueryBuilder::PARAM_STR,
54-
))
55-
->andWhere($qb->expr()->eq(
56-
'ds.principaluri',
57-
$qb->createNamedParameter($remoteUserPrincipalUri, IQueryBuilder::PARAM_STR),
58-
IQueryBuilder::PARAM_STR,
59-
))
60-
->andWhere($qb->expr()->eq(
61-
'ds.token',
62-
$qb->createNamedParameter($password, IQueryBuilder::PARAM_STR),
63-
IQueryBuilder::PARAM_STR,
64-
));
65-
$result = $qb->executeQuery();
66-
$rows = $result->fetchAll();
67-
$result->closeCursor();
41+
$rows = $this->sharingMapper->getSharedCalendarsForRemoteUser(
42+
$remoteUserPrincipalUri,
43+
$password,
44+
);
6845

6946
// Is the requested calendar actually shared with the remote user?
7047
foreach ($rows as $row) {
@@ -87,7 +64,7 @@ public function check(RequestInterface $request, ResponseInterface $response): a
8764

8865
$auth = new BasicAuth($this->realm, $request, $response);
8966
$userpass = $auth->getCredentials();
90-
if (!$userpass) {
67+
if ($userpass === null || count($userpass) !== 2) {
9168
return [false, "No 'Authorization: Basic' header found. Either the client didn't send one, or the server is misconfigured"];
9269
}
9370
$principal = $this->validateUserPass($request->getPath(), $userpass[0], $userpass[1]);

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,16 @@ public function __construct(
4747
private function decodeRemoteUserPrincipal(string $principal): ?string {
4848
// Expected format: principals/remote-users/abcdef123
4949
[$prefix, $collection, $encodedId] = explode('/', $principal);
50-
if ($prefix !== 'principals' && $collection !== 'remote-users') {
50+
if ($prefix !== 'principals' || $collection !== 'remote-users') {
5151
return null;
5252
}
5353

54-
return base64_decode($encodedId);
54+
$decodedId = base64_decode($encodedId);
55+
if (!is_string($decodedId)) {
56+
return null;
57+
}
58+
59+
return $decodedId;
5560
}
5661

5762
/**
@@ -67,7 +72,7 @@ public function shareWith(IShareable $shareable, string $principal, int $access)
6772

6873
// 1. Validate share data
6974
$shareWith = $this->decodeRemoteUserPrincipal($principal);
70-
if (!$shareWith) {
75+
if ($shareWith === null) {
7176
$this->logger->error($baseError . 'Principal of sharee is not belonging to a remote user', [
7277
'shareable' => $shareable->getName(),
7378
'encodedShareWith' => $principal,
@@ -77,7 +82,7 @@ public function shareWith(IShareable $shareable, string $principal, int $access)
7782

7883
[,, $ownerUid] = explode('/', $shareable->getOwner());
7984
$owner = $this->userManager->get($ownerUid);
80-
if (!$owner) {
85+
if ($owner === null) {
8186
$this->logger->error($baseError . 'Shareable is not owned by a user on this server', [
8287
'shareable' => $shareable->getName(),
8388
'shareWith' => $shareWith,

apps/dav/lib/DAV/Sharing/SharingMapper.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,4 +173,43 @@ public function getPrincipalUrisByPrefix(string $resourceType, string $prefix):
173173

174174
return $rows;
175175
}
176+
177+
/**
178+
* @psalm-return array{uri: string, principaluri: string}[]
179+
* @throws \OCP\DB\Exception
180+
*/
181+
public function getSharedCalendarsForRemoteUser(
182+
string $remoteUserPrincipalUri,
183+
string $token,
184+
): array {
185+
$qb = $this->db->getQueryBuilder();
186+
$qb->select('c.uri', 'c.principaluri')
187+
->from('dav_shares', 'ds')
188+
->join('ds', 'calendars', 'c', $qb->expr()->eq(
189+
'ds.resourceid',
190+
'c.id',
191+
IQueryBuilder::PARAM_INT,
192+
))
193+
->where($qb->expr()->eq(
194+
'ds.type',
195+
$qb->createNamedParameter('calendar', IQueryBuilder::PARAM_STR),
196+
IQueryBuilder::PARAM_STR,
197+
))
198+
->andWhere($qb->expr()->eq(
199+
'ds.principaluri',
200+
$qb->createNamedParameter($remoteUserPrincipalUri, IQueryBuilder::PARAM_STR),
201+
IQueryBuilder::PARAM_STR,
202+
))
203+
->andWhere($qb->expr()->eq(
204+
'ds.token',
205+
$qb->createNamedParameter($token, IQueryBuilder::PARAM_STR),
206+
IQueryBuilder::PARAM_STR,
207+
));
208+
$result = $qb->executeQuery();
209+
$rows = $result->fetchAll();
210+
$result->closeCursor();
211+
212+
return $rows;
213+
}
214+
176215
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
7+
* SPDX-License-Identifier: AGPL-3.0-or-later
8+
*/
9+
10+
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
11+
12+
use OCA\DAV\CalDAV\Federation\CalendarFederationConfig;
13+
use OCP\AppFramework\Services\IAppConfig;
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use Test\TestCase;
17+
18+
class CalendarFederationConfigTest extends TestCase {
19+
private CalendarFederationConfig $config;
20+
21+
private IAppConfig&MockObject $appConfig;
22+
23+
protected function setUp(): void {
24+
parent::setUp();
25+
26+
$this->appConfig = $this->createMock(IAppConfig::class);
27+
28+
$this->config = new CalendarFederationConfig(
29+
$this->appConfig,
30+
);
31+
}
32+
33+
public static function provideIsFederationEnabledData(): array {
34+
return [
35+
[true],
36+
[false],
37+
];
38+
}
39+
40+
#[DataProvider('provideIsFederationEnabledData')]
41+
public function testIsFederationEnabled(bool $configValue): void {
42+
$this->appConfig->expects(self::once())
43+
->method('getAppValueBool')
44+
->with('enableCalendarFederation', true)
45+
->willReturn($configValue);
46+
47+
$this->assertEquals($configValue, $this->config->isFederationEnabled());
48+
}
49+
}

0 commit comments

Comments
 (0)