Skip to content

Commit c682e1e

Browse files
committed
feat(Interaction): Add support for Unified Sharing permissions
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent 5c5af25 commit c682e1e

3 files changed

Lines changed: 70 additions & 31 deletions

File tree

apps/files_sharing/lib/Listener/RestrictInteractionListener.php

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
namespace OCA\Files_Sharing\Listener;
1111

12+
use OCA\Files\Sharing\Permission\NodeCreateSharePermissionType;
13+
use OCA\Files\Sharing\Permission\NodeDeleteSharePermissionType;
14+
use OCA\Files\Sharing\Permission\NodeReadSharePermissionType;
15+
use OCA\Files\Sharing\Permission\NodeUpdateSharePermissionType;
1216
use OCA\Files_Sharing\AppInfo\Application;
1317
use OCP\Constants;
1418
use OCP\EventDispatcher\Event;
@@ -56,37 +60,41 @@ public function handle(Event $event): void {
5660
throw new InteractionRestrictedException('Cannot share home folder node.', $this->l10n->t('You cannot share your home folder.'));
5761
}
5862

59-
if ($event->action->filesSharingPermissions !== null) {
60-
if ($resource->getNode() instanceof File) {
61-
if (($event->action->filesSharingPermissions & Constants::PERMISSION_DELETE) === Constants::PERMISSION_DELETE) {
62-
throw new InteractionRestrictedException('Cannot share file node with delete permission.', $this->l10n->t('File cannot be shared with delete permission.'));
63-
}
64-
65-
if (($event->action->filesSharingPermissions & Constants::PERMISSION_CREATE) === Constants::PERMISSION_CREATE) {
66-
throw new InteractionRestrictedException('Cannot share file node with create permission.', $this->l10n->t('File cannot be shared with create permission.'));
67-
}
63+
// These checks are only for files_sharing, because they operate on shares that can only contain a single source.
64+
// With Unified Sharing, there could be multiple sources like a file and a folder in the same share. Because it grants permission on a share and not a single node, this check doesn't work.
65+
if ($event->action->filesSharingPermissions !== null && $resource->getNode() instanceof File) {
66+
if (($event->action->filesSharingPermissions & Constants::PERMISSION_DELETE) === Constants::PERMISSION_DELETE) {
67+
throw new InteractionRestrictedException('Cannot share file node with delete permission.', $this->l10n->t('File cannot be shared with delete permission.'));
6868
}
6969

70-
foreach ($event->receivers as $receiver) {
71-
if (!$receiver instanceof LinkReceiver
72-
&& !$receiver instanceof EmailReceiver
73-
&& ($event->action->filesSharingPermissions & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ) {
74-
throw new InteractionRestrictedException('No read permission on the share.', $this->l10n->t('File share needs at least read permission.'));
75-
}
70+
if (($event->action->filesSharingPermissions & Constants::PERMISSION_CREATE) === Constants::PERMISSION_CREATE) {
71+
throw new InteractionRestrictedException('Cannot share file node with create permission.', $this->l10n->t('File cannot be shared with create permission.'));
72+
}
73+
}
7674

77-
if (($receiver instanceof LinkReceiver || $receiver instanceof EmailReceiver)
78-
&& $resource->getNode() instanceof Folder
79-
&& ($event->action->filesSharingPermissions & (Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)) !== 0
80-
&& !$this->manager->shareApiLinkAllowPublicUpload()) {
81-
throw new InteractionRestrictedException('Public upload is not allowed.', $this->l10n->t('Public upload is not allowed.'));
82-
}
75+
foreach ($event->receivers as $receiver) {
76+
if (!$receiver instanceof LinkReceiver
77+
&& !$receiver instanceof EmailReceiver
78+
&& (($event->action->filesSharingPermissions !== null && ($event->action->filesSharingPermissions & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ)
79+
|| ($event->action->unifiedSharingPermissions !== null && !in_array(NodeReadSharePermissionType::class, $event->action->unifiedSharingPermissions)))) {
80+
throw new InteractionRestrictedException('No read permission on the share.', $this->l10n->t('File share needs at least read permission.'));
8381
}
8482

85-
if (($event->action->filesSharingPermissions & ~$resource->getNodePermissions()) !== 0) {
86-
$path = $userFolder->getRelativePath($resource->getNode()->getPath());
87-
throw new InteractionRestrictedException('Cannot share node with more permissions than the node already has.', $this->l10n->t('You cannot share "%s" with more permission than you have yourself.', [$path]));
83+
if (($receiver instanceof LinkReceiver || $receiver instanceof EmailReceiver)
84+
&& $resource->getNode() instanceof Folder
85+
&& ((($event->action->filesSharingPermissions !== null && ($event->action->filesSharingPermissions & (Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)) !== 0))
86+
|| ($event->action->unifiedSharingPermissions !== null && array_intersect($event->action->unifiedSharingPermissions, [NodeCreateSharePermissionType::class, NodeUpdateSharePermissionType::class, NodeDeleteSharePermissionType::class]) !== []))
87+
&& !$this->manager->shareApiLinkAllowPublicUpload()) {
88+
throw new InteractionRestrictedException('Public upload is not allowed.', $this->l10n->t('Public upload is not allowed.'));
8889
}
8990
}
91+
92+
// Unified Sharing may grant more permissions on the share, than a specific resources allows.
93+
// Therefore we don't check this here, as the permission must be correctly applied later anyway.
94+
if (($event->action->filesSharingPermissions !== null && ($event->action->filesSharingPermissions & ~$resource->getNodePermissions()) !== 0)) {
95+
$path = $userFolder->getRelativePath($resource->getNode()->getPath());
96+
throw new InteractionRestrictedException('Cannot share node with more permissions than the node already has.', $this->l10n->t('You cannot share "%s" with more permission than you have yourself.', [$path]));
97+
}
9098
}
9199
}
92100
}

apps/files_sharing/tests/Listener/RestrictInteractionListenerTest.php

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99

1010
namespace OCA\Files_Sharing\Tests\Listener;
1111

12+
use OC\Core\Sharing\Permission\ReshareSharePermissionType;
13+
use OCA\Files\Sharing\Permission\NodeCreateSharePermissionType;
14+
use OCA\Files\Sharing\Permission\NodeDeleteSharePermissionType;
15+
use OCA\Files\Sharing\Permission\NodeReadSharePermissionType;
16+
use OCA\Files\Sharing\Permission\NodeUpdateSharePermissionType;
1217
use OCP\Constants;
1318
use OCP\Files\IRootFolder;
1419
use OCP\Files\ISetupManager;
@@ -92,6 +97,9 @@ public function testNodeResourceShareActionIncreasePermission(): void {
9297
foreach ([$fileNode, $folderNode] as $node) {
9398
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(Constants::PERMISSION_READ | Constants::PERMISSION_SHARE | Constants::PERMISSION_UPDATE), []);
9499
$this->assertEquals('You cannot share "/' . $node->getName() . '" with more permission than you have yourself.', $event->isInteractionRestricted());
100+
101+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(null, [NodeReadSharePermissionType::class, ReshareSharePermissionType::class, NodeUpdateSharePermissionType::class]), []);
102+
$this->assertFalse($event->isInteractionRestricted());
95103
}
96104
}
97105

@@ -103,6 +111,9 @@ public function testNodeResourceShareActionIncreasePermissionFileDelete(): void
103111

104112
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(Constants::PERMISSION_READ | Constants::PERMISSION_SHARE | Constants::PERMISSION_DELETE), []);
105113
$this->assertEquals('File cannot be shared with delete permission.', $event->isInteractionRestricted());
114+
115+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(null, [NodeReadSharePermissionType::class, ReshareSharePermissionType::class, NodeDeleteSharePermissionType::class]), []);
116+
$this->assertFalse($event->isInteractionRestricted());
106117
}
107118

