Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jenkins] Copy file to owner's trash when recipient moves out of share #27079

Closed
Closed
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
24 changes: 24 additions & 0 deletions apps/files_trashbin/lib/Storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ public static function preRenameHook($params) {
// in cross-storage cases, a rename is a copy + unlink,
// that last unlink must not go to trash
self::$disableTrash = true;

$path1 = $params[Filesystem::signal_param_oldpath];
$path2 = $params[Filesystem::signal_param_newpath];

$view = Filesystem::getView();
$absolutePath1 = Filesystem::normalizePath($view->getAbsolutePath($path1));

$mount1 = $view->getMount($path1);
$mount2 = $view->getMount($path2);
$sourceStorage = $mount1->getStorage();
$targetStorage = $mount2->getStorage();
$sourceInternalPath = $mount1->getInternalPath($absolutePath1);
// check whether this is a cross-storage move from a *local* shared storage
if ($sourceInternalPath !== '' && $sourceStorage !== $targetStorage && $sourceStorage->instanceOfStorage('OCA\Files_Sharing\SharedStorage')) {
$ownerPath = $sourceStorage->getSourcePath($sourceInternalPath);
$owner = $sourceStorage->getOwner($sourceInternalPath);
if ($owner !== null && $owner !== '' && $ownerPath !== null && substr($ownerPath, 0, 6) === 'files/') {
// ownerPath is in the format "files/path/to/file.txt", strip "files"
$ownerPath = substr($ownerPath, 6);

// make a backup copy for the owner
\OCA\Files_Trashbin\Trashbin::copyBackupForOwner($ownerPath, $owner, time());
}
}
}

/**
Expand Down
54 changes: 43 additions & 11 deletions apps/files_trashbin/lib/Trashbin.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,42 @@ private static function copyFilesToUser($sourcePath, $owner, $targetPath, $user,
$target = $user . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;
$source = $owner . '/files_trashbin/files/' . $sourceFilename . '.d' . $timestamp;
self::copy_recursive($source, $target, $view);
}

/**
* Make a backup of a file into the trashbin for the owner
*
* @param string $ownerPath path relative to the owner's home folder and containing "files"
* @param string $owner user id of the owner
* @param int $timestamp deletion timestamp
*/
public static function copyBackupForOwner($ownerPath, $owner, $timestamp) {
self::setUpTrash($owner);

$targetFilename = basename($ownerPath);
$targetLocation = dirname($ownerPath);
$source = $owner . '/files/' . ltrim($ownerPath, '/');
$target = $owner . '/files_trashbin/files/' . $targetFilename . '.d' . $timestamp;

$view = new View('/');
self::copy_recursive($source, $target, $view);

self::retainVersions($targetFilename, $owner, $ownerPath, $timestamp, true);

if ($view->file_exists($target)) {
$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
$result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
if (!$result) {
\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated for the files owner', \OCP\Util::ERROR);
}
self::insertTrashEntry($owner, $targetFilename, $targetLocation, $timestamp);
self::scheduleExpire($owner);
}
}

/**
*
*/
public static function insertTrashEntry($user, $targetFilename, $targetLocation, $timestamp) {
$query = \OC_DB::prepare("INSERT INTO `*PREFIX*files_trash` (`id`,`timestamp`,`location`,`user`) VALUES (?,?,?,?)");
$result = $query->execute([$targetFilename, $timestamp, $targetLocation, $user]);
if (!$result) {
\OCP\Util::writeLog('files_trashbin', 'trash bin database couldn\'t be updated for the files owner', \OCP\Util::ERROR);
}
}

