Skip to content

Commit 80c63dc

Browse files
authored
Merge pull request #35040 from nextcloud/backport/32211/stable23
[stable23] Add repair command to fix wrong share ownership
2 parents 41b4cae + adfdb96 commit 80c63dc

File tree

4 files changed

+201
-0
lines changed

4 files changed

+201
-0
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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+
/** @var IDBConnection $dbConnection */
42+
private $dbConnection;
43+
/** @var IUserManager $userManager */
44+
private $userManager;
45+
46+
public function __construct(
47+
IDBConnection $dbConnection,
48+
IUserManager $userManager
49+
) {
50+
$this->dbConnection = $dbConnection;
51+
$this->userManager = $userManager;
52+
parent::__construct();
53+
}
54+
55+
protected function configure() {
56+
$this
57+
->setName('maintenance:repair-share-owner')
58+
->setDescription('repair invalid share-owner entries in the database')
59+
->addOption('no-confirm', 'y', InputOption::VALUE_NONE, "Don't ask for confirmation before repairing the shares")
60+
->addArgument('user', InputArgument::OPTIONAL, "User to fix incoming shares for, if omitted all users will be fixed");
61+
}
62+
63+
protected function execute(InputInterface $input, OutputInterface $output): int {
64+
$noConfirm = $input->getOption('no-confirm');
65+
$userId = $input->getArgument('user');
66+
if ($userId) {
67+
$user = $this->userManager->get($userId);
68+
if (!$user) {
69+
$output->writeln("<error>user $userId not found</error>");
70+
return 1;
71+
}
72+
$shares = $this->getWrongShareOwnershipForUser($user);
73+
} else {
74+
$shares = $this->getWrongShareOwnership();
75+
}
76+
77+
if ($shares) {
78+
$output->writeln("");
79+
$output->writeln("Found " . count($shares) . " shares with invalid share owner");
80+
foreach ($shares as $share) {
81+
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
82+
$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']}\"");
83+
}
84+
$output->writeln("");
85+
86+
if (!$noConfirm) {
87+
$helper = $this->getHelper('question');
88+
$question = new ConfirmationQuestion('Repair these shares? [y/N]', false);
89+
90+
if (!$helper->ask($input, $output, $question)) {
91+
return 0;
92+
}
93+
}
94+
$output->writeln("Repairing " . count($shares) . " shares");
95+
$this->repairShares($shares);
96+
} else {
97+
$output->writeln("Found no shares with invalid share owner");
98+
}
99+
100+
return 0;
101+
}
102+
103+
/**
104+
* @return array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[]
105+
* @throws \OCP\DB\Exception
106+
*/
107+
protected function getWrongShareOwnership(): array {
108+
$qb = $this->dbConnection->getQueryBuilder();
109+
$brokenShares = $qb
110+
->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target')
111+
->from('share', 's')
112+
->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR)))
113+
->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id'))
114+
->where($qb->expr()->neq('m.user_id', 's.uid_owner'))
115+
->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), $qb->func()->concat('m.user_id', $qb->expr()->literal('/'))), 'm.mount_point'))
116+
->executeQuery()
117+
->fetchAll();
118+
119+
$found = [];
120+
121+
foreach ($brokenShares as $share) {
122+
$found[] = [
123+
'shareId' => (int) $share['id'],
124+
'fileTarget' => $share['file_target'],
125+
'initiator' => $share['uid_initiator'],
126+
'receiver' => $share['share_with'],
127+
'owner' => $share['uid_owner'],
128+
'mountOwner' => $share['user_id'],
129+
];
130+
}
131+
132+
return $found;
133+
}
134+
135+
/**
136+
* @param IUser $user
137+
* @return array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[]
138+
* @throws \OCP\DB\Exception
139+
*/
140+
protected function getWrongShareOwnershipForUser(IUser $user): array {
141+
$qb = $this->dbConnection->getQueryBuilder();
142+
$brokenShares = $qb
143+
->select('s.id', 'm.user_id', 's.uid_owner', 's.uid_initiator', 's.share_with', 's.file_target')
144+
->from('share', 's')
145+
->join('s', 'filecache', 'f', $qb->expr()->eq('s.item_source', $qb->expr()->castColumn('f.fileid', IQueryBuilder::PARAM_STR)))
146+
->join('s', 'mounts', 'm', $qb->expr()->eq('f.storage', 'm.storage_id'))
147+
->where($qb->expr()->neq('m.user_id', 's.uid_owner'))
148+
->andWhere($qb->expr()->eq($qb->func()->concat($qb->expr()->literal('/'), $qb->func()->concat('m.user_id', $qb->expr()->literal('/'))), 'm.mount_point'))
149+
->andWhere($qb->expr()->eq('s.share_with', $qb->createNamedParameter($user->getUID())))
150+
->executeQuery()
151+
->fetchAll();
152+
153+
$found = [];
154+
155+
foreach ($brokenShares as $share) {
156+
$found[] = [
157+
'shareId' => (int) $share['id'],
158+
'fileTarget' => $share['file_target'],
159+
'initiator' => $share['uid_initiator'],
160+
'receiver' => $share['share_with'],
161+
'owner' => $share['uid_owner'],
162+
'mountOwner' => $share['user_id'],
163+
];
164+
}
165+
166+
return $found;
167+
}
168+
169+
/**
170+
* @param array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string}[] $shares
171+
* @return void
172+
*/
173+
protected function repairShares(array $shares) {
174+
$this->dbConnection->beginTransaction();
175+
176+
$update = $this->dbConnection->getQueryBuilder();
177+
$update->update('share')
178+
->set('uid_owner', $update->createParameter('share_owner'))
179+
->set('uid_initiator', $update->createParameter('share_initiator'))
180+
->where($update->expr()->eq('id', $update->createParameter('share_id')));
181+
182+
foreach ($shares as $share) {
183+
/** @var array{shareId: int, fileTarget: string, initiator: string, receiver: string, owner: string, mountOwner: string} $share */
184+
$update->setParameter('share_id', $share['shareId'], IQueryBuilder::PARAM_INT);
185+
$update->setParameter('share_owner', $share['mountOwner']);
186+
187+
// if the broken owner is also the initiator it's safe to update them both, otherwise we don't touch the initiator
188+
if ($share['initiator'] === $share['owner']) {
189+
$update->setParameter('share_initiator', $share['mountOwner']);
190+
} else {
191+
$update->setParameter('share_initiator', $share['initiator']);
192+
}
193+
$update->executeStatement();
194+
}
195+
196+
$this->dbConnection->commit();
197+
}
198+
}

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
@@ -882,6 +882,7 @@
882882
'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => $baseDir . '/core/Command/Maintenance/Mimetype/UpdateJS.php',
883883
'OC\\Core\\Command\\Maintenance\\Mode' => $baseDir . '/core/Command/Maintenance/Mode.php',
884884
'OC\\Core\\Command\\Maintenance\\Repair' => $baseDir . '/core/Command/Maintenance/Repair.php',
885+
'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => $baseDir . '/core/Command/Maintenance/RepairShareOwnership.php',
885886
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => $baseDir . '/core/Command/Maintenance/UpdateHtaccess.php',
886887
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => $baseDir . '/core/Command/Maintenance/UpdateTheme.php',
887888
'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
@@ -911,6 +911,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
911911
'OC\\Core\\Command\\Maintenance\\Mimetype\\UpdateJS' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mimetype/UpdateJS.php',
912912
'OC\\Core\\Command\\Maintenance\\Mode' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Mode.php',
913913
'OC\\Core\\Command\\Maintenance\\Repair' => __DIR__ . '/../../..' . '/core/Command/Maintenance/Repair.php',
914+
'OC\\Core\\Command\\Maintenance\\RepairShareOwnership' => __DIR__ . '/../../..' . '/core/Command/Maintenance/RepairShareOwnership.php',
914915
'OC\\Core\\Command\\Maintenance\\UpdateHtaccess' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateHtaccess.php',
915916
'OC\\Core\\Command\\Maintenance\\UpdateTheme' => __DIR__ . '/../../..' . '/core/Command/Maintenance/UpdateTheme.php',
916917
'OC\\Core\\Command\\Preview\\Repair' => __DIR__ . '/../../..' . '/core/Command/Preview/Repair.php',

0 commit comments

Comments
 (0)