diff options
author | Christoph Wurst <christoph@winzerhof-wurst.at> | 2024-03-11 16:27:23 +0100 |
---|---|---|
committer | backportbot[bot] <backportbot[bot]@users.noreply.github.com> | 2024-03-14 13:06:38 +0000 |
commit | 40b391dc998dfad2bfc3f7b6459e76a34c60d851 (patch) | |
tree | 4f69de25de9b592368c066ccd37a42d7c610b3e1 | |
parent | 2d276c8a65d815b423acdc3d76e52e46585bff2d (diff) | |
download | nextcloud-server-backport/44130/stable26.tar.gz nextcloud-server-backport/44130/stable26.zip |
fix(dav): Add occ command to fix missing caldav sync tokensbackport/44130/stable26
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
[skip ci]
-rw-r--r-- | apps/dav/appinfo/info.xml | 1 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | apps/dav/composer/composer/autoload_static.php | 1 | ||||
-rw-r--r-- | apps/dav/lib/CalDAV/CalDavBackend.php | 44 | ||||
-rw-r--r-- | apps/dav/lib/Command/FixCalendarSyncCommand.php | 86 | ||||
-rw-r--r-- | apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php | 5 | ||||
-rw-r--r-- | apps/dav/tests/unit/CalDAV/CalDavBackendTest.php | 89 |
7 files changed, 224 insertions, 3 deletions
diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index dd657564ea9..60d6b417847 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -50,6 +50,7 @@ <command>OCA\DAV\Command\CreateAddressBook</command> <command>OCA\DAV\Command\CreateCalendar</command> <command>OCA\DAV\Command\DeleteCalendar</command> + <command>OCA\DAV\Command\FixCalendarSyncCommand</command> <command>OCA\DAV\Command\MoveCalendar</command> <command>OCA\DAV\Command\ListCalendars</command> <command>OCA\DAV\Command\RetentionCleanupCommand</command> diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index 4286a6b89e3..ef7594e2462 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -129,6 +129,7 @@ return array( 'OCA\\DAV\\Command\\CreateAddressBook' => $baseDir . '/../lib/Command/CreateAddressBook.php', 'OCA\\DAV\\Command\\CreateCalendar' => $baseDir . '/../lib/Command/CreateCalendar.php', 'OCA\\DAV\\Command\\DeleteCalendar' => $baseDir . '/../lib/Command/DeleteCalendar.php', + 'OCA\\DAV\\Command\\FixCalendarSyncCommand' => $baseDir . '/../lib/Command/FixCalendarSyncCommand.php', 'OCA\\DAV\\Command\\ListCalendars' => $baseDir . '/../lib/Command/ListCalendars.php', 'OCA\\DAV\\Command\\MoveCalendar' => $baseDir . '/../lib/Command/MoveCalendar.php', 'OCA\\DAV\\Command\\RemoveInvalidShares' => $baseDir . '/../lib/Command/RemoveInvalidShares.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 2b5d4231b36..47215328587 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -144,6 +144,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Command\\CreateAddressBook' => __DIR__ . '/..' . '/../lib/Command/CreateAddressBook.php', 'OCA\\DAV\\Command\\CreateCalendar' => __DIR__ . '/..' . '/../lib/Command/CreateCalendar.php', 'OCA\\DAV\\Command\\DeleteCalendar' => __DIR__ . '/..' . '/../lib/Command/DeleteCalendar.php', + 'OCA\\DAV\\Command\\FixCalendarSyncCommand' => __DIR__ . '/..' . '/../lib/Command/FixCalendarSyncCommand.php', 'OCA\\DAV\\Command\\ListCalendars' => __DIR__ . '/..' . '/../lib/Command/ListCalendars.php', 'OCA\\DAV\\Command\\MoveCalendar' => __DIR__ . '/..' . '/../lib/Command/MoveCalendar.php', 'OCA\\DAV\\Command\\RemoveInvalidShares' => __DIR__ . '/..' . '/../lib/Command/RemoveInvalidShares.php', diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 24189d3f5f5..6289e22ac96 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -99,6 +99,7 @@ use Sabre\VObject\Property; use Sabre\VObject\Reader; use Sabre\VObject\Recur\EventIterator; use function array_column; +use function array_map; use function array_merge; use function array_values; use function explode; @@ -2706,7 +2707,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription * Adds a change record to the calendarchanges table. * * @param mixed $calendarId - * @param string $objectUri + * @param string[] $objectUris * @param int $operation 1 = add, 2 = modify, 3 = delete. * @param int $calendarType * @return void @@ -2740,6 +2741,47 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription ]); } + public function restoreChanges(int $calendarId, int $calendarType = self::CALENDAR_TYPE_CALENDAR): void { + $this->cachedObjects = []; + + $this->atomic(function () use ($calendarId, $calendarType) { + $qbAdded = $this->db->getQueryBuilder(); + $qbAdded->select('uri') + ->from('calendarobjects') + ->where( + $qbAdded->expr()->andX( + $qbAdded->expr()->eq('calendarid', $qbAdded->createNamedParameter($calendarId)), + $qbAdded->expr()->eq('calendartype', $qbAdded->createNamedParameter($calendarType)), + $qbAdded->expr()->isNull('deleted_at'), + ) + ); + $resultAdded = $qbAdded->executeQuery(); + $addedUris = $resultAdded->fetchAll(\PDO::FETCH_COLUMN); + $resultAdded->closeCursor(); + // Track everything as changed + // Tracking the creation is not necessary because \OCA\DAV\CalDAV\CalDavBackend::getChangesForCalendar + // only returns the last change per object. + $this->addChanges($calendarId, $addedUris, 2, $calendarType); + + $qbDeleted = $this->db->getQueryBuilder(); + $qbDeleted->select('uri') + ->from('calendarobjects') + ->where( + $qbDeleted->expr()->andX( + $qbDeleted->expr()->eq('calendarid', $qbDeleted->createNamedParameter($calendarId)), + $qbDeleted->expr()->eq('calendartype', $qbDeleted->createNamedParameter($calendarType)), + $qbDeleted->expr()->isNotNull('deleted_at'), + ) + ); + $resultDeleted = $qbDeleted->executeQuery(); + $deletedUris = array_map(function (string $uri) { + return str_replace("-deleted.ics", ".ics", $uri); + }, $resultDeleted->fetchAll(\PDO::FETCH_COLUMN)); + $resultDeleted->closeCursor(); + $this->addChanges($calendarId, $deletedUris, 3, $calendarType); + }, $this->db); + } + /** * Parses some information from calendar objects, used for optimized * calendar-queries. diff --git a/apps/dav/lib/Command/FixCalendarSyncCommand.php b/apps/dav/lib/Command/FixCalendarSyncCommand.php new file mode 100644 index 00000000000..6db32ff6d5e --- /dev/null +++ b/apps/dav/lib/Command/FixCalendarSyncCommand.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); + +/* + * @copyright 2024 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2024 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +namespace OCA\DAV\Command; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; + +class FixCalendarSyncCommand extends Command { + + public function __construct(private IUserManager $userManager, + private CalDavBackend $calDavBackend) { + parent::__construct('dav:fix-missing-caldav-changes'); + } + + protected function configure(): void { + $this->setDescription('Insert missing calendarchanges rows for existing events'); + $this->addArgument( + 'user', + InputArgument::OPTIONAL, + 'User to fix calendar sync tokens for, if omitted all users will be fixed', + null, + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $userArg = $input->getArgument('user'); + if ($userArg !== null) { + $user = $this->userManager->get($userArg); + if ($user === null) { + $output->writeln("<error>User $userArg does not exist</error>"); + return 1; + } + + $this->fixUserCalendars($user); + } else { + $progress = new ProgressBar($output); + $this->userManager->callForSeenUsers(function (IUser $user) use ($progress) { + $this->fixUserCalendars($user, $progress); + }); + $progress->finish(); + } + return 0; + } + + private function fixUserCalendars(IUser $user, ?ProgressBar $progress = null): void { + $calendars = $this->calDavBackend->getCalendarsForUser("principals/users/" . $user->getUID()); + + foreach ($calendars as $calendar) { + $this->calDavBackend->restoreChanges($calendar['id']); + } + + if ($progress !== null) { + $progress->advance(); + } + } + +} diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index b4a414297d7..960100db0d2 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -34,6 +34,7 @@ use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IUserManager; use OCP\IUserSession; @@ -111,12 +112,12 @@ abstract class AbstractCalDavBackend extends TestCase { ->withAnyParameters() ->willReturn([self::UNIT_TEST_GROUP, self::UNIT_TEST_GROUP2]); - $db = \OC::$server->getDatabaseConnection(); + $this->db = \OC::$server->getDatabaseConnection(); $this->random = \OC::$server->getSecureRandom(); $this->logger = $this->createMock(LoggerInterface::class); $this->config = $this->createMock(IConfig::class); $this->backend = new CalDavBackend( - $db, + $this->db, $this->principal, $this->userManager, $this->groupManager, diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index 868205301dd..46121a4638d 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -1489,4 +1489,93 @@ EOD; } } } + + public function testRestoreChanges(): void { + $calendarId = $this->createTestCalendar(); + $uri1 = static::getUniqueID('calobj1') . '.ics'; + $calData = <<<EOD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Nextcloud Calendar +BEGIN:VEVENT +CREATED;VALUE=DATE-TIME:20130910T125139Z +UID:47d15e3ec8 +LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z +DTSTAMP;VALUE=DATE-TIME:20130910T125139Z +SUMMARY:Test Event +DTSTART;VALUE=DATE-TIME:20130912T130000Z +DTEND;VALUE=DATE-TIME:20130912T140000Z +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR +EOD; + $this->backend->createCalendarObject($calendarId, $uri1, $calData); + $calData = <<<EOD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Nextcloud Calendar +BEGIN:VEVENT +CREATED;VALUE=DATE-TIME:20130910T125139Z +UID:47d15e3ec8 +LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z +DTSTAMP;VALUE=DATE-TIME:20130910T125139Z +SUMMARY:Test Event – UPDATED +DTSTART;VALUE=DATE-TIME:20130912T130000Z +DTEND;VALUE=DATE-TIME:20130912T140000Z +CLASS:PUBLIC +SEQUENCE:1 +END:VEVENT +END:VCALENDAR +EOD; + $this->backend->updateCalendarObject($calendarId, $uri1, $calData); + $uri2 = static::getUniqueID('calobj2') . '.ics'; + $calData = <<<EOD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Nextcloud Calendar +BEGIN:VEVENT +CREATED;VALUE=DATE-TIME:20130910T125139Z +UID:47d15e3ec9 +LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z +DTSTAMP;VALUE=DATE-TIME:20130910T125139Z +SUMMARY:Test Event +DTSTART;VALUE=DATE-TIME:20130912T130000Z +DTEND;VALUE=DATE-TIME:20130912T140000Z +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR +EOD; + $this->backend->createCalendarObject($calendarId, $uri2, $calData); + $changesBefore = $this->backend->getChangesForCalendar($calendarId, null, 1); + $this->backend->deleteCalendarObject($calendarId, $uri2); + $uri3 = static::getUniqueID('calobj3') . '.ics'; + $calData = <<<EOD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:Nextcloud Calendar +BEGIN:VEVENT +CREATED;VALUE=DATE-TIME:20130910T125139Z +UID:47d15e3e10 +LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z +DTSTAMP;VALUE=DATE-TIME:20130910T125139Z +SUMMARY:Test Event +DTSTART;VALUE=DATE-TIME:20130912T130000Z +DTEND;VALUE=DATE-TIME:20130912T140000Z +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR +EOD; + $this->backend->createCalendarObject($calendarId, $uri3, $calData); + $deleteChanges = $this->db->getQueryBuilder(); + $deleteChanges->delete('calendarchanges') + ->where($deleteChanges->expr()->eq('calendarid', $deleteChanges->createNamedParameter($calendarId))); + $deleteChanges->executeStatement(); + + $this->backend->restoreChanges($calendarId); + + $changesAfter = $this->backend->getChangesForCalendar($calendarId, $changesBefore['syncToken'], 1); + self::assertEquals([], $changesAfter['added']); + self::assertEqualsCanonicalizing([$uri1, $uri3], $changesAfter['modified']); + self::assertEquals([$uri2], $changesAfter['deleted']); + } } |