Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/files_sharing/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => $baseDir . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => $baseDir . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => $baseDir . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => $baseDir . '/../lib/Listener/SharesUpdatedListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => $baseDir . '/../lib/Listener/UserAddedToGroupListener.php',
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => $baseDir . '/../lib/Listener/UserShareAcceptanceListener.php',
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => $baseDir . '/../lib/Middleware/OCSShareAPIMiddleware.php',
Expand All @@ -94,6 +95,7 @@
'OCA\\Files_Sharing\\Settings\\Personal' => $baseDir . '/../lib/Settings/Personal.php',
'OCA\\Files_Sharing\\ShareBackend\\File' => $baseDir . '/../lib/ShareBackend/File.php',
'OCA\\Files_Sharing\\ShareBackend\\Folder' => $baseDir . '/../lib/ShareBackend/Folder.php',
'OCA\\Files_Sharing\\ShareTargetValidator' => $baseDir . '/../lib/ShareTargetValidator.php',
'OCA\\Files_Sharing\\SharedMount' => $baseDir . '/../lib/SharedMount.php',
'OCA\\Files_Sharing\\SharedStorage' => $baseDir . '/../lib/SharedStorage.php',
'OCA\\Files_Sharing\\SharesReminderJob' => $baseDir . '/../lib/SharesReminderJob.php',
Expand Down
2 changes: 2 additions & 0 deletions apps/files_sharing/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Listener\\LoadPublicFileRequestAuthListener' => __DIR__ . '/..' . '/../lib/Listener/LoadPublicFileRequestAuthListener.php',
'OCA\\Files_Sharing\\Listener\\LoadSidebarListener' => __DIR__ . '/..' . '/../lib/Listener/LoadSidebarListener.php',
'OCA\\Files_Sharing\\Listener\\ShareInteractionListener' => __DIR__ . '/..' . '/../lib/Listener/ShareInteractionListener.php',
'OCA\\Files_Sharing\\Listener\\SharesUpdatedListener' => __DIR__ . '/..' . '/../lib/Listener/SharesUpdatedListener.php',
'OCA\\Files_Sharing\\Listener\\UserAddedToGroupListener' => __DIR__ . '/..' . '/../lib/Listener/UserAddedToGroupListener.php',
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
Expand All @@ -109,6 +110,7 @@ class ComposerStaticInitFiles_Sharing
'OCA\\Files_Sharing\\Settings\\Personal' => __DIR__ . '/..' . '/../lib/Settings/Personal.php',
'OCA\\Files_Sharing\\ShareBackend\\File' => __DIR__ . '/..' . '/../lib/ShareBackend/File.php',
'OCA\\Files_Sharing\\ShareBackend\\Folder' => __DIR__ . '/..' . '/../lib/ShareBackend/Folder.php',
'OCA\\Files_Sharing\\ShareTargetValidator' => __DIR__ . '/..' . '/../lib/ShareTargetValidator.php',
'OCA\\Files_Sharing\\SharedMount' => __DIR__ . '/..' . '/../lib/SharedMount.php',
'OCA\\Files_Sharing\\SharedStorage' => __DIR__ . '/..' . '/../lib/SharedStorage.php',
'OCA\\Files_Sharing\\SharesReminderJob' => __DIR__ . '/..' . '/../lib/SharesReminderJob.php',
Expand Down
9 changes: 9 additions & 0 deletions apps/files_sharing/lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use OCA\Files_Sharing\Listener\LoadPublicFileRequestAuthListener;
use OCA\Files_Sharing\Listener\LoadSidebarListener;
use OCA\Files_Sharing\Listener\ShareInteractionListener;
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
use OCA\Files_Sharing\Listener\UserAddedToGroupListener;
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
Expand All @@ -49,9 +50,11 @@
use OCP\Group\Events\GroupChangedEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IDBConnection;
use OCP\IGroup;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use OCP\User\Events\UserChangedEvent;
use OCP\User\Events\UserDeletedEvent;
use OCP\Util;
Expand Down Expand Up @@ -109,6 +112,12 @@ function () use ($c) {
// File request auth
$context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);

// Update mounts
$context->registerEventListener(ShareCreatedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(ShareDeletedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
$context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);

$context->registerConfigLexicon(ConfigLexicon::class);
}