108119
public function testNodeResourceShareActionIncreasePermissionFileCreate(): void {
@@ -113,6 +124,9 @@ public function testNodeResourceShareActionIncreasePermissionFileCreate(): void
113124

114125
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(Constants::PERMISSION_READ | Constants::PERMISSION_SHARE | Constants::PERMISSION_CREATE), []);
115126
$this->assertEquals('File cannot be shared with create permission.', $event->isInteractionRestricted());
127+
128+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(null, [NodeReadSharePermissionType::class, ReshareSharePermissionType::class, NodeCreateSharePermissionType::class]), []);
129+
$this->assertFalse($event->isInteractionRestricted());
116130
}
117131

118132
public function testNodeResourceShareActionFileHasDeletePermission(): void {
@@ -123,6 +137,9 @@ public function testNodeResourceShareActionFileHasDeletePermission(): void {
123137

124138
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(Constants::PERMISSION_DELETE), []);
125139
$this->assertEquals('File cannot be shared with delete permission.', $event->isInteractionRestricted());
140+
141+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(null, [NodeDeleteSharePermissionType::class]), []);
142+
$this->assertFalse($event->isInteractionRestricted());
126143
}
127144

128145
public function testNodeResourceShareActionFileHasCreatePermission(): void {
@@ -133,6 +150,9 @@ public function testNodeResourceShareActionFileHasCreatePermission(): void {
133150

134151
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(Constants::PERMISSION_CREATE), []);
135152
$this->assertEquals('File cannot be shared with create permission.', $event->isInteractionRestricted());
153+
154+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [new NodeResource($node->getId(), $this->user->getUID(), $node)], new ShareAction(null, [NodeCreateSharePermissionType::class]), []);
155+
$this->assertFalse($event->isInteractionRestricted());
136156
}
137157

