Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
fix: delete re-shares when deleting the parent share
Note: Removed part about fix command from original PR

Signed-off-by: Luka Trovic <[email protected]>
Signed-off-by: Côme Chilliet <[email protected]>
(cherry picked from commit 42181c2)
  • Loading branch information
luka-nextcloud authored and backportbot[bot] committed Dec 20, 2024
commit 7a3a7794266b615192cb2ccab9163d0d7683e5cf
3 changes: 3 additions & 0 deletions apps/files/lib/Service/OwnershipTransferService.php
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,9 @@ private function restoreShares(
}
} 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>');
}
Expand Down
3 changes: 2 additions & 1 deletion apps/files_sharing/tests/EtagPropagationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ public function testOwnerUnshares() {
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() {
Expand Down
69 changes: 69 additions & 0 deletions lib/private/Share20/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
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;
Expand Down Expand Up @@ -1155,6 +1156,71 @@ protected function deleteChildren(IShare $share) {
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
*
Expand All @@ -1178,6 +1244,9 @@ public function deleteShare(IShare $share) {
$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));
}

Expand Down
120 changes: 116 additions & 4 deletions tests/lib/Share20/ManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
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;
Expand Down Expand Up @@ -241,7 +242,7 @@ public function dataTestDelete() {
*/
public function testDelete($shareType, $sharedWith) {
$manager = $this->createManagerMock()
->setMethods(['getShareById', 'deleteChildren'])
->setMethods(['getShareById', 'deleteChildren', 'deleteReshare'])
->getMock();

$manager->method('deleteChildren')->willReturn([]);
Expand All @@ -259,6 +260,7 @@ public function testDelete($shareType, $sharedWith) {
->setTarget('myTarget');

$manager->expects($this->once())->method('deleteChildren')->with($share);
$manager->expects($this->once())->method('deleteReshare')->with($share);

$this->defaultProvider
->expects($this->once())
Expand All @@ -283,7 +285,7 @@ public function testDelete($shareType, $sharedWith) {

public function testDeleteLazyShare() {
$manager = $this->createManagerMock()
->setMethods(['getShareById', 'deleteChildren'])
->setMethods(['getShareById', 'deleteChildren', 'deleteReshare'])
->getMock();

$manager->method('deleteChildren')->willReturn([]);
Expand All @@ -302,6 +304,7 @@ public function testDeleteLazyShare() {
$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())
Expand All @@ -326,7 +329,7 @@ public function testDeleteLazyShare() {

public function testDeleteNested() {
$manager = $this->createManagerMock()
->setMethods(['getShareById'])
->setMethods(['getShareById', 'deleteReshare'])
->getMock();

$path = $this->createMock(File::class);
Expand Down Expand Up @@ -483,7 +486,116 @@ public function testDeleteChildren() {
$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
Expand Down