diff options
author | Robin Appelman <robin@icewind.nl> | 2023-07-10 13:17:17 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-07-10 13:17:17 +0200 |
commit | a84fa17c7324599f9b872967e7b96d8a504a89b0 (patch) | |
tree | e7457206d165d3dbae825242989f43df1b6bc584 /apps/files_sharing/lib | |
parent | 0d78334c7d29b8df5e9babe824febcbeec655723 (diff) | |
parent | 50501f8515361577e74c9e639853fc8e68178c2f (diff) | |
download | nextcloud-server-a84fa17c7324599f9b872967e7b96d8a504a89b0.tar.gz nextcloud-server-a84fa17c7324599f9b872967e7b96d8a504a89b0.zip |
Merge pull request #39170 from nextcloud/orphan-share-command
add command do delete orphan shares
Diffstat (limited to 'apps/files_sharing/lib')
-rw-r--r-- | apps/files_sharing/lib/Command/DeleteOrphanShares.php | 96 | ||||
-rw-r--r-- | apps/files_sharing/lib/OrphanHelper.php | 86 |
2 files changed, 182 insertions, 0 deletions
diff --git a/apps/files_sharing/lib/Command/DeleteOrphanShares.php b/apps/files_sharing/lib/Command/DeleteOrphanShares.php new file mode 100644 index 00000000000..310f27ebfa3 --- /dev/null +++ b/apps/files_sharing/lib/Command/DeleteOrphanShares.php @@ -0,0 +1,96 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl> + * + * @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\Files_Sharing\Command; + + +use Symfony\Component\Console\Question\ConfirmationQuestion; +use OC\Core\Command\Base; +use OCA\Files_Sharing\OrphanHelper; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class DeleteOrphanShares extends Base { + private OrphanHelper $orphanHelper; + + public function __construct(OrphanHelper $orphanHelper) { + parent::__construct(); + $this->orphanHelper = $orphanHelper; + } + + protected function configure(): void { + $this + ->setName('sharing:delete-orphan-shares') + ->setDescription('Delete shares where the owner no longer has access to the file') + ->addOption( + 'force', + 'f', + InputOption::VALUE_NONE, + 'delete the shares without asking' + ); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $force = $input->getOption('force'); + $shares = $this->orphanHelper->getAllShares(); + + $orphans = []; + foreach ($shares as $share) { + if (!$this->orphanHelper->isShareValid($share['owner'], $share['fileid'])) { + $orphans[] = $share['id']; + $exists = $this->orphanHelper->fileExists($share['fileid']); + $output->writeln("<info>{$share['target']}</info> owned by <info>{$share['owner']}</info>"); + if ($exists) { + $output->writeln(" file still exists but the share owner lost access to it, run <info>occ info:file {$share['fileid']}</info> for more information about the file"); + } else { + $output->writeln(" file no longer exists"); + } + } + } + + $count = count($orphans); + + if ($count === 0) { + $output->writeln("No orphan shares detected"); + return 0; + } + + if ($force) { + $doDelete = true; + } else { + $output->writeln(""); + /** @var QuestionHelper $helper */ + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion("Delete <info>$count</info> orphan shares? [y/N] ", false); + $doDelete = $helper->ask($input, $output, $question); + } + + if ($doDelete) { + $this->orphanHelper->deleteShares($orphans); + } + + return 0; + } +} diff --git a/apps/files_sharing/lib/OrphanHelper.php b/apps/files_sharing/lib/OrphanHelper.php new file mode 100644 index 00000000000..6d15680f882 --- /dev/null +++ b/apps/files_sharing/lib/OrphanHelper.php @@ -0,0 +1,86 @@ +<?php + +declare(strict_types=1); +/** + * @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl> + * + * @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\Files_Sharing; + +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\Files\IRootFolder; +use OCP\IDBConnection; + +class OrphanHelper { + private IDBConnection $connection; + private IRootFolder $rootFolder; + + public function __construct( + IDBConnection $connection, + IRootFolder $rootFolder + ) { + $this->connection = $connection; + $this->rootFolder = $rootFolder; + } + + public function isShareValid(string $owner, int $fileId): bool { + $userFolder = $this->rootFolder->getUserFolder($owner); + $nodes = $userFolder->getById($fileId); + return count($nodes) > 0; + } + + /** + * @param int[] $ids + * @return void + */ + public function deleteShares(array $ids): void { + $query = $this->connection->getQueryBuilder(); + $query->delete('share') + ->where($query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY))); + $query->executeStatement(); + } + + public function fileExists(int $fileId): bool { + $query = $this->connection->getQueryBuilder(); + $query->select('fileid') + ->from('filecache') + ->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT))); + return $query->executeQuery()->fetchOne() !== false; + } + + /** + * @return \Traversable<int, array{id: int, owner: string, fileid: int, target: string}> + */ + public function getAllShares() { + $query = $this->connection->getQueryBuilder(); + $query->select('id', 'file_source', 'uid_owner', 'file_target') + ->from('share') + ->where($query->expr()->eq('item_type', $query->createNamedParameter('file'))) + ->orWhere($query->expr()->eq('item_type', $query->createNamedParameter('folder'))); + $result = $query->executeQuery(); + while ($row = $result->fetch()) { + yield [ + 'id' => (int)$row['id'], + 'owner' => (string)$row['uid_owner'], + 'fileid' => (int)$row['file_source'], + 'target' => (string)$row['file_target'], + ]; + } + } +} |