Skip to content

Commit 332bad9

Browse files
committed
feat: perform share mount validation on share instead of on mount
Signed-off-by: Robin Appelman <[email protected]>
1 parent bbabf50 commit 332bad9

File tree

7 files changed

+235
-89
lines changed

7 files changed

+235
-89
lines changed

apps/files_sharing/composer/composer/autoload_classmap.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
7070
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
7171
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
72+
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => $baseDir . '/../lib/Listener/SharesUpdatedListener.php',
7273
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
7374
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => $baseDir . '/../lib/Listener/UserShareAcceptanceListener.php',
7475
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => $baseDir . '/../lib/Middleware/OCSShareAPIMiddleware.php',
@@ -94,6 +95,7 @@
9495
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
9596
'OCA\\Files_Sharing\\ShareBackend\\File' => $baseDir . '/../lib/ShareBackend/File.php',
9697
'OCA\\Files_Sharing\\ShareBackend\\Folder' => $baseDir . '/../lib/ShareBackend/Folder.php',
98+
'OCA\\Files_Sharing\\ShareTargetValidator' => $baseDir . '/../lib/ShareTargetValidator.php',
9799
'OCA\\Files_Sharing\\SharedMount' => $baseDir . '/../lib/SharedMount.php',
98100
'OCA\\Files_Sharing\\SharedStorage' => $baseDir . '/../lib/SharedStorage.php',
99101
'OCA\\Files_Sharing\\SharesReminderJob' => $baseDir . '/../lib/SharesReminderJob.php',

apps/files_sharing/composer/composer/autoload_static.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class ComposerStaticInitFiles_Sharing
8484
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
8585
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
8686
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
87+
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__ . '/..' . '/../lib/Listener/SharesUpdatedListener.php',
8788
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
8889
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
8990
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
@@ -109,6 +110,7 @@ class ComposerStaticInitFiles_Sharing
109110
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
110111
'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
111112
'OCA\\Files_Sharing\\ShareBackend\\Folder' => __DIR__ . '/..' . '/../lib/ShareBackend/Folder.php',
113+
'OCA\\Files_Sharing\\ShareTargetValidator' => __DIR__ . '/..' . '/../lib/ShareTargetValidator.php',
112114
'OCA\\Files_Sharing\\SharedMount' => __DIR__ . '/..' . '/../lib/SharedMount.php',
113115
'OCA\\Files_Sharing\\SharedStorage' => __DIR__ . '/..' . '/../lib/SharedStorage.php',
114116
'OCA\\Files_Sharing\\SharesReminderJob' => __DIR__ . '/..' . '/../lib/SharesReminderJob.php',

apps/files_sharing/lib/AppInfo/Application.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
2525
use OCA\Files_Sharing\Listener\LoadSidebarListener;
2626
use OCA\Files_Sharing\Listener\ShareInteractionListener;
27+
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
2728
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
2829
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
2930
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
@@ -49,9 +50,11 @@
4950
use OCP\Group\Events\GroupChangedEvent;
5051
use OCP\Group\Events\GroupDeletedEvent;
5152
use OCP\Group\Events\UserAddedEvent;
53+
use OCP\Group\Events\UserRemovedEvent;
5254
use OCP\IDBConnection;
5355
use OCP\IGroup;
5456
use OCP\Share\Events\ShareCreatedEvent;
57+
use OCP\Share\Events\ShareDeletedEvent;
5558
use OCP\User\Events\UserChangedEvent;
5659
use OCP\User\Events\UserDeletedEvent;
5760
use OCP\Util;
@@ -109,6 +112,12 @@ function () use ($c) {
109112
// File request auth
110113
$context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);
111114

