aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/lib/Command
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_sharing/lib/Command')
-rw-r--r--apps/files_sharing/lib/Command/CleanupRemoteStorages.php18
-rw-r--r--apps/files_sharing/lib/Command/DeleteOrphanShares.php13
-rw-r--r--apps/files_sharing/lib/Command/ExiprationNotification.php29
-rw-r--r--apps/files_sharing/lib/Command/FixShareOwners.php65
-rw-r--r--apps/files_sharing/lib/Command/ListShares.php161
5 files changed, 247 insertions, 39 deletions
diff --git a/apps/files_sharing/lib/Command/CleanupRemoteStorages.php b/apps/files_sharing/lib/Command/CleanupRemoteStorages.php
index bb7f374de58..809481e5c0f 100644
--- a/apps/files_sharing/lib/Command/CleanupRemoteStorages.php
+++ b/apps/files_sharing/lib/Command/CleanupRemoteStorages.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud GmbH.
@@ -20,19 +21,10 @@ use Symfony\Component\Console\Output\OutputInterface;
*/
class CleanupRemoteStorages extends Command {
- /**
- * @var IDBConnection
- */
- protected $connection;
-
- /**
- * @var ICloudIdManager
- */
- private $cloudIdManager;
-
- public function __construct(IDBConnection $connection, ICloudIdManager $cloudIdManager) {
- $this->connection = $connection;
- $this->cloudIdManager = $cloudIdManager;
+ public function __construct(
+ protected IDBConnection $connection,
+ private ICloudIdManager $cloudIdManager,
+ ) {
parent::__construct();
}
diff --git a/apps/files_sharing/lib/Command/DeleteOrphanShares.php b/apps/files_sharing/lib/Command/DeleteOrphanShares.php
index 028738a4318..a7e96387d60 100644
--- a/apps/files_sharing/lib/Command/DeleteOrphanShares.php
+++ b/apps/files_sharing/lib/Command/DeleteOrphanShares.php
@@ -17,11 +17,10 @@ use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class DeleteOrphanShares extends Base {
- private OrphanHelper $orphanHelper;
-
- public function __construct(OrphanHelper $orphanHelper) {
+ public function __construct(
+ private OrphanHelper $orphanHelper,
+ ) {
parent::__construct();
- $this->orphanHelper = $orphanHelper;
}
protected function configure(): void {
@@ -49,7 +48,7 @@ class DeleteOrphanShares extends Base {
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");
+ $output->writeln(' file no longer exists');
}
}
}
@@ -57,14 +56,14 @@ class DeleteOrphanShares extends Base {
$count = count($orphans);
if ($count === 0) {
- $output->writeln("No orphan shares detected");
+ $output->writeln('No orphan shares detected');
return 0;
}
if ($force) {
$doDelete = true;
} else {
- $output->writeln("");
+ $output->writeln('');
/** @var QuestionHelper $helper */
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion("Delete <info>$count</info> orphan shares? [y/N] ", false);
diff --git a/apps/files_sharing/lib/Command/ExiprationNotification.php b/apps/files_sharing/lib/Command/ExiprationNotification.php
index f4f41dc7011..b7ea5c5f14e 100644
--- a/apps/files_sharing/lib/Command/ExiprationNotification.php
+++ b/apps/files_sharing/lib/Command/ExiprationNotification.php
@@ -8,6 +8,7 @@ declare(strict_types=1);
*/
namespace OCA\Files_Sharing\Command;
+use OCA\Files_Sharing\OrphanHelper;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IDBConnection;
use OCP\Notification\IManager as NotificationManager;
@@ -18,25 +19,14 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ExiprationNotification extends Command {
- /** @var NotificationManager */
- private $notificationManager;
- /** @var IDBConnection */
- private $connection;
- /** @var ITimeFactory */
- private $time;
- /** @var ShareManager */
- private $shareManager;
-
- public function __construct(ITimeFactory $time,
- NotificationManager $notificationManager,
- IDBConnection $connection,
- ShareManager $shareManager) {
+ public function __construct(
+ private ITimeFactory $time,
+ private NotificationManager $notificationManager,
+ private IDBConnection $connection,
+ private ShareManager $shareManager,
+ private OrphanHelper $orphanHelper,
+ ) {
parent::__construct();
-
- $this->notificationManager = $notificationManager;
- $this->connection = $connection;
- $this->time = $time;
- $this->shareManager = $shareManager;
}
protected function configure() {
@@ -62,7 +52,8 @@ class ExiprationNotification extends Command {
foreach ($shares as $share) {
if ($share->getExpirationDate() === null
|| $share->getExpirationDate()->getTimestamp() < $minTime->getTimestamp()
- || $share->getExpirationDate()->getTimestamp() > $maxTime->getTimestamp()) {
+ || $share->getExpirationDate()->getTimestamp() > $maxTime->getTimestamp()
+ || !$this->orphanHelper->isShareValid($share->getSharedBy(), $share->getNodeId())) {
continue;
}
diff --git a/apps/files_sharing/lib/Command/FixShareOwners.php b/apps/files_sharing/lib/Command/FixShareOwners.php
new file mode 100644
index 00000000000..1cf5f82f5a8
--- /dev/null
+++ b/apps/files_sharing/lib/Command/FixShareOwners.php
@@ -0,0 +1,65 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Files_Sharing\Command;
+
+use OC\Core\Command\Base;
+use OCA\Files_Sharing\OrphanHelper;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class FixShareOwners extends Base {
+ public function __construct(
+ private readonly OrphanHelper $orphanHelper,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('sharing:fix-share-owners')
+ ->setDescription('Fix owner of broken shares after transfer ownership on old versions')
+ ->addOption(
+ 'dry-run',
+ null,
+ InputOption::VALUE_NONE,
+ 'only show which shares would be updated'
+ );
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ $shares = $this->orphanHelper->getAllShares();
+ $dryRun = $input->getOption('dry-run');
+ $count = 0;
+
+ foreach ($shares as $share) {
+ if ($this->orphanHelper->isShareValid($share['owner'], $share['fileid']) || !$this->orphanHelper->fileExists($share['fileid'])) {
+ continue;
+ }
+
+ $owner = $this->orphanHelper->findOwner($share['fileid']);
+
+ if ($owner !== null) {
+ if ($dryRun) {
+ $output->writeln("Share with id <info>{$share['id']}</info> (target: <info>{$share['target']}</info>) can be updated to owner <info>$owner</info>");
+ } else {
+ $this->orphanHelper->updateShareOwner($share['id'], $owner);
+ $output->writeln("Share with id <info>{$share['id']}</info> (target: <info>{$share['target']}</info>) updated to owner <info>$owner</info>");
+ }
+ $count++;
+ }
+ }
+
+ if ($count === 0) {
+ $output->writeln('No broken shares detected');
+ }
+
+ return static::SUCCESS;
+ }
+}
diff --git a/apps/files_sharing/lib/Command/ListShares.php b/apps/files_sharing/lib/Command/ListShares.php
new file mode 100644
index 00000000000..2d5cdbf7812
--- /dev/null
+++ b/apps/files_sharing/lib/Command/ListShares.php
@@ -0,0 +1,161 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+namespace OCA\Files_Sharing\Command;
+
+use OC\Core\Command\Base;
+use OCP\Files\Folder;
+use OCP\Files\IRootFolder;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+use OCP\Share\IManager;
+use OCP\Share\IShare;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ListShares extends Base {
+ /** @var array<string, Node> */
+ private array $fileCache = [];
+
+ private const SHARE_TYPE_NAMES = [
+ IShare::TYPE_USER => 'user',
+ IShare::TYPE_GROUP => 'group',
+ IShare::TYPE_LINK => 'link',
+ IShare::TYPE_EMAIL => 'email',
+ IShare::TYPE_REMOTE => 'remote',
+ IShare::TYPE_REMOTE_GROUP => 'group',
+ IShare::TYPE_ROOM => 'room',
+ IShare::TYPE_DECK => 'deck',
+ ];
+
+ public function __construct(
+ private readonly IManager $shareManager,
+ private readonly IRootFolder $rootFolder,
+ ) {
+ parent::__construct();
+ }
+
+ protected function configure() {
+ parent::configure();
+ $this
+ ->setName('share:list')
+ ->setDescription('List available shares')
+ ->addOption('owner', null, InputOption::VALUE_REQUIRED, 'only show shares owned by a specific user')
+ ->addOption('recipient', null, InputOption::VALUE_REQUIRED, 'only show shares with a specific recipient')
+ ->addOption('by', null, InputOption::VALUE_REQUIRED, 'only show shares with by as specific user')
+ ->addOption('file', null, InputOption::VALUE_REQUIRED, 'only show shares of a specific file')
+ ->addOption('parent', null, InputOption::VALUE_REQUIRED, 'only show shares of files inside a specific folder')
+ ->addOption('recursive', null, InputOption::VALUE_NONE, 'also show shares nested deep inside the specified parent folder')
+ ->addOption('type', null, InputOption::VALUE_REQUIRED, 'only show shares of a specific type')
+ ->addOption('status', null, InputOption::VALUE_REQUIRED, 'only show shares with a specific status');
+ }
+
+ public function execute(InputInterface $input, OutputInterface $output): int {
+ if ($input->getOption('recursive') && !$input->getOption('parent')) {
+ $output->writeln("<error>recursive option can't be used without parent option</error>");
+ return 1;
+ }
+
+ // todo: do some pre-filtering instead of first querying all shares
+ /** @var \Iterator<IShare> $allShares */
+ $allShares = $this->shareManager->getAllShares();
+ $shares = new \CallbackFilterIterator($allShares, function (IShare $share) use ($input) {
+ return $this->shouldShowShare($input, $share);
+ });
+ $shares = iterator_to_array($shares);
+ $data = array_map(function (IShare $share) {
+ return [
+ 'id' => $share->getId(),
+ 'file' => $share->getNodeId(),
+ 'target-path' => $share->getTarget(),
+ 'source-path' => $share->getNode()->getPath(),
+ 'owner' => $share->getShareOwner(),
+ 'recipient' => $share->getSharedWith(),
+ 'by' => $share->getSharedBy(),
+ 'type' => self::SHARE_TYPE_NAMES[$share->getShareType()] ?? 'unknown',
+ ];
+ }, $shares);
+
+ $this->writeTableInOutputFormat($input, $output, $data);
+ return 0;
+ }
+
+ private function getFileId(string $file): int {
+ if (is_numeric($file)) {
+ return (int)$file;
+ }
+ return $this->getFile($file)->getId();
+ }
+
+ private function getFile(string $file): Node {
+ if (isset($this->fileCache[$file])) {
+ return $this->fileCache[$file];
+ }
+
+ if (is_numeric($file)) {
+ $node = $this->rootFolder->getFirstNodeById((int)$file);
+ if (!$node) {
+ throw new NotFoundException("File with id $file not found");
+ }
+ } else {
+ $node = $this->rootFolder->get($file);
+ }
+ $this->fileCache[$file] = $node;
+ return $node;
+ }
+
+ private function getShareType(string $type): int {
+ foreach (self::SHARE_TYPE_NAMES as $shareType => $shareTypeName) {
+ if ($shareTypeName === $type) {
+ return $shareType;
+ }
+ }
+ throw new \Exception("Unknown share type $type");
+ }
+
+ private function shouldShowShare(InputInterface $input, IShare $share): bool {
+ if ($input->getOption('owner') && $share->getShareOwner() !== $input->getOption('owner')) {
+ return false;
+ }
+ if ($input->getOption('recipient') && $share->getSharedWith() !== $input->getOption('recipient')) {
+ return false;
+ }
+ if ($input->getOption('by') && $share->getSharedBy() !== $input->getOption('by')) {
+ return false;
+ }
+ if ($input->getOption('file') && $share->getNodeId() !== $this->getFileId($input->getOption('file'))) {
+ return false;
+ }
+ if ($input->getOption('parent')) {
+ $parent = $this->getFile($input->getOption('parent'));
+ if (!$parent instanceof Folder) {
+ throw new \Exception("Parent {$parent->getPath()} is not a folder");
+ }
+ $recursive = $input->getOption('recursive');
+ if (!$recursive) {
+ $shareCacheEntry = $share->getNodeCacheEntry();
+ if (!$shareCacheEntry) {
+ $shareCacheEntry = $share->getNode();
+ }
+ if ($shareCacheEntry->getParentId() !== $parent->getId()) {
+ return false;
+ }
+ } else {
+ $shareNode = $share->getNode();
+ if ($parent->getRelativePath($shareNode->getPath()) === null) {
+ return false;
+ }
+ }
+ }
+ if ($input->getOption('type') && $share->getShareType() !== $this->getShareType($input->getOption('type'))) {
+ return false;
+ }
+ return true;
+ }
+}