diff options
author | Robin Appelman <robin@icewind.nl> | 2022-11-10 18:36:36 +0300 |
---|---|---|
committer | Robin Appelman <robin@icewind.nl> | 2022-11-10 18:36:36 +0300 |
commit | 5b0a5d1521d2150bd70219355e4e47e68ef9d44b (patch) | |
tree | cf18fddb0d9c61e548c9dcabda2300a87861ecc8 | |
parent | 35905126aa49435f41416f0f21bc862531cc7f76 (diff) |
[stable22/wip] Add repair command to fix wrong share ownershipbackport/32211/stable22-test
Signed-off-by: Robin Appelman <robin@icewind.nl>
-rw-r--r-- | core/Command/Maintenance/RepairShareOwnership.php | 198 | ||||
-rw-r--r-- | core/register_command.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 1 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 1 |
4 files changed, 201 insertions, 0 deletions
diff --git a/core/Command/Maintenance/RepairShareOwnership.php b/core/Command/Maintenance/RepairShareOwnership.php new file mode 100644 index 00000000000..e3a2f07d134 --- /dev/null +++ b/core/Command/Maintenance/RepairShareOwnership.php @@ -0,0 +1,198 @@ +<?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\Core\Command\Maintenance; + +use Symfony\Component\Console\Command\Command; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use OCP\IUser; +use OCP\IUserManager; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class RepairShareOwnership extends Command { + /** @var IDBConnection $dbConnection */ + private $dbConnection; + /** @var IUserManager $userManager */ + private $userManager; + + public function __construct( + IDBConnection $dbConnection, + IUserManager $userManager + ) { + $this->dbConnection = $dbConnection; + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure() { + $this + ->setName('maintenance:repair-share-owner') + ->setDescription('repair invalid share-owner entries in the database') + ->addOption('no-confirm', 'y', InputOption::VALUE_NONE, "Don't ask for confirmation before repairing the shares") + ->addArgument('user', InputArgument::OPTIONAL, "User to fix incoming shares for, if omitted all users will be fixed"); + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $noConfirm = $input->getOption('no-confirm'); + $userId = $input->getArgument('user'); + if ($userId) { + $user = $this->userManager->get($userId); + if (!$user) { + $output->writeln("<error>user $userId not found</error>"); + return 1; + } + $shares = $this->getWrongShareOwnershipForUser($user); + } else { + $shares = $this->getWrongShareOwnership(); + } + + if ($shares) { + $output->writeln(""); + $output->writeln("Found " . count($shares) . " shares with invalid share owner"); + foreach ($shares as $share) { + /** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */ + $output->writeln(" - share ${share['shareId']} from \"${share['initiator']}\" to \"${share['receiver']}\" at \"${share['fileTarget']}\", owned by \"${share['owner']}\", that should be owned by \"${share['mountOwner']}\""); + } + $output->writeln(""); + + if (!$noConfirm) { + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('Repair these shares? [y/N]', false); + + if (!$helper->ask($input, $output, $question)) { + return 0; + } + } + $output->writeln("Repairing " . count($shares) . " shares"); + $this->repairShares($shares); + } else { + $output->writeln("Found no shares with invalid share owner"); + } + + return 0; + } + + /** + * @return array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[] + * @throws \OCP\DB\Exception + */ + protected function getWrongShareOwnership(): array { + $qb = $this->dbConnection->getQueryBuilder(); + $brokenShares = $qb + ->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target') + ->from('share', 's') + ->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR))) + ->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id')) + ->where($qb->expr()->neq('m.user_id', 's.uid_owner')) + ->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), $qb->func()->concat('m.user_id', $qb->expr()->literal('/'))), 'm.mount_point')) + ->executeQuery() + ->fetchAll(); + + $found = []; + + foreach ($brokenShares as $share) { + $found[] = [ + 'shareId' => (int) $share['id'], + 'fileTarget' => $share['file_target'], + 'initiator' => $share['uid_initiator'], + 'receiver' => $share['share_with'], + 'owner' => $share['uid_owner'], + 'mountOwner' => $share['user_id'], + ]; + } + + return $found; + } + + /** + * @param IUser $user + * @return array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[] + * @throws \OCP\DB\Exception + */ + protected function getWrongShareOwnershipForUser(IUser $user): array { + $qb = $this->dbConnection->getQueryBuilder(); + $brokenShares = $qb + ->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target') + ->from('share', 's') + ->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR))) + ->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id')) + ->where($qb->expr()->neq('m.user_id', 's.uid_owner')) + ->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), $qb->func()->concat('m.user_id', $qb->expr()->literal('/'))), 'm.mount_point')) + ->andWhere($qb->expr()->eq('s.share_with', $qb->createNamedParameter($user->getUID()))) + ->executeQuery() + ->fetchAll(); + + $found = []; + + foreach ($brokenShares as $share) { + $found[] = [ + 'shareId' => (int) $share['id'], + 'fileTarget' => $share['file_target'], + 'initiator' => $share['uid_initiator'], + 'receiver' => $share['share_with'], + 'owner' => $share['uid_owner'], + 'mountOwner' => $share['user_id'], + ]; + } + + return $found; + } + + /** + * @param array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[] $shares + * @return void + */ + protected function repairShares(array $shares) { + $this->dbConnection->beginTransaction(); + + $update = $this->dbConnection->getQueryBuilder(); + $update->update('share') + ->set('uid_owner', $update->createParameter('share_owner')) + ->set('uid_initiator', $update->createParameter('share_initiator')) + ->where($update->expr()->eq('id', $update->createParameter('share_id'))); + + foreach ($shares as $share) { + /** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */ + $update->setParameter('share_id', $share['shareId'], IQueryBuilder::PARAM_INT); + $update->setParameter('share_owner', $share['mountOwner']); + + // if the broken owner is also the initiator it's safe to update them both, otherwise we don't touch the initiator + if ($share['initiator'] === $share['owner']) { + $update->setParameter('share_initiator', $share['mountOwner']); + } else { + $update->setParameter('share_initiator', $share['initiator']); + } + $update->executeStatement(); + } + + $this->dbConnection->commit(); + } +} diff --git a/core/register_command.php b/core/register_command.php index 5ec12ee1b8b..3c84c83a145 100644 --- a/core/register_command.php +++ b/core/register_command.php @@ -172,6 +172,7 @@ if (\OC::$server->getConfig()->getSystemValue('installed', false)) { \OC::$server->getEventDispatcher(), \OC::$server->getAppManager() )); + $application->add(\OC::$server->query(OC\Core\Command\Maintenance\RepairShareOwnership::class)); $application->add(\OC::$server->query(\OC\Core\Command\Preview\Repair::class)); $application->add(\OC::$server->query(\OC\Core\Command\Preview\ResetRenderedTexts::class)); diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index b5455d6b24c..a4582194e26 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -868,6 +868,7 @@ return array( 'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => $baseDir . '/core/Command/Maintenance/Mimetype/UpdateJS.php', 'OC\\Core\\Command\\Maintenance\\Mode' => $baseDir . '/core/Command/Maintenance/Mode.php', 'OC\\Core\\Command\\Maintenance\\Repair' => $baseDir . '/core/Command/Maintenance/Repair.php', + 'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => $baseDir . '/core/Command/Maintenance/RepairShareOwnership.php', 'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php', 'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php', 'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 2f8d39f45ab..7798edc0fac 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -897,6 +897,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mimetype/UpdateJS.php', 'OC\\Core\\Command\\Maintenance\\Mode' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mode.php', 'OC\\Core\\Command\\Maintenance\\Repair' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Repair.php', + 'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => __DIR__ . '/../../..' . '/core/Command/Maintenance/RepairShareOwnership.php', 'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php', 'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php', 'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php', |