Skip to content

Commit 40c4657

Browse files
committed
fix(CalDAV): Check for user status before serving public calendars
Signed-off-by: David Dreschner <david.dreschner@nextcloud.com>
1 parent 0556391 commit 40c4657

4 files changed

Lines changed: 86 additions & 7 deletions

File tree

apps/dav/lib/CalDAV/PublicCalendarRoot.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88

99
namespace OCA\DAV\CalDAV;
1010

11+
use OCP\IAppConfig;
1112
use OCP\IConfig;
1213
use OCP\IL10N;
14+
use OCP\IUserManager;
1315
use Psr\Log\LoggerInterface;
1416
use Sabre\DAV\Collection;
17+
use Sabre\DAV\Exception\NotFound;
18+
use Sabre\Uri;
1519

1620
class PublicCalendarRoot extends Collection {
1721

@@ -21,12 +25,15 @@ class PublicCalendarRoot extends Collection {
2125
* @param CalDavBackend $caldavBackend
2226
* @param IL10N $l10n
2327
* @param IConfig $config
28+
* @param IAppConfig $appConfig
2429
*/
2530
public function __construct(
2631
protected CalDavBackend $caldavBackend,
2732
protected IL10N $l10n,
2833
protected IConfig $config,
34+
protected IAppConfig $appConfig,
2935
private LoggerInterface $logger,
36+
private IUserManager $userManager,
3037
) {
3138
}
3239

@@ -44,6 +51,9 @@ public function getName() {
4451
#[\Override]
4552
public function getChild($name) {
4653
$calendar = $this->caldavBackend->getPublicCalendar($name);
54+
if (!$this->validateVisibility((string)$calendar['principaluri'])) {
55+
throw new NotFound('Node with name \'' . $name . '\' could not be found');
56+
}
4757
return new PublicCalendar($this->caldavBackend, $calendar, $this->l10n, $this->config, $this->logger);
4858
}
4959

@@ -54,4 +64,26 @@ public function getChild($name) {
5464
public function getChildren() {
5565
return [];
5666
}
67+
68+
/**
69+
* Checks if the public calendar should be visible or not, based on
70+
* the configuration of the `hide_disabled_user_shares` setting within
71+
* `files_sharing` and the status of the owning user (disabled or not).
72+
*/
73+
private function validateVisibility(string $principalUri): bool {
74+
$hideCalendarsOfDisabledUsers = $this->appConfig->getValueString(
75+
'files_sharing', 'hide_disabled_user_shares', 'yes'
76+
) === 'yes';
77+
78+
if (!$hideCalendarsOfDisabledUsers) {
79+
return true;
80+
}
81+
82+
[$prefix, $name] = Uri\split($principalUri);
83+
if ($prefix !== 'principals/users') {
84+
return true;
85+
}
86+
87+
return $this->userManager->get((string)$name)?->isEnabled() !== false;
88+
}
5789
}

apps/dav/lib/RootCollection.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
use OCP\Comments\ICommentsManager;
3838
use OCP\EventDispatcher\IEventDispatcher;
3939
use OCP\Files\IRootFolder;
40+
use OCP\IAppConfig;
4041
use OCP\ICacheFactory;
4142
use OCP\IConfig;
4243
use OCP\IDBConnection;
@@ -63,6 +64,7 @@ public function __construct() {
6364
$db = Server::get(IDBConnection::class);
6465
$dispatcher = Server::get(IEventDispatcher::class);
6566
$config = Server::get(IConfig::class);
67+
$appConfig = Server::get(IAppConfig::class);
6668
$proxyMapper = Server::get(ProxyMapper::class);
6769
$rootFolder = Server::get(IRootFolder::class);
6870
$federatedCalendarFactory = Server::get(FederatedCalendarFactory::class);
@@ -125,7 +127,7 @@ public function __construct() {
125127
$roomCalendarRoot = new CalendarRoot($calendarRoomPrincipalBackend, $caldavBackend, 'principals/calendar-rooms', $logger, $l10n, $config, $federatedCalendarFactory);
126128
$roomCalendarRoot->disableListing = $disableListing;
127129

128-
$publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config, $logger);
130+
$publicCalendarRoot = new PublicCalendarRoot($caldavBackend, $l10n, $config, $appConfig, $logger, $userManager);
129131

130132
$systemTagCollection = Server::get(SystemTagsByIdCollection::class);
131133
$systemTagRelationsCollection = new SystemTagsRelationsCollection(

apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,19 @@
1515
use OCA\DAV\CalDAV\PublicCalendarRoot;
1616
use OCA\DAV\Connector\Sabre\Principal;
1717
use OCP\EventDispatcher\IEventDispatcher;
18+
use OCP\IAppConfig;
1819
use OCP\ICacheFactory;
1920
use OCP\IConfig;
2021
use OCP\IDBConnection;
2122
use OCP\IGroupManager;
2223
use OCP\IL10N;
24+
use OCP\IUser;
2325
use OCP\IUserManager;
2426
use OCP\Security\ISecureRandom;
2527
use OCP\Server;
2628
use PHPUnit\Framework\MockObject\MockObject;
2729
use Psr\Log\LoggerInterface;
30+
use Sabre\DAV\Exception\NotFound;
2831
use Test\TestCase;
2932

3033
/**
@@ -36,13 +39,15 @@
3639
#[\PHPUnit\Framework\Attributes\Group(name: 'DB')]
3740
class PublicCalendarRootTest extends TestCase {
3841
public const UNIT_TEST_USER = '';
42+
private const DISABLED_USER_PRINCIPAL = 'principals/users/disabled-caldav-unit-test';
3943
private CalDavBackend $backend;
4044
private PublicCalendarRoot $publicCalendarRoot;
4145
private IL10N&MockObject $l10n;
4246
private Principal&MockObject $principal;
4347
protected IUserManager&MockObject $userManager;
4448
protected IGroupManager&MockObject $groupManager;
4549
protected IConfig&MockObject $config;
50+
protected IAppConfig&MockObject $appConfig;
4651
private ISecureRandom $random;
4752
private LoggerInterface&MockObject $logger;
4853
protected ICacheFactory&MockObject $cacheFactory;
@@ -87,9 +92,10 @@ protected function setUp(): void {
8792
);
8893
$this->l10n = $this->createMock(IL10N::class);
8994
$this->config = $this->createMock(IConfig::class);
95+
$this->appConfig = $this->createMock(IAppConfig::class);
9096

9197
$this->publicCalendarRoot = new PublicCalendarRoot($this->backend,
92-
$this->l10n, $this->config, $this->logger);
98+
$this->l10n, $this->config, $this->appConfig, $this->logger, $this->userManager);
9399
}
94100

95101
protected function tearDown(): void {
@@ -106,7 +112,10 @@ protected function tearDown(): void {
106112
->withAnyParameters()
107113
->willReturn([]);
108114

109-
$books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER);
115+
$books = array_merge(
116+
$this->backend->getCalendarsForUser(self::UNIT_TEST_USER),
117+
$this->backend->getCalendarsForUser(self::DISABLED_USER_PRINCIPAL),
118+
);
110119
foreach ($books as $book) {
111120
$this->backend->deleteCalendar($book['id'], true);
112121
}
@@ -136,10 +145,32 @@ public function testGetChildren(): void {
136145
$this->assertSame([], $calendarResults);
137146
}
138147

139-
protected function createPublicCalendar(): Calendar {
140-
$this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []);
148+
public function testGetChildHidesCalendarOfDisabledUser(): void {
149+
$calendar = $this->createPublicCalendar(self::DISABLED_USER_PRINCIPAL);
150+
$publicUri = $calendar->getPublishStatus();
151+
152+
$this->mockDisabledOwner();
153+
$this->setHideDisabledUserShares(true);
154+
155+
$this->expectException(NotFound::class);
156+
$this->publicCalendarRoot->getChild($publicUri);
157+
}
158+
159+
public function testGetChildServesCalendarOfDisabledUserWhenHidingIsDisabled(): void {
160+
$calendar = $this->createPublicCalendar(self::DISABLED_USER_PRINCIPAL);
161+
$publicUri = $calendar->getPublishStatus();
162+
163+
$this->mockDisabledOwner();
164+
$this->setHideDisabledUserShares(false);
165+
166+
$calendarResult = $this->publicCalendarRoot->getChild($publicUri);
167+
$this->assertEquals($calendar, $calendarResult);
168+
}
169+
170+
protected function createPublicCalendar(string $principal = self::UNIT_TEST_USER): Calendar {
171+
$this->backend->createCalendar($principal, 'Example', []);
141172

142-
$calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0];
173+
$calendarInfo = $this->backend->getCalendarsForUser($principal)[0];
143174
$calendar = new PublicCalendar($this->backend, $calendarInfo, $this->l10n, $this->config, $this->logger);
144175
$publicUri = $calendar->setPublishStatus(true);
145176

@@ -148,4 +179,18 @@ protected function createPublicCalendar(): Calendar {
148179

149180
return $calendar;
150181
}
182+
183+
private function mockDisabledOwner(): void {
184+
$disabledUser = $this->createMock(IUser::class);
185+
$disabledUser->method('isEnabled')
186+
->willReturn(false);
187+
$this->userManager->method('get')
188+
->willReturn($disabledUser);
189+
}
190+
191+
private function setHideDisabledUserShares(bool $hide): void {
192+
$this->appConfig->method('getValueString')
193+
->with('files_sharing', 'hide_disabled_user_shares', 'yes')
194+
->willReturn($hide ? 'yes' : 'no');
195+
}
151196
}

lib/private/Sharing/SharingManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,7 +698,7 @@ private function validateInteraction(ShareAccessContext $accessContext, Share $s
698698
$action = new ShareAction(null, array_values(array_map(static fn (SharePermission $permission): string => $permission->class, $share->getEnabledPermissions())));
699699

700700
$usersToCheck = [];
701-
if ($share->owner->instance === null && ($ownerUser = $this->userManager->get($share->owner->userId)) !== null) {
701+
if ($share->owner->instance === null && ($ownerUser = $this->userManager->get($share->owner->userId)) instanceof IUser) {
702702
$usersToCheck[] = $ownerUser;
703703
}
704704

0 commit comments

Comments
 (0)