115+
// Update mounts
116+
$context->registerEventListener(ShareCreatedEvent::class, SharesUpdatedListener::class);
117+
$context->registerEventListener(ShareDeletedEvent::class, SharesUpdatedListener::class);
118+
$context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
119+
$context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);
120+
112121
$context->registerConfigLexicon(ConfigLexicon::class);
113122
}
114123

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <[email protected]>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_Sharing\Listener;
10+
11+
use OC\User\LazyUser;
12+
use OCA\Files_Sharing\MountProvider;
13+
use OCA\Files_Sharing\ShareTargetValidator;
14+
use OCP\EventDispatcher\Event;
15+
use OCP\EventDispatcher\IEventListener;
16+
use OCP\Files\Config\ICachedMountInfo;
17+
use OCP\Files\Config\IUserMountCache;
18+
use OCP\Files\Storage\IStorageFactory;
19+
use OCP\Group\Events\UserAddedEvent;
20+
use OCP\Group\Events\UserRemovedEvent;
21+
use OCP\Share\IManager;
22+
use OCP\IUser;
23+
use OCP\Share\Events\ShareCreatedEvent;
24+
use OCP\Share\Events\ShareDeletedEvent;
25+
26+
/**
27+
* Listen to various events that can change what shares a user has access to
28+
*
29+
* @template-implements IEventListener<UserAddedEvent|UserRemovedEvent|ShareCreatedEvent|ShareDeletedEvent>
30+
*/
31+
class SharesUpdatedListener implements IEventListener {
32+
public function __construct(
33+
private readonly IManager $shareManager,
34+
private readonly IUserMountCache $userMountCache,
35+
private readonly MountProvider $shareMountProvider,
36+
private readonly IStorageFactory $storageFactory,
37+
private readonly ShareTargetValidator $shareTargetValidator,
38+
) {
39+
}
40+
41+
public function handle(Event $event): void {
42+
if ($event instanceof UserAddedEvent || $event instanceof UserRemovedEvent) {
43+
$this->updateForUser($event->getUser());
44+
}
45+
if ($event instanceof ShareCreatedEvent || $event instanceof ShareDeletedEvent) {
46+
foreach ($this->shareManager->getUsersForShare($event->getShare()) as $user) {
47+
$this->updateForUser($user);
48+
}
49+
}
50+
}
51+
52+
private function updateForUser(IUser $user): void {
53+
$allCachedMounts = $this->userMountCache->getMountsForUser($user);
54+
$cachedMounts = array_filter($allCachedMounts, fn (ICachedMountInfo $mount) => $mount->getMountProvider() === MountProvider::class);
55+
56+
$shares = $this->shareMountProvider->getSuperSharesForUser($user);
57+
58+
$foundUpdate = count($shares) !== count($cachedMounts);
59+
foreach ($shares as &$share) {
60+
[$parentShare, $groupedShares] = $share;
61+
$mountPoint = '/' . $user->getUID() . '/files/' . $parentShare->getTarget();
62+
$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
63+
if (!isset($cachedMounts[$mountKey])) {
64+
$foundUpdate = true;
65+
$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $allCachedMounts, $groupedShares);
66+
}
67+
}
68+
69+
if ($foundUpdate) {
70+
$mounts = $this->shareMountProvider->getMountsFromSuperShares($user, $shares, $this->storageFactory);
71+
$this->userMountCache->registerMounts($user, $mounts, [MountProvider::class]);
72+
}
73+
}
74+
}

apps/files_sharing/lib/MountProvider.php

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public function __construct(
5050
* @return IMountPoint[]
5151
*/
5252
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
53+
return $this->getMountsFromSuperShares($user, $this->getSuperSharesForUser($user), $loader);
54+
}
55+
56+
/**
57+
* @param IUser $user
58+
* @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
59+
*/
60+
public function getSuperSharesForUser(IUser $user): array {
5361
$userId = $user->getUID();
5462
$shares = array_merge(
5563
$this->shareManager->getSharedWith($userId, IShare::TYPE_USER, null, -1),
@@ -61,9 +69,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
6169
);
6270

6371
$shares = $this->filterShares($shares, $userId);
64-
$superShares = $this->buildSuperShares($shares, $user);
65-
66-
return $this->getMountsFromSuperShares($userId, $superShares, $loader, $user);
72+
return $this->buildSuperShares($shares, $user);
6773
}
6874

