mirror of
https://github.com/nextcloud/server.git
synced 2026-06-12 18:21:40 -04:00
When an ownCloud-migrated group share (which has no per-user USERGROUP subshare) is renamed for the first time, DefaultShareProvider::move() inserted a new USERGROUP row without setting `accepted`. The column defaulted to 0 (STATUS_PENDING), causing MountProvider to skip the share on the next login — the shared file disappeared for the recipient. Fix: set accepted = STATUS_ACCEPTED explicitly on the INSERT in DefaultShareProvider::move() for the TYPE_GROUP branch. Secondary fix: SharedMount::moveMount() silently returned true when updateFileTarget() threw (e.g. group no longer exists on an OC-migrated instance). Set $result = false in the catch block so View::rename() propagates the failure instead of silently corrupting VFS state. An opt-in occ command (sharing:fix-owncloud-group-shares) with --dry-run support is included to repair existing broken instances. It targets only TYPE_USERGROUP subshares with accepted=STATUS_PENDING and permissions!=0 (shares that were accepted but broken by the missing column default), leaving explicitly declined shares (permissions=0) untouched. AI-Assisted-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Anna Larch <anna@nextcloud.com>
88 lines
3.1 KiB
PHP
88 lines
3.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace OCA\Files_Sharing\Command;
|
|
|
|
use OC\Core\Command\Base;
|
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
|
use OCP\IDBConnection;
|
|
use OCP\Share\IShare;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputOption;
|
|
use Symfony\Component\Console\Output\OutputInterface;
|
|
|
|
/**
|
|
* Fixes USERGROUP subshares that were created without `accepted = STATUS_ACCEPTED`
|
|
* by a rename on an ownCloud-migrated instance.
|
|
*
|
|
* When an OC-migrated group share (which has no per-user USERGROUP subshare) is
|
|
* renamed for the first time, DefaultShareProvider::move() inserted a new USERGROUP
|
|
* row without setting `accepted`. The column defaulted to 0 (STATUS_PENDING), causing
|
|
* MountProvider to skip the share on the next login — the file disappeared for the
|
|
* recipient.
|
|
*
|
|
* USERGROUP subshares with permissions = 0 were explicitly declined by the user
|
|
* and are left untouched.
|
|
*/
|
|
class FixOwncloudGroupSubshareStatus extends Base {
|
|
|
|
public function __construct(
|
|
private IDBConnection $connection,
|
|
) {
|
|
parent::__construct();
|
|
}
|
|
|
|
#[\Override]
|
|
protected function configure(): void {
|
|
$this
|
|
->setName('sharing:fix-owncloud-group-shares')
|
|
->setDescription('Fix group share subshares left pending after renaming on an ownCloud-migrated instance')
|
|
->addOption(
|
|
'dry-run',
|
|
null,
|
|
InputOption::VALUE_NONE,
|
|
'Show how many shares would be fixed without making any changes',
|
|
);
|
|
}
|
|
|
|
#[\Override]
|
|
public function execute(InputInterface $input, OutputInterface $output): int {
|
|
$dryRun = $input->getOption('dry-run');
|
|
|
|
$qb = $this->connection->getQueryBuilder();
|
|
$count = (int)$qb->select($qb->func()->count('id'))
|
|
->from('share')
|
|
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP, IQueryBuilder::PARAM_INT)))
|
|
->andWhere($qb->expr()->eq('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING, IQueryBuilder::PARAM_INT)))
|
|
->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
|
->executeQuery()
|
|
->fetchOne();
|
|
|
|
if ($count === 0) {
|
|
$output->writeln('No affected group share subshares found.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
if ($dryRun) {
|
|
$output->writeln("Would fix <info>$count</info> group share subshare(s) (dry-run, no changes made).");
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$qb = $this->connection->getQueryBuilder();
|
|
$qb->update('share')
|
|
->set('accepted', $qb->createNamedParameter(IShare::STATUS_ACCEPTED, IQueryBuilder::PARAM_INT))
|
|
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USERGROUP, IQueryBuilder::PARAM_INT)))
|
|
->andWhere($qb->expr()->eq('accepted', $qb->createNamedParameter(IShare::STATUS_PENDING, IQueryBuilder::PARAM_INT)))
|
|
->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
|
->executeStatement();
|
|
|
|
$output->writeln("Fixed <info>$count</info> group share subshare(s).");
|
|
return self::SUCCESS;
|
|
}
|
|
}
|