diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index 5c298ffbb2c39..c874e0cb180a4 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -40,6 +40,7 @@ OCA\DAV\Migration\CalDAVRemoveEmptyValue OCA\DAV\Migration\BuildCalendarSearchIndex OCA\DAV\Migration\BuildSocialSearchIndex + OCA\DAV\Migration\DisableSystemAddressBook OCA\DAV\Migration\RefreshWebcalJobRegistrar OCA\DAV\Migration\RegisterBuildReminderIndexBackgroundJob OCA\DAV\Migration\RegisterUpdateCalendarResourcesRoomBackgroundJob diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 36ef79c37c999..08c5b395b2e60 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -168,6 +168,7 @@ 'OCA\\DAV\\CardDAV\\Integration\\ExternalAddressBook' => $baseDir . '/../lib/CardDAV/Integration/ExternalAddressBook.php', 'OCA\\DAV\\CardDAV\\Integration\\IAddressBookProvider' => $baseDir . '/../lib/CardDAV/Integration/IAddressBookProvider.php', 'OCA\\DAV\\CardDAV\\MultiGetExportPlugin' => $baseDir . '/../lib/CardDAV/MultiGetExportPlugin.php', + 'OCA\\DAV\\CardDAV\\Notification\\Notifier' => $baseDir . '/../lib/CardDAV/Notification/Notifier.php', 'OCA\\DAV\\CardDAV\\PhotoCache' => $baseDir . '/../lib/CardDAV/PhotoCache.php', 'OCA\\DAV\\CardDAV\\Plugin' => $baseDir . '/../lib/CardDAV/Plugin.php', 'OCA\\DAV\\CardDAV\\Security\\CardDavRateLimitingPlugin' => $baseDir . '/../lib/CardDAV/Security/CardDavRateLimitingPlugin.php', @@ -345,6 +346,7 @@ 'OCA\\DAV\\Migration\\ChunkCleanup' => $baseDir . '/../lib/Migration/ChunkCleanup.php', 'OCA\\DAV\\Migration\\CreateSystemAddressBookStep' => $baseDir . '/../lib/Migration/CreateSystemAddressBookStep.php', 'OCA\\DAV\\Migration\\DeleteSchedulingObjects' => $baseDir . '/../lib/Migration/DeleteSchedulingObjects.php', + 'OCA\\DAV\\Migration\\DisableSystemAddressBook' => $baseDir . '/../lib/Migration/DisableSystemAddressBook.php', 'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => $baseDir . '/../lib/Migration/FixBirthdayCalendarComponent.php', 'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => $baseDir . '/../lib/Migration/RefreshWebcalJobRegistrar.php', 'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => $baseDir . '/../lib/Migration/RegenerateBirthdayCalendars.php', @@ -407,6 +409,7 @@ 'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php', 'OCA\\DAV\\Settings\\ExampleContentSettings' => $baseDir . '/../lib/Settings/ExampleContentSettings.php', 'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => $baseDir . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php', + 'OCA\\DAV\\SetupChecks\\SystemAddressBookSize' => $baseDir . '/../lib/SetupChecks/SystemAddressBookSize.php', 'OCA\\DAV\\SetupChecks\\WebdavEndpoint' => $baseDir . '/../lib/SetupChecks/WebdavEndpoint.php', 'OCA\\DAV\\Storage\\PublicOwnerWrapper' => $baseDir . '/../lib/Storage/PublicOwnerWrapper.php', 'OCA\\DAV\\Storage\\PublicShareWrapper' => $baseDir . '/../lib/Storage/PublicShareWrapper.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 75b8c9193e8d6..4c7d43cddbfd5 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -183,6 +183,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\CardDAV\\Integration\\ExternalAddressBook' => __DIR__ . '/..' . '/../lib/CardDAV/Integration/ExternalAddressBook.php', 'OCA\\DAV\\CardDAV\\Integration\\IAddressBookProvider' => __DIR__ . '/..' . '/../lib/CardDAV/Integration/IAddressBookProvider.php', 'OCA\\DAV\\CardDAV\\MultiGetExportPlugin' => __DIR__ . '/..' . '/../lib/CardDAV/MultiGetExportPlugin.php', + 'OCA\\DAV\\CardDAV\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/CardDAV/Notification/Notifier.php', 'OCA\\DAV\\CardDAV\\PhotoCache' => __DIR__ . '/..' . '/../lib/CardDAV/PhotoCache.php', 'OCA\\DAV\\CardDAV\\Plugin' => __DIR__ . '/..' . '/../lib/CardDAV/Plugin.php', 'OCA\\DAV\\CardDAV\\Security\\CardDavRateLimitingPlugin' => __DIR__ . '/..' . '/../lib/CardDAV/Security/CardDavRateLimitingPlugin.php', @@ -360,6 +361,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\ChunkCleanup' => __DIR__ . '/..' . '/../lib/Migration/ChunkCleanup.php', 'OCA\\DAV\\Migration\\CreateSystemAddressBookStep' => __DIR__ . '/..' . '/../lib/Migration/CreateSystemAddressBookStep.php', 'OCA\\DAV\\Migration\\DeleteSchedulingObjects' => __DIR__ . '/..' . '/../lib/Migration/DeleteSchedulingObjects.php', + 'OCA\\DAV\\Migration\\DisableSystemAddressBook' => __DIR__ . '/..' . '/../lib/Migration/DisableSystemAddressBook.php', 'OCA\\DAV\\Migration\\FixBirthdayCalendarComponent' => __DIR__ . '/..' . '/../lib/Migration/FixBirthdayCalendarComponent.php', 'OCA\\DAV\\Migration\\RefreshWebcalJobRegistrar' => __DIR__ . '/..' . '/../lib/Migration/RefreshWebcalJobRegistrar.php', 'OCA\\DAV\\Migration\\RegenerateBirthdayCalendars' => __DIR__ . '/..' . '/../lib/Migration/RegenerateBirthdayCalendars.php', @@ -422,6 +424,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php', 'OCA\\DAV\\Settings\\ExampleContentSettings' => __DIR__ . '/..' . '/../lib/Settings/ExampleContentSettings.php', 'OCA\\DAV\\SetupChecks\\NeedsSystemAddressBookSync' => __DIR__ . '/..' . '/../lib/SetupChecks/NeedsSystemAddressBookSync.php', + 'OCA\\DAV\\SetupChecks\\SystemAddressBookSize' => __DIR__ . '/..' . '/../lib/SetupChecks/SystemAddressBookSize.php', 'OCA\\DAV\\SetupChecks\\WebdavEndpoint' => __DIR__ . '/..' . '/../lib/SetupChecks/WebdavEndpoint.php', 'OCA\\DAV\\Storage\\PublicOwnerWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicOwnerWrapper.php', 'OCA\\DAV\\Storage\\PublicShareWrapper' => __DIR__ . '/..' . '/../lib/Storage/PublicShareWrapper.php', diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 6d1eeeeba532c..dfc4216713d3e 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -18,9 +18,10 @@ use OCA\DAV\CalDAV\Reminder\NotificationProvider\EmailProvider; use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; -use OCA\DAV\CalDAV\Reminder\Notifier; +use OCA\DAV\CalDAV\Reminder\Notifier as NotifierCalDAV; use OCA\DAV\Capabilities; use OCA\DAV\CardDAV\ContactsManager; +use OCA\DAV\CardDAV\Notification\Notifier as NotifierCardDAV; use OCA\DAV\CardDAV\SyncService; use OCA\DAV\Events\AddressBookCreatedEvent; use OCA\DAV\Events\AddressBookDeletedEvent; @@ -64,6 +65,7 @@ use OCA\DAV\Search\TasksSearchProvider; use OCA\DAV\Settings\Admin\SystemAddressBookSettings; use OCA\DAV\SetupChecks\NeedsSystemAddressBookSync; +use OCA\DAV\SetupChecks\SystemAddressBookSize; use OCA\DAV\SetupChecks\WebdavEndpoint; use OCA\DAV\UserMigration\CalendarMigrator; use OCA\DAV\UserMigration\ContactsMigrator; @@ -209,7 +211,8 @@ public function register(IRegistrationContext $context): void { $context->registerEventListener(CalendarObjectUpdatedEvent::class, CalendarFederationNotificationListener::class); $context->registerEventListener(CalendarObjectDeletedEvent::class, CalendarFederationNotificationListener::class); - $context->registerNotifierService(Notifier::class); + $context->registerNotifierService(NotifierCalDAV::class); + $context->registerNotifierService(NotifierCardDAV::class); $context->registerCalendarProvider(CalendarProvider::class); $context->registerCalendarProvider(CachedSubscriptionProvider::class); @@ -218,6 +221,7 @@ public function register(IRegistrationContext $context): void { $context->registerUserMigrator(ContactsMigrator::class); $context->registerSetupCheck(NeedsSystemAddressBookSync::class); + $context->registerSetupCheck(SystemAddressBookSize::class); $context->registerSetupCheck(WebdavEndpoint::class); // register admin settings form and listener(s) diff --git a/apps/dav/lib/CardDAV/Notification/Notifier.php b/apps/dav/lib/CardDAV/Notification/Notifier.php new file mode 100644 index 0000000000000..7620bd06cd903 --- /dev/null +++ b/apps/dav/lib/CardDAV/Notification/Notifier.php @@ -0,0 +1,69 @@ +l10nFactory->get(Application::APP_ID)->t('Contacts'); + } + + /** + * @inheritDoc + */ + public function prepare(INotification $notification, string $languageCode): INotification { + if ($notification->getApp() !== Application::APP_ID) { + throw new InvalidArgumentException(); + } + + $l = $this->l10nFactory->get(Application::APP_ID, $languageCode); + + return match ($notification->getSubject()) { + 'SystemAddressBookDisabled' => $this->parseSystemAddressBookDisabled($notification, $l), + default => throw new UnknownNotificationException() + }; + } + + /** + * Generates a notification for the system address book being disabled. + */ + protected function parseSystemAddressBookDisabled(INotification $notification, IL10N $l): INotification { + $command = 'occ config:app:set dav system_addressbook_exposed --value="yes"'; + $notification->setParsedSubject( + $l->t('System address book disabled') + )->setParsedMessage( + $l->t('The system contacts address book has been automatically disabled during upgrade. This means that the address book will no longer be available to users in the contacts app or other clients. The system contacts address book was disabled because the amount of contacts in the address book exceeded the maximum recommended number of contacts. This limit is set to prevent performance issues. You can re-enable the system address book with the following command {command}', ['command' => $command]) + ); + return $notification; + } + +} diff --git a/apps/dav/lib/Migration/DisableSystemAddressBook.php b/apps/dav/lib/Migration/DisableSystemAddressBook.php new file mode 100644 index 0000000000000..f2530c4a1b1a5 --- /dev/null +++ b/apps/dav/lib/Migration/DisableSystemAddressBook.php @@ -0,0 +1,65 @@ +appConfig->hasAppKey('system_addressbook_exposed') === true) { + $output->info('Skipping repair step system address book exposed was previously set'); + return; + } + // We use count seen because getting a user count from the backend can be very slow + $limit = $this->appConfig->getAppValueInt('system_addressbook_limit', 5000); + if ($this->userManager->countSeenUsers() <= $limit) { + $output->info("Skipping repair step system address book has less then the threshold $limit of contacts no need to disable"); + return; + } + $this->appConfig->setAppValueBool('system_addressbook_exposed', false); + $output->warning("System address book disabled because it has more then the threshold of $limit contacts this can be re-enabled later"); + // Notify all admin users about the system address book being disabled + foreach ($this->groupManager->get('admin')->getUsers() as $user) { + $notification = $this->notificationManager->createNotification(); + $notification->setApp(Application::APP_ID) + ->setUser($user->getUID()) + ->setDateTime(new \DateTime()) + ->setSubject('SystemSystemAddressBookDisabled', []); + $this->notificationManager->notify($notification); + } + } +} diff --git a/apps/dav/lib/SetupChecks/SystemAddressBookSize.php b/apps/dav/lib/SetupChecks/SystemAddressBookSize.php new file mode 100644 index 0000000000000..ad7d8b9c2cc2f --- /dev/null +++ b/apps/dav/lib/SetupChecks/SystemAddressBookSize.php @@ -0,0 +1,50 @@ +l10n->t('DAV system address book size'); + } + + public function getCategory(): string { + return 'dav'; + } + + public function run(): SetupResult { + if (!$this->appConfig->getValueBool(Application::APP_ID, 'system_addressbook_exposed', true)) { + return SetupResult::success($this->l10n->t('The system address book is disabled')); + } + + // We use count seen because getting a user count from the backend can be very slow + $count = $this->userManager->countSeenUsers(); + $limit = $this->appConfig->getValueInt(Application::APP_ID, 'system_addressbook_limit', 5000); + + if ($count > $limit) { + return SetupResult::warning($this->l10n->t('The system address book is enabled, but contains more than the configured limit of %d contacts', [$limit])); + } else { + return SetupResult::success($this->l10n->t('The system address book is enabled and contains less than the configured limit of %d contacts', [$limit])); + } + } +} diff --git a/apps/dav/tests/unit/SetupChecks/SystemAddressBookSizeTest.php b/apps/dav/tests/unit/SetupChecks/SystemAddressBookSizeTest.php new file mode 100644 index 0000000000000..175d91a0c7d21 --- /dev/null +++ b/apps/dav/tests/unit/SetupChecks/SystemAddressBookSizeTest.php @@ -0,0 +1,69 @@ +appConfig = $this->createMock(IAppConfig::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->l10n = $this->createMock(IL10N::class); + $this->l10n->method('t')->willReturnCallback(fn ($text, $parameters = []) => vsprintf($text, $parameters)); + } + + public function testSystemAddressBookDisabled() { + $this->appConfig->method('getValueBool') + ->with(Application::APP_ID, 'system_addressbook_exposed', true) + ->willReturn(false); + + $check = new SystemAddressBookSize($this->appConfig, $this->userManager, $this->l10n); + $result = $check->run(); + $this->assertEquals('success', $result->getSeverity()); + $this->assertStringContainsString('disabled', $result->getDescription()); + } + + public function testSystemAddressBookOverLimit() { + $this->appConfig->method('getValueBool') + ->willReturn(true); + $this->userManager->method('countSeenUsers') + ->willReturn(6000); + $this->appConfig->method('getValueInt') + ->willReturn(5000); + + $check = new SystemAddressBookSize($this->appConfig, $this->userManager, $this->l10n); + $result = $check->run(); + $this->assertEquals('warning', $result->getSeverity()); + $this->assertStringContainsString('more than the configured limit', $result->getDescription()); + } + + public function testSystemAddressBookUnderLimit() { + $this->appConfig->method('getValueBool') + ->willReturn(true); + $this->userManager->method('countSeenUsers') + ->willReturn(1000); + $this->appConfig->method('getValueInt') + ->willReturn(5000); + + $check = new SystemAddressBookSize($this->appConfig, $this->userManager, $this->l10n); + $result = $check->run(); + $this->assertEquals('success', $result->getSeverity()); + $this->assertStringContainsString('less than the configured limit', $result->getDescription()); + } +}