Skip to content

Commit 9c937a3

Browse files
authored
Merge pull request #41667 from nextcloud/backport/39285/stable23
[stable23] add command do delete orphan shares
2 parents cde8e93 + 3fd5795 commit 9c937a3

File tree

5 files changed

+193
-0
lines changed

5 files changed

+193
-0
lines changed

apps/files_sharing/appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Turning the feature off removes shared files and folders on the server for all s
4343
<commands>
4444
<command>OCA\Files_Sharing\Command\CleanupRemoteStorages</command>
4545
<command>OCA\Files_Sharing\Command\ExiprationNotification</command>
46+
<command>OCA\Files_Sharing\Command\DeleteOrphanShares</command>
4647
</commands>
4748

4849
<settings>

apps/files_sharing/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
'OCA\\Files_Sharing\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
2525
'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => $baseDir . '/../lib/Collaboration/ShareRecipientSorter.php',
2626
'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => $baseDir . '/../lib/Command/CleanupRemoteStorages.php',
27+
'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => $baseDir . '/../lib/Command/DeleteOrphanShares.php',
2728
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => $baseDir . '/../lib/Command/ExiprationNotification.php',
2829
'OCA\\Files_Sharing\\Controller\\AcceptController' => $baseDir . '/../lib/Controller/AcceptController.php',
2930
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php',
@@ -71,6 +72,7 @@
7172
'OCA\\Files_Sharing\\MountProvider' => $baseDir . '/../lib/MountProvider.php',
7273
'OCA\\Files_Sharing\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
7374
'OCA\\Files_Sharing\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
75+
'OCA\\Files_Sharing\\OrphanHelper' => $baseDir . '/../lib/OrphanHelper.php',
7476
'OCA\\Files_Sharing\\Scanner' => $baseDir . '/../lib/Scanner.php',
7577
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
7678
'OCA\\Files_Sharing\\ShareBackend\\File' => $baseDir . '/../lib/ShareBackend/File.php',

