mirror of
https://github.com/nextcloud/server.git
synced 2026-04-15 22:11:17 -04:00
fix(sharing): add command to fix broken shares after ownership transferring
Signed-off-by: Luka Trovic <luka@nextcloud.com>
This commit is contained in:
parent
8995272f60
commit
2ca51919db
6 changed files with 208 additions and 0 deletions
|
|
@ -49,6 +49,7 @@ Turning the feature off removes shared files and folders on the server for all s
|
|||
<command>OCA\Files_Sharing\Command\CleanupRemoteStorages</command>
|
||||
<command>OCA\Files_Sharing\Command\ExiprationNotification</command>
|
||||
<command>OCA\Files_Sharing\Command\DeleteOrphanShares</command>
|
||||
<command>OCA\Files_Sharing\Command\FixShareOwners</command>
|
||||
</commands>
|
||||
|
||||
<settings>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ return array(
|
|||
'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => $baseDir . '/../lib/Command/CleanupRemoteStorages.php',
|
||||
'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => $baseDir . '/../lib/Command/DeleteOrphanShares.php',
|
||||
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => $baseDir . '/../lib/Command/ExiprationNotification.php',
|
||||
'OCA\\Files_Sharing\\Command\\FixShareOwners' => $baseDir . '/../lib/Command/FixShareOwners.php',
|
||||
'OCA\\Files_Sharing\\Controller\\AcceptController' => $baseDir . '/../lib/Controller/AcceptController.php',
|
||||
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php',
|
||||
'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => $baseDir . '/../lib/Controller/ExternalSharesController.php',
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class ComposerStaticInitFiles_Sharing
|
|||
'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__ . '/..' . '/../lib/Command/CleanupRemoteStorages.php',
|
||||
'OCA\\Files_Sharing\\Command\\DeleteOrphanShares' => __DIR__ . '/..' . '/../lib/Command/DeleteOrphanShares.php',
|
||||
'OCA\\Files_Sharing\\Command\\ExiprationNotification' => __DIR__ . '/..' . '/../lib/Command/ExiprationNotification.php',
|
||||
'OCA\\Files_Sharing\\Command\\FixShareOwners' => __DIR__ . '/..' . '/../lib/Command/FixShareOwners.php',
|
||||
'OCA\\Files_Sharing\\Controller\\AcceptController' => __DIR__ . '/..' . '/../lib/Controller/AcceptController.php',
|
||||
'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php',
|
||||
'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php',
|
||||
|
|
|
|||
65
apps/files_sharing/lib/Command/FixShareOwners.php
Normal file
65
apps/files_sharing/lib/Command/FixShareOwners.php
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ namespace OCA\Files_Sharing;
|
|||
|
||||
use OC\User\NoUserException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ class OrphanHelper {
|
|||
public function __construct(
|
||||
private IDBConnection $connection,
|
||||
private IRootFolder $rootFolder,
|
||||
private IUserMountCache $userMountCache,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -68,4 +70,26 @@ class OrphanHelper {
|
|||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function findOwner(int $fileId): ?string {
|
||||
$mounts = $this->userMountCache->getMountsForFileId($fileId);
|
||||
if (!$mounts) {
|
||||
return null;
|
||||
}
|
||||
foreach ($mounts as $mount) {
|
||||
$userHomeMountPoint = '/' . $mount->getUser()->getUID() . '/';
|
||||
if ($mount->getMountPoint() === $userHomeMountPoint) {
|
||||
return $mount->getUser()->getUID();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function updateShareOwner(int $shareId, string $owner): void {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->update('share')
|
||||
->set('uid_owner', $query->createNamedParameter($owner))
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($shareId, IQueryBuilder::PARAM_INT)));
|
||||
$query->executeStatement();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
116
apps/files_sharing/tests/Command/FixShareOwnersTest.php
Normal file
116
apps/files_sharing/tests/Command/FixShareOwnersTest.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Files_Sharing\Tests\Command;
|
||||
|
||||
use OCA\Files_Sharing\Command\FixShareOwners;
|
||||
use OCA\Files_Sharing\OrphanHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* Class FixShareOwnersTest
|
||||
*
|
||||
* @package OCA\Files_Sharing\Tests\Command
|
||||
*/
|
||||
class FixShareOwnersTest extends TestCase {
|
||||
/**
|
||||
* @var FixShareOwners
|
||||
*/
|
||||
private $command;
|
||||
|
||||
/**
|
||||
* @var OrphanHelper|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private $orphanHelper;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->orphanHelper = $this->createMock(OrphanHelper::class);
|
||||
$this->command = new FixShareOwners($this->orphanHelper);
|
||||
}
|
||||
|
||||
public function testExecuteNoSharesDetected() {
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('getAllShares')
|
||||
->willReturn([
|
||||
['id' => 1, 'owner' => 'user1', 'fileid' => 1, 'target' => 'target1'],
|
||||
['id' => 2, 'owner' => 'user2', 'fileid' => 2, 'target' => 'target2'],
|
||||
]);
|
||||
$this->orphanHelper->expects($this->exactly(2))
|
||||
->method('isShareValid')
|
||||
->willReturn(true);
|
||||
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
|
||||
$output->expects($this->once())
|
||||
->method('writeln')
|
||||
->with('No broken shares detected');
|
||||
$this->command->execute($input, $output);
|
||||
}
|
||||
|
||||
public function testExecuteSharesDetected() {
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('getAllShares')
|
||||
->willReturn([
|
||||
['id' => 1, 'owner' => 'user1', 'fileid' => 1, 'target' => 'target1'],
|
||||
['id' => 2, 'owner' => 'user2', 'fileid' => 2, 'target' => 'target2'],
|
||||
]);
|
||||
$this->orphanHelper->expects($this->exactly(2))
|
||||
->method('isShareValid')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('fileExists')
|
||||
->willReturn(true);
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('findOwner')
|
||||
->willReturn('newOwner');
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('updateShareOwner');
|
||||
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
|
||||
$output->expects($this->once())
|
||||
->method('writeln')
|
||||
->with('Share with id <info>2</info> (target: <info>target2</info>) updated to owner <info>newOwner</info>');
|
||||
$this->command->execute($input, $output);
|
||||
}
|
||||
|
||||
public function testExecuteSharesDetectedDryRun() {
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('getAllShares')
|
||||
->willReturn([
|
||||
['id' => 1, 'owner' => 'user1', 'fileid' => 1, 'target' => 'target1'],
|
||||
['id' => 2, 'owner' => 'user2', 'fileid' => 2, 'target' => 'target2'],
|
||||
]);
|
||||
$this->orphanHelper->expects($this->exactly(2))
|
||||
->method('isShareValid')
|
||||
->willReturnOnConsecutiveCalls(true, false);
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('fileExists')
|
||||
->willReturn(true);
|
||||
$this->orphanHelper->expects($this->once())
|
||||
->method('findOwner')
|
||||
->willReturn('newOwner');
|
||||
$this->orphanHelper->expects($this->never())
|
||||
->method('updateShareOwner');
|
||||
|
||||
$input = $this->createMock(InputInterface::class);
|
||||
$output = $this->createMock(OutputInterface::class);
|
||||
|
||||
$output->expects($this->once())
|
||||
->method('writeln')
|
||||
->with('Share with id <info>2</info> (target: <info>target2</info>) can be updated to owner <info>newOwner</info>');
|
||||
$input->expects($this->once())
|
||||
->method('getOption')
|
||||
->with('dry-run')
|
||||
->willReturn(true);
|
||||
$this->command->execute($input, $output);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue