diff options
Diffstat (limited to 'apps/dav/lib')
-rw-r--r-- | apps/dav/lib/CalDAV/CalendarImpl.php | 30 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/EmbeddedCalDavServer.php | 118 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/UpcomingEventsService.php | 8 | ||||
-rw-r--r-- | apps/dav/lib/Command/GetAbsenceCommand.php | 62 | ||||
-rw-r--r-- | apps/dav/lib/Command/SetAbsenceCommand.php | 95 | ||||
-rw-r--r-- | apps/dav/lib/Connector/Sabre/FilesReportPlugin.php | 2 | ||||
-rw-r--r-- | apps/dav/lib/Connector/Sabre/Principal.php | 3 | ||||
-rw-r--r-- | apps/dav/lib/Connector/Sabre/ServerFactory.php | 2 |
8 files changed, 304 insertions, 16 deletions
diff --git a/apps/dav/lib/CalDAV/CalendarImpl.php b/apps/dav/lib/CalDAV/CalendarImpl.php index b79bf7ea2d0..5f912da732e 100644 --- a/apps/dav/lib/CalDAV/CalendarImpl.php +++ b/apps/dav/lib/CalDAV/CalendarImpl.php @@ -156,19 +156,15 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs } /** - * Create a new calendar event for this calendar - * by way of an ICS string - * - * @param string $name the file name - needs to contain the .ics ending - * @param string $calendarData a string containing a valid VEVENT ics - * * @throws CalendarException */ - public function createFromString(string $name, string $calendarData): void { - $server = new InvitationResponseServer(false); - + private function createFromStringInServer( + string $name, + string $calendarData, + \OCA\DAV\Connector\Sabre\Server $server, + ): void { /** @var CustomPrincipalPlugin $plugin */ - $plugin = $server->getServer()->getPlugin('auth'); + $plugin = $server->getPlugin('auth'); // we're working around the previous implementation // that only allowed the public system principal to be used // so set the custom principal here @@ -184,14 +180,14 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs // Force calendar change URI /** @var Schedule\Plugin $schedulingPlugin */ - $schedulingPlugin = $server->getServer()->getPlugin('caldav-schedule'); + $schedulingPlugin = $server->getPlugin('caldav-schedule'); $schedulingPlugin->setPathOfCalendarObjectChange($fullCalendarFilename); $stream = fopen('php://memory', 'rb+'); fwrite($stream, $calendarData); rewind($stream); try { - $server->getServer()->createFile($fullCalendarFilename, $stream); + $server->createFile($fullCalendarFilename, $stream); } catch (Conflict $e) { throw new CalendarException('Could not create new calendar event: ' . $e->getMessage(), 0, $e); } finally { @@ -199,6 +195,16 @@ class CalendarImpl implements ICreateFromString, IHandleImipMessage, ICalendarIs } } + public function createFromString(string $name, string $calendarData): void { + $server = new EmbeddedCalDavServer(false); + $this->createFromStringInServer($name, $calendarData, $server->getServer()); + } + + public function createFromStringMinimal(string $name, string $calendarData): void { + $server = new InvitationResponseServer(false); + $this->createFromStringInServer($name, $calendarData, $server->getServer()); + } + /** * @throws CalendarException */ diff --git a/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php b/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php new file mode 100644 index 00000000000..21d8c06fa99 --- /dev/null +++ b/apps/dav/lib/CalDAV/EmbeddedCalDavServer.php @@ -0,0 +1,118 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\CalDAV; + +use OCA\DAV\AppInfo\PluginManager; +use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin; +use OCA\DAV\CalDAV\Auth\PublicPrincipalPlugin; +use OCA\DAV\CalDAV\Publishing\PublishPlugin; +use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin; +use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use OCA\DAV\Connector\Sabre\CachingTree; +use OCA\DAV\Connector\Sabre\DavAclPlugin; +use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; +use OCA\DAV\Connector\Sabre\LockPlugin; +use OCA\DAV\Connector\Sabre\MaintenancePlugin; +use OCA\DAV\Events\SabrePluginAuthInitEvent; +use OCA\DAV\RootCollection; +use OCA\Theming\ThemingDefaults; +use OCP\App\IAppManager; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\IAppConfig; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\L10N\IFactory as IL10NFactory; +use OCP\Server; +use Psr\Log\LoggerInterface; + +class EmbeddedCalDavServer { + private readonly \OCA\DAV\Connector\Sabre\Server $server; + + public function __construct(bool $public = true) { + $baseUri = \OC::$WEBROOT . '/remote.php/dav/'; + $logger = Server::get(LoggerInterface::class); + $dispatcher = Server::get(IEventDispatcher::class); + $appConfig = Server::get(IAppConfig::class); + $l10nFactory = Server::get(IL10NFactory::class); + $l10n = $l10nFactory->get('dav'); + + $root = new RootCollection(); + $this->server = new \OCA\DAV\Connector\Sabre\Server(new CachingTree($root)); + + // Add maintenance plugin + $this->server->addPlugin(new MaintenancePlugin(Server::get(IConfig::class), $l10n)); + + // Set URL explicitly due to reverse-proxy situations + $this->server->httpRequest->setUrl($baseUri); + $this->server->setBaseUri($baseUri); + + $this->server->addPlugin(new BlockLegacyClientPlugin( + Server::get(IConfig::class), + Server::get(ThemingDefaults::class), + )); + $this->server->addPlugin(new AnonymousOptionsPlugin()); + + // allow custom principal uri option + if ($public) { + $this->server->addPlugin(new PublicPrincipalPlugin()); + } else { + $this->server->addPlugin(new CustomPrincipalPlugin()); + } + + // allow setup of additional auth backends + $event = new SabrePluginAuthInitEvent($this->server); + $dispatcher->dispatchTyped($event); + + $this->server->addPlugin(new ExceptionLoggerPlugin('webdav', $logger)); + $this->server->addPlugin(new LockPlugin()); + $this->server->addPlugin(new \Sabre\DAV\Sync\Plugin()); + + // acl + $acl = new DavAclPlugin(); + $acl->principalCollectionSet = [ + 'principals/users', 'principals/groups' + ]; + $this->server->addPlugin($acl); + + // calendar plugins + $this->server->addPlugin(new \OCA\DAV\CalDAV\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\ICSExportPlugin()); + $this->server->addPlugin(new \OCA\DAV\CalDAV\Schedule\Plugin(Server::get(IConfig::class), Server::get(LoggerInterface::class), Server::get(DefaultCalendarValidator::class))); + $this->server->addPlugin(new \Sabre\CalDAV\Subscriptions\Plugin()); + $this->server->addPlugin(new \Sabre\CalDAV\Notifications\Plugin()); + //$this->server->addPlugin(new \OCA\DAV\DAV\Sharing\Plugin($authBackend, \OC::$server->getRequest())); + $this->server->addPlugin(new PublishPlugin( + Server::get(IConfig::class), + Server::get(IURLGenerator::class) + )); + if ($appConfig->getValueString('dav', 'sendInvitations', 'yes') === 'yes') { + $this->server->addPlugin(Server::get(\OCA\DAV\CalDAV\Schedule\IMipPlugin::class)); + } + + // wait with registering these until auth is handled and the filesystem is setup + $this->server->on('beforeMethod:*', function () use ($root): void { + // register plugins from apps + $pluginManager = new PluginManager( + \OC::$server, + Server::get(IAppManager::class) + ); + foreach ($pluginManager->getAppPlugins() as $appPlugin) { + $this->server->addPlugin($appPlugin); + } + foreach ($pluginManager->getAppCollections() as $appCollection) { + $root->addChild($appCollection); + } + }); + } + + public function getServer(): \OCA\DAV\Connector\Sabre\Server { + return $this->server; + } +} diff --git a/apps/dav/lib/CalDAV/UpcomingEventsService.php b/apps/dav/lib/CalDAV/UpcomingEventsService.php index 6614d937ff7..1a8aed5bd71 100644 --- a/apps/dav/lib/CalDAV/UpcomingEventsService.php +++ b/apps/dav/lib/CalDAV/UpcomingEventsService.php @@ -47,7 +47,7 @@ class UpcomingEventsService { $this->userManager->get($userId), ); - return array_map(function (array $event) use ($userId, $calendarAppEnabled) { + return array_filter(array_map(function (array $event) use ($userId, $calendarAppEnabled) { $calendarAppUrl = null; if ($calendarAppEnabled) { @@ -67,6 +67,10 @@ class UpcomingEventsService { $calendarAppUrl = $this->urlGenerator->linkToRouteAbsolute('calendar.view.indexdirect.edit', $arguments); } + if (isset($event['objects'][0]['STATUS']) && $event['objects'][0]['STATUS'][0] === 'CANCELLED') { + return false; + } + return new UpcomingEvent( $event['uri'], ($event['objects'][0]['RECURRENCE-ID'][0] ?? null)?->getTimeStamp(), @@ -76,7 +80,7 @@ class UpcomingEventsService { $event['objects'][0]['LOCATION'][0] ?? null, $calendarAppUrl, ); - }, $events); + }, $events)); } } diff --git a/apps/dav/lib/Command/GetAbsenceCommand.php b/apps/dav/lib/Command/GetAbsenceCommand.php new file mode 100644 index 00000000000..50d8df4ab38 --- /dev/null +++ b/apps/dav/lib/Command/GetAbsenceCommand.php @@ -0,0 +1,62 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCA\DAV\Command; + +use OCA\DAV\Service\AbsenceService; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class GetAbsenceCommand extends Command { + + public function __construct( + private IUserManager $userManager, + private AbsenceService $absenceService, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this->setName('dav:absence:get'); + $this->addArgument( + 'user-id', + InputArgument::REQUIRED, + 'User ID of the affected account' + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $userId = $input->getArgument('user-id'); + + $user = $this->userManager->get($userId); + if ($user === null) { + $output->writeln('<error>User not found</error>'); + return 1; + } + + $absence = $this->absenceService->getAbsence($userId); + if ($absence === null) { + $output->writeln('<info>No absence set</info>'); + return 0; + } + + $output->writeln('<info>Start day:</info> ' . $absence->getFirstDay()); + $output->writeln('<info>End day:</info> ' . $absence->getLastDay()); + $output->writeln('<info>Short message:</info> ' . $absence->getStatus()); + $output->writeln('<info>Message:</info> ' . $absence->getMessage()); + $output->writeln('<info>Replacement user:</info> ' . ($absence->getReplacementUserId() ?? 'none')); + $output->writeln('<info>Replacement display name:</info> ' . ($absence->getReplacementUserDisplayName() ?? 'none')); + + return 0; + } + +} diff --git a/apps/dav/lib/Command/SetAbsenceCommand.php b/apps/dav/lib/Command/SetAbsenceCommand.php new file mode 100644 index 00000000000..bf91a163f95 --- /dev/null +++ b/apps/dav/lib/Command/SetAbsenceCommand.php @@ -0,0 +1,95 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCA\DAV\Command; + +use OCA\DAV\Service\AbsenceService; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class SetAbsenceCommand extends Command { + + public function __construct( + private IUserManager $userManager, + private AbsenceService $absenceService, + ) { + parent::__construct(); + } + + protected function configure(): void { + $this->setName('dav:absence:set'); + $this->addArgument( + 'user-id', + InputArgument::REQUIRED, + 'User ID of the affected account' + ); + $this->addArgument( + 'first-day', + InputArgument::REQUIRED, + 'Inclusive start day formatted as YYYY-MM-DD' + ); + $this->addArgument( + 'last-day', + InputArgument::REQUIRED, + 'Inclusive end day formatted as YYYY-MM-DD' + ); + $this->addArgument( + 'short-message', + InputArgument::REQUIRED, + 'Short message' + ); + $this->addArgument( + 'message', + InputArgument::REQUIRED, + 'Message' + ); + $this->addArgument( + 'replacement-user-id', + InputArgument::OPTIONAL, + 'Replacement user id' + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $userId = $input->getArgument('user-id'); + + $user = $this->userManager->get($userId); + if ($user === null) { + $output->writeln('<error>User not found</error>'); + return 1; + } + + $replacementUserId = $input->getArgument('replacement-user-id'); + if ($replacementUserId === null) { + $replacementUser = null; + } else { + $replacementUser = $this->userManager->get($replacementUserId); + if ($replacementUser === null) { + $output->writeln('<error>Replacement user not found</error>'); + return 2; + } + } + + $this->absenceService->createOrUpdateAbsence( + $user, + $input->getArgument('first-day'), + $input->getArgument('last-day'), + $input->getArgument('short-message'), + $input->getArgument('message'), + $replacementUser?->getUID(), + $replacementUser?->getDisplayName(), + ); + + return 0; + } + +} diff --git a/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php b/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php index 9e625ce3184..b59d1373af5 100644 --- a/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesReportPlugin.php @@ -156,7 +156,7 @@ class FilesReportPlugin extends ServerPlugin { // to user backends. I.e. the final result may return more results than requested. $resultNodes = $this->processFilterRulesForFileNodes($filterRules, $limit ?? null, $offset ?? null); } catch (TagNotFoundException $e) { - throw new PreconditionFailed('Cannot filter by non-existing tag', 0, $e); + throw new PreconditionFailed('Cannot filter by non-existing tag'); } $results = []; diff --git a/apps/dav/lib/Connector/Sabre/Principal.php b/apps/dav/lib/Connector/Sabre/Principal.php index b61cabedf5f..d6ea9fd887d 100644 --- a/apps/dav/lib/Connector/Sabre/Principal.php +++ b/apps/dav/lib/Connector/Sabre/Principal.php @@ -186,6 +186,9 @@ class Principal implements BackendInterface { if ($this->hasGroups || $needGroups) { $userGroups = $this->groupManager->getUserGroups($user); foreach ($userGroups as $userGroup) { + if ($userGroup->hideFromCollaboration()) { + continue; + } $groups[] = 'principals/groups/' . urlencode($userGroup->getGID()); } } diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index 214412e1744..3749b506d16 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -184,7 +184,7 @@ class ServerFactory { !$this->config->getSystemValue('debug', false) ) ); - $server->addPlugin(new QuotaPlugin($view, true)); + $server->addPlugin(new QuotaPlugin($view)); $server->addPlugin(new ChecksumUpdatePlugin()); // Allow view-only plugin for webdav requests |