fix(caldav): respect federation settings

Signed-off-by: Hamza <hamzamahjoubi221@gmail.com>
This commit is contained in:
Hamza 2026-04-02 14:14:09 +02:00 committed by backportbot[bot]
parent 64f8a02b93
commit 4fe03d470b
6 changed files with 218 additions and 5 deletions

View file

@ -9,15 +9,36 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCP\AppFramework\Services\IAppConfig;
use OCP\IAppConfig;
class CalendarFederationConfig {
public function __construct(
private readonly IAppConfig $appConfig,
private \OCP\GlobalScale\IConfig $gsConfig,
) {
}
public function isFederationEnabled(): bool {
return $this->appConfig->getAppValueBool('enableCalendarFederation', true);
return $this->appConfig->getValueBool('dav', 'enableCalendarFederation', true);
}
/**
* Check if users are allowed to create federated shares
*/
public function isOutgoingServer2serverShareEnabled(): bool {
if ($this->gsConfig->onlyInternalFederation()) {
return false;
}
return $this->appConfig->getValueBool('files_sharing', 'outgoing_server2server_share_enabled', true);
}
/**
* Check if users are allowed to receive federated shares
*/
public function isIncomingServer2serverShareEnabled(): bool {
if ($this->gsConfig->onlyInternalFederation()) {
return false;
}
return $this->appConfig->getValueBool('files_sharing', 'incoming_server2server_share_enabled', true);
}
}

View file

