Skip to content

Commit f28cfed

Browse files
committed
feat(Interaction): Implement all existing restrictions
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent 57be2ee commit f28cfed

25 files changed

Lines changed: 1177 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: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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\Interaction\InteractionRestrictedException;
16+
use OCP\Interaction\Resources\NodeResource;
17+
use OCP\Interaction\RestrictInteractionEvent;
18+
19+
/**
20+
* @template-implements IEventListener<RestrictInteractionEvent>
21+
*/
22+
final class RestrictInteractionListener implements IEventListener {
23+
/**
24+
* @param RestrictInteractionEvent $event
25+
*/
26+
#[\Override]
27+
public function handle(Event $event): void {
28+
if ($event->resource instanceof NodeResource && ($event->resource->getNodePermissions($event->user->getUID()) & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ) {
29+
throw new InteractionRestrictedException('No read permission on the node.');
30+
}
31+
}
32+
}
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: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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\Interaction\Actions\ShareAction;
18+
use OCP\Interaction\InteractionRestrictedException;
19+
use OCP\Interaction\Receivers\EmailReceiver;
20+
use OCP\Interaction\Receivers\LinkReceiver;
21+
use OCP\Interaction\Resources\NodeResource;
22+
use OCP\Interaction\RestrictInteractionEvent;
23+
use OCP\Share\IManager;
24+
25+
/**
26+
* @template-implements IEventListener<RestrictInteractionEvent>
27+
*/
28+
final readonly class RestrictInteractionListener implements IEventListener {
29+
30+
public function __construct(
31+
private IManager $manager,
32+
) {
33+
}
34+
35+
/**
36+
* @param RestrictInteractionEvent $event
37+
*/
38+
#[\Override]
39+
public function handle(Event $event): void {
40+
if ($event->resource instanceof NodeResource && $event->action instanceof ShareAction) {
41+
$nodePermissions = $event->resource->getNodePermissions($event->user->getUID());
42+
43+
if (($nodePermissions & Constants::PERMISSION_SHARE) !== Constants::PERMISSION_SHARE) {
44+
throw new InteractionRestrictedException('No share permission on the node.');
45+
}
46+
47+
if ($event->action->filesSharingPermissions !== null) {
48+
if (($event->action->filesSharingPermissions & ~$nodePermissions) !== 0) {
49+
throw new InteractionRestrictedException('Cannot share node with more permissions than the node already has.');
50+
}
51+
52+
if ($event->resource->getNode($event->user->getUID()) instanceof File) {
53+
if (($event->action->filesSharingPermissions & Constants::PERMISSION_DELETE) === Constants::PERMISSION_DELETE) {
54+
throw new InteractionRestrictedException('Cannot share file node with delete permission.');
55+
}
56+
57+
if (($event->action->filesSharingPermissions & Constants::PERMISSION_CREATE) === Constants::PERMISSION_CREATE) {
58+
throw new InteractionRestrictedException('Cannot share file node with create permission.');
59+
}
60+
}
61+
62+
if (!$event->receiver instanceof LinkReceiver
63+
&& !$event->receiver instanceof EmailReceiver
64+
&& ($event->action->filesSharingPermissions & Constants::PERMISSION_READ) !== Constants::PERMISSION_READ) {
65+
throw new InteractionRestrictedException('No read permission on the share.');
66+
}
67+
68+
if (($event->receiver instanceof LinkReceiver || $event->receiver instanceof EmailReceiver)
69+
&& $event->resource->getNode($event->user->getUID()) instanceof Folder
70+
&& ($event->action->filesSharingPermissions & (Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE)) !== 0
71+
&& !$this->manager->shareApiLinkAllowPublicUpload()) {
72+
throw new InteractionRestrictedException('Public upload is not allowed.');
73+
}
74+
}
75+
}
76+
}
77+
}

0 commit comments

Comments
 (0)