Skip to content

Commit ed59bb3

Browse files
Merge pull request nextcloud#41664 from nextcloud/backport/39285/stable25
[stable25] add command do delete orphan shares
2 parents e6cad83 + 39d34d9 commit ed59bb3

File tree

5 files changed

+187
-0
lines changed

5 files changed

+187
-0
lines changed

apps/files_sharing/appinfo/info.xml

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

4748
<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',
@@ -73,6 +74,7 @@
7374
'OCA\\Files_Sharing\\MountProvider' => $baseDir . '/../lib/MountProvider.php',
7475
'OCA\\Files_Sharing\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
7576
'OCA\\Files_Sharing\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
77+
'OCA\\Files_Sharing\\OrphanHelper' => $baseDir . '/../lib/OrphanHelper.php',
7678
'OCA\\Files_Sharing\\Scanner' => $baseDir . '/../lib/Scanner.php',
7779
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
7880
'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',
@@ -88,6 +89,7 @@ class ComposerStaticInitFiles_Sharing
8889
'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php',
8990
'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
9091
'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
92+
'OCA\\Files_Sharing\\OrphanHelper' => __DIR__ . '/..' . '/../lib/OrphanHelper.php',
9193
'OCA\\Files_Sharing\\Scanner' => __DIR__ . '/..' . '/../lib/Scanner.php',
9294
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
9395
'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
private OrphanHelper $orphanHelper;
37+
38+
public function __construct(OrphanHelper $orphanHelper) {
39+
parent::__construct();
40+
$this->orphanHelper = $orphanHelper;
41+
}
42+
43+
protected function configure(): void {
44+
$this
45+
->setName('sharing:delete-orphan-shares')
46+
->setDescription('Delete shares where the owner no longer has access to the file')
47+
->addOption(
48+
'force',
49+
'f',
50+
InputOption::VALUE_NONE,
51+
'delete the shares without asking'
52+
);
53+
}
54+
55+
public function execute(InputInterface $input, OutputInterface $output): int {
56+
$force = $input->getOption('force');
57+
$shares = $this->orphanHelper->getAllShares();
58+
59+
$orphans = [];
60+
foreach ($shares as $share) {
61+
if (!$this->orphanHelper->isShareValid($share['owner'], $share['fileid'])) {
62+
$orphans[] = $share['id'];
63+
$exists = $this->orphanHelper->fileExists($share['fileid']);
64+
$output->writeln("<info>{$share['target']}</info> owned by <info>{$share['owner']}</info>");
65+
if ($exists) {
66+
$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");
67+
} else {
68+
$output->writeln(" file no longer exists");
69+
}
70+
}
71+
}
72+
73+
$count = count($orphans);
74+
75+
if ($count === 0) {
76+
$output->writeln("No orphan shares detected");
77+
return 0;
78+
}
79+
80+
if ($force) {
81+
$doDelete = true;
82+
} else {
83+
$output->writeln("");
84+
/** @var QuestionHelper $helper */
85+
$helper = $this->getHelper('question');
86+
$question = new ConfirmationQuestion("Delete <info>$count</info> orphan shares? [y/N] ", false);
87+
$doDelete = $helper->ask($input, $output, $question);
88+
}
89+
90+
if ($doDelete) {
91+
$this->orphanHelper->deleteShares($orphans);
92+
}
93+
94+
return 0;
95+
}
96+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
private IDBConnection $connection;
32+
private IRootFolder $rootFolder;
33+
34+
public function __construct(
35+
IDBConnection $connection,
36+
IRootFolder $rootFolder
37+
) {
38+
$this->connection = $connection;
39+
$this->rootFolder = $rootFolder;
40+
}
41+
42+
public function isShareValid(string $owner, int $fileId): bool {
43+
$userFolder = $this->rootFolder->getUserFolder($owner);
44+
$nodes = $userFolder->getById($fileId);
45+
return count($nodes) > 0;
46+
}
47+
48+
/**
49+
* @param int[] $ids
50+
* @return void
51+
*/
52+
public function deleteShares(array $ids): void {
53+
$query = $this->connection->getQueryBuilder();
54+
$query->delete('share')
55+
->where($query->expr()->in('id', $query->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)));
56+
$query->executeStatement();
57+
}
58+
59+
public function fileExists(int $fileId): bool {
60+
$query = $this->connection->getQueryBuilder();
61+
$query->select('fileid')
62+
->from('filecache')
63+
->where($query->expr()->eq('fileid', $query->createNamedParameter($fileId, IQueryBuilder::PARAM_INT)));
64+
return $query->executeQuery()->fetchOne() !== false;
65+
}
66+
67+
/**
68+
* @return \Traversable<int, array{id: int, owner: string, fileid: int, target: string}>
69+
*/
70+
public function getAllShares() {
71+
$query = $this->connection->getQueryBuilder();
72+
$query->select('id', 'file_source', 'uid_owner', 'file_target')
73+
->from('share')
74+
->where($query->expr()->eq('item_type', $query->createNamedParameter('file')))
75+
->orWhere($query->expr()->eq('item_type', $query->createNamedParameter('folder')));
76+
$result = $query->executeQuery();
77+
while ($row = $result->fetch()) {
78+
yield [
79+
'id' => (int)$row['id'],
80+
'owner' => (string)$row['uid_owner'],
81+
'fileid' => (int)$row['file_source'],
82+
'target' => (string)$row['file_target'],
83+
];
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)