Expand Down
71 changes: 71 additions & 0 deletions apps/files_sharing/lib/Listener/SharesUpdatedListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Robin Appelman <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Files_Sharing\Listener;

use OCA\Files_Sharing\MountProvider;
use OCA\Files_Sharing\ShareTargetValidator;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Config\IUserMountCache;
use OCP\Files\Storage\IStorageFactory;
use OCP\Group\Events\UserAddedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IUser;
use OCP\Share\Events\ShareCreatedEvent;
use OCP\Share\Events\ShareDeletedEvent;
use OCP\Share\IManager;

/**
* Listen to various events that can change what shares a user has access to
*
* @template-implements IEventListener<UserAddedEvent|UserRemovedEvent|ShareCreatedEvent|ShareDeletedEvent>
*/
class SharesUpdatedListener implements IEventListener {
public function __construct(
private readonly IManager $shareManager,
private readonly IUserMountCache $userMountCache,
private readonly MountProvider $shareMountProvider,
private readonly IStorageFactory $storageFactory,
private readonly ShareTargetValidator $shareTargetValidator,
) {
}

public function handle(Event $event): void {
if ($event instanceof UserAddedEvent || $event instanceof UserRemovedEvent) {
$this->updateForUser($event->getUser());
}
if ($event instanceof ShareCreatedEvent || $event instanceof ShareDeletedEvent) {
foreach ($this->shareManager->getUsersForShare($event->getShare()) as $user) {
$this->updateForUser($user);
}
}
}

private function updateForUser(IUser $user): void {
$cachedMounts = $this->userMountCache->getMountsForUser($user);

$shares = $this->shareMountProvider->getSuperSharesForUser($user);

$foundUpdate = count($shares) !== count($cachedMounts);
foreach ($shares as &$share) {
[$parentShare, $groupedShares] = $share;
$mountPoint = '/' . $user->getUID() . '/files/' . $parentShare->getTarget();
$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
if (!isset($cachedMounts[$mountKey])) {
$foundUpdate = true;
$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $cachedMounts, $groupedShares);
}
}

if ($foundUpdate) {
$mounts = $this->shareMountProvider->getMountsFromSuperShares($user, $shares, $this->storageFactory);
$this->userMountCache->registerMounts($user, $mounts, [MountProvider::class]);
}
}
}
24 changes: 13 additions & 11 deletions apps/files_sharing/lib/MountProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@ public function __construct(
* @return IMountPoint[]
*/
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
return $this->getMountsFromSuperShares($user, $this->getSuperSharesForUser($user), $loader);
}

