From 6748cc62c97beeda946fbeb1d42d7bb532a33e9f Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 26 May 2023 17:23:29 +0200 Subject: [PATCH] add commands to list shares and set share owner Signed-off-by: Robin Appelman --- apps/files_sharing/appinfo/info.xml | 2 + .../composer/composer/autoload_classmap.php | 2 + .../composer/composer/autoload_static.php | 2 + .../files_sharing/lib/Command/ListCommand.php | 197 ++++++++++++++++++ apps/files_sharing/lib/Command/SetOwner.php | 127 +++++++++++ 5 files changed, 330 insertions(+) create mode 100644 apps/files_sharing/lib/Command/ListCommand.php create mode 100644 apps/files_sharing/lib/Command/SetOwner.php diff --git a/apps/files_sharing/appinfo/info.xml b/apps/files_sharing/appinfo/info.xml index 72a2ed7c13848..b193d58e72be0 100644 --- a/apps/files_sharing/appinfo/info.xml +++ b/apps/files_sharing/appinfo/info.xml @@ -42,6 +42,8 @@ Turning the feature off removes shared files and folders on the server for all s OCA\Files_Sharing\Command\CleanupRemoteStorages OCA\Files_Sharing\Command\ExiprationNotification + OCA\Files_Sharing\Command\ListCommand + OCA\Files_Sharing\Command\SetOwner diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index 50cbfe40d8aae..a9c9c95e5eac5 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -25,6 +25,8 @@ 'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => $baseDir . '/../lib/Collaboration/ShareRecipientSorter.php', 'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => $baseDir . '/../lib/Command/CleanupRemoteStorages.php', 'OCA\\Files_Sharing\\Command\\ExiprationNotification' => $baseDir . '/../lib/Command/ExiprationNotification.php', + 'OCA\\Files_Sharing\\Command\\ListCommand' => $baseDir . '/../lib/Command/ListCommand.php', + 'OCA\\Files_Sharing\\Command\\SetOwner' => $baseDir . '/../lib/Command/SetOwner.php', 'OCA\\Files_Sharing\\Controller\\AcceptController' => $baseDir . '/../lib/Controller/AcceptController.php', 'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php', 'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => $baseDir . '/../lib/Controller/ExternalSharesController.php', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index 4ba0fd52421a0..c8243f93955f9 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -40,6 +40,8 @@ class ComposerStaticInitFiles_Sharing 'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => __DIR__ . '/..' . '/../lib/Collaboration/ShareRecipientSorter.php', 'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__ . '/..' . '/../lib/Command/CleanupRemoteStorages.php', 'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php', + 'OCA\\Files_Sharing\\Command\\ListCommand' => __DIR__ . '/..' . '/../lib/Command/ListCommand.php', + 'OCA\\Files_Sharing\\Command\\SetOwner' => __DIR__ . '/..' . '/../lib/Command/SetOwner.php', 'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php', 'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php', 'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php', diff --git a/apps/files_sharing/lib/Command/ListCommand.php b/apps/files_sharing/lib/Command/ListCommand.php new file mode 100644 index 0000000000000..a94159b2a4051 --- /dev/null +++ b/apps/files_sharing/lib/Command/ListCommand.php @@ -0,0 +1,197 @@ + + * + * @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 . + * + */ + +namespace OCA\Files_Sharing\Command; + +use OC\Core\Command\Base; +use OCP\IUserManager; +use OCP\Share\IManager; +use OCP\Share\IShare; +use Symfony\Component\Console\Helper\Table; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +class ListCommand extends Base { + private IManager $shareManager; + + public function __construct( + IManager $shareManager + ) { + $this->shareManager = $shareManager; + parent::__construct(); + } + + protected function configure(): void { + parent::configure(); + $this + ->setName('sharing:list') + ->setDescription('List shares') + ->addOption('owner', null, InputOption::VALUE_REQUIRED, "Limit shares by share owner") + ->addOption('shared-by', null, InputOption::VALUE_REQUIRED, "Limit shares by share initiator") + ->addOption('shared-with', null, InputOption::VALUE_REQUIRED, "Limit shares by share recipient") + ->addOption('share-type', null, InputOption::VALUE_REQUIRED, "Limit shares by share recipient") + ->addOption('file-id', null, InputOption::VALUE_REQUIRED, "Limit shares to a specific file or folder id"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $ownerInput = $input->getOption('owner'); + $sharedByInput = $input->getOption('shared-by'); + $sharedWithInput = $input->getOption('shared-with'); + $shareTypeInput = $input->getOption('share-type'); + $fileInput = $input->getOption('file-id'); + + if ($shareTypeInput) { + $shareType = $this->parseShareType($shareTypeInput); + if ($shareType === null) { + $output->writeln("Unknown share type $shareTypeInput"); + $output->writeln("possible values: user, group, link, " . + "email, remote, circle, guest, remote_group, " . + "room, deck, deck_user, science-mesh"); + return 1; + } + } else { + $shareTypeInput = null; + } + + $allShares = $this->shareManager->getAllShares(); + + $filteredShares = new \CallbackFilterIterator($allShares, function(IShare $share) use ($ownerInput, $shareType, $sharedByInput, $sharedWithInput, $fileInput) { + return $this->filterShare($share, $ownerInput, $sharedByInput, $sharedWithInput, $shareType, $fileInput); + }); + + $shareData = []; + foreach ($filteredShares as $share) { + /** @var IShare $share */ + $shareData[] = [ + 'share-id' => $share->getFullId(), + 'share-owner' => $share->getShareOwner(), + 'shared-by' => $share->getSharedBy(), + 'shared-with' => $share->getSharedWith(), + 'share-type' => $this->formatShareType($share->getShareType()), + 'file-id' => $share->getNodeId(), + ]; + } + + $outputFormat = $input->getOption('output'); + if ($outputFormat === self::OUTPUT_FORMAT_JSON || $outputFormat === self::OUTPUT_FORMAT_JSON_PRETTY) { + $this->writeArrayInOutputFormat($input, $output, $shareData); + } else { + $table = new Table($output); + $table + ->setHeaders(['share-id', 'share-owner', 'shared-by', 'shared-with', 'share-type', 'file-id']) + ->setRows($shareData); + $table->render(); + } + + return 0; + } + + private function filterShare( + IShare $share, + string $ownerInput = null, + string $sharedByInput = null, + string $sharedWithInput = null, + int $shareType = null, + int $fileInput = null + ): bool { + if ($ownerInput && $share->getShareOwner() !== $ownerInput) { + return false; + } + if ($sharedByInput && $share->getSharedBy() !== $sharedByInput) { + return false; + } + if ($sharedWithInput && $share->getSharedWith() !== $sharedWithInput) { + return false; + } + if ($shareType && $share->getShareType() !== $shareType) { + return false; + } + if ($fileInput && $share->getNodeId() !== $fileInput) { + return false; + } + return true; + } + + private function parseShareType(string $type): ?int { + switch ($type) { + case 'user': + return IShare::TYPE_USER; + case 'group': + return IShare::TYPE_GROUP; + case 'link': + return IShare::TYPE_LINK; + case 'email': + return IShare::TYPE_EMAIL; + case 'remote': + return IShare::TYPE_REMOTE; + case 'circle': + return IShare::TYPE_CIRCLE; + case 'guest': + return IShare::TYPE_GUEST; + case 'remote_group': + return IShare::TYPE_REMOTE_GROUP; + case 'room': + return IShare::TYPE_ROOM; + case 'deck': + return IShare::TYPE_DECK; + case 'deck_user': + return IShare::TYPE_DECK_USER; + case 'science-mesh': + return IShare::TYPE_SCIENCEMESH; + default: + return null; + } + } + + private function formatShareType(int $type): string { + switch ($type) { + case IShare::TYPE_USER: + return 'user'; + case IShare::TYPE_GROUP: + return 'group'; + case IShare::TYPE_LINK: + return 'link'; + case IShare::TYPE_EMAIL: + return 'email'; + case IShare::TYPE_REMOTE: + return 'remote'; + case IShare::TYPE_CIRCLE: + return 'circle'; + case IShare::TYPE_GUEST: + return 'guest'; + case IShare::TYPE_REMOTE_GROUP: + return 'remote_group'; + case IShare::TYPE_ROOM: + return 'room'; + case IShare::TYPE_DECK: + return 'deck'; + case IShare::TYPE_DECK_USER: + return 'deck_user'; + case IShare::TYPE_SCIENCEMESH: + return 'science-mesh'; + default: + return 'other'; + } + } +} diff --git a/apps/files_sharing/lib/Command/SetOwner.php b/apps/files_sharing/lib/Command/SetOwner.php new file mode 100644 index 0000000000000..05ba039a0b87d --- /dev/null +++ b/apps/files_sharing/lib/Command/SetOwner.php @@ -0,0 +1,127 @@ + + * + * @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 . + * + */ + +namespace OCA\Files_Sharing\Command; + +use OC\Core\Command\Info\FileUtils; +use OCA\Files_Sharing\SharedMount; +use OCA\Files_Sharing\SharedStorage; +use OCP\Files\File; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\IUserManager; +use OCP\Share\IManager; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\ConfirmationQuestion; + +class SetOwner extends Command { + private FileUtils $fileUtils; + private IRootFolder $rootFolder; + private IManager $shareManager; + private IUserManager $userManager; + + public function __construct( + FileUtils $fileUtils, + IRootFolder $rootFolder, + IManager $shareManager, + IUserManager $userManager + ) { + $this->fileUtils = $fileUtils; + $this->rootFolder = $rootFolder; + $this->shareManager = $shareManager; + $this->userManager = $userManager; + parent::__construct(); + } + + protected function configure(): void { + $this + ->setName('sharing:set-owner') + ->setDescription('Change the owner of a share, note that the new owner must already have access to the file') + ->addArgument('share-id', InputArgument::REQUIRED, "Id of the share to set the owner of") + ->addArgument('new-owner', InputArgument::REQUIRED, "User id of to set the owner to"); + } + + public function execute(InputInterface $input, OutputInterface $output): int { + $shareId = $input->getArgument('share-id'); + $targetId = $input->getArgument('new-owner'); + + $target = $this->userManager->get($targetId); + if (!$target) { + $output->writeln("User $targetId not found"); + return 1; + } + + $share = $this->shareManager->getShareById($shareId); + if (!$share) { + $output->writeln("Share $shareId not found"); + return 1; + } + + $sourceFile = $share->getNode(); + $usersWithAccessToSource = $this->fileUtils->getFilesByUser($sourceFile); + + $targetHasNonSharedAccess = false; + $targetHasSharedAccess = false; + $fileOrFolder = ($sourceFile instanceof File) ? "file" : "folder";; + $sourceName = $sourceFile->getName(); + $targetNode = null; + + if (isset($usersWithAccessToSource[$target->getUID()])) { + $targetSourceNodes = $usersWithAccessToSource[$target->getUID()]; + foreach ($targetSourceNodes as $targetSourceNode) { + $targetNode = $targetSourceNode; + $mount = $targetSourceNode->getMountPoint(); + if ($mount instanceof SharedMount) { + if ($mount->getShare()->getId() === $share->getId()) { + $targetHasSharedAccess = true; + continue; + } + } + $targetHasNonSharedAccess = true; + } + } + + if (!$targetHasSharedAccess && !$targetHasNonSharedAccess) { + $output->writeln("$targetId has no access to the $fileOrFolder $sourceName shared by $shareId"); + return 1; + } + + if (!$targetHasNonSharedAccess && $targetHasSharedAccess) { + $output->writeln("Target user $targetId only has access to the shared $fileOrFolder $sourceName through the share $shareId."); + return 1; + } + + $share->setNode($targetNode); + $share->setShareOwner($target->getUID()); + $share->setSharedBy($target->getUID()); + + $this->shareManager->updateShare($share); + + return 0; + } + +}