]> source.dussan.org Git - nextcloud-server.git/commitdiff
add repair job for unencoded calendars
authorArthur Schiwon <blizzz@arthur-schiwon.de>
Fri, 29 Jan 2021 16:00:18 +0000 (17:00 +0100)
committerArthur Schiwon <blizzz@arthur-schiwon.de>
Wed, 17 Feb 2021 14:59:49 +0000 (15:59 +0100)
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
lib/composer/composer/autoload_classmap.php
lib/composer/composer/autoload_static.php
lib/private/Repair.php
lib/private/Repair/RepairDavShares.php [new file with mode: 0644]
tests/lib/Repair/RepairDavSharesTest.php [new file with mode: 0644]

index 343f7675286bbf21673bf80a8abbece1add2a760..254a475ded49f626af636f448822aefe9061e778 100644 (file)
@@ -1271,6 +1271,7 @@ return array(
     'OC\\Repair\\Owncloud\\SaveAccountsTableData' => $baseDir . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php',
     'OC\\Repair\\Owncloud\\UpdateLanguageCodes' => $baseDir . '/lib/private/Repair/Owncloud/UpdateLanguageCodes.php',
     'OC\\Repair\\RemoveLinkShares' => $baseDir . '/lib/private/Repair/RemoveLinkShares.php',
+    'OC\\Repair\\RepairDavShares' => $baseDir . '/lib/private/Repair/RepairDavShares.php',
     'OC\\Repair\\RepairInvalidShares' => $baseDir . '/lib/private/Repair/RepairInvalidShares.php',
     'OC\\Repair\\RepairMimeTypes' => $baseDir . '/lib/private/Repair/RepairMimeTypes.php',
     'OC\\Repair\\SqliteAutoincrement' => $baseDir . '/lib/private/Repair/SqliteAutoincrement.php',
index 9f36d5828ca8cc0f47c197cb6924a70cdf51a53a..33aeae79616827bf6dc07b825db8d4f6d8ca3b8f 100644 (file)
@@ -1300,6 +1300,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\Repair\\Owncloud\\SaveAccountsTableData' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/SaveAccountsTableData.php',
         'OC\\Repair\\Owncloud\\UpdateLanguageCodes' => __DIR__ . '/../../..' . '/lib/private/Repair/Owncloud/UpdateLanguageCodes.php',
         'OC\\Repair\\RemoveLinkShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RemoveLinkShares.php',
+        'OC\\Repair\\RepairDavShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairDavShares.php',
         'OC\\Repair\\RepairInvalidShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairInvalidShares.php',
         'OC\\Repair\\RepairMimeTypes' => __DIR__ . '/../../..' . '/lib/private/Repair/RepairMimeTypes.php',
         'OC\\Repair\\SqliteAutoincrement' => __DIR__ . '/../../..' . '/lib/private/Repair/SqliteAutoincrement.php',
index f1fd6457418bc7960032de7dfd6d5990cd170bcd..1b034b3be5e7ecf5142938726323177a8fb47d9e 100644 (file)
@@ -60,6 +60,7 @@ use OC\Repair\OldGroupMembershipShares;
 use OC\Repair\Owncloud\DropAccountTermsTable;
 use OC\Repair\Owncloud\SaveAccountsTableData;
 use OC\Repair\RemoveLinkShares;
+use OC\Repair\RepairDavShares;
 use OC\Repair\RepairInvalidShares;
 use OC\Repair\RepairMimeTypes;
 use OC\Repair\SqliteAutoincrement;
diff --git a/lib/private/Repair/RepairDavShares.php b/lib/private/Repair/RepairDavShares.php
new file mode 100644 (file)
index 0000000..ff4c514
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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 OC\Repair;
+
+use OCP\DB\Exception;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use OCP\Migration\IOutput;
+use OCP\Migration\IRepairStep;
+use Psr\Log\LoggerInterface;
+use function strlen;
+use function substr;
+use function urldecode;
+use function urlencode;
+
+class RepairDavShares implements IRepairStep {
+       protected const GROUP_PRINCIPAL_PREFIX = 'principals/groups/';
+
+       /** @var IConfig */
+       private $config;
+       /** @var IDBConnection */
+       private $dbc;
+       /** @var IGroupManager */
+       private $groupManager;
+       /** @var LoggerInterface */
+       private $logger;
+
+       public function __construct(
+               IConfig $config,
+               IDBConnection $dbc,
+               IGroupManager $groupManager,
+               LoggerInterface $logger
+       ) {
+               $this->config = $config;
+               $this->dbc = $dbc;
+               $this->groupManager = $groupManager;
+               $this->logger = $logger;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function getName() {
+               return 'Repair DAV shares';
+       }
+
+       protected function repairUnencodedGroupShares() {
+               $qb = $this->dbc->getQueryBuilder();
+               $qb->select(['id', 'principaluri'])
+                       ->from('dav_shares')
+                       ->where($qb->expr()->like('principaluri', $qb->createNamedParameter(self::GROUP_PRINCIPAL_PREFIX . '%')));
+
+               $updateQuery = $this->dbc->getQueryBuilder();
+               $updateQuery->update('dav_shares')
+                       ->set('principaluri', $updateQuery->createParameter('updatedPrincipalUri'))
+                       ->where($updateQuery->expr()->eq('id', $updateQuery->createParameter('shareId')));
+
+               $statement = $qb->execute();
+               while ($share = $statement->fetch()) {
+                       $gid = substr($share['principaluri'], strlen(self::GROUP_PRINCIPAL_PREFIX));
+                       $decodedGid = urldecode($gid);
+                       $encodedGid = urlencode($gid);
+                       if ($gid === $encodedGid
+                               || !$this->groupManager->groupExists($gid)
+                               || ($gid !== $decodedGid && $this->groupManager->groupExists($decodedGid))
+                       ) {
+                               continue;
+                       }
+
+                       // Repair when
+                       // + the group name needs encoding
+                       // + AND it is not encoded yet
+                       // + AND there are no ambivalent groups
+
+                       try {
+                               $fixedPrincipal = self::GROUP_PRINCIPAL_PREFIX . $encodedGid;
+                               $logParameters = [
+                                       'app' => 'core',
+                                       'id' => $share['id'],
+                                       'old' => $share['principaluri'],
+                                       'new' => $fixedPrincipal,
+                               ];
+                               $updateQuery
+                                       ->setParameter('updatedPrincipalUri', $fixedPrincipal)
+                                       ->setParameter('shareId', $share['id'])
+                                       ->execute();
+                               $this->logger->info('Repaired principal for dav share {id} from {old} to {new}', $logParameters);
+                       } catch (Exception $e) {
+                               $logParameters['message'] = $e->getMessage();
+                               $logParameters['exception'] = $e;
+                               $this->logger->info('Could not repair principal for dav share {id} from {old} to {new}: {message}', $logParameters);
+                       }
+               }
+               return true;
+       }
+
+       /**
+        * @inheritDoc
+        */
+       public function run(IOutput $output) {
+               $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0');
+               if (version_compare($versionFromBeforeUpdate, '20.0.7', '<')
+                       && $this->repairUnencodedGroupShares()
+               ) {
+                       $output->info('Repaired DAV group shares');
+               }
+       }
+}
diff --git a/tests/lib/Repair/RepairDavSharesTest.php b/tests/lib/Repair/RepairDavSharesTest.php
new file mode 100644 (file)
index 0000000..32f09f5
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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 Test\Repair;
+
+use OC\Repair\RepairDavShares;
+use OCP\DB\IResult;
+use OCP\DB\QueryBuilder\IExpressionBuilder;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IConfig;
+use OCP\IDBConnection;
+use OCP\IGroupManager;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+use OCP\Migration\IOutput;
+use function in_array;
+
+class RepairDavSharesTest extends TestCase {
+
+       /** @var IOutput|\PHPUnit\Framework\MockObject\MockObject */
+       protected $output;
+       /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
+       protected $config;
+       /** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */
+       protected $dbc;
+       /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */
+       protected $groupManager;
+       /** @var \PHPUnit\Framework\MockObject\MockObject|LoggerInterface */
+       protected $logger;
+       /** @var RepairDavSharesTest */
+       protected $repair;
+
+       public function setUp(): void {
+               parent::setUp();
+
+               $this->output = $this->createMock(IOutput::class);
+
+               $this->config = $this->createMock(IConfig::class);
+               $this->dbc = $this->createMock(IDBConnection::class);
+               $this->groupManager = $this->createMock(IGroupManager::class);
+               $this->logger = $this->createMock(LoggerInterface::class);
+
+               $this->repair = new RepairDavShares(
+                       $this->config,
+                       $this->dbc,
+                       $this->groupManager,
+                       $this->logger
+               );
+       }
+
+       public function testRun() {
+               $this->config->expects($this->any())
+                       ->method('getSystemValue')
+                       ->with('version', '0.0.0')
+                       ->willReturn('20.0.2');
+
+               $this->output->expects($this->once())
+                       ->method('info')
+                       ->with('Repaired DAV group shares');
+
+               $existingGroups = [
+                       'Innocent',
+                       'Wants Repair',
+                       'Well förmed',
+                       'family+friends',
+                       'family friends',
+               ];
+
+               $shareResultData = [
+                       [
+                               // No update, nothing to escape
+                               'id' => 0,
+                               'principaluri' => 'principals/groups/Innocent',
+                       ],
+                       [
+                               // Update
+                               'id' => 1,
+                               'principaluri' => 'principals/groups/Wants Repair',
+                       ],
+                       [
+                               // No update, already proper
+                               'id' => 2,
+                               'principaluri' => 'principals/groups/Well+f%C3%B6rmed',
+                       ],
+                       [
+                               // No update, unknown group
+                               'id' => 3,
+                               'principaluri' => 'principals/groups/Not known',
+                       ],
+                       [
+                               // No update, unknown group
+                               'id' => 4,
+                               'principaluri' => 'principals/groups/Also%2F%2FNot%23Known',
+                       ],
+                       [
+                               // No update, group exists in both forms
+                               'id' => 5,
+                               'principaluri' => 'principals/groups/family+friends',
+                       ],
+                       [
+                               // No update, already proper
+                               'id' => 6,
+                               'principaluri' => 'principals/groups/family%2Bfriends',
+                       ],
+                       [
+                               // Update
+                               'id' => 7,
+                               'principaluri' => 'principals/groups/family friends',
+                       ],
+               ];
+
+               $shareResults = $this->createMock(IResult::class);
+               $shareResults->expects($this->any())
+                       ->method('fetch')
+                       ->willReturnCallback(function () use (&$shareResultData) {
+                               return array_pop($shareResultData);
+                       });
+
+               $expressionBuilder = $this->createMock(IExpressionBuilder::class);
+
+               $selectMock = $this->createMock(IQueryBuilder::class);
+               $selectMock->expects($this->any())
+                       ->method('expr')
+                       ->willReturn($expressionBuilder);
+               $selectMock->expects($this->once())
+                       ->method('select')
+                       ->willReturnSelf();
+               $selectMock->expects($this->once())
+                       ->method('from')
+                       ->willReturnSelf();
+               $selectMock->expects($this->once())
+                       ->method('where')
+                       ->willReturnSelf();
+               $selectMock->expects($this->once())
+                       ->method('execute')
+                       ->willReturn($shareResults);
+
+               $updateMock = $this->createMock(IQueryBuilder::class);
+               $updateMock->expects($this->any())
+                       ->method('expr')
+                       ->willReturn($expressionBuilder);
+               $updateMock->expects($this->once())
+                       ->method('update')
+                       ->willReturnSelf();
+               $updateMock->expects($this->any())
+                       ->method('set')
+                       ->willReturnSelf();
+               $updateMock->expects($this->once())
+                       ->method('where')
+                       ->willReturnSelf();
+               $updateMock->expects($this->exactly(4))
+                       ->method('setParameter')
+                       ->withConsecutive(
+                               ['updatedPrincipalUri', 'principals/groups/' . urlencode('family friends')],
+                               ['shareId', 7],
+                               ['updatedPrincipalUri', 'principals/groups/' . urlencode('Wants Repair')],
+                               ['shareId', 1],
+                       )
+                       ->willReturnSelf();
+               $updateMock->expects($this->exactly(2))
+                       ->method('execute');
+
+               $this->dbc->expects($this->atLeast(2))
+                       ->method('getQueryBuilder')
+                       ->willReturnOnConsecutiveCalls($selectMock, $updateMock);
+
+               $this->groupManager->expects($this->any())
+                       ->method('groupExists')
+                       ->willReturnCallback(function (string $gid) use ($existingGroups) {
+                               return in_array($gid, $existingGroups);
+                       });
+
+               $this->repair->run($this->output);
+       }
+}