diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-01-19 11:36:24 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2025-02-04 16:22:11 +0100 |
commit | f9bd0ed46247a708fa7c99e3671d36c651a5855e (patch) | |
tree | 91c46d28a45510f1485bcf7b2b4e0a93580ce519 | |
parent | 3d9d99d103cc9fe8fb33acaac6ab66d92f8dd5c4 (diff) | |
download | nextcloud-server-f9bd0ed46247a708fa7c99e3671d36c651a5855e.tar.gz nextcloud-server-f9bd0ed46247a708fa7c99e3671d36c651a5855e.zip |
fix(sharing): Ensure download restrictions are not droppedbackport/50642/stable30
When a user receives a share with share-permissions but also with
download restrictions (hide download or the modern download permission attribute),
then re-shares of that share must always also include those restrictions.
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
3 files changed, 220 insertions, 110 deletions
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index f54d419d90b..17378d7ce21 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -649,7 +649,6 @@ class ShareAPIController extends OCSController { } $share->setSharedBy($this->currentUser); - $this->checkInheritedAttributes($share); // Handle mail send if (is_null($sendMail)) { @@ -779,6 +778,7 @@ class ShareAPIController extends OCSController { } $share->setShareType($shareType); + $this->checkInheritedAttributes($share); if ($note !== '') { $share->setNote($note); @@ -1261,7 +1261,6 @@ class ShareAPIController extends OCSController { if ($attributes !== null) { $share = $this->setShareAttributes($share, $attributes); } - $this->checkInheritedAttributes($share); // Handle mail send if ($sendMail === 'true' || $sendMail === 'false') { @@ -1345,6 +1344,7 @@ class ShareAPIController extends OCSController { } try { + $this->checkInheritedAttributes($share); $share = $this->shareManager->updateShare($share); } catch (HintException $e) { $code = $e->getCode() === 0 ? 403 : $e->getCode(); @@ -2047,30 +2047,48 @@ class ShareAPIController extends OCSController { if (!$share->getSharedBy()) { return; // Probably in a test } + + $canDownload = false; + $hideDownload = true; + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); - $node = $userFolder->getFirstNodeById($share->getNodeId()); - if (!$node) { - return; - } - if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) { - $storage = $node->getStorage(); - if ($storage instanceof Wrapper) { - $storage = $storage->getInstanceOfStorage(SharedStorage::class); - if ($storage === null) { - throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null'); - } - } else { - throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper'); + $nodes = $userFolder->getById($share->getNodeId()); + foreach ($nodes as $node) { + // Owner always can download it - so allow it and break + if ($node->getOwner()?->getUID() === $share->getSharedBy()) { + $canDownload = true; + $hideDownload = false; + break; } - /** @var \OCA\Files_Sharing\SharedStorage $storage */ - $inheritedAttributes = $storage->getShare()->getAttributes(); - if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) { - $share->setHideDownload(true); - $attributes = $share->getAttributes(); - if ($attributes) { - $attributes->setAttribute('permissions', 'download', false); - $share->setAttributes($attributes); + + if ($node->getStorage()->instanceOfStorage(SharedStorage::class)) { + $storage = $node->getStorage(); + if ($storage instanceof Wrapper) { + $storage = $storage->getInstanceOfStorage(SharedStorage::class); + if ($storage === null) { + throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null'); + } + } else { + throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper'); } + + /** @var SharedStorage $storage */ + $originalShare = $storage->getShare(); + $inheritedAttributes = $originalShare->getAttributes(); + // hide if hidden and also the current share enforces hide (can only be false if one share is false or user is owner) + $hideDownload = $hideDownload && $originalShare->getHideDownload(); + // allow download if already allowed by previous share or when the current share allows downloading + $canDownload = $canDownload || $inheritedAttributes === null || $inheritedAttributes->getAttribute('permissions', 'download') !== false; + } + } + + if ($hideDownload || !$canDownload) { + $share->setHideDownload(true); + + if (!$canDownload) { + $attributes = $share->getAttributes() ?? $share->newAttributes(); + $attributes->setAttribute('permissions', 'download', false); + $share->setAttributes($attributes); } } } diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php index e04b4929259..097dc631280 100644 --- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php @@ -55,26 +55,26 @@ class ShareAPIControllerTest extends TestCase { private ShareAPIController $ocs; private string $currentUser; - private \OC\Share20\Manager|MockObject $shareManager; - private IGroupManager|MockObject $groupManager; - private IUserManager|MockObject $userManager; - private IRequest|MockObject $request; - private IRootFolder|MockObject $rootFolder; - private IURLGenerator|MockObject $urlGenerator; - private IL10N|MockObject $l; - private IConfig|MockObject $config; - private IAppManager|MockObject $appManager; - private ContainerInterface|MockObject $serverContainer; - private IUserStatusManager|MockObject $userStatusManager; - private IPreview|MockObject $previewManager; - private IDateTimeZone|MockObject $dateTimeZone; - private LoggerInterface|MockObject $logger; - private IProviderFactory|MockObject $factory; - private IMailer|MockObject $mailer; + private \OC\Share20\Manager&MockObject $shareManager; + private IGroupManager&MockObject $groupManager; + private IUserManager&MockObject $userManager; + private IRequest&MockObject $request; + private IRootFolder&MockObject $rootFolder; + private IURLGenerator&MockObject $urlGenerator; + private IL10N&MockObject $l; + private IConfig&MockObject $config; + private IAppManager&MockObject $appManager; + private ContainerInterface&MockObject $serverContainer; + private IUserStatusManager&MockObject $userStatusManager; + private IPreview&MockObject $previewManager; + private IDateTimeZone&MockObject $dateTimeZone; + private LoggerInterface&MockObject $logger; + private IProviderFactory&MockObject $factory; + private IMailer&MockObject $mailer; protected function setUp(): void { - /** @var IManager|MockObject */ - $this->shareManager = $this->createMock(IManager::class); + /** @var IManager&MockObject */ + $this->shareManager = $this->createMock(\OC\Share20\Manager::class); $this->shareManager ->expects($this->any()) ->method('shareApiEnabled') @@ -154,7 +154,8 @@ class ShareAPIControllerTest extends TestCase { $this->factory, $this->mailer, $this->currentUser, - ])->setMethods(['formatShare']) + ]) + ->onlyMethods(['formatShare']) ->getMock(); } @@ -233,10 +234,20 @@ class ShareAPIControllerTest extends TestCase { $this->expectExceptionMessage('Could not delete share'); $node = $this->getMockBuilder(File::class)->getMock(); + $node->method('getId')->willReturn(42); $share = $this->newShare(); $share->setNode($node); + $userFolder = $this->getMockBuilder(Folder::class)->getMock(); + $this->rootFolder->method('getUserFolder') + ->with($this->currentUser) + ->willReturn($userFolder); + + $userFolder->method('getById') + ->with($share->getNodeId()) + ->willReturn([$node]); + $this->shareManager ->expects($this->once()) ->method('getShareById') @@ -398,9 +409,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with($share->getNodeId()) - ->willReturn($share->getNode()); + ->willReturn([$share->getNode()]); $this->shareManager->expects($this->once()) ->method('deleteFromSelf') @@ -461,9 +472,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with($share->getNodeId()) - ->willReturn($share->getNode()); + ->willReturn([$share->getNode()]); $this->shareManager->expects($this->never()) ->method('deleteFromSelf'); @@ -772,8 +783,9 @@ class ShareAPIControllerTest extends TestCase { $this->mailer, $this->currentUser, - ])->setMethods(['canAccessShare']) - ->getMock(); + ]) + ->onlyMethods(['canAccessShare']) + ->getMock(); $ocs->expects($this->any()) ->method('canAccessShare') @@ -793,7 +805,6 @@ class ShareAPIControllerTest extends TestCase { $userFolder->method('getById') ->with($share->getNodeId()) ->willReturn([$share->getNode()]); - $userFolder->method('getFirstNodeById') ->with($share->getNodeId()) ->willReturn($share->getNode()); @@ -832,9 +843,8 @@ class ShareAPIControllerTest extends TestCase { ]); $this->dateTimeZone->method('getTimezone')->willReturn(new \DateTimeZone('UTC')); - $d = $ocs->getShare($share->getId())->getData()[0]; - - $this->assertEquals($result, $ocs->getShare($share->getId())->getData()[0]); + $data = $ocs->getShare($share->getId())->getData()[0]; + $this->assertEquals($result, $data); } @@ -1384,7 +1394,7 @@ class ShareAPIControllerTest extends TestCase { * @dataProvider dataGetShares */ public function testGetShares(array $getSharesParameters, array $shares, array $extraShareTypes, array $expected): void { - /** @var ShareAPIController|MockObject $ocs */ + /** @var ShareAPIController&MockObject $ocs */ $ocs = $this->getMockBuilder(ShareAPIController::class) ->setConstructorArgs([ Application::APP_ID, @@ -1489,6 +1499,9 @@ class ShareAPIControllerTest extends TestCase { $userFolder->method('getFirstNodeById') ->with($share->getNodeId()) ->willReturn($file); + $userFolder->method('getById') + ->with($share->getNodeId()) + ->willReturn([$file]); $file->method('getPermissions') ->will($this->onConsecutiveCalls(\OCP\Constants::PERMISSION_SHARE, \OCP\Constants::PERMISSION_READ)); @@ -1582,9 +1595,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with($share->getNodeId()) - ->willReturn($share->getNode()); + ->willReturn([$share->getNode()]); if (!$helperAvailable) { $this->appManager->method('isEnabledForUser') @@ -1672,8 +1685,7 @@ class ShareAPIControllerTest extends TestCase { $this->shareManager->method('newShare')->willReturn($share); [$userFolder, $path] = $this->getNonSharedUserFile(); - $this->rootFolder->expects($this->exactly(2)) - ->method('getUserFolder') + $this->rootFolder->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); @@ -1700,8 +1712,7 @@ class ShareAPIControllerTest extends TestCase { $this->shareManager->method('newShare')->willReturn($share); [$userFolder, $path] = $this->getNonSharedUserFile(); - $this->rootFolder->expects($this->exactly(2)) - ->method('getUserFolder') + $this->rootFolder->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); @@ -1800,10 +1811,9 @@ class ShareAPIControllerTest extends TestCase { $this->shareManager->method('allowGroupSharing')->willReturn(true); [$userFolder, $path] = $this->getNonSharedUserFile(); - $this->rootFolder->expects($this->exactly(2)) - ->method('getUserFolder') - ->with('currentUser') - ->willReturn($userFolder); + $this->rootFolder->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); $userFolder->expects($this->once()) ->method('get') @@ -1905,8 +1915,7 @@ class ShareAPIControllerTest extends TestCase { $this->shareManager->method('newShare')->willReturn($share); [$userFolder, $path] = $this->getNonSharedUserFolder(); - $this->rootFolder->expects($this->exactly(2)) - ->method('getUserFolder') + $this->rootFolder->method('getUserFolder') ->with('currentUser') ->willReturn($userFolder); @@ -2462,10 +2471,9 @@ class ShareAPIControllerTest extends TestCase { $this->shareManager->method('newShare')->willReturn($share); [$userFolder, $path] = $this->getNonSharedUserFolder(); - $this->rootFolder->expects($this->exactly(2)) - ->method('getUserFolder') - ->with('currentUser') - ->willReturn($userFolder); + $this->rootFolder->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); $path->method('getPath')->willReturn('valid-path'); $userFolder->expects($this->once()) @@ -2500,10 +2508,9 @@ class ShareAPIControllerTest extends TestCase { $this->shareManager->method('newShare')->willReturn($share); [$userFolder, $path] = $this->getNonSharedUserFile(); - $this->rootFolder->expects($this->exactly(2)) - ->method('getUserFolder') - ->with('currentUser') - ->willReturn($userFolder); + $this->rootFolder->method('getUserFolder') + ->with('currentUser') + ->willReturn($userFolder); $userFolder->expects($this->once()) ->method('get') @@ -2634,9 +2641,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with($share->getNodeId()) - ->willReturn($share->getNode()); + ->willReturn([$share->getNode()]); $this->ocs->updateShare(42); } @@ -2727,6 +2734,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); + $userFolder->method('getById') + ->with(42) + ->willReturn([$node]); $userFolder->method('getFirstNodeById') ->with(42) ->willReturn($node); @@ -2781,9 +2791,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -2831,9 +2841,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -2889,9 +2899,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -2947,9 +2957,9 @@ class ShareAPIControllerTest extends TestCase { $ocs = $this->mockFormatShare(); [$userFolder, $folder] = $this->getNonSharedUserFolder(); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -2994,9 +3004,9 @@ class ShareAPIControllerTest extends TestCase { $ocs = $this->mockFormatShare(); [$userFolder, $folder] = $this->getNonSharedUserFolder(); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -3022,10 +3032,13 @@ class ShareAPIControllerTest extends TestCase { $ocs = $this->mockFormatShare(); - [$userFolder, $file] = $this->getNonSharedUserFile(); - $userFolder->method('getFirstNodeById') + $file = $this->getMockBuilder(File::class)->getMock(); + $file->method('getId') + ->willReturn(42); + [$userFolder, $folder] = $this->getNonSharedUserFolder(); + $userFolder->method('getById') ->with(42) - ->willReturn($file); + ->willReturn([$folder]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -3059,9 +3072,9 @@ class ShareAPIControllerTest extends TestCase { [$userFolder, $node] = $this->getNonSharedUserFolder(); $node->method('getId')->willReturn(42); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($node); + ->willReturn([$node]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -3110,9 +3123,9 @@ class ShareAPIControllerTest extends TestCase { $date->setTime(0, 0, 0); [$userFolder, $node] = $this->getNonSharedUserFolder(); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($node); + ->willReturn([$node]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -3168,9 +3181,9 @@ class ShareAPIControllerTest extends TestCase { $date->setTime(0, 0, 0); [$userFolder, $node] = $this->getNonSharedUserFolder(); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($node); + ->willReturn([$node]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -3208,9 +3221,9 @@ class ShareAPIControllerTest extends TestCase { $date->setTime(0, 0, 0); [$userFolder, $node] = $this->getNonSharedUserFolder(); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($node); + ->willReturn([$node]); $this->rootFolder->method('getUserFolder') ->with($this->currentUser) ->willReturn($userFolder); @@ -3302,9 +3315,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($node); + ->willReturn([$node]); $mountPoint = $this->createMock(IMountPoint::class); $node->method('getMountPoint') @@ -3370,9 +3383,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($node); + ->willReturn([$node]); $mountPoint = $this->createMock(IMountPoint::class); $node->method('getMountPoint') @@ -3431,9 +3444,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -3491,9 +3504,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -3551,9 +3564,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -3599,9 +3612,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($file); + ->willReturn([$file]); $mountPoint = $this->createMock(IMountPoint::class); $file->method('getMountPoint') @@ -3665,6 +3678,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); + $userFolder->method('getById') + ->with(42) + ->willReturn([$folder]); $userFolder->method('getFirstNodeById') ->with(42) ->willReturn($folder); @@ -3735,9 +3751,9 @@ class ShareAPIControllerTest extends TestCase { ->with($this->currentUser) ->willReturn($userFolder); - $userFolder->method('getFirstNodeById') + $userFolder->method('getById') ->with(42) - ->willReturn($folder); + ->willReturn([$folder]); $mountPoint = $this->createMock(IMountPoint::class); $folder->method('getMountPoint') @@ -4981,6 +4997,9 @@ class ShareAPIControllerTest extends TestCase { $userFolder->method('getStorage')->willReturn($storage); $node->method('getStorage')->willReturn($storage); $node->method('getId')->willReturn(42); + $user = $this->createMock(IUser::class); + $user->method('getUID')->willReturn($this->currentUser); + $node->method('getOwner')->willReturn($user); return [$userFolder, $node]; } diff --git a/build/integration/sharing_features/sharing-v1-part2.feature b/build/integration/sharing_features/sharing-v1-part2.feature index 8cc97fe71ee..e4da8436508 100644 --- a/build/integration/sharing_features/sharing-v1-part2.feature +++ b/build/integration/sharing_features/sharing-v1-part2.feature @@ -701,6 +701,79 @@ Feature: sharing Then the OCS status code should be "404" And the HTTP status code should be "200" + Scenario: download restrictions can not be dropped + As an "admin" + Given user "user0" exists + And user "user1" exists + And user "user2" exists + And User "user0" uploads file with content "foo" to "/tmp.txt" + And As an "user0" + And creating a share with + | path | /tmp.txt | + | shareType | 0 | + | shareWith | user1 | + | permissions | 17 | + | attributes | [{"scope":"permissions","key":"download","value":false}] | + And As an "user1" + And accepting last share + When Getting info of last share + Then Share fields of last share match with + | uid_owner | user0 | + | uid_file_owner | user0 | + | permissions | 17 | + | attributes | [{"scope":"permissions","key":"download","value":false}] | + When creating a share with + | path | /tmp.txt | + | shareType | 0 | + | shareWith | user2 | + | permissions | 1 | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + When As an "user2" + And accepting last share + And Getting info of last share + Then Share fields of last share match with + | share_type | 0 | + | permissions | 1 | + | uid_owner | user1 | + | uid_file_owner | user0 | + | attributes | [{"scope":"permissions","key":"download","value":false}] | + + Scenario: download restrictions can not be dropped when re-sharing even on link shares + As an "admin" + Given user "user0" exists + And user "user1" exists + And User "user0" uploads file with content "foo" to "/tmp.txt" + And As an "user0" + And creating a share with + | path | /tmp.txt | + | shareType | 0 | + | shareWith | user1 | + | permissions | 17 | + | attributes | [{"scope":"permissions","key":"download","value":false}] | + And As an "user1" + And accepting last share + When Getting info of last share + Then Share fields of last share match with + | uid_owner | user0 | + | attributes | [{"scope":"permissions","key":"download","value":false}] | + When creating a share with + | path | /tmp.txt | + | shareType | 3 | + | permissions | 1 | + And Getting info of last share + And Updating last share with + | hideDownload | false | + Then the OCS status code should be "100" + And the HTTP status code should be "200" + When Getting info of last share + Then Share fields of last share match with + | share_type | 3 | + | uid_owner | user1 | + | uid_file_owner | user0 | + | hide_download | 1 | + | attributes | [{"scope":"permissions","key":"download","value":false}] | + Scenario: User is not allowed to reshare file with additional delete permissions As an "admin" Given user "user0" exists |