Expand Down Expand Up @@ -231,7 +259,6 @@ public static function move2trash($file_path) {
$location = $path_parts['dirname'];
$timestamp = time();

// disable proxy to prevent recursive calls
$trashPath = '/files_trashbin/files/' . $filename . '.d' . $timestamp;

/** @var \OC\Files\Storage\Storage $trashStorage */
Expand Down Expand Up @@ -293,25 +320,30 @@ public static function move2trash($file_path) {
* @param string $owner owner user id
* @param string $ownerPath path relative to the owner's home storage
* @param integer $timestamp when the file was deleted
* @param bool $forceCopy true to only make a copy of the versions into the trashbin
*/
private static function retainVersions($filename, $owner, $ownerPath, $timestamp) {
private static function retainVersions($filename, $owner, $ownerPath, $timestamp, $forceCopy = false) {
if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {

$user = User::getUser();
$rootView = new View('/');

if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
if ($owner !== $user) {
if ($owner !== $user || $forceCopy) {
self::copy_recursive($owner . '/files_versions/' . $ownerPath, $owner . '/files_trashbin/versions/' . basename($ownerPath) . '.d' . $timestamp, $rootView);
}
self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
if (!$forceCopy) {
self::move($rootView, $owner . '/files_versions/' . $ownerPath, $user . '/files_trashbin/versions/' . $filename . '.d' . $timestamp);
}
} else if ($versions = \OCA\Files_Versions\Storage::getVersions($owner, $ownerPath)) {

foreach ($versions as $v) {
if ($owner !== $user) {
if ($owner !== $user || $forceCopy) {
self::copy($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $owner . '/files_trashbin/versions/' . $v['name'] . '.v' . $v['version'] . '.d' . $timestamp);
}
self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
if (!$forceCopy) {
self::move($rootView, $owner . '/files_versions' . $v['path'] . '.v' . $v['version'], $user . '/files_trashbin/versions/' . $filename . '.v' . $v['version'] . '.d' . $timestamp);
}
}
}
}
Expand Down
71 changes: 71 additions & 0 deletions apps/files_trashbin/tests/StorageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,77 @@ public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() {
$this->assertEquals(0, count($results));
}

/**
* Test that the owner receives a backup of the file that was moved
* out of the shared folder
*/
public function testOwnerBackupWhenMovingFileOutOfShare() {
\OCA\Files_Versions\Hooks::connectHooks();

$this->userView->mkdir('share');
$this->userView->mkdir('share/sub');
// trigger a version (multiple would not work because of the expire logic)
$this->userView->file_put_contents('share/test.txt', 'v1');
$this->userView->file_put_contents('share/test.txt', 'v2');

$this->userView->file_put_contents('share/sub/testsub.txt', 'v1');
$this->userView->file_put_contents('share/sub/testsub.txt', 'v2');

$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/');
$this->assertEquals(2, count($results));
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/sub');
$this->assertEquals(1, count($results));

$recipientUser = $this->getUniqueId('recipient_');
\OC::$server->getUserManager()->createUser($recipientUser, $recipientUser);

$node = \OC::$server->getUserFolder($this->user)->get('share');
$share = \OC::$server->getShareManager()->newShare();
$share->setNode($node)
->setShareType(\OCP\Share::SHARE_TYPE_USER)
->setSharedBy($this->user)
->setSharedWith($recipientUser)
->setPermissions(\OCP\Constants::PERMISSION_ALL);
\OC::$server->getShareManager()->createShare($share);

$this->loginAsUser($recipientUser);

// delete as recipient
$recipientView = new \OC\Files\View('/' . $recipientUser . '/files');
// rename received share folder to catch potential issues if using the wrong name in the code
$recipientView->rename('share', 'share_renamed');
$recipientView->rename('share_renamed/test.txt', 'test.txt');
$recipientView->rename('share_renamed/sub', 'sub');

// rescan trash storage for both users
list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin');
$rootStorage->getScanner()->scan('');

// check if file and versions are in trashbin for owner
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
$this->assertEquals(2, count($results), 'Files in owner\'s trashbin');
// grab subdir name
$subDirName = $results[0]->getName();
$name = $results[1]->getName();
$this->assertEquals('test.txt.d', substr($name, 0, strlen('test.txt.d')));

$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
$this->assertEquals(2, count($results), 'Versions in owner\'s trashbin');
// note: entry 0 is the "sub" entry for versions
$name = $results[1]->getName();
$this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));

// check if sub-file and versions are in trashbin for owner
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $subDirName);
$this->assertEquals(1, count($results), 'Subfile in owner\'s trashbin');
$this->assertEquals('testsub.txt', $results[0]->getName());

$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $subDirName);
$this->assertEquals(1, count($results), 'Versions in owner\'s trashbin');
$name = $results[0]->getName();
$this->assertEquals('testsub.txt.v', substr($name, 0, strlen('testsub.txt.v')));
}

/**
* Delete should fail if the source file can't be deleted.
*/
Expand Down
28 changes: 28 additions & 0 deletions build/integration/features/sharing-v1.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1123,3 +1123,31 @@ Feature: sharing
And as "user1" the folder "/sub" exists in trash
And as "user1" the file "/sub/shared_file.txt" exists in trash

Scenario: moving file out of a share as recipient creates a backup for the owner
Given As an "admin"
And user "user0" exists
And user "user1" exists
And user "user0" created a folder "/shared"
And User "user0" moved file "/textfile0.txt" to "/shared/shared_file.txt"
And file "/shared" of user "user0" is shared with user "user1"
And User "user1" moved folder "/shared" to "/shared_renamed"
When User "user1" moved file "/shared_renamed/shared_file.txt" to "/taken_out.txt"
Then as "user1" the file "/taken_out.txt" exists
And as "user0" the file "/shared/shared_file.txt" does not exist
And as "user0" the file "/shared_file.txt" exists in trash

Scenario: moving folder out of a share as recipient creates a backup for the owner
Given As an "admin"
And user "user0" exists
And user "user1" exists
And user "user0" created a folder "/shared"
And user "user0" created a folder "/shared/sub"
And User "user0" moved file "/textfile0.txt" to "/shared/sub/shared_file.txt"
And file "/shared" of user "user0" is shared with user "user1"
And User "user1" moved folder "/shared" to "/shared_renamed"
When User "user1" moved folder "/shared_renamed/sub" to "/taken_out"
Then as "user1" the file "/taken_out" exists
And as "user0" the folder "/shared/sub" does not exist
And as "user0" the folder "/sub" exists in trash
And as "user0" the file "/sub/shared_file.txt" exists in trash