138158
/** @psalm-suppress DeprecatedMethod The configs are not migrated to IAppConfig, so using deprecated IConfig is required for now. */
@@ -157,8 +177,13 @@ public function testNodeResourceShareActionNoLinkEmailReceiverMissingReadPermiss
157177
new RoomReceiver(''),
158178
new UserReceiver(''),
159179
] as $receiver) {
160-
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [$resource], new ShareAction(Constants::PERMISSION_ALL & ~Constants::PERMISSION_READ), [$receiver]);
161-
$this->assertEquals('File share needs at least read permission.', $event->isInteractionRestricted());
180+
foreach ([
181+
new ShareAction(Constants::PERMISSION_ALL & ~Constants::PERMISSION_READ),
182+
new ShareAction(null, [NodeUpdateSharePermissionType::class, NodeCreateSharePermissionType::class, NodeDeleteSharePermissionType::class, ReshareSharePermissionType::class]),
183+
] as $action) {
184+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [$resource], $action, [$receiver]);
185+
$this->assertEquals('File share needs at least read permission.', $event->isInteractionRestricted());
186+
}
162187
}
163188

164189
$config->deleteAppValue('files_sharing', 'outgoing_server2server_group_share_enabled');
@@ -181,11 +206,14 @@ public function testNodeResourceShareActionLinkEmailReceiverPublicUploadDisabled
181206
new EmailReceiver('test@example.org'),
182207
] as $receiver) {
183208
foreach ([
184-
Constants::PERMISSION_CREATE,
185-
Constants::PERMISSION_UPDATE,
186-
Constants::PERMISSION_DELETE,
187-
] as $permissions) {
188-
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [$resource], new ShareAction($permissions), [$receiver]);
209+
new ShareAction(Constants::PERMISSION_CREATE),
210+
new ShareAction(null, [NodeCreateSharePermissionType::class]),
211+
new ShareAction(Constants::PERMISSION_UPDATE),
212+
new ShareAction(null, [NodeUpdateSharePermissionType::class]),
213+
new ShareAction(Constants::PERMISSION_DELETE),
214+
new ShareAction(null, [NodeDeleteSharePermissionType::class]),
215+
] as $action) {
216+
$event = new RestrictInteractionEvent($this->user->getUID(), $this->user, [$resource], $action, [$receiver]);
189217
$this->assertEquals('Public upload is not allowed.', $event->isInteractionRestricted());
190218
}
191219
}

lib/public/Interaction/Actions/ShareAction.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCP\AppFramework\Attribute\Consumable;
1313
use OCP\Constants;
1414
use OCP\Interaction\InteractionAction;
15+
use OCP\Sharing\Permission\ISharePermissionType;
1516

1617
/**
1718
* Used when a user wants to share a resource to a receiver.
@@ -26,6 +27,8 @@
2627
public function __construct(
2728
/** @var ?int-mask-of<Constants::PERMISSION_*> */
2829
public ?int $filesSharingPermissions = null,
30+
/** @var ?list<class-string<ISharePermissionType>> */
31+
public ?array $unifiedSharingPermissions = null,
2932
) {
3033
}
3134
}

0 commit comments

Comments
 (0)