aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuka Trovic <luka@nextcloud.com>2024-08-07 11:13:35 +0200
committerCôme Chilliet <come.chilliet@nextcloud.com>2024-12-19 14:39:44 +0100
commit024f98a8a15e5ebb6bca8fb7b9a611ed1e3c9c31 (patch)
tree30f13946f2cbf960f9fe1c96d48fd0acf0f77053
parent021b335065a3d9451bc1638e34f48a2f99049c30 (diff)
downloadnextcloud-server-024f98a8a15e5ebb6bca8fb7b9a611ed1e3c9c31.tar.gz
nextcloud-server-024f98a8a15e5ebb6bca8fb7b9a611ed1e3c9c31.zip
fix: delete re-shares when deleting the parent share
Note: Removed part about fix command from original PR Signed-off-by: Luka Trovic <luka@nextcloud.com> Signed-off-by: Côme Chilliet <come.chilliet@nextcloud.com> (cherry picked from commit 42181c2f490025860e22907255b6917583c798af)
-rw-r--r--apps/files/lib/Service/OwnershipTransferService.php3
-rw-r--r--apps/files_sharing/tests/EtagPropagationTest.php3
-rw-r--r--lib/private/Share20/Manager.php69
-rw-r--r--tests/lib/Share20/ManagerTest.php120
4 files changed, 190 insertions, 5 deletions
diff --git a/apps/files/lib/Service/OwnershipTransferService.php b/apps/files/lib/Service/OwnershipTransferService.php
index d4bc3033b1b..c47e51923ce 100644
--- a/apps/files/lib/Service/OwnershipTransferService.php
+++ b/apps/files/lib/Service/OwnershipTransferService.php
@@ -469,6 +469,9 @@ class OwnershipTransferService {
}
} catch (\OCP\Files\NotFoundException $e) {
$output->writeln('<error>Share with id ' . $share->getId() . ' points at deleted file, skipping</error>');
+ } catch (\OCP\Share\Exceptions\GenericShareException $e) {
+ $output->writeln('<error>Share with id ' . $share->getId() . ' is broken, deleting</error>');
+ $this->shareManager->deleteShare($share);
} catch (\Throwable $e) {
$output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '</error>');
}
diff --git a/apps/files_sharing/tests/EtagPropagationTest.php b/apps/files_sharing/tests/EtagPropagationTest.php
index 3f9ddfc413d..327726acc97 100644
--- a/apps/files_sharing/tests/EtagPropagationTest.php
+++ b/apps/files_sharing/tests/EtagPropagationTest.php
@@ -277,7 +277,8 @@ class EtagPropagationTest extends PropagationTestCase {
self::TEST_FILES_SHARING_API_USER2,
]);
- $this->assertAllUnchanged();
+ $this->assertEtagsNotChanged([self::TEST_FILES_SHARING_API_USER1, self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_USER3]);
+ $this->assertEtagsChanged([self::TEST_FILES_SHARING_API_USER4]);
}
public function testOwnerUnsharesFlatReshares() {
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index 2cc11c2c634..c4761aa81cf 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -18,6 +18,7 @@ use OCP\Files\Folder;
use OCP\Files\IRootFolder;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Node;
+use OCP\Files\NotFoundException;
use OCP\HintException;
use OCP\IConfig;
use OCP\IDateTimeZone;
@@ -1031,6 +1032,71 @@ class Manager implements IManager {
return $deletedShares;
}
+ public function deleteReshare(IShare $share) {
+ // Skip if node not found
+ try {
+ $node = $share->getNode();
+ } catch (NotFoundException) {
+ return;
+ }
+
+ $userIds = [];
+
+ if ($share->getShareType() === IShare::TYPE_USER) {
+ $userIds[] = $share->getSharedWith();
+ }
+
+ if ($share->getShareType() === IShare::TYPE_GROUP) {
+ $group = $this->groupManager->get($share->getSharedWith());
+ $users = $group->getUsers();
+
+ foreach ($users as $user) {
+ // Skip if share owner is member of shared group
+ if ($user->getUID() === $share->getShareOwner()) {
+ continue;
+ }
+ $userIds[] = $user->getUID();
+ }
+ }
+
+ $reshareRecords = [];
+ $shareTypes = [
+ IShare::TYPE_GROUP,
+ IShare::TYPE_USER,
+ IShare::TYPE_LINK,
+ IShare::TYPE_REMOTE,
+ IShare::TYPE_EMAIL
+ ];
+
+ foreach ($userIds as $userId) {
+ foreach ($shareTypes as $shareType) {
+ $provider = $this->factory->getProviderForType($shareType);
+ $shares = $provider->getSharesBy($userId, $shareType, $node, false, -1, 0);
+ foreach ($shares as $child) {
+ $reshareRecords[] = $child;
+ }
+ }
+
+ if ($share->getNodeType() === 'folder') {
+ $sharesInFolder = $this->getSharesInFolder($userId, $node, true);
+
+ foreach ($sharesInFolder as $shares) {
+ foreach ($shares as $child) {
+ $reshareRecords[] = $child;
+ }
+ }
+ }
+ }
+
+ foreach ($reshareRecords as $child) {
+ try {
+ $this->generalCreateChecks($child);
+ } catch (GenericShareException $e) {
+ $this->deleteShare($child);
+ }
+ }
+ }
+
/**
* Delete a share
*
@@ -1054,6 +1120,9 @@ class Manager implements IManager {
$provider = $this->factory->getProviderForType($share->getShareType());
$provider->delete($share);
+ // Delete shares that shared by the "share with user/group"
+ $this->deleteReshare($share);
+
$this->dispatcher->dispatchTyped(new ShareDeletedEvent($share));
}
diff --git a/tests/lib/Share20/ManagerTest.php b/tests/lib/Share20/ManagerTest.php
index c0cf0ca9c12..4acdfc8bc12 100644
--- a/tests/lib/Share20/ManagerTest.php
+++ b/tests/lib/Share20/ManagerTest.php
@@ -46,6 +46,7 @@ use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use OCP\Share\Events\ShareDeletedFromSelfEvent;
use OCP\Share\Exceptions\AlreadySharedException;
+use OCP\Share\Exceptions\GenericShareException;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IProviderFactory;
@@ -227,7 +228,7 @@ class ManagerTest extends \Test\TestCase {
*/
public function testDelete($shareType, $sharedWith) {
$manager = $this->createManagerMock()
- ->setMethods(['getShareById', 'deleteChildren'])
+ ->setMethods(['getShareById', 'deleteChildren', 'deleteReshare'])
->getMock();
$manager->method('deleteChildren')->willReturn([]);
@@ -245,6 +246,7 @@ class ManagerTest extends \Test\TestCase {
->setTarget('myTarget');
$manager->expects($this->once())->method('deleteChildren')->with($share);
+ $manager->expects($this->once())->method('deleteReshare')->with($share);
$this->defaultProvider
->expects($this->once())
@@ -269,7 +271,7 @@ class ManagerTest extends \Test\TestCase {
public function testDeleteLazyShare() {
$manager = $this->createManagerMock()
- ->setMethods(['getShareById', 'deleteChildren'])
+ ->setMethods(['getShareById', 'deleteChildren', 'deleteReshare'])
->getMock();
$manager->method('deleteChildren')->willReturn([]);
@@ -288,6 +290,7 @@ class ManagerTest extends \Test\TestCase {
$this->rootFolder->expects($this->never())->method($this->anything());
$manager->expects($this->once())->method('deleteChildren')->with($share);
+ $manager->expects($this->once())->method('deleteReshare')->with($share);
$this->defaultProvider
->expects($this->once())
@@ -312,7 +315,7 @@ class ManagerTest extends \Test\TestCase {
public function testDeleteNested() {
$manager = $this->createManagerMock()
- ->setMethods(['getShareById'])
+ ->setMethods(['getShareById', 'deleteReshare'])
->getMock();
$path = $this->createMock(File::class);
@@ -469,7 +472,116 @@ class ManagerTest extends \Test\TestCase {
$this->assertSame($shares, $result);
}
- public function testGetShareById() {
+ public function testDeleteReshareWhenUserHasOneShare(): void {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'generalCreateChecks'])
+ ->getMock();
+
+ $folder = $this->createMock(Folder::class);
+
+ $share = $this->createMock(IShare::class);
+ $share->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $share->method('getNodeType')->willReturn('folder');
+ $share->method('getSharedWith')->willReturn('UserB');
+ $share->method('getNode')->willReturn($folder);
+
+ $reShare = $this->createMock(IShare::class);
+ $reShare->method('getSharedBy')->willReturn('UserB');
+ $reShare->method('getSharedWith')->willReturn('UserC');
+ $reShare->method('getNode')->willReturn($folder);
+
+ $reShareInSubFolder = $this->createMock(IShare::class);
+ $reShareInSubFolder->method('getSharedBy')->willReturn('UserB');
+
+ $manager->method('getSharesInFolder')->willReturn([$reShareInSubFolder]);
+ $manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
+
+ $this->defaultProvider->method('getSharesBy')
+ ->willReturn([$reShare]);
+
+ $manager->expects($this->atLeast(2))->method('deleteShare')->withConsecutive([$reShare], [$reShareInSubFolder]);
+
+ $manager->deleteReshare($share);
+ }
+
+ public function testDeleteReshareWhenUserHasAnotherShare(): void {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith', 'generalCreateChecks'])
+ ->getMock();
+
+ $folder = $this->createMock(Folder::class);
+
+ $share = $this->createMock(IShare::class);
+ $share->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $share->method('getNodeType')->willReturn('folder');
+ $share->method('getSharedWith')->willReturn('UserB');
+ $share->method('getNode')->willReturn($folder);
+
+ $reShare = $this->createMock(IShare::class);
+ $reShare->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $reShare->method('getNodeType')->willReturn('folder');
+ $reShare->method('getSharedBy')->willReturn('UserB');
+ $reShare->method('getNode')->willReturn($folder);
+
+ $this->defaultProvider->method('getSharesBy')->willReturn([$reShare]);
+ $manager->method('getSharesInFolder')->willReturn([]);
+ $manager->method('generalCreateChecks')->willReturn(true);
+
+ $manager->expects($this->never())->method('deleteShare');
+
+ $manager->deleteReshare($share);
+ }
+
+ public function testDeleteReshareOfUsersInGroupShare(): void {
+ $manager = $this->createManagerMock()
+ ->setMethods(['deleteShare', 'getSharesInFolder', 'getSharedWith', 'generalCreateChecks'])
+ ->getMock();
+
+ $folder = $this->createMock(Folder::class);
+
+ $userA = $this->createMock(IUser::class);
+ $userA->method('getUID')->willReturn('userA');
+
+ $share = $this->createMock(IShare::class);
+ $share->method('getShareType')->willReturn(IShare::TYPE_GROUP);
+ $share->method('getNodeType')->willReturn('folder');
+ $share->method('getSharedWith')->willReturn('Group');
+ $share->method('getNode')->willReturn($folder);
+ $share->method('getShareOwner')->willReturn($userA);
+
+ $reShare1 = $this->createMock(IShare::class);
+ $reShare1->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $reShare1->method('getNodeType')->willReturn('folder');
+ $reShare1->method('getSharedBy')->willReturn('UserB');
+ $reShare1->method('getNode')->willReturn($folder);
+
+ $reShare2 = $this->createMock(IShare::class);
+ $reShare2->method('getShareType')->willReturn(IShare::TYPE_USER);
+ $reShare2->method('getNodeType')->willReturn('folder');
+ $reShare2->method('getSharedBy')->willReturn('UserC');
+ $reShare2->method('getNode')->willReturn($folder);
+
+ $userB = $this->createMock(IUser::class);
+ $userB->method('getUID')->willReturn('userB');
+ $userC = $this->createMock(IUser::class);
+ $userC->method('getUID')->willReturn('userC');
+ $group = $this->createMock(IGroup::class);
+ $group->method('getUsers')->willReturn([$userB, $userC]);
+ $this->groupManager->method('get')->with('Group')->willReturn($group);
+
+ $this->defaultProvider->method('getSharesBy')
+ ->willReturn([]);
+ $manager->method('generalCreateChecks')->willThrowException(new GenericShareException());
+
+ $manager->method('getSharedWith')->willReturn([]);
+ $manager->expects($this->exactly(2))->method('getSharesInFolder')->willReturnOnConsecutiveCalls([[$reShare1]], [[$reShare2]]);
+
+ $manager->expects($this->exactly(2))->method('deleteShare')->withConsecutive([$reShare1], [$reShare2]);
+
+ $manager->deleteReshare($share);
+ }
+
+ public function testGetShareById(): void {
$share = $this->createMock(IShare::class);
$this->defaultProvider