From 06aba2a5c860a71fe16fe35385ce05d47829973a Mon Sep 17 00:00:00 2001 From: Richard Steinmetz Date: Sun, 17 Dec 2023 16:55:16 +0100 Subject: [PATCH 1/4] fix(dav): allow multiple organizers if possible This is very hacky! However, we want to allow saving events with multiple organizers. Those events are not RFC compliant, but sometimes imported from major external calendar services (e.g. Google). If the current user is not an organizer of the event we ignore the exception as no scheduling messages will be sent anyway. Signed-off-by: Richard Steinmetz --- apps/dav/lib/CalDAV/Schedule/Plugin.php | 68 ++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/apps/dav/lib/CalDAV/Schedule/Plugin.php b/apps/dav/lib/CalDAV/Schedule/Plugin.php index 2845ccdf6c2..232ee607c94 100644 --- a/apps/dav/lib/CalDAV/Schedule/Plugin.php +++ b/apps/dav/lib/CalDAV/Schedule/Plugin.php @@ -36,11 +36,14 @@ use OCA\DAV\CalDAV\CalendarHome; use OCP\IConfig; use Psr\Log\LoggerInterface; use Sabre\CalDAV\ICalendar; +use Sabre\CalDAV\ICalendarObject; +use Sabre\CalDAV\Schedule\ISchedulingObject; use Sabre\DAV\INode; use Sabre\DAV\IProperties; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\Xml\Property\LocalHref; +use Sabre\DAVACL\IACL; use Sabre\DAVACL\IPrincipal; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; @@ -50,6 +53,7 @@ use Sabre\VObject\Component\VEvent; use Sabre\VObject\DateTimeParser; use Sabre\VObject\FreeBusyGenerator; use Sabre\VObject\ITip; +use Sabre\VObject\ITip\SameOrganizerForAllComponentsException; use Sabre\VObject\Parameter; use Sabre\VObject\Property; use Sabre\VObject\Reader; @@ -161,7 +165,29 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin { $this->pathOfCalendarObjectChange = $request->getPath(); } - parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew); + try { + parent::calendarObjectChange($request, $response, $vCal, $calendarPath, $modified, $isNew); + } catch (SameOrganizerForAllComponentsException $e) { + $this->handleSameOrganizerException($e, $vCal, $calendarPath); + } + } + + /** + * @inheritDoc + */ + public function beforeUnbind($path): void { + try { + parent::beforeUnbind($path); + } catch (SameOrganizerForAllComponentsException $e) { + $node = $this->server->tree->getNodeForPath($path); + if (!$node instanceof ICalendarObject || $node instanceof ISchedulingObject) { + throw $e; + } + + /** @var VCalendar $vCal */ + $vCal = Reader::read($node->get()); + $this->handleSameOrganizerException($e, $vCal, $path); + } } /** @@ -630,4 +656,44 @@ EOF; '{DAV:}displayname' => $displayName, ]); } + + /** + * Try to handle the given exception gracefully or throw it if necessary. + * + * @throws SameOrganizerForAllComponentsException If the exception should not be ignored + */ + private function handleSameOrganizerException( + SameOrganizerForAllComponentsException $e, + VCalendar $vCal, + string $calendarPath, + ): void { + // This is very hacky! However, we want to allow saving events with multiple + // organizers. Those events are not RFC compliant, but sometimes imported from major + // external calendar services (e.g. Google). If the current user is not an organizer of + // the event we ignore the exception as no scheduling messages will be sent anyway. + + // It would be cleaner to patch Sabre to validate organizers *after* checking if + // scheduling messages are necessary. Currently, organizers are validated first and + // afterwards the broker checks if messages should be scheduled. So the code will throw + // even if the organizers are not relevant. This is to ensure compliance with RFCs but + // a bit too strict for real world usage. + + if (!isset($vCal->VEVENT)) { + throw $e; + } + + $calendarNode = $this->server->tree->getNodeForPath($calendarPath); + if (!($calendarNode instanceof IACL)) { + // Should always be an instance of IACL but just to be sure + throw $e; + } + + $addresses = $this->getAddressesForPrincipal($calendarNode->getOwner()); + foreach ($vCal->VEVENT as $vevent) { + if (in_array($vevent->ORGANIZER->getNormalizedValue(), $addresses, true)) { + // User is an organizer => throw the exception + throw $e; + } + } + } } From 6948c074e98b96b7a7b62ca8be3fc9b62f914c37 Mon Sep 17 00:00:00 2001 From: Louis Chemineau Date: Mon, 18 Dec 2023 11:44:44 +0100 Subject: [PATCH 2/4] Use FileInfo's metadata for hidden prop Signed-off-by: Louis Chemineau --- apps/dav/lib/Connector/Sabre/FilesPlugin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index 81cdfa464b5..f7904e87883 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -388,9 +388,9 @@ class FilesPlugin extends ServerPlugin { } $propFind->handle(self::HIDDEN_PROPERTYNAME, function () use ($node) { - $filesMetadataManager = \OCP\Server::get(IFilesMetadataManager::class); - $metadata = $filesMetadataManager->getMetadata((int)$node->getFileId(), true); - return $metadata->hasKey('files-live-photo') && $node->getFileInfo()->getMimetype() === 'video/quicktime' ? 'true' : 'false'; + $isLivePhoto = isset($node->getFileInfo()->getMetadata()['files-live-photo']); + $isMovFile = $node->getFileInfo()->getMimetype() === 'video/quicktime'; + return ($isLivePhoto && $isMovFile) ? 'true' : 'false'; }); /** From 928fee8ab47841cbc970a1e3b5176eb902fe748b Mon Sep 17 00:00:00 2001 From: Marcel Klehr Date: Tue, 24 Oct 2023 14:20:45 +0200 Subject: [PATCH 3/4] enh(SpeechToText): Allow providers to declare a dynamic ID instead of using className this allows AppAPI to register anonymous classes as SpeechToText providers Signed-off-by: Marcel Klehr --- .../lib/Settings/Admin/ArtificialIntelligence.php | 3 ++- lib/private/SpeechToText/SpeechToTextManager.php | 10 ++++++++-- .../SpeechToText/ISpeechToTextProviderWithId.php | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 lib/public/SpeechToText/ISpeechToTextProviderWithId.php diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index 0af82a74c5e..8a283ba1e70 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -31,6 +31,7 @@ use OCP\IConfig; use OCP\IL10N; use OCP\Settings\IDelegatedSettings; use OCP\SpeechToText\ISpeechToTextManager; +use OCP\SpeechToText\ISpeechToTextProviderWithId; use OCP\TextProcessing\IManager; use OCP\TextProcessing\IProvider; use OCP\TextProcessing\ITaskType; @@ -69,7 +70,7 @@ class ArtificialIntelligence implements IDelegatedSettings { $sttProviders = []; foreach ($this->sttManager->getProviders() as $provider) { $sttProviders[] = [ - 'class' => $provider::class, + 'class' => $provider instanceof ISpeechToTextProviderWithId ? $provider->getId() : $provider::class, 'name' => $provider->getName(), ]; } diff --git a/lib/private/SpeechToText/SpeechToTextManager.php b/lib/private/SpeechToText/SpeechToTextManager.php index bdd04ad3651..520700cf198 100644 --- a/lib/private/SpeechToText/SpeechToTextManager.php +++ b/lib/private/SpeechToText/SpeechToTextManager.php @@ -39,6 +39,7 @@ use OCP\IServerContainer; use OCP\PreConditionNotMetException; use OCP\SpeechToText\ISpeechToTextManager; use OCP\SpeechToText\ISpeechToTextProvider; +use OCP\SpeechToText\ISpeechToTextProviderWithId; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; use Psr\Log\LoggerInterface; @@ -117,8 +118,13 @@ class SpeechToTextManager implements ISpeechToTextManager { $json = $this->config->getAppValue('core', 'ai.stt_provider', ''); if ($json !== '') { - $className = json_decode($json, true); - $provider = current(array_filter($providers, fn ($provider) => $provider::class === $className)); + $classNameOrId = json_decode($json, true); + $provider = current(array_filter($providers, function ($provider) use ($classNameOrId) { + if ($provider instanceof ISpeechToTextProviderWithId) { + return $provider->getId() === $classNameOrId; + } + return $provider::class === $classNameOrId; + })); if ($provider !== false) { $providers = [$provider]; } diff --git a/lib/public/SpeechToText/ISpeechToTextProviderWithId.php b/lib/public/SpeechToText/ISpeechToTextProviderWithId.php new file mode 100644 index 00000000000..0fb337f4602 --- /dev/null +++ b/lib/public/SpeechToText/ISpeechToTextProviderWithId.php @@ -0,0 +1,14 @@ + Date: Tue, 19 Dec 2023 13:59:30 +0100 Subject: [PATCH 4/4] Update autoloaders Signed-off-by: Marcel Klehr --- lib/composer/composer/autoload_classmap.php | 1 + lib/composer/composer/autoload_static.php | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index bca214e3289..41c9452bfbf 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -658,6 +658,7 @@ return array( 'OCP\\SpeechToText\\Events\\TranscriptionSuccessfulEvent' => $baseDir . '/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php', 'OCP\\SpeechToText\\ISpeechToTextManager' => $baseDir . '/lib/public/SpeechToText/ISpeechToTextManager.php', 'OCP\\SpeechToText\\ISpeechToTextProvider' => $baseDir . '/lib/public/SpeechToText/ISpeechToTextProvider.php', + 'OCP\\SpeechToText\\ISpeechToTextProviderWithId' => $baseDir . '/lib/public/SpeechToText/ISpeechToTextProviderWithId.php', 'OCP\\Support\\CrashReport\\ICollectBreadcrumbs' => $baseDir . '/lib/public/Support/CrashReport/ICollectBreadcrumbs.php', 'OCP\\Support\\CrashReport\\IMessageReporter' => $baseDir . '/lib/public/Support/CrashReport/IMessageReporter.php', 'OCP\\Support\\CrashReport\\IRegistry' => $baseDir . '/lib/public/Support/CrashReport/IRegistry.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index df74352d3cf..470c499aeee 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -691,6 +691,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\SpeechToText\\Events\\TranscriptionSuccessfulEvent' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/Events/TranscriptionSuccessfulEvent.php', 'OCP\\SpeechToText\\ISpeechToTextManager' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/ISpeechToTextManager.php', 'OCP\\SpeechToText\\ISpeechToTextProvider' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/ISpeechToTextProvider.php', + 'OCP\\SpeechToText\\ISpeechToTextProviderWithId' => __DIR__ . '/../../..' . '/lib/public/SpeechToText/ISpeechToTextProviderWithId.php', 'OCP\\Support\\CrashReport\\ICollectBreadcrumbs' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/ICollectBreadcrumbs.php', 'OCP\\Support\\CrashReport\\IMessageReporter' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IMessageReporter.php', 'OCP\\Support\\CrashReport\\IRegistry' => __DIR__ . '/../../..' . '/lib/public/Support/CrashReport/IRegistry.php',