Skip to content

Commit 5494f52

Browse files
committed
feat(Interaction): Implement all existing restrictions
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent 9558bfe commit 5494f52

25 files changed

Lines changed: 1051 additions & 0 deletions

apps/files/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
'OCA\\Files\\Listener\\NodeAddedToFavoriteListener' => $baseDir . '/../lib/Listener/NodeAddedToFavoriteListener.php',
7979
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => $baseDir . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
8080
'OCA\\Files\\Listener\\RenderReferenceEventListener' => $baseDir . '/../lib/Listener/RenderReferenceEventListener.php',
81+
'OCA\\Files\\Listener\\RestrictInteractionListener' => $baseDir . '/../lib/Listener/RestrictInteractionListener.php',
8182
'OCA\\Files\\Listener\\SyncLivePhotosListener' => $baseDir . '/../lib/Listener/SyncLivePhotosListener.php',
8283
'OCA\\Files\\Listener\\UserFirstTimeLoggedInListener' => $baseDir . '/../lib/Listener/UserFirstTimeLoggedInListener.php',
8384
'OCA\\Files\\Migration\\Version11301Date20191205150729' => $baseDir . '/../lib/Migration/Version11301Date20191205150729.php',

apps/files/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class ComposerStaticInitFiles
9393
'OCA\\Files\\Listener\\NodeAddedToFavoriteListener' => __DIR__ . '/..' . '/../lib/Listener/NodeAddedToFavoriteListener.php',
9494
'OCA\\Files\\Listener\\NodeRemovedFromFavoriteListener' => __DIR__ . '/..' . '/../lib/Listener/NodeRemovedFromFavoriteListener.php',
9595
'OCA\\Files\\Listener\\RenderReferenceEventListener' => __DIR__ . '/..' . '/../lib/Listener/RenderReferenceEventListener.php',
96+
'OCA\\Files\\Listener\\RestrictInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/RestrictInteractionListener.php',
9697
'OCA\\Files\\Listener\\SyncLivePhotosListener' => __DIR__ . '/..' . '/../lib/Listener/SyncLivePhotosListener.php',
9798
'OCA\\Files\\Listener\\UserFirstTimeLoggedInListener' => __DIR__ . '/..' . '/../lib/Listener/UserFirstTimeLoggedInListener.php',
9899
'OCA\\Files\\Migration\\Version11301Date20191205150729' => __DIR__ . '/..' . '/../lib/Migration/Version11301Date20191205150729.php',

apps/files/lib/AppInfo/Application.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use OCA\Files\Listener\NodeAddedToFavoriteListener;
2525
use OCA\Files\Listener\NodeRemovedFromFavoriteListener;
2626
use OCA\Files\Listener\RenderReferenceEventListener;
27+
use OCA\Files\Listener\RestrictInteractionListener;
2728
use OCA\Files\Listener\SyncLivePhotosListener;
2829
use OCA\Files\Listener\UserFirstTimeLoggedInListener;
2930
use OCA\Files\Notification\Notifier;
@@ -41,6 +42,7 @@
4142
use OCP\Files\Events\Node\NodeCopiedEvent;
4243
use OCP\Files\Events\NodeAddedToFavorite;
4344
use OCP\Files\Events\NodeRemovedFromFavorite;
45+
use OCP\Interaction\RestrictInteractionEvent;
4446
use OCP\User\Events\UserFirstTimeLoggedInEvent;
4547
use OCP\Util;
4648

@@ -79,6 +81,7 @@ public function register(IRegistrationContext $context): void {
7981

8082
$context->registerConfigLexicon(ConfigLexicon::class);
8183

84+
$context->registerEventListener(RestrictInteractionEvent::class, RestrictInteractionListener::class);
8285
}
8386

