]> source.dussan.org Git - nextcloud-server.git/commitdiff
invalidated duplicated UUIDs prior to migration change
authorArthur Schiwon <blizzz@arthur-schiwon.de>
Wed, 2 Feb 2022 17:10:10 +0000 (18:10 +0100)
committerbackportbot[bot] <backportbot[bot]@users.noreply.github.com>
Thu, 10 Feb 2022 18:48:32 +0000 (18:48 +0000)
- in a proper setup there are no duplicated UUIDs
- not all setups are proper
- log warning to admin

Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>
apps/user_ldap/lib/Migration/Version1130Date20211102154716.php

index 8695f90ca65bafbb8237bf26e1c4a0ce19a22d76..94f0f0aa82e6ec39a52630b35350442b37a64cb0 100644 (file)
@@ -27,6 +27,7 @@ declare(strict_types=1);
 namespace OCA\User_LDAP\Migration;
 
 use Closure;
+use Generator;
 use OCP\DB\Exception;
 use OCP\DB\ISchemaWrapper;
 use OCP\DB\QueryBuilder\IQueryBuilder;
@@ -52,6 +53,12 @@ class Version1130Date20211102154716 extends SimpleMigrationStep {
                return 'Adjust LDAP user and group ldap_dn column lengths and add ldap_dn_hash columns';
        }
 
+       public function preSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
+               foreach (['ldap_user_mapping', 'ldap_group_mapping'] as $tableName) {
+                       $this->processDuplicateUUIDs($tableName);
+               }
+       }
+
        /**
         * @param IOutput $output
         * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
@@ -172,4 +179,87 @@ class Version1130Date20211102154716 extends SimpleMigrationStep {
                        ->where($qb->expr()->eq('owncloud_name', $qb->createParameter('name')));
                return $qb;
        }
+
+       /**
+        * @throws Exception
+        */
+       protected function processDuplicateUUIDs(string $table): void {
+               $uuids = $this->getDuplicatedUuids($table);
+               $idsWithUuidToInvalidate = [];
+               foreach ($uuids as $uuid) {
+                       array_push($idsWithUuidToInvalidate, ...$this->getNextcloudIdsByUuid($table, $uuid));
+               }
+               $this->invalidateUuids($table, $idsWithUuidToInvalidate);
+       }
+
+       /**
+        * @throws Exception
+        */
+       protected function invalidateUuids(string $table, array $idList): void {
+               $update = $this->dbc->getQueryBuilder();
+               $update->update($table)
+                       ->set('directory_uuid', $update->createParameter('invalidatedUuid'))
+                       ->where($update->expr()->eq('owncloud_name', $update->createParameter('nextcloudId')));
+
+               while ($nextcloudId = array_shift($idList)) {
+                       $update->setParameter('nextcloudId', $nextcloudId);
+                       $update->setParameter('invalidatedUuid', 'invalidated_' . \bin2hex(\random_bytes(6)));
+                       try {
+                               $update->executeStatement();
+                               $this->logger->warning(
+                                       'LDAP user or group with ID {nid} has a duplicated UUID value which therefore was invalidated. You may double-check your LDAP configuration and trigger an update of the UUID.',
+                                       [
+                                               'app' => 'user_ldap',
+                                               'nid' => $nextcloudId,
+                                       ]
+                               );
+                       } catch (Exception $e) {
+                               // Catch possible, but unlikely duplications if new invalidated errors.
+                               // There is the theoretical chance of an infinity loop is, when
+                               // the constraint violation has a different background. I cannot
+                               // think of one at the moment.
+                               if ($e->getReason() !== Exception::REASON_CONSTRAINT_VIOLATION) {
+                                       throw $e;
+                               }
+                               $idList[] = $nextcloudId;
+                       }
+               }
+       }
+
+       /**
+        * @throws \OCP\DB\Exception
+        * @return array<string>
+        */
+       protected function getNextcloudIdsByUuid(string $table, string $uuid): array {
+               $select = $this->dbc->getQueryBuilder();
+               $select->select('owncloud_name')
+                       ->from($table)
+                       ->where($select->expr()->eq('directory_uuid', $select->createNamedParameter($uuid)));
+
+               $result = $select->executeQuery();
+               $idList = [];
+               while ($id = $result->fetchOne()) {
+                       $idList[] = $id;
+               }
+               $result->closeCursor();
+               return $idList;
+       }
+
+       /**
+        * @return Generator<string>
+        * @throws \OCP\DB\Exception
+        */
+       protected function getDuplicatedUuids(string $table): Generator{
+               $select = $this->dbc->getQueryBuilder();
+               $select->select('directory_uuid')
+                       ->from($table)
+                       ->groupBy('directory_uuid')
+                       ->having($select->expr()->gt($select->func()->count('owncloud_name'), $select->createNamedParameter(1)));
+
+               $result = $select->executeQuery();
+               while ($uuid = $result->fetchOne()) {
+                       yield $uuid;
+               }
+               $result->closeCursor();
+       }
 }