Skip to content

Commit fd452e5

Browse files
authored
Merge pull request #35039 from nextcloud/backport/32211/stable24
[stable24] Add repair command to fix wrong share ownership
2 parents 8f2cf3f + b6f7952 commit fd452e5

4 files changed

Lines changed: 199 additions & 0 deletions

File tree

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
7+
*
8+
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
9+
*
10+
* @license GNU AGPL version 3 or any later version
11+
*
12+
* This program is free software: you can redistribute it and/or modify
13+
* it under the terms of the GNU Affero General Public License as
14+
* published by the Free Software Foundation, either version 3 of the
15+
* License, or (at your option) any later version.
16+
*
17+
* This program is distributed in the hope that it will be useful,
18+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
19+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20+
* GNU Affero General Public License for more details.
21+
*
22+
* You should have received a copy of the GNU Affero General Public License
23+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
24+
*
25+
*/
26+
27+
namespace OC\Core\Command\Maintenance;
28+
29+
use Symfony\Component\Console\Command\Command;
30+
use OCP\DB\QueryBuilder\IQueryBuilder;
31+
use OCP\IDBConnection;
32+
use OCP\IUser;
33+
use OCP\IUserManager;
34+
use Symfony\Component\Console\Input\InputArgument;
35+
use Symfony\Component\Console\Input\InputInterface;
36+
use Symfony\Component\Console\Input\InputOption;
37+
use Symfony\Component\Console\Output\OutputInterface;
38+
use Symfony\Component\Console\Question\ConfirmationQuestion;
39+
40+
class RepairShareOwnership extends Command {
41+
private IDBConnection $dbConnection;
42+
private IUserManager $userManager;
43+
44+
public function __construct(
45+
IDBConnection $dbConnection,
46+
IUserManager $userManager
47+
) {
48+
$this->dbConnection = $dbConnection;
49+
$this->userManager = $userManager;
50+
parent::__construct();
51+
}
52+
53+
protected function configure() {
54+
$this
55+
->setName('maintenance:repair-share-owner')
56+
->setDescription('repair invalid share-owner entries in the database')
57+
->addOption('no-confirm', 'y', InputOption::VALUE_NONE, "Don't ask for confirmation before repairing the shares")
58+
->addArgument('user', InputArgument::OPTIONAL, "User to fix incoming shares for, if omitted all users will be fixed");
59+
}
60+
61+
protected function execute(InputInterface $input, OutputInterface $output): int {
62+
$noConfirm = $input->getOption('no-confirm');
63+
$userId = $input->getArgument('user');
64+
if ($userId) {
65+
$user = $this->userManager->get($userId);
66+
if (!$user) {
67+
$output->writeln("<error>user $userId not found</error>");
68+
return 1;
69+
}
70+
$shares = $this->getWrongShareOwnershipForUser($user);
71+
} else {
72+
$shares = $this->getWrongShareOwnership();
73+
}
74+
75+
if ($shares) {
76+
$output->writeln("");
77+
$output->writeln("Found " . count($shares) . " shares with invalid share owner");
78+
foreach ($shares as $share) {
79+
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
80+
$output->writeln(" - share ${share['shareId']} from \"${share['initiator']}\" to \"${share['receiver']}\" at \"${share['fileTarget']}\", owned by \"${share['owner']}\", that should be owned by \"${share['mountOwner']}\"");
81+
}
82+
$output->writeln("");
83+
84+
if (!$noConfirm) {
85+
$helper = $this->getHelper('question');
86+
$question = new ConfirmationQuestion('Repair these shares? [y/N]', false);
87+
88+
if (!$helper->ask($input, $output, $question)) {
89+
return 0;
90+
}
91+
}
92+
$output->writeln("Repairing " . count($shares) . " shares");
93+
$this->repairShares($shares);
94+
} else {
95+
$output->writeln("Found no shares with invalid share owner");
96+
}
97+
98+
return 0;
99+
}
100+
101+
/**
102+
* @return array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[]
103+
* @throws \OCP\DB\Exception
104+
*/
105+
protected function getWrongShareOwnership(): array {
106+
$qb = $this->dbConnection->getQueryBuilder();
107+
$brokenShares = $qb
108+
->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target')
109+
->from('share', 's')
110+
->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR)))
111+
->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id'))
112+
->where($qb->expr()->neq('m.user_id', 's.uid_owner'))
113+
->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), 'm.user_id', $qb->expr()->literal('/')), 'm.mount_point'))
114+
->executeQuery()
115+
->fetchAll();
116+
117+
$found = [];
118+
119+
foreach ($brokenShares as $share) {
120+
$found[] = [
121+
'shareId' => (int) $share['id'],
122+
'fileTarget' => $share['file_target'],
123+
'initiator' => $share['uid_initiator'],
124+
'receiver' => $share['share_with'],
125+
'owner' => $share['uid_owner'],
126+
'mountOwner' => $share['user_id'],
127+
];
128+
}
129+
130+
return $found;
131+
}
132+
133+
/**
134+
* @param IUser $user
135+
* @return array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[]
136+
* @throws \OCP\DB\Exception
137+
*/
138+
protected function getWrongShareOwnershipForUser(IUser $user): array {
139+
$qb = $this->dbConnection->getQueryBuilder();
140+
$brokenShares = $qb
141+
->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target')
142+
->from('share', 's')
143+
->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR)))
144+
->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id'))
145+
->where($qb->expr()->neq('m.user_id', 's.uid_owner'))
146+
->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), 'm.user_id', $qb->expr()->literal('/')), 'm.mount_point'))
147+
->andWhere($qb->expr()->eq('s.share_with', $qb->createNamedParameter($user->getUID())))
148+
->executeQuery()
149+
->fetchAll();
150+
151+
$found = [];
152+
153+
foreach ($brokenShares as $share) {
154+
$found[] = [
155+
'shareId' => (int) $share['id'],
156+
'fileTarget' => $share['file_target'],
157+
'initiator' => $share['uid_initiator'],
158+
'receiver' => $share['share_with'],
159+
'owner' => $share['uid_owner'],
160+
'mountOwner' => $share['user_id'],
161+
];
162+
}
163+
164+
return $found;
165+
}
166+
167+
/**
168+
* @param array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[] $shares
169+
* @return void
170+
*/
171+
protected function repairShares(array $shares) {
172+
$this->dbConnection->beginTransaction();
173+
174+
$update = $this->dbConnection->getQueryBuilder();
175+
$update->update('share')
176+
->set('uid_owner', $update->createParameter('share_owner'))
177+
->set('uid_initiator', $update->createParameter('share_initiator'))
178+
->where($update->expr()->eq('id', $update->createParameter('share_id')));
179+
180+
foreach ($shares as $share) {
181+
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
182+
$update->setParameter('share_id', $share['shareId'], IQueryBuilder::PARAM_INT);
183+
$update->setParameter('share_owner', $share['mountOwner']);
184+
185+
// if the broken owner is also the initiator it's safe to update them both, otherwise we don't touch the initiator
186+
if ($share['initiator'] === $share['owner']) {
187+
$update->setParameter('share_initiator', $share['mountOwner']);
188+
} else {
189+
$update->setParameter('share_initiator', $share['initiator']);
190+
}
191+
$update->executeStatement();
192+
}
193+
194+
$this->dbConnection->commit();
195+
}
196+
}

