Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions apps/files/lib/Service/OwnershipTransferService.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use OCA\GroupFolders\Mount\GroupMountPoint;
use OCP\Console\IOutput;
use OCP\Console\Verbosity;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Encryption\IManager as IEncryptionManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\IHomeMountProvider;
Expand All @@ -29,6 +30,7 @@
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
Expand All @@ -55,6 +57,7 @@ public function __construct(
private IFactory $l10nFactory,
private IRootFolder $rootFolder,
private IEventDispatcher $eventDispatcher,
private IDBConnection $connection,
) {
}

Expand Down Expand Up @@ -524,6 +527,8 @@ private function restoreShares(
if ($shareMountPoint) {
$this->mountManager->removeMount($shareMountPoint->getMountPoint());
}

$this->promoteLinkShares($share);
$this->shareManager->deleteShare($share);
} else {
if ($share->getShareOwner() === $sourceUid) {
Expand Down Expand Up @@ -604,11 +609,13 @@ private function transferIncomingShares(
$shareTarget = $finalShareTarget . $shareTarget;
if ($share->getShareType() === IShare::TYPE_USER
&& $share->getSharedBy() === $destinationUid) {
$this->promoteLinkShares($share);
$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->promoteLinkShares($destinationShare);
$this->shareManager->deleteShare($destinationShare);
$share->setSharedWith($destinationUid);
// trigger refetching of the node so that the new owner and mountpoint are taken into account
Expand All @@ -628,8 +635,10 @@ private function transferIncomingShares(
$this->mountManager->moveMount($oldMountPoint, $newMountPoint);
continue;
}
$this->promoteLinkShares($share);
$this->shareManager->deleteShare($share);
} elseif ($share->getShareOwner() === $destinationUid) {
$this->promoteLinkShares($share);
$this->shareManager->deleteShare($share);
} else {
$share->setSharedWith($destinationUid);
Expand Down Expand Up @@ -661,6 +670,19 @@ private function transferIncomingShares(
$output->writeln('');
}

/**
* Links or email shares need to be promoted before the original share
* is dropped, as the deletion is cascaded.
*/
private function promoteLinkShares(IShare $share): void {
$qb = $this->connection->getQueryBuilder();
$qb->update('share')
->set('parent', $qb->createNamedParameter(null, IQueryBuilder::PARAM_NULL))
->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
->andWhere($qb->expr()->in('share_type', $qb->createNamedParameter([IShare::TYPE_LINK, IShare::TYPE_EMAIL], IQueryBuilder::PARAM_INT_ARRAY)))
->executeStatement();
}

private function getShareMountPoint(string $uid, string $target): string {
return '/' . $uid . '/files/' . trim($target, '/') . '/';
}
Expand Down
42 changes: 1 addition & 41 deletions build/integration/features/bootstrap/CommandLine.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,7 @@
require __DIR__ . '/autoload.php';

trait CommandLine {
/** @var int return code of last command */
private int $lastCode = 0;
/** @var string stdout of last command */
private string $lastStdOut = '';
/** @var string stderr of last command */
private string $lastStdErr = '';
protected string $ocPath = '../..';

/**
* Invokes an OCC command
*
* @param string[] $args OCC command, the part behind "occ". For example: "files:transfer-ownership"
* @return int exit code
*/
public function runOcc(array $args = [], string $inputString = ''): int {
$args = array_map(function ($arg) {
return escapeshellarg($arg);
}, $args);
$args[] = '--no-ansi';
$args = implode(' ', $args);

$descriptor = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open('php console.php ' . $args, $descriptor, $pipes, $this->ocPath);
if ($inputString !== '') {
fwrite($pipes[0], $inputString . "\n");
fclose($pipes[0]);
}
$this->lastStdOut = stream_get_contents($pipes[1]);
$this->lastStdErr = stream_get_contents($pipes[2]);
$this->lastCode = proc_close($process);

// Clean opcode cache
$client = new GuzzleHttp\Client();
$client->request('GET', 'http://localhost:8080/apps/testing/clean_opcode_cache.php');

return $this->lastCode;
}
use OccRunner;

/**
* @Given /^invoking occ with "([^"]*)"$/
Expand Down
1 change: 1 addition & 0 deletions build/integration/features/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
class FeatureContext implements Context, SnippetAcceptingContext {
use AppConfiguration;
use OccRunner;
use ContactsMenu;
use ExternalStorage;
use Search;
Expand Down
6 changes: 3 additions & 3 deletions build/integration/features/bootstrap/Mail.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
trait Mail {
// CommandLine trait is expected to be used in the class that uses this
// OccRunner trait is expected to be used in the class that uses this
// trait.

/**
Expand All @@ -23,7 +23,7 @@ public function killDummyMailServer() {

exec('kill ' . $this->fakeSmtpServerPid);

$this->invokingTheCommand('config:system:delete mail_smtpport');
$this->runOcc(['config:system:delete', 'mail_smtpport']);
}

/**
Expand All @@ -32,7 +32,7 @@ public function killDummyMailServer() {
public function dummyMailServerIsListening() {
// Default smtpport (25) is restricted for regular users, so the
// FakeSMTP uses 2525 instead.
$this->invokingTheCommand('config:system:set mail_smtpport --value=2525 --type integer');
$this->runOcc(explode(' ', 'config:system:set mail_smtpport --value=2525 --type integer'));

$this->fakeSmtpServerPid = exec('php features/bootstrap/FakeSMTPHelper.php >/dev/null 2>&1 & echo $!');
}
Expand Down
57 changes: 57 additions & 0 deletions build/integration/features/bootstrap/OccRunner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/

require __DIR__ . '/autoload.php';

/**
* Plumbing for running occ commands in behat tests, which does not include
* the behat step definitions for command line tests.
*/
trait OccRunner {
/** @var int return code of last command */
private int $lastCode = 0;
/** @var string stdout of last command */
private string $lastStdOut = '';
/** @var string stderr of last command */
private string $lastStdErr = '';
protected string $ocPath = '../..';

/**
* Invokes an OCC command
*
* @param string[] $args OCC command, the part behind "occ". For example: "files:transfer-ownership"
* @return int exit code
*/
public function runOcc(array $args = [], string $inputString = ''): int {
$args = array_map(function ($arg) {
return escapeshellarg($arg);
}, $args);
$args[] = '--no-ansi';
$args = implode(' ', $args);

$descriptor = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open('php console.php ' . $args, $descriptor, $pipes, $this->ocPath);
if ($inputString !== '') {
fwrite($pipes[0], $inputString . "\n");
fclose($pipes[0]);
}
$this->lastStdOut = stream_get_contents($pipes[1]);
$this->lastStdErr = stream_get_contents($pipes[2]);
$this->lastCode = proc_close($process);

// Clean opcode cache
$client = new GuzzleHttp\Client();
$client->request('GET', 'http://localhost:8080/apps/testing/clean_opcode_cache.php');

return $this->lastCode;
}
}
65 changes: 65 additions & 0 deletions build/integration/files_features/transfer-ownership.feature
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,71 @@ Feature: transfer-ownership
| uid_file_owner | user3 |
| share_with | group1 |

Scenario: transferring ownership of folder shared with transfer recipient who created a link share
Given user "user0" exists
And user "user1" exists
And User "user0" created a folder "/test"
And User "user0" uploads file "data/textfile.txt" to "/test/somefile.txt"
And folder "/test" of user "user0" is shared with user "user1" with permissions 31
And user "user1" accepts last share
And as "user1" creating a share with
| path | test/somefile.txt |
| shareType | 3 |
And the OCS status code should be "100"
When transferring ownership from "user0" to "user1"
And the command was successful
Then last link share can be downloaded

Scenario: transferring ownership of folder shared with transfer recipient who created a mail share
Given dummy mail server is listening
And user "user0" exists
And user "user1" exists
And User "user0" created a folder "/test"
And User "user0" uploads file "data/textfile.txt" to "/test/somefile.txt"
And folder "/test" of user "user0" is shared with user "user1" with permissions 31
And user "user1" accepts last share
And as "user1" creating a share with
| path | test/somefile.txt |
| shareType | 4 |
| shareWith | dumy@test.com |
And the OCS status code should be "100"
When transferring ownership from "user0" to "user1"
And the command was successful
Then last share can be downloaded

Scenario: transferring ownership of folder owned by transfer recipient with a link share created by the source
Given user "user0" exists
And user "user1" exists
And user "user2" exists
And User "user1" created a folder "/test"
And User "user1" uploads file "data/textfile.txt" to "/test/somefile.txt"
And folder "/test" of user "user1" is shared with user "user2" with permissions 31
And user "user2" accepts last share
And folder "/test" of user "user2" is shared with user "user0" with permissions 31
And user "user0" accepts last share
And as "user0" creating a share with
| path | test/somefile.txt |
| shareType | 3 |
And the OCS status code should be "100"
When transferring ownership from "user0" to "user1"
And the command was successful
Then last link share can be downloaded

Scenario: transferring ownership of folder shared directly by transfer recipient with a link share created by the source
Given user "user0" exists
And user "user1" exists
And User "user1" created a folder "/test"
And User "user1" uploads file "data/textfile.txt" to "/test/somefile.txt"
And folder "/test" of user "user1" is shared with user "user0" with permissions 31
And user "user0" accepts last share
And as "user0" creating a share with
| path | test/somefile.txt |
| shareType | 3 |
And the OCS status code should be "100"
When transferring ownership from "user0" to "user1"
And the command was successful
Then last link share can be downloaded

Scenario: transferring ownership transfers received shares
Given user "user0" exists
And user "user1" exists
Expand Down
Loading