diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index 48f197f9bf937..709e46fe1722a 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -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', @@ -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', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index 110a64fb3acfa..5966ee54b9920 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -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', @@ -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', diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 8ddb3afaf33aa..1bdcfab8e4d88 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -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; @@ -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; @@ -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); } diff --git a/apps/files_sharing/lib/Listener/SharesUpdatedListener.php b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php new file mode 100644 index 0000000000000..4ea8b1ad8f34e --- /dev/null +++ b/apps/files_sharing/lib/Listener/SharesUpdatedListener.php @@ -0,0 +1,71 @@ + + * 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 + */ +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]); + } + } +} diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php index a2ca0076124aa..1d54dd4556923 100644 --- a/apps/files_sharing/lib/MountProvider.php +++ b/apps/files_sharing/lib/MountProvider.php @@ -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}> 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), @@ -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); } /** @@ -246,18 +252,18 @@ private function adjustTarget( } /** * @param string $userId - * @param array $superShares + * @param list}> $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'); @@ -290,7 +296,6 @@ private function getMountsFromSuperShares( $shareId = (int)$parentShare->getId(); $mount = new SharedMount( '\OCA\Files_Sharing\SharedStorage', - $allMounts, [ 'user' => $userId, // parent share @@ -301,11 +306,8 @@ private function getMountsFromSuperShares( 'sharingDisabledForUser' => $sharingDisabledForUser ], $loader, - $view, - $foldersExistCache, $this->eventDispatcher, $user, - $shareId <= $maxValidatedShare, ); $newMaxValidatedShare = max($shareId, $newMaxValidatedShare); diff --git a/apps/files_sharing/lib/ShareTargetValidator.php b/apps/files_sharing/lib/ShareTargetValidator.php new file mode 100644 index 0000000000000..cd85e69c21e13 --- /dev/null +++ b/apps/files_sharing/lib/ShareTargetValidator.php @@ -0,0 +1,141 @@ + + * 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()); + } + } +} diff --git a/apps/files_sharing/lib/SharedMount.php b/apps/files_sharing/lib/SharedMount.php index 7dc95533d7497..8759272eb60b5 100644 --- a/apps/files_sharing/lib/SharedMount.php +++ b/apps/files_sharing/lib/SharedMount.php @@ -11,16 +11,13 @@ use OC\Files\Filesystem; use OC\Files\Mount\MountPoint; use OC\Files\Mount\MoveableMount; -use OC\Files\View; use OCA\Files_Sharing\Exceptions\BrokenPath; -use OCP\Cache\CappedMemoryCache; use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\Storage\IStorageFactory; use OCP\IDBConnection; use OCP\IUser; use OCP\Server; -use OCP\Share\Events\VerifyMountPointEvent; use OCP\Share\IShare; use Psr\Log\LoggerInterface; @@ -41,73 +38,19 @@ class SharedMount extends MountPoint implements MoveableMount, ISharedMountPoint public function __construct( $storage, - array $mountpoints, $arguments, IStorageFactory $loader, - private View $recipientView, - CappedMemoryCache $folderExistCache, private IEventDispatcher $eventDispatcher, private IUser $user, - bool $alreadyVerified, ) { $this->superShare = $arguments['superShare']; $this->groupedShares = $arguments['groupedShares']; $absMountPoint = '/' . $user->getUID() . '/files/' . trim($this->superShare->getTarget(), '/') . '/'; - // after the mountpoint is verified for the first time, only new mountpoints (e.g. groupfolders can overwrite the target) - if (!$alreadyVerified || isset($mountpoints[$absMountPoint])) { - $newMountPoint = $this->verifyMountPoint($this->superShare, $mountpoints, $folderExistCache); - $absMountPoint = '/' . $user->getUID() . '/files/' . trim($newMountPoint, '/') . '/'; - } - parent::__construct($storage, $absMountPoint, $arguments, $loader, null, null, MountProvider::class); } - /** - * check if the parent folder exists otherwise move the mount point up - * - * @param IShare $share - * @param SharedMount[] $mountpoints - * @param CappedMemoryCache $folderExistCache - * @return string - */ - private function verifyMountPoint( - IShare $share, - array $mountpoints, - CappedMemoryCache $folderExistCache, - ) { - $mountPoint = basename($share->getTarget()); - $parent = dirname($share->getTarget()); - - $event = new VerifyMountPointEvent($share, $this->recipientView, $parent); - $this->eventDispatcher->dispatchTyped($event); - $parent = $event->getParent(); - - $cached = $folderExistCache->get($parent); - if ($cached) { - $parentExists = $cached; - } else { - $parentExists = $this->recipientView->is_dir($parent); - $folderExistCache->set($parent, $parentExists); - } - if (!$parentExists) { - $parent = Helper::getShareFolder($this->recipientView, $this->user->getUID()); - } - - $newMountPoint = $this->generateUniqueTarget( - Filesystem::normalizePath($parent . '/' . $mountPoint), - $this->recipientView, - $mountpoints - ); - - if ($newMountPoint !== $share->getTarget()) { - $this->updateFileTarget($newMountPoint, $share); - } - - return $newMountPoint; - } - /** * update fileTarget in the database if the mount point changed * @@ -126,30 +69,6 @@ private function updateFileTarget($newPath, &$share) { $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->user)); } - - /** - * @param string $path - * @param View $view - * @param SharedMount[] $mountpoints - * @return mixed - */ - private function generateUniqueTarget($path, $view, array $mountpoints) { - $pathinfo = pathinfo($path); - $ext = isset($pathinfo['extension']) ? '.' . $pathinfo['extension'] : ''; - $name = $pathinfo['filename']; - $dir = $pathinfo['dirname']; - - $i = 2; - $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; - while ($view->file_exists($path) || isset($mountpoints[$absolutePath])) { - $path = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext); - $absolutePath = $this->recipientView->getAbsolutePath($path) . '/'; - $i++; - } - - return $path; - } - /** * Format a path to be relative to the /user/files/ directory * @@ -267,4 +186,8 @@ public function getNumericStorageId() { public function getMountType() { return 'shared'; } + + public function getUser(): IUser { + return $this->user; + } } diff --git a/apps/files_sharing/tests/ShareTargetValidatorTest.php b/apps/files_sharing/tests/ShareTargetValidatorTest.php new file mode 100644 index 0000000000000..0df52688a09ed --- /dev/null +++ b/apps/files_sharing/tests/ShareTargetValidatorTest.php @@ -0,0 +1,139 @@ + + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\Files_Sharing\Tests; + +use OCA\Files_Sharing\ShareTargetValidator; +use OCP\Constants; +use OCP\Files\Config\ICachedMountInfo; +use OCP\IUser; +use OCP\Server; +use OCP\Share\IShare; + +#[\PHPUnit\Framework\Attributes\Group('DB')] +class ShareTargetValidatorTest extends TestCase { + private ShareTargetValidator $targetValidator; + + private IUser $user2; + + protected function setUp(): void { + parent::setUp(); + + $this->folder = '/folder_share_storage_test'; + $this->folder2 = '/folder_share_storage_test2'; + + $this->filename = '/share-api-storage.txt'; + + + $this->view->mkdir($this->folder); + $this->view->mkdir($this->folder2); + + // save file with content + $this->view->file_put_contents($this->filename, 'root file'); + $this->view->file_put_contents($this->folder . $this->filename, 'file in subfolder'); + $this->view->file_put_contents($this->folder2 . $this->filename, 'file in subfolder2'); + + $this->targetValidator = Server::get(ShareTargetValidator::class); + $this->user2 = $this->createMock(IUser::class); + $this->user2->method('getUID') + ->willReturn(self::TEST_FILES_SHARING_API_USER2); + } + + + /** + * test if the mount point moves up if the parent folder no longer exists + */ + public function testShareMountLoseParentFolder(): void { + // share to user + $share = $this->share( + IShare::TYPE_USER, + $this->folder, + self::TEST_FILES_SHARING_API_USER1, + self::TEST_FILES_SHARING_API_USER2, + Constants::PERMISSION_ALL); + $this->shareManager->acceptShare($share, self::TEST_FILES_SHARING_API_USER2); + + $share->setTarget('/foo/bar' . $this->folder); + $this->shareManager->moveShare($share, self::TEST_FILES_SHARING_API_USER2); + + $share = $this->shareManager->getShareById($share->getFullId()); + $this->assertSame('/foo/bar' . $this->folder, $share->getTarget()); + + $this->targetValidator->verifyMountPoint($this->user2, $share, [], [$share]); + + $share = $this->shareManager->getShareById($share->getFullId()); + $this->assertSame($this->folder, $share->getTarget()); + + //cleanup + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + $this->shareManager->deleteShare($share); + $this->view->unlink($this->folder); + } + + /** + * test if the mount point gets renamed if a folder exists at the target + */ + public function testShareMountOverFolder(): void { + self::loginHelper(self::TEST_FILES_SHARING_API_USER2); + $this->view2->mkdir('bar'); + + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + + // share to user + $share = $this->share( + IShare::TYPE_USER, + $this->folder, + self::TEST_FILES_SHARING_API_USER1, + self::TEST_FILES_SHARING_API_USER2, + Constants::PERMISSION_ALL); + $this->shareManager->acceptShare($share, self::TEST_FILES_SHARING_API_USER2); + + $share->setTarget('/bar'); + $this->shareManager->moveShare($share, self::TEST_FILES_SHARING_API_USER2); + + $share = $this->shareManager->getShareById($share->getFullId()); + + $this->targetValidator->verifyMountPoint($this->user2, $share, [], [$share]); + + $share = $this->shareManager->getShareById($share->getFullId()); + $this->assertSame('/bar (2)', $share->getTarget()); + + //cleanup + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + $this->shareManager->deleteShare($share); + $this->view->unlink($this->folder); + } + + /** + * test if the mount point gets renamed if another share exists at the target + */ + public function testShareMountOverShare(): void { + // share to user + $share2 = $this->share( + IShare::TYPE_USER, + $this->folder2, + self::TEST_FILES_SHARING_API_USER1, + self::TEST_FILES_SHARING_API_USER2, + Constants::PERMISSION_ALL); + $this->shareManager->acceptShare($share2, self::TEST_FILES_SHARING_API_USER2); + + $conflictingMount = $this->createMock(ICachedMountInfo::class); + $this->targetValidator->verifyMountPoint($this->user2, $share2, [ + '/' . $this->user2->getUID() . '/files' . $this->folder2 . '/' => $conflictingMount + ], [$share2]); + + $share2 = $this->shareManager->getShareById($share2->getFullId()); + + $this->assertSame("{$this->folder2} (2)", $share2->getTarget()); + + //cleanup + self::loginHelper(self::TEST_FILES_SHARING_API_USER1); + $this->shareManager->deleteShare($share2); + $this->view->unlink($this->folder); + } +} diff --git a/apps/files_sharing/tests/SharedMountTest.php b/apps/files_sharing/tests/SharedMountTest.php index f70dae065d234..b10c2b7eb4647 100644 --- a/apps/files_sharing/tests/SharedMountTest.php +++ b/apps/files_sharing/tests/SharedMountTest.php @@ -8,12 +8,8 @@ namespace OCA\Files_Sharing\Tests; use OC\Files\Filesystem; -use OC\Files\View; -use OC\Memcache\ArrayCache; -use OCA\Files_Sharing\MountProvider; use OCA\Files_Sharing\SharedMount; use OCP\Constants; -use OCP\ICacheFactory; use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IUserManager; @@ -25,14 +21,10 @@ */ #[\PHPUnit\Framework\Attributes\Group('SLOWDB')] class SharedMountTest extends TestCase { + private IGroupManager $groupManager; + private IUserManager $userManager; - /** @var IGroupManager */ - private $groupManager; - - /** @var IUserManager */ - private $userManager; - - private $folder2; + private string $folder2; protected function setUp(): void { parent::setUp(); @@ -68,78 +60,6 @@ protected function tearDown(): void { parent::tearDown(); } - /** - * test if the mount point moves up if the parent folder no longer exists - */ - public function testShareMountLoseParentFolder(): void { - - // share to user - $share = $this->share( - IShare::TYPE_USER, - $this->folder, - self::TEST_FILES_SHARING_API_USER1, - self::TEST_FILES_SHARING_API_USER2, - Constants::PERMISSION_ALL); - $this->shareManager->acceptShare($share, self::TEST_FILES_SHARING_API_USER2); - - $share->setTarget('/foo/bar' . $this->folder); - $this->shareManager->moveShare($share, self::TEST_FILES_SHARING_API_USER2); - - $share = $this->shareManager->getShareById($share->getFullId()); - $this->assertSame('/foo/bar' . $this->folder, $share->getTarget()); - - self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - // share should have moved up - - $share = $this->shareManager->getShareById($share->getFullId()); - $this->assertSame($this->folder, $share->getTarget()); - - //cleanup - self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - $this->shareManager->deleteShare($share); - $this->view->unlink($this->folder); - } - - public function testDeleteParentOfMountPoint(): void { - // share to user - $share = $this->share( - IShare::TYPE_USER, - $this->folder, - self::TEST_FILES_SHARING_API_USER1, - self::TEST_FILES_SHARING_API_USER2, - Constants::PERMISSION_ALL - ); - - self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - $user2View = new View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files'); - $this->assertTrue($user2View->file_exists($this->folder)); - - // create a local folder - $result = $user2View->mkdir('localfolder'); - $this->assertTrue($result); - - // move mount point to local folder - $result = $user2View->rename($this->folder, '/localfolder/' . $this->folder); - $this->assertTrue($result); - - // mount point in the root folder should no longer exist - $this->assertFalse($user2View->is_dir($this->folder)); - - // delete the local folder - $result = $user2View->unlink('/localfolder'); - $this->assertTrue($result); - - //enforce reload of the mount points - self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - - //mount point should be back at the root - $this->assertTrue($user2View->is_dir($this->folder)); - - //cleanup - self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - $this->view->unlink($this->folder); - } - public function testMoveSharedFile(): void { $share = $this->share( IShare::TYPE_USER, @@ -313,111 +233,6 @@ public function testPermissionUpgradeOnUserDeletedGroupShare(): void { $testGroup->removeUser($user2); $testGroup->removeUser($user3); } - - /** - * test if the mount point gets renamed if a folder exists at the target - */ - public function testShareMountOverFolder(): void { - self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - $this->view2->mkdir('bar'); - - self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - - // share to user - $share = $this->share( - IShare::TYPE_USER, - $this->folder, - self::TEST_FILES_SHARING_API_USER1, - self::TEST_FILES_SHARING_API_USER2, - Constants::PERMISSION_ALL); - $this->shareManager->acceptShare($share, self::TEST_FILES_SHARING_API_USER2); - - $share->setTarget('/bar'); - $this->shareManager->moveShare($share, self::TEST_FILES_SHARING_API_USER2); - - $share = $this->shareManager->getShareById($share->getFullId()); - - self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - // share should have been moved - - $share = $this->shareManager->getShareById($share->getFullId()); - $this->assertSame('/bar (2)', $share->getTarget()); - - //cleanup - self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - $this->shareManager->deleteShare($share); - $this->view->unlink($this->folder); - } - - /** - * test if the mount point gets renamed if another share exists at the target - */ - public function testShareMountOverShare(): void { - // create a shared cache - $caches = []; - $cacheFactory = $this->createMock(ICacheFactory::class); - $cacheFactory->method('createLocal') - ->willReturnCallback(function (string $prefix) use (&$caches) { - if (!isset($caches[$prefix])) { - $caches[$prefix] = new ArrayCache($prefix); - } - return $caches[$prefix]; - }); - $cacheFactory->method('createDistributed') - ->willReturnCallback(function (string $prefix) use (&$caches) { - if (!isset($caches[$prefix])) { - $caches[$prefix] = new ArrayCache($prefix); - } - return $caches[$prefix]; - }); - - // hack to overwrite the cache factory, we can't use the proper "overwriteService" since the mount provider is created before this test is called - $mountProvider = Server::get(MountProvider::class); - $reflectionClass = new \ReflectionClass($mountProvider); - $reflectionCacheFactory = $reflectionClass->getProperty('cacheFactory'); - $reflectionCacheFactory->setValue($mountProvider, $cacheFactory); - - // share to user - $share = $this->share( - IShare::TYPE_USER, - $this->folder, - self::TEST_FILES_SHARING_API_USER1, - self::TEST_FILES_SHARING_API_USER2, - Constants::PERMISSION_ALL); - $this->shareManager->acceptShare($share, self::TEST_FILES_SHARING_API_USER2); - - $share->setTarget('/foobar'); - $this->shareManager->moveShare($share, self::TEST_FILES_SHARING_API_USER2); - - - // share to user - $share2 = $this->share( - IShare::TYPE_USER, - $this->folder2, - self::TEST_FILES_SHARING_API_USER1, - self::TEST_FILES_SHARING_API_USER2, - Constants::PERMISSION_ALL); - $this->shareManager->acceptShare($share2, self::TEST_FILES_SHARING_API_USER2); - - $share2->setTarget('/foobar'); - $this->shareManager->moveShare($share2, self::TEST_FILES_SHARING_API_USER2); - - self::loginHelper(self::TEST_FILES_SHARING_API_USER2); - // one of the shares should have been moved - - $share = $this->shareManager->getShareById($share->getFullId()); - $share2 = $this->shareManager->getShareById($share2->getFullId()); - - // we don't know or care which share got the "(2)" just that one of them did - $this->assertNotEquals($share->getTarget(), $share2->getTarget()); - $this->assertSame('/foobar', min($share->getTarget(), $share2->getTarget())); - $this->assertSame('/foobar (2)', max($share->getTarget(), $share2->getTarget())); - - //cleanup - self::loginHelper(self::TEST_FILES_SHARING_API_USER1); - $this->shareManager->deleteShare($share); - $this->view->unlink($this->folder); - } } class DummyTestClassSharedMount extends SharedMount { diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 0b6dd29f2040b..5d515c66dd32a 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -824,6 +824,7 @@ 'OCP\\Share\\IShare' => $baseDir . '/lib/public/Share/IShare.php', 'OCP\\Share\\IShareHelper' => $baseDir . '/lib/public/Share/IShareHelper.php', 'OCP\\Share\\IShareProvider' => $baseDir . '/lib/public/Share/IShareProvider.php', + 'OCP\\Share\\IShareProviderGetUsers' => $baseDir . '/lib/public/Share/IShareProviderGetUsers.php', 'OCP\\Share\\IShareProviderSupportsAccept' => $baseDir . '/lib/public/Share/IShareProviderSupportsAccept.php', 'OCP\\Share\\IShareProviderSupportsAllSharesInFolder' => $baseDir . '/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php', 'OCP\\Share\\IShareProviderWithNotification' => $baseDir . '/lib/public/Share/IShareProviderWithNotification.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 9c34d2cd4debd..d25b0f94870d0 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -865,6 +865,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2 'OCP\\Share\\IShare' => __DIR__ . '/../../..' . '/lib/public/Share/IShare.php', 'OCP\\Share\\IShareHelper' => __DIR__ . '/../../..' . '/lib/public/Share/IShareHelper.php', 'OCP\\Share\\IShareProvider' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProvider.php', + 'OCP\\Share\\IShareProviderGetUsers' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderGetUsers.php', 'OCP\\Share\\IShareProviderSupportsAccept' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderSupportsAccept.php', 'OCP\\Share\\IShareProviderSupportsAllSharesInFolder' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderSupportsAllSharesInFolder.php', 'OCP\\Share\\IShareProviderWithNotification' => __DIR__ . '/../../..' . '/lib/public/Share/IShareProviderWithNotification.php', diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index bc74002fcbc09..3104ac7eff779 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -33,6 +33,7 @@ use OCP\Share\IAttributes; use OCP\Share\IManager; use OCP\Share\IShare; +use OCP\Share\IShareProviderGetUsers; use OCP\Share\IShareProviderSupportsAccept; use OCP\Share\IShareProviderSupportsAllSharesInFolder; use OCP\Share\IShareProviderWithNotification; @@ -44,7 +45,11 @@ * * @package OC\Share20 */ -class DefaultShareProvider implements IShareProviderWithNotification, IShareProviderSupportsAccept, IShareProviderSupportsAllSharesInFolder { +class DefaultShareProvider implements + IShareProviderWithNotification, + IShareProviderSupportsAccept, + IShareProviderSupportsAllSharesInFolder, + IShareProviderGetUsers { public function __construct( private IDBConnection $dbConn, private IUserManager $userManager, @@ -1678,4 +1683,15 @@ protected function formatShareAttributes(?IAttributes $attributes): ?string { } return \json_encode($compressedAttributes); } + + public function getUsersForShare(IShare $share): iterable { + if ($share->getShareType() === IShare::TYPE_USER) { + return [new LazyUser($share->getSharedWith(), $this->userManager)]; + } elseif ($share->getShareType() === IShare::TYPE_GROUP) { + $group = $this->groupManager->get($share->getSharedWith()); + return $group->getUsers(); + } else { + return []; + } + } } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index 1dada16de4e86..72da67ec91bef 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1884,4 +1884,13 @@ private function dispatchEvent(Event $event, string $name): void { $this->logger->error("Error while sending ' . $name . ' event", ['exception' => $e]); } } + + public function getUsersForShare(IShare $share): iterable { + $provider = $this->factory->getProviderForType($share->getShareType()); + if ($provider instanceof Share\IShareProviderGetUsers) { + return $provider->getUsersForShare($share); + } else { + return []; + } + } } diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 92bd7b825f3fe..8b238abc13f68 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -518,4 +518,13 @@ public function getAllShares(): iterable; * @since 31.0.0 */ public function generateToken(): string; + + /** + * Get all users with access to a share + * + * @param IShare $share + * @return iterable + * @since 33.0.0 + */ + public function getUsersForShare(IShare $share): iterable; } diff --git a/lib/public/Share/IShareProviderGetUsers.php b/lib/public/Share/IShareProviderGetUsers.php new file mode 100644 index 0000000000000..44e2d8eab7014 --- /dev/null +++ b/lib/public/Share/IShareProviderGetUsers.php @@ -0,0 +1,28 @@ + + * @since 33.0.0 + */ + public function getUsersForShare(IShare $share): iterable; +}