core/register_command.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@
173173
\OC::$server->getEventDispatcher(),
174174
\OC::$server->getAppManager()
175175
));
176+
$application->add(\OC::$server->query(OC\Core\Command\Maintenance\RepairShareOwnership::class));
176177

177178
$application->add(\OC::$server->query(\OC\Core\Command\Preview\Repair::class));
178179
$application->add(\OC::$server->query(\OC\Core\Command\Preview\ResetRenderedTexts::class));

lib/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,6 +916,7 @@
916916
'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => $baseDir . '/core/Command/Maintenance/Mimetype/UpdateJS.php',
917917
'OC\\Core\\Command\\Maintenance\\Mode' => $baseDir . '/core/Command/Maintenance/Mode.php',
918918
'OC\\Core\\Command\\Maintenance\\Repair' => $baseDir . '/core/Command/Maintenance/Repair.php',
919+
'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => $baseDir . '/core/Command/Maintenance/RepairShareOwnership.php',
919920
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php',
920921
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php',
921922
'OC\\Core\\Command\\Preview\\Repair' => $baseDir . '/core/Command/Preview/Repair.php',

lib/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
945945
'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mimetype/UpdateJS.php',
946946
'OC\\Core\\Command\\Maintenance\\Mode' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mode.php',
947947
'OC\\Core\\Command\\Maintenance\\Repair' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Repair.php',
948+
'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => __DIR__ . '/../../..' . '/core/Command/Maintenance/RepairShareOwnership.php',
948949
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php',
949950
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php',
950951
'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php',

0 commit comments

Comments
 (0)