6975
/**
@@ -246,18 +252,18 @@ private function adjustTarget(
246252
}
247253
/**
248254
* @param string $userId
249-
* @param array $superShares
255+
* @param list<array{IShare, array<IShare>}> $superShares
250256
* @param IStorageFactory $loader
251257
* @param IUser $user
252258
* @return array
253259
* @throws Exception
254260
*/
255-
private function getMountsFromSuperShares(
256-
string $userId,
261+
public function getMountsFromSuperShares(
262+
IUser $user,
257263
array $superShares,
258264
IStorageFactory $loader,
259-
IUser $user,
260265
): array {
266+
$userId = $user->getUID();
261267
$allMounts = $this->mountManager->getAll();
262268
$mounts = [];
263269
$view = new View('/' . $userId . '/files');
@@ -290,7 +296,6 @@ private function getMountsFromSuperShares(
290296
$shareId = (int)$parentShare->getId();
291297
$mount = new SharedMount(
292298
'\OCA\Files_Sharing\SharedStorage',
293-
$allMounts,
294299
[
295300
'user' => $userId,
296301
// parent share
@@ -301,11 +306,8 @@ private function getMountsFromSuperShares(
301306
'sharingDisabledForUser' => $sharingDisabledForUser
302307
],
303308
$loader,
304-
$view,
305-
$foldersExistCache,
306309
$this->eventDispatcher,
307310
$user,
308-
$shareId <= $maxValidatedShare,
309311
);
310312

311313
$newMaxValidatedShare = max($shareId, $newMaxValidatedShare);
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2025 Robin Appelman <[email protected]>
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_Sharing;
10+
11+
use OC\Files\Filesystem;
12+
use OC\Files\SetupManager;
13+
use OC\Files\View;
14+
use OCP\Cache\CappedMemoryCache;
15+
use OCP\EventDispatcher\IEventDispatcher;
16+
use OCP\Files\Config\ICachedMountInfo;
17+
use OCP\Files\Mount\IMountManager;
18+
use OCP\Files\Mount\IMountPoint;
19+
use OCP\IUser;
20+
use OCP\Share\Events\VerifyMountPointEvent;
21+
use OCP\Share\IManager;
22+
use OCP\Share\IShare;
23+
24+
/**
25+
* Validate that mount target is valid
26+
*/
27+
class ShareTargetValidator {
28+
private CappedMemoryCache $folderExistsCache;
29+
30+
public function __construct(
31+
private readonly IManager $shareManager,
32+
private readonly IEventDispatcher $eventDispatcher,
33+
private readonly SetupManager $setupManager,
34+
private readonly IMountManager $mountManager,
35+
) {
36+
$this->folderExistsCache = new CappedMemoryCache();
37+
}
38+
39+
/**
40+
* check if the parent folder exists otherwise move the mount point up
41+
*
42+
* @param ICachedMountInfo[] $allCachedMounts
43+
* @param IShare[] $childShares
44+
* @return string
45+
*/
46+
public function verifyMountPoint(
47+
IUser $user,
48+
IShare &$share,
49+
array $allCachedMounts,
50+
array $childShares,
51+
): string {
52+
$mountPoint = basename($share->getTarget());
53+
$parent = dirname($share->getTarget());
54+
55+
/**
56+
* @psalm-suppress InternalClass
57+
* @psalm-suppress InternalMethod
58+
*/
59+
$recipientView = new View('/' . $user->getUID() . '/files');
60+
$event = new VerifyMountPointEvent($share, $recipientView, $parent);
61+
$this->eventDispatcher->dispatchTyped($event);
62+
$parent = $event->getParent();
63+
64+
/** @psalm-suppress InternalMethod */
65+
$absoluteParent = $recipientView->getAbsolutePath($parent);
66+
$this->setupManager->setupForPath($absoluteParent);
67+
$parentMount = $this->mountManager->find($absoluteParent);
68+
69+
$cached = $this->folderExistsCache->get($parent);
70+
if ($cached) {
71+
$parentExists = $cached;
72+
} else {
73+
$parentCache = $parentMount->getStorage()->getCache();
74+
$parentExists = $parentCache->inCache($parentMount->getInternalPath($absoluteParent));
75+
$this->folderExistsCache->set($parent, $parentExists);
76+
}
77+
if (!$parentExists) {
78+
$parent = Helper::getShareFolder($recipientView, $user->getUID());
79+
}
80+
81+
$newAbsoluteMountPoint = $this->generateUniqueTarget(
82+
Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
83+
$parentMount,
84+
$allCachedMounts,
85+
);
86+
87+
/** @psalm-suppress InternalMethod */
88+
$newMountPoint = $recipientView->getRelativePath($newAbsoluteMountPoint);
89+
if ($newMountPoint === null) {
90+
return $share->getTarget();
91+
}
92+
93+
if ($newMountPoint !== $share->getTarget()) {
94+
$this->updateFileTarget($user, $newMountPoint, $share, $childShares);
95+
}
96+
97+
return $newMountPoint;
98+
}
99+
100+
101+
/**
102+
* @param ICachedMountInfo[] $allCachedMounts
103+
*/
104+
private function generateUniqueTarget(string $absolutePath, IMountPoint $parentMount, array $allCachedMounts): string {
105+
$pathInfo = pathinfo($absolutePath);
106+
$ext = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
107+
$name = $pathInfo['filename'];
108+
$dir = $pathInfo['dirname'];
109+
110+
$i = 2;
111+
$parentCache = $parentMount->getStorage()->getCache();
112+
$internalPath = $parentMount->getInternalPath($absolutePath);
113+
while ($parentCache->inCache($internalPath) || isset($allCachedMounts[$absolutePath])) {
114+
$absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
115+
$internalPath = $parentMount->getInternalPath($absolutePath);
116+
$i++;
117+
}
118+
119+
return $absolutePath;
120+
}
121+
122+
/**
123+
* update fileTarget in the database if the mount point changed
124+
*
125+
* @param IShare[] $childShares
126+
*/
127+
private function updateFileTarget(IUser $user, string $newPath, IShare &$share, array $childShares) {
128+
$share->setTarget($newPath);
129+
130+
foreach ($childShares as $tmpShare) {
131+
$tmpShare->setTarget($newPath);
132+
$this->shareManager->moveShare($tmpShare, $user->getUID());
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)