@ -55,6 +55,15 @@ class CalendarFederationProvider implements ICloudFederationProvider {
);
}
if (!$this->calendarFederationConfig->isIncomingServer2serverShareEnabled()) {
$this->logger->debug('Received a federated calendar share which is not allowed on this instance');
throw new ProviderCouldNotAddShareException(
'Instance does not support receiving federated calendar shares',
'',
Http::STATUS_SERVICE_UNAVAILABLE,
);
}
if (!in_array($share->getShareType(), $this->getSupportedShareTypes(), true)) {
$this->logger->debug('Received a federation invite for invalid share type');
throw new ProviderCouldNotAddShareException(

View file

@ -34,6 +34,7 @@ class FederationSharingService {
private readonly LoggerInterface $logger,
private readonly ISecureRandom $random,
private readonly SharingMapper $sharingMapper,
private readonly CalendarFederationConfig $config,
) {
}
@ -70,6 +71,14 @@ class FederationSharingService {
public function shareWith(IShareable $shareable, string $principal, int $access): void {
$baseError = 'Failed to create federated calendar share: ';
if (!$this->config->isOutgoingServer2serverShareEnabled()) {
$this->logger->error('cannot share with remote user because federated sharing is disabled on this instance', [
'shareable' => $shareable->getName(),
'encodedShareWith' => $principal,
]);
return;
}
// 1. Validate share data
$shareWith = $this->decodeRemoteUserPrincipal($principal);
if ($shareWith === null) {

View file

@ -10,7 +10,8 @@ declare(strict_types=1);
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
use OCA\DAV\CalDAV\Federation\CalendarFederationConfig;
use OCP\AppFramework\Services\IAppConfig;
use OCP\GlobalScale\IConfig;
use OCP\IAppConfig;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
@ -19,14 +20,17 @@ class CalendarFederationConfigTest extends TestCase {
private CalendarFederationConfig $config;
private IAppConfig&MockObject $appConfig;
private IConfig&MockObject $gsConfig;
protected function setUp(): void {
parent::setUp();
$this->appConfig = $this->createMock(IAppConfig::class);
$this->gsConfig = $this->createMock(IConfig::class);
$this->config = new CalendarFederationConfig(
$this->appConfig,
$this->gsConfig,
);
}
@ -40,10 +44,74 @@ class CalendarFederationConfigTest extends TestCase {
#[DataProvider(methodName: 'provideIsFederationEnabledData')]
public function testIsFederationEnabled(bool $configValue): void {
$this->appConfig->expects(self::once())
->method('getAppValueBool')
->with('enableCalendarFederation', true)
->method('getValueBool')
->with('dav', 'enableCalendarFederation', true)
->willReturn($configValue);
$this->assertEquals($configValue, $this->config->isFederationEnabled());
}
public static function provideIsOutgoingServer2serverShareEnabledData(): array {
return [
[false, false, false],
[false, true, true],
[true, false, false],
[true, false, false],
];
}
#[DataProvider(methodName: 'provideIsOutgoingServer2serverShareEnabledData')]
public function testIsOutgoingServer2serverShareEnabled(
bool $globalScaleEnabled,
bool $expected,
bool $configValue,
): void {
$this->gsConfig->expects(self::once())
->method('onlyInternalFederation')
->willReturn($globalScaleEnabled);
if (!$globalScaleEnabled) {
$this->appConfig->expects(self::once())
->method('getValueBool')
->with('files_sharing', 'outgoing_server2server_share_enabled', true)
->willReturn($configValue);
} else {
$this->appConfig->expects(self::never())
->method('getValueBool');
}
$this->assertEquals($expected, $this->config->isOutgoingServer2serverShareEnabled());
}
public static function provideIsIncomingServer2serverShareEnabledData(): array {
return [
[false, false, false],
[false, true, true],
[true, false, false],
[true, false, true],
];
}
#[DataProvider(methodName: 'provideIsIncomingServer2serverShareEnabledData')]
public function testIsIncomingServer2serverShareEnabled(
bool $globalScaleEnabled,
bool $expected,
bool $configValue,
): void {
$this->gsConfig->expects(self::once())
->method('onlyInternalFederation')
->willReturn($globalScaleEnabled);
if (!$globalScaleEnabled) {
$this->appConfig->expects(self::once())
->method('getValueBool')
->with('files_sharing', 'incoming_server2server_share_enabled', true)
->willReturn($configValue);
} else {
$this->appConfig->expects(self::never())
->method('getValueBool');
}
$this->assertEquals($expected, $this->config->isIncomingServer2serverShareEnabled());
}
}

View file

@ -91,6 +91,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->with(
@ -150,6 +154,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$existingCalendar = new FederatedCalendarEntity();
$existingCalendar->setId(10);
$existingCalendar->setPrincipaluri('principals/users/sharee1');
@ -204,6 +212,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())
@ -232,6 +244,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())
@ -261,6 +277,30 @@ class CalendarFederationProviderTest extends TestCase {
$this->calendarFederationProvider->shareReceived($share);
}
public function testShareReceivedWithIncomingServer2serverShareDisabled(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
->willReturn('user');
$this->calendarFederationConfig->expects(self::once())
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(false);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())
->method('add');
$this->expectException(ProviderCouldNotAddShareException::class);
$this->expectExceptionMessage('Instance does not support receiving federated calendar shares');
$this->expectExceptionCode(503);
$this->calendarFederationProvider->shareReceived($share);
}
public function testShareReceivedWithUnsupportedShareType(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
@ -270,6 +310,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())
@ -322,6 +366,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())
@ -359,6 +407,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->with(
@ -418,6 +470,10 @@ class CalendarFederationProviderTest extends TestCase {
->method('isFederationEnabled')
->willReturn(true);
$this->calendarFederationConfig->expects(self::once())
->method('isIncomingServer2serverShareEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->jobList->expects(self::never())

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\Federation\CalendarFederationConfig;
use OCA\DAV\CalDAV\Federation\FederationSharingService;
use OCA\DAV\DAV\Sharing\IShareable;
use OCA\DAV\DAV\Sharing\SharingMapper;
@ -38,6 +39,7 @@ class FederationSharingServiceTest extends TestCase {
private readonly LoggerInterface&MockObject $logger;
private readonly ISecureRandom&MockObject $random;
private readonly SharingMapper&MockObject $sharingMapper;
private readonly CalendarFederationConfig&MockObject $config;
protected function setUp(): void {
parent::setUp();
@ -49,6 +51,7 @@ class FederationSharingServiceTest extends TestCase {
$this->logger = $this->createMock(LoggerInterface::class);
$this->random = $this->createMock(ISecureRandom::class);
$this->sharingMapper = $this->createMock(SharingMapper::class);
$this->config = $this->createMock(CalendarFederationConfig::class);
$this->federationSharingService = new FederationSharingService(
$this->federationManager,
@ -58,6 +61,7 @@ class FederationSharingServiceTest extends TestCase {
$this->logger,
$this->random,
$this->sharingMapper,
$this->config,
);
}
@ -83,6 +87,10 @@ class FederationSharingServiceTest extends TestCase {
]
});
$this->config->expects(self::once())
->method('isOutgoingServer2serverShareEnabled')
->willReturn(true);
$hostUser = $this->createMock(IUser::class);
$hostUser->method('getCloudId')
->willReturn('host1@nextcloud.host');
@ -195,6 +203,10 @@ class FederationSharingServiceTest extends TestCase {
]
});
$this->config->expects(self::once())
->method('isOutgoingServer2serverShareEnabled')
->willReturn(true);
$hostUser = $this->createMock(IUser::class);
$hostUser->method('getCloudId')
->willReturn('host1@nextcloud.host');
@ -299,6 +311,10 @@ class FederationSharingServiceTest extends TestCase {
]
});
$this->config->expects(self::once())
->method('isOutgoingServer2serverShareEnabled')
->willReturn(true);
$hostUser = $this->createMock(IUser::class);
$hostUser->method('getCloudId')
->willReturn('host1@nextcloud.host');
@ -381,6 +397,34 @@ class FederationSharingServiceTest extends TestCase {
);
}
public function testShareWithWithOutgoingServer2serverShareDisabled(): void {
$shareable = $this->createMock(Calendar::class);
$shareable->method('getOwner')
->willReturn('principals/users/host1');
$shareable->method('getName')
->willReturn('cal1');
$this->config->expects(self::once())
->method('isOutgoingServer2serverShareEnabled')
->willReturn(false);
$this->userManager->expects(self::never())
->method('get');
$this->federationManager->expects(self::never())
->method('sendCloudShare');
$this->sharingMapper->expects(self::never())
->method('deleteShare');
$this->sharingMapper->expects(self::never())
->method('shareWithToken');
$this->federationSharingService->shareWith(
$shareable,
'principals/remote-users/cmVtb3RlMUBuZXh0Y2xvdWQucmVtb3Rl',
3, // Read-only
);
}
public static function provideInvalidRemoteUserPrincipalData(): array {
return [
['principals/users/foobar'],
@ -418,6 +462,9 @@ class FederationSharingServiceTest extends TestCase {
$shareable->method('getOwner')
->willReturn('principals/users/host1');
$this->config->method('isOutgoingServer2serverShareEnabled')
->willReturn(true);
$this->userManager->expects(self::once())
->method('get')
->with('host1')
@ -442,6 +489,9 @@ class FederationSharingServiceTest extends TestCase {
$shareable->method('getOwner')
->willReturn('principals/users/host1');
$this->config->method('isOutgoingServer2serverShareEnabled')
->willReturn(true);
$this->userManager->expects(self::once())
->method('get')
->with('host1')