aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files/lib/Command/TransferOwnership.php43
-rw-r--r--apps/files/lib/Service/OwnershipTransferService.php141
-rw-r--r--build/integration/features/bootstrap/CommandLineContext.php16
-rw-r--r--build/integration/features/transfer-ownership.feature29
4 files changed, 223 insertions, 6 deletions
diff --git a/apps/files/lib/Command/TransferOwnership.php b/apps/files/lib/Command/TransferOwnership.php
index 71853d632e7..50aa0b21a5f 100644
--- a/apps/files/lib/Command/TransferOwnership.php
+++ b/apps/files/lib/Command/TransferOwnership.php
@@ -31,12 +31,14 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
+
namespace OCA\Files\Command;
use OCA\Files\Exception\TransferOwnershipException;
use OCA\Files\Service\OwnershipTransferService;
use OCP\IUser;
use OCP\IUserManager;
+use OCP\IConfig;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
@@ -51,17 +53,22 @@ class TransferOwnership extends Command {
/** @var OwnershipTransferService */
private $transferService;
+ /** @var IConfig */
+ private $config;
+
public function __construct(IUserManager $userManager,
- OwnershipTransferService $transferService) {
+ OwnershipTransferService $transferService,
+ IConfig $config) {
parent::__construct();
$this->userManager = $userManager;
$this->transferService = $transferService;
+ $this->config = $config;
}
protected function configure() {
$this
->setName('files:transfer-ownership')
- ->setDescription('All files and folders are moved to another user - shares are moved as well.')
+ ->setDescription('All files and folders are moved to another user - outgoing shares and incoming user file shares (optionally) are moved as well.')
->addArgument(
'source-user',
InputArgument::REQUIRED,
@@ -83,6 +90,12 @@ class TransferOwnership extends Command {
null,
InputOption::VALUE_NONE,
'move data from source user to root directory of destination user, which must be empty'
+ )->addOption(
+ 'transfer-incoming-shares',
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'transfer incoming user file shares to destination user. Usage: --transfer-incoming-shares=1 (value required)',
+ '2'
);
}
@@ -111,12 +124,36 @@ class TransferOwnership extends Command {
}
try {
+ $includeIncomingArgument = $input->getOption('transfer-incoming-shares');
+
+ switch ($includeIncomingArgument) {
+ case '0':
+ $includeIncoming = false;
+ break;
+ case '1':
+ $includeIncoming = true;
+ break;
+ case '2':
+ $includeIncoming = $this->config->getSystemValue('transferIncomingShares', false);
+ if (gettype($includeIncoming) !== 'boolean') {
+ $output->writeln("<error> config.php: 'transfer-incoming-shares': wrong usage. Transfer aborted.</error>");
+ return 1;
+ }
+ break;
+ default:
+ $output->writeln("<error>Option --transfer-incoming-shares: wrong usage. Transfer aborted.</error>");
+ return 1;
+ break;
+ }
+
$this->transferService->transfer(
$sourceUserObject,
$destinationUserObject,
ltrim($input->getOption('path'), '/'),
$output,
- $input->getOption('move') === true
+ $input->getOption('move') === true,
+ false,
+ $includeIncoming
);
} catch (TransferOwnershipException $e) {
$output->writeln("<error>" . $e->getMessage() . "</error>");
diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php
index cc54df50541..ecb2365ef1b 100644
--- a/apps/files/lib/Service/OwnershipTransferService.php
+++ b/apps/files/lib/Service/OwnershipTransferService.php
@@ -29,6 +29,7 @@ declare(strict_types=1);
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
+
namespace OCA\Files\Service;
use Closure;
@@ -93,7 +94,8 @@ class OwnershipTransferService {
string $path,
?OutputInterface $output = null,
bool $move = false,
- bool $firstLogin = false): void {
+ bool $firstLogin = false,
+ bool $transferIncomingShares = false): void {
$output = $output ?? new NullOutput();
$sourceUid = $sourceUser->getUID();
$destinationUid = $destinationUser->getUID();
@@ -180,6 +182,31 @@ class OwnershipTransferService {
$shares,
$output
);
+
+ // transfer the incoming shares
+ if ($transferIncomingShares === true) {
+ $sourceShares = $this->collectIncomingShares(
+ $sourceUid,
+ $output,
+ $view
+ );
+ $destinationShares = $this->collectIncomingShares(
+ $destinationUid,
+ $output,
+ $view,
+ true
+ );
+ $this->transferIncomingShares(
+ $sourceUid,
+ $destinationUid,
+ $sourceShares,
+ $destinationShares,
+ $output,
+ $path,
+ $finalTarget,
+ $move
+ );
+ }
}
private function walkFiles(View $view, $path, Closure $callBack) {
@@ -253,6 +280,7 @@ class OwnershipTransferService {
$shares = [];
$progress = new ProgressBar($output);
+
foreach ([IShare::TYPE_GROUP, IShare::TYPE_USER, IShare::TYPE_LINK, IShare::TYPE_REMOTE, IShare::TYPE_ROOM, IShare::TYPE_EMAIL, IShare::TYPE_CIRCLE, IShare::TYPE_DECK] as $shareType) {
$offset = 0;
while (true) {
@@ -288,6 +316,41 @@ class OwnershipTransferService {
return $shares;
}
+ private function collectIncomingShares(string $sourceUid,
+ OutputInterface $output,
+ View $view,
+ bool $addKeys = false): array {
+ $output->writeln("Collecting all incoming share information for files and folders of $sourceUid ...");
+
+ $shares = [];
+ $progress = new ProgressBar($output);
+
+ $offset = 0;
+ while (true) {
+ $sharePage = $this->shareManager->getSharedWith($sourceUid, IShare::TYPE_USER, null, 50, $offset);
+ $progress->advance(count($sharePage));
+ if (empty($sharePage)) {
+ break;
+ }
+ if ($addKeys) {
+ foreach ($sharePage as $singleShare) {
+ $shares[$singleShare->getNodeId()] = $singleShare;
+ }
+ } else {
+ foreach ($sharePage as $singleShare) {
+ $shares[] = $singleShare;
+ }
+ }
+
+ $offset += 50;
+ }
+
+
+ $progress->finish();
+ $output->writeln('');
+ return $shares;
+ }
+
/**
* @throws TransferOwnershipException
*/
@@ -356,4 +419,80 @@ class OwnershipTransferService {
$progress->finish();
$output->writeln('');
}
+
+ private function transferIncomingShares(string $sourceUid,
+ string $destinationUid,
+ array $sourceShares,
+ array $destinationShares,
+ OutputInterface $output,
+ string $path,
+ string $finalTarget,
+ bool $move): void {
+ $output->writeln("Restoring incoming shares ...");
+ $progress = new ProgressBar($output, count($sourceShares));
+ $prefix = "$destinationUid/files";
+ if (substr($finalTarget, 0, strlen($prefix)) === $prefix) {
+ $finalShareTarget = substr($finalTarget, strlen($prefix));
+ }
+ foreach ($sourceShares as $share) {
+ try {
+ // Only restore if share is in given path.
+ $pathToCheck = '/' . trim($path) . '/';
+ if (substr($share->getTarget(), 0, strlen($pathToCheck)) !== $pathToCheck) {
+ continue;
+ }
+ $shareTarget = $share->getTarget();
+ $shareTarget = $finalShareTarget . $shareTarget;
+ if ($share->getShareType() === IShare::TYPE_USER &&
+ $share->getSharedBy() === $destinationUid) {
+ $this->shareManager->deleteShare($share);
+ } elseif (isset($destinationShares[$share->getNodeId()])) {
+ $destinationShare = $destinationShares[$share->getNodeId()];
+ // Keep the share which has the most permissions and discard the other one.
+ if ($destinationShare->getPermissions() < $share->getPermissions()) {
+ $this->shareManager->deleteShare($destinationShare);
+ $share->setSharedWith($destinationUid);
+ // trigger refetching of the node so that the new owner and mountpoint are taken into account
+ // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
+ $this->userMountCache->clear();
+ $share->setNodeId($share->getNode()->getId());
+ $this->shareManager->updateShare($share);
+ // The share is already transferred.
+ $progress->advance();
+ if ($move) {
+ continue;
+ }
+ $share->setTarget($shareTarget);
+ $this->shareManager->moveShare($share, $destinationUid);
+ continue;
+ }
+ $this->shareManager->deleteShare($share);
+ } elseif ($share->getShareOwner() === $destinationUid) {
+ $this->shareManager->deleteShare($share);
+ } else {
+ $share->setSharedWith($destinationUid);
+ $share->setNodeId($share->getNode()->getId());
+ $this->shareManager->updateShare($share);
+ // trigger refetching of the node so that the new owner and mountpoint are taken into account
+ // otherwise the checks on the share update will fail due to the original node not being available in the new user scope
+ $this->userMountCache->clear();
+ // The share is already transferred.
+ $progress->advance();
+ if ($move) {
+ continue;
+ }
+ $share->setTarget($shareTarget);
+ $this->shareManager->moveShare($share, $destinationUid);
+ continue;
+ }
+ } catch (\OCP\Files\NotFoundException $e) {
+ $output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
+ } catch (\Throwable $e) {
+ $output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getTraceAsString() . '</error>');
+ }
+ $progress->advance();
+ }
+ $progress->finish();
+ $output->writeln('');
+ }
}
diff --git a/build/integration/features/bootstrap/CommandLineContext.php b/build/integration/features/bootstrap/CommandLineContext.php
index b435aaceca0..41756e448bf 100644
--- a/build/integration/features/bootstrap/CommandLineContext.php
+++ b/build/integration/features/bootstrap/CommandLineContext.php
@@ -97,7 +97,7 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership from "([^"]+)" to "([^"]+)"/
+ * @When /^transferring ownership from "([^"]+)" to "([^"]+)"$/
*/
public function transferringOwnership($user1, $user2) {
if ($this->runOcc(['files:transfer-ownership', $user1, $user2]) === 0) {
@@ -109,7 +109,7 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)"/
+ * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)"$/
*/
public function transferringOwnershipPath($path, $user1, $user2) {
$path = '--path=' . $path;
@@ -121,6 +121,18 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
}
+ /**
+ * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)" with received shares$/
+ */
+ public function transferringOwnershipPathWithIncomingShares($path, $user1, $user2) {
+ $path = '--path=' . $path;
+ if ($this->runOcc(['files:transfer-ownership', $path, $user1, $user2, '--transfer-incoming-shares=1']) === 0) {
+ $this->lastTransferPath = $this->findLastTransferFolderForUser($user1, $user2);
+ } else {
+ // failure
+ $this->lastTransferPath = null;
+ }
+ }
/**
* @When /^using received transfer folder of "([^"]+)" as dav path$/
diff --git a/build/integration/features/transfer-ownership.feature b/build/integration/features/transfer-ownership.feature
index b18453cf3ec..22e34dcf7af 100644
--- a/build/integration/features/transfer-ownership.feature
+++ b/build/integration/features/transfer-ownership.feature
@@ -533,6 +533,35 @@ Feature: transfer-ownership
And Getting info of last share
And the OCS status code should be "404"
+ Scenario: transferring ownership transfers received shares into subdir when requested
+ Given user "user0" exists
+ And user "user1" exists
+ And user "user2" exists
+ And User "user2" created a folder "/transfer-share"
+ And User "user2" created a folder "/do-not-transfer"
+ And User "user0" created a folder "/sub"
+ And folder "/transfer-share" of user "user2" is shared with user "user0" with permissions 31
+ And user "user0" accepts last share
+ And User "user0" moved folder "/transfer-share" to "/sub/transfer-share"
+ And folder "/do-not-transfer" of user "user2" is shared with user "user0" with permissions 31
+ And user "user0" accepts last share
+ When transferring ownership of path "sub" from "user0" to "user1" with received shares
+ And the command was successful
+ And As an "user1"
+ And using received transfer folder of "user1" as dav path
+ Then as "user1" the folder "/sub" exists
+ And as "user1" the folder "/do-not-transfer" does not exist
+ And as "user1" the folder "/sub/do-not-transfer" does not exist
+ And as "user1" the folder "/sub/transfer-share" exists
+ And using old dav path
+ And as "user1" the folder "/transfer-share" does not exist
+ And as "user1" the folder "/do-not-transfer" does not exist
+ And using old dav path
+ And as "user0" the folder "/sub" does not exist
+ And as "user0" the folder "/do-not-transfer" exists
+ And Getting info of last share
+ And the OCS status code should be "404"
+
Scenario: transferring ownership does not transfer external storage
Given user "user0" exists
And user "user1" exists