/**
* @param IUser $user
* @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
*/
public function getSuperSharesForUser(IUser $user): array {
$userId = $user->getUID();
$shares = array_merge(
$this->shareManager->getSharedWith($userId, IShare::TYPE_USER, null, -1),
Expand All @@ -61,9 +69,7 @@ public function getMountsForUser(IUser $user, IStorageFactory $loader) {
);

$shares = $this->filterShares($shares, $userId);
$superShares = $this->buildSuperShares($shares, $user);

return $this->getMountsFromSuperShares($userId, $superShares, $loader, $user);
return $this->buildSuperShares($shares, $user);
}

/**
Expand Down Expand Up @@ -246,18 +252,18 @@ private function adjustTarget(
}
/**
* @param string $userId
* @param array $superShares
* @param list<array{IShare, array<IShare>}> $superShares
* @param IStorageFactory $loader
* @param IUser $user
* @return array
* @throws Exception
*/
private function getMountsFromSuperShares(
string $userId,
public function getMountsFromSuperShares(
IUser $user,
array $superShares,
IStorageFactory $loader,
IUser $user,
): array {
$userId = $user->getUID();
$allMounts = $this->mountManager->getAll();
$mounts = [];
$view = new View('/' . $userId . '/files');
Expand Down Expand Up @@ -290,7 +296,6 @@ private function getMountsFromSuperShares(
$shareId = (int)$parentShare->getId();
$mount = new SharedMount(
'\OCA\Files_Sharing\SharedStorage',
$allMounts,
[
'user' => $userId,
// parent share
Expand All @@ -301,11 +306,8 @@ private function getMountsFromSuperShares(
'sharingDisabledForUser' => $sharingDisabledForUser
],
$loader,
$view,
$foldersExistCache,
$this->eventDispatcher,
$user,
$shareId <= $maxValidatedShare,
);

$newMaxValidatedShare = max($shareId, $newMaxValidatedShare);
Expand Down
141 changes: 141 additions & 0 deletions apps/files_sharing/lib/ShareTargetValidator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Robin Appelman <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Files_Sharing;

use OC\Files\Filesystem;
use OC\Files\SetupManager;
use OC\Files\View;
use OCP\Cache\CappedMemoryCache;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Config\ICachedMountInfo;
use OCP\Files\Mount\IMountManager;
use OCP\Files\Mount\IMountPoint;
use OCP\IUser;
use OCP\Share\Events\VerifyMountPointEvent;
use OCP\Share\IManager;
use OCP\Share\IShare;

/**
* Validate that mount target is valid
*/
class ShareTargetValidator {
private CappedMemoryCache $folderExistsCache;

public function __construct(
private readonly IManager $shareManager,
private readonly IEventDispatcher $eventDispatcher,
private readonly SetupManager $setupManager,
private readonly IMountManager $mountManager,
) {
$this->folderExistsCache = new CappedMemoryCache();
}

private function getViewForUser(IUser $user): View {
/**
* @psalm-suppress InternalClass
* @psalm-suppress InternalMethod
*/
return new View('/' . $user->getUID() . '/files');
}

/**
* check if the parent folder exists otherwise move the mount point up
*
* @param ICachedMountInfo[] $allCachedMounts
* @param IShare[] $childShares
* @return string
*/
public function verifyMountPoint(
IUser $user,
IShare &$share,
array $allCachedMounts,
array $childShares,
): string {
$mountPoint = basename($share->getTarget());
$parent = dirname($share->getTarget());

$recipientView = $this->getViewForUser($user);
$event = new VerifyMountPointEvent($share, $recipientView, $parent);
$this->eventDispatcher->dispatchTyped($event);
$parent = $event->getParent();

/** @psalm-suppress InternalMethod */
$absoluteParent = $recipientView->getAbsolutePath($parent);
$this->setupManager->setupForPath($absoluteParent);
$parentMount = $this->mountManager->find($absoluteParent);

$cached = $this->folderExistsCache->get($parent);
if ($cached) {
$parentExists = $cached;
} else {
$parentCache = $parentMount->getStorage()->getCache();
$parentExists = $parentCache->inCache($parentMount->getInternalPath($absoluteParent));
$this->folderExistsCache->set($parent, $parentExists);
}
if (!$parentExists) {
$parent = Helper::getShareFolder($recipientView, $user->getUID());
/** @psalm-suppress InternalMethod */
$absoluteParent = $recipientView->getAbsolutePath($parent);
}

$newAbsoluteMountPoint = $this->generateUniqueTarget(
Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
$parentMount,
$allCachedMounts,
);

/** @psalm-suppress InternalMethod */
$newMountPoint = $recipientView->getRelativePath($newAbsoluteMountPoint);
if ($newMountPoint === null) {
return $share->getTarget();
}

if ($newMountPoint !== $share->getTarget()) {
$this->updateFileTarget($user, $newMountPoint, $share, $childShares);
}

return $newMountPoint;
}


/**
* @param ICachedMountInfo[] $allCachedMounts
*/
private function generateUniqueTarget(string $absolutePath, IMountPoint $parentMount, array $allCachedMounts): string {
$pathInfo = pathinfo($absolutePath);
$ext = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
$name = $pathInfo['filename'];
$dir = $pathInfo['dirname'];

$i = 2;
$parentCache = $parentMount->getStorage()->getCache();
$internalPath = $parentMount->getInternalPath($absolutePath);
while ($parentCache->inCache($internalPath) || isset($allCachedMounts[$absolutePath . '/'])) {
$absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
$internalPath = $parentMount->getInternalPath($absolutePath);
$i++;
}

return $absolutePath;
}

/**
* update fileTarget in the database if the mount point changed
*
* @param IShare[] $childShares
*/
private function updateFileTarget(IUser $user, string $newPath, IShare &$share, array $childShares) {
$share->setTarget($newPath);

foreach ($childShares as $tmpShare) {
$tmpShare->setTarget($newPath);
$this->shareManager->moveShare($tmpShare, $user->getUID());
}
}
}
Loading
Loading