8487
#[\Override]
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OCA\Files\Listener;
11+
12+
use OCP\Constants;
13+
use OCP\EventDispatcher\Event;
14+
use OCP\EventDispatcher\IEventListener;
15+
use OCP\Files\IRootFolder;
16+
use OCP\Files\Mount\IMovableMount;
17+
use OCP\Files\Node;
18+
use OCP\Interaction\InteractionRestrictedException;
19+
use OCP\Interaction\Resources\NodeResource;
20+
use OCP\Interaction\RestrictInteractionEvent;
21+
use OCP\Server;
22+
23+
/**
24+
* @template-implements IEventListener<RestrictInteractionEvent>
25+
*/
26+
final class RestrictInteractionListener implements IEventListener {
27+
/**
28+
* @param RestrictInteractionEvent $event
29+
*/
30+
#[\Override]
31+
public function handle(Event $event): void {
32+
if ($event->resource instanceof NodeResource) {
33+
$nodePermissions = array_reduce(
34+
Server::get(IRootFolder::class)->getUserFolder($event->user->getUID())->getById($event->resource->getNode($event->user->getUID())->getId()),
35+
static fn (int $nodePermissions, Node $node): int => $nodePermissions | ($node->getInternalPath() === '' && !$node->getMountPoint() instanceof IMovableMount ? $node->getStorage()->getPermissions('') : $node->getPermissions()),
36+
0,
37+
);
38+
39+
if (($nodePermissions & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ) {
40+
throw new InteractionRestrictedException('No read permission on the node.');
41+
}
42+
}
43+
}
44+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OCA\Files\Tests\Listener;
11+
12+
use Exception;
13+
use OCP\Constants;
14+
use OCP\EventDispatcher\IEventDispatcher;
15+
use OCP\Files\IRootFolder;
16+
use OCP\Files\ISetupManager;
17+
use OCP\Interaction\Resources\NodeResource;
18+
use OCP\Interaction\RestrictInteractionEvent;
19+
use OCP\IUser;
20+
use OCP\IUserManager;
21+
use OCP\Server;
22+
use PHPUnit\Framework\Attributes\Group;
23+
use Test\TestCase;
24+
25+
#[Group('DB')]
26+
final class RestrictInteractionListenerTest extends TestCase {
27+
private IUser $user;
28+
29+
#[\Override]
30+
protected function setUp(): void {
31+
parent::setUp();
32+
$user = Server::get(IUserManager::class)->createUser('user', 'password');
33+
$this->assertNotFalse($user);
34+
$this->user = $user;
35+
36+
Server::get(ISetupManager::class)->setupForUser($user);
37+
}
38+
39+
#[\Override]
40+
protected function tearDown(): void {
41+
Server::get(ISetupManager::class)->tearDown();
42+
43+
$this->assertTrue($this->user->delete());
44+
45+
parent::tearDown();
46+
}
47+
48+
public function testNodeResourceShareActionMissingReadPermission(): void {
49+
$userFolder = Server::get(IRootFolder::class)->getUserFolder($this->user->getUID());
50+
51+
$fileNode = $userFolder->newFile('foo.txt', 'bar');
52+
$fileNode->getStorage()->getCache()->update($fileNode->getId(), ['permissions' => Constants::PERMISSION_ALL & ~Constants::PERMISSION_READ]);
53+
54+
$folderNode = $userFolder->newFolder('foo');
55+
$folderNode->getStorage()->getCache()->update($folderNode->getId(), ['permissions' => Constants::PERMISSION_ALL & ~Constants::PERMISSION_READ]);
56+
57+
foreach ([$fileNode, $folderNode] as $node) {
58+
$event = new RestrictInteractionEvent($this->user, new NodeResource($node->getId(), $node), null, null);
59+
60+
try {
61+
Server::get(IEventDispatcher::class)->dispatchTyped($event);
62+
$this->fail('Interaction not restricted.');
63+
} catch (Exception $e) {
64+
$this->assertEquals('No read permission on the node.', $e->getMessage());
65+
}
66+
}
67+
}
68+
}

apps/files_sharing/composer/composer/autoload_classmap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => $baseDir . '/../lib/Listener/LoadAdditionalListener.php',
7171
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
7272
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
73+
'OCA\\Files_Sharing\\Listener\\RestrictInteractionListener' => $baseDir . '/../lib/Listener/RestrictInteractionListener.php',
7374
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
7475
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => $baseDir . '/../lib/Listener/SharesUpdatedListener.php',
7576
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',

apps/files_sharing/composer/composer/autoload_static.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class ComposerStaticInitFiles_Sharing
8585
'OCA\\Files_Sharing\\Listener\\LoadAdditionalListener' => __DIR__ . '/..' . '/../lib/Listener/LoadAdditionalListener.php',
8686
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
8787
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
88+
'OCA\\Files_Sharing\\Listener\\RestrictInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/RestrictInteractionListener.php',
8889
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
8990
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__ . '/..' . '/../lib/Listener/SharesUpdatedListener.php',
9091
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',

apps/files_sharing/lib/AppInfo/Application.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use OCA\Files_Sharing\Listener\LoadAdditionalListener;
2525
use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
2626
use OCA\Files_Sharing\Listener\LoadSidebarListener;
27+
use OCA\Files_Sharing\Listener\RestrictInteractionListener;
2728
use OCA\Files_Sharing\Listener\ShareInteractionListener;
2829
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
2930
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
@@ -56,6 +57,7 @@
5657
use OCP\IConfig;
5758
use OCP\IDBConnection;
5859
use OCP\IGroup;
60+
use OCP\Interaction\RestrictInteractionEvent;
5961
use OCP\Share\Events\BeforeShareDeletedEvent;
6062
use OCP\Share\Events\ShareCreatedEvent;
6163
use OCP\Share\Events\ShareMovedEvent;
@@ -132,6 +134,8 @@ function () use ($c) {
132134
$context->registerEventListener(UserHomeSetupEvent::class, UserHomeSetupListener::class);
133135

134136
$context->registerConfigLexicon(ConfigLexicon::class);
137+
138+
$context->registerEventListener(RestrictInteractionEvent::class, RestrictInteractionListener::class);
135139
}
136140

137141
#[\Override]
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
/*
4+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OCA\Files_Sharing\Listener;
11+
12+
use OCP\Constants;
13+
use OCP\EventDispatcher\Event;
14+
use OCP\EventDispatcher\IEventListener;
15+
use OCP\Files\File;
16+
use OCP\Files\Folder;
17+
use OCP\Files\IRootFolder;
18+
use OCP\Files\Mount\IMovableMount;
19+
use OCP\Files\Node;
20+
use OCP\Interaction\Actions\ShareAction;
21+
use OCP\Interaction\InteractionRestrictedException;
22+
use OCP\Interaction\Receivers\EmailReceiver;
23+
use OCP\Interaction\Receivers\LinkReceiver;
24+
use OCP\Interaction\Resources\NodeResource;
25+
use OCP\Interaction\RestrictInteractionEvent;
26+
use OCP\Server;
27+
use OCP\Share\IManager;
28+
29+
/**
30+
* @template-implements IEventListener<RestrictInteractionEvent>
31+
*/
32+
final class RestrictInteractionListener implements IEventListener {
33+
/**
34+
* @param RestrictInteractionEvent $event
35+
*/
36+
#[\Override]
37+
public function handle(Event $event): void {
38+
if ($event->resource instanceof NodeResource && $event->action instanceof ShareAction) {
39+
$nodePermissions = array_reduce(
40+
Server::get(IRootFolder::class)->getUserFolder($event->user->getUID())->getById($event->resource->getNode($event->user->getUID())->getId()),
41+
static fn (int $nodePermissions, Node $node): int => $nodePermissions | ($node->getInternalPath() === '' && !$node->getMountPoint() instanceof IMovableMount ? $node->getStorage()->getPermissions('') : $node->getPermissions()),
42+
0,
43+
);
44+
45+
if (($nodePermissions & Constants::PERMISSION_SHARE) !== Constants::PERMISSION_SHARE) {
46+
throw new InteractionRestrictedException('No share permission on the node.');
47+
}
48+
49+
if ($event->action->filesSharingPermissions !== null) {
50+
if (($event->action->filesSharingPermissions & ~$nodePermissions) !== 0) {
51+
throw new InteractionRestrictedException('Cannot share node with more permissions than the node already has.');
52+
}
53+
54+
if ($event->resource->getNode($event->user->getUID()) instanceof File) {
55+
if (($event->action->filesSharingPermissions & Constants::PERMISSION_DELETE) === Constants::PERMISSION_DELETE) {
56+
throw new InteractionRestrictedException('Cannot share file node with delete permission.');
57+
}
58+
59+
if (($event->action->filesSharingPermissions & Constants::PERMISSION_CREATE) === Constants::PERMISSION_CREATE) {
60+
throw new InteractionRestrictedException('Cannot share file node with create permission.');
61+
}
62+
}
63+
64+
if (!$event->receiver instanceof LinkReceiver
65+
&& !$event->receiver instanceof EmailReceiver
66+
&& ($event->action->filesSharingPermissions & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ) {
67+
throw new InteractionRestrictedException('No read permission on the share.');
68+
}
69+
70+
if (($event->receiver instanceof LinkReceiver || $event->receiver instanceof EmailReceiver)
71+
&& $event->resource->getNode($event->user->getUID()) instanceof Folder
72+
&& ($event->action->filesSharingPermissions & (Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)) !== 0
73+
&& !Server::get(IManager::class)->shareApiLinkAllowPublicUpload()) {
74+
throw new InteractionRestrictedException('Public upload is not allowed.');
75+
}
76+
}
77+
}
78+
}
79+
}

0 commit comments

Comments
 (0)