apps/files_sharing/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class ComposerStaticInitFiles_Sharing
3939
'OCA\\Files_Sharing\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
4040
'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => __DIR__ . '/..' . '/../lib/Collaboration/ShareRecipientSorter.php',
4141
'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__ . '/..' . '/../lib/Command/CleanupRemoteStorages.php',
42+
'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanShares.php',
4243
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php',
4344
'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php',
4445
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php',
@@ -86,6 +87,7 @@ class ComposerStaticInitFiles_Sharing
8687
'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php',
8788
'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
8889
'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
90+
'OCA\\Files_Sharing\\OrphanHelper' => __DIR__ . '/..' . '/../lib/OrphanHelper.php',
8991
'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php',
9092
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
9193
'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
namespace OCA\Files_Sharing\Command;
25+
26+
27+
use Symfony\Component\Console\Question\ConfirmationQuestion;
28+
use OC\Core\Command\Base;
29+
use OCA\Files_Sharing\OrphanHelper;
30+
use Symfony\Component\Console\Helper\QuestionHelper;
31+
use Symfony\Component\Console\Input\InputInterface;
32+
use Symfony\Component\Console\Input\InputOption;
33+
use Symfony\Component\Console\Output\OutputInterface;
34+
35+
class DeleteOrphanShares extends Base {
36+
37+
/** @var OrphanHelper $orphanHelper */
38+
private $orphanHelper;
39+
40+
public function __construct(OrphanHelper $orphanHelper) {
41+
parent::__construct();
42+
$this->orphanHelper = $orphanHelper;
43+
}
44+
45+
protected function configure(): void {
46+
$this
47+
->setName('sharing:delete-orphan-shares')
48+
->setDescription('Delete shares where the owner no longer has access to the file')
49+
->addOption(
50+
'force',
51+
'f',
52+
InputOption::VALUE_NONE,
53+
'delete the shares without asking'
54+
);
55+
}
56+
57+
public function execute(InputInterface $input, OutputInterface $output): int {
58+
$force = $input->getOption('force');
59+
$shares = $this->orphanHelper->getAllShares();
60+
61+
$orphans = [];
62+
foreach ($shares as $share) {
63+
if (!$this->orphanHelper->isShareValid($share['owner'], $share['fileid'])) {
64+
$orphans[] = $share['id'];
65+
$exists = $this->orphanHelper->fileExists($share['fileid']);
66+
$output->writeln("<info>{$share['target']}</info> owned by <info>{$share['owner']}</info>");
67+
if ($exists) {
68+
$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");
69+
} else {
70+
$output->writeln(" file no longer exists");
71+
}
72+
}
73+
}
74+
75+
$count = count($orphans);
76+
77+
if ($count === 0) {
78+
$output->writeln("No orphan shares detected");
79+
return 0;
80+
}
81+
82+
if ($force) {
83+
$doDelete = true;
84+
} else {
85+
$output->writeln("");
86+
/** @var QuestionHelper $helper */
87+
$helper = $this->getHelper('question');
88+
$question = new ConfirmationQuestion("Delete <info>$count</info> orphan shares? [y/N] ", false);
89+
$doDelete = $helper->ask($input, $output, $question);
90+
}
91+
92+
if ($doDelete) {
93+
$this->orphanHelper->deleteShares($orphans);
94+
}
95+
96+
return 0;
97+
}
98+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* @copyright Copyright (c) 2023 Robin Appelman <robin@icewind.nl>
6+
*
7+
* @license GNU AGPL version 3 or any later version
8+
*
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU Affero General Public License as
11+
* published by the Free Software Foundation, either version 3 of the
12+
* License, or (at your option) any later version.
13+
*
14+
* This program is distributed in the hope that it will be useful,
15+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
* GNU Affero General Public License for more details.
18+
*
19+
* You should have received a copy of the GNU Affero General Public License
20+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
*
22+
*/
23+
24+
namespace OCA\Files_Sharing;
25+
26+
use OCP\DB\QueryBuilder\IQueryBuilder;
27+
use OCP\Files\IRootFolder;
28+
use OCP\IDBConnection;
29+
30+
class OrphanHelper {
31+
32+
/** @var IDBConnection $connection */
33+
private $connection;
34+
35+
/** @var IRootFolder $rootFolder */
36+
private $rootFolder;
37+
38+
public function __construct(
39+
IDBConnection $connection,
40+
IRootFolder $rootFolder
41+
) {
42+
$this->connection = $connection;
43+
$this->rootFolder = $rootFolder;
44+
}
45+
46+
public function isShareValid(string $owner, int $fileId): bool {
47+
$userFolder = $this->rootFolder->getUserFolder($owner);
48+
$nodes = $userFolder->getById($fileId);
49+
return count($nodes) > 0;
50+
}
51+
52+
/**
53+
* @param int[] $ids
54+
* @return void
55+
*/
56+
public function deleteShares(array $ids): void {
57+
$query = $this->connection->getQueryBuilder();
58+
$query->delete('share')
59+
->where($query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
60+
$query->executeStatement();
61+
}
62+
63+
public function fileExists(int $fileId): bool {
64+
$query = $this->connection->getQueryBuilder();
65+
$query->select('fileid')
66+
->from('filecache')
67+
->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
68+
return $query->executeQuery()->fetchOne() !== false;
69+
}
70+
71+
/**
72+
* @return \Traversable<int, array{id: int, owner: string, fileid: int, target: string}>
73+
*/
74+
public function getAllShares() {
75+
$query = $this->connection->getQueryBuilder();
76+
$query->select('id', 'file_source', 'uid_owner', 'file_target')
77+
->from('share')
78+
->where($query->expr()->eq('item_type', $query->createNamedParameter('file')))
79+
->orWhere($query->expr()->eq('item_type', $query->createNamedParameter('folder')));
80+
$result = $query->executeQuery();
81+
while ($row = $result->fetch()) {
82+
yield [
83+
'id' => (int)$row['id'],
84+
'owner' => (string)$row['uid_owner'],
85+
'fileid' => (int)$row['file_source'],
86+
'target' => (string)$row['file_target'],
87+
];
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)