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;
+ }
+
+}