mirror of
https://github.com/nextcloud/server.git
synced 2026-05-27 03:43:40 -04:00
Merge pull request #59177 from nextcloud/revert-59172-revert-58894-stable33-authoritative-share
[stable33] authoritative share - revival
This commit is contained in:
commit
84e2ebec72
53 changed files with 2119 additions and 428 deletions
|
|
@ -8,17 +8,18 @@ declare(strict_types=1);
|
|||
|
||||
namespace OCA\Files\Command\Mount;
|
||||
|
||||
use OC\Core\Command\Base;
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\Config\IMountProviderCollection;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\IUserManager;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ListMounts extends Command {
|
||||
class ListMounts extends Base {
|
||||
public function __construct(
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IUserMountCache $userMountCache,
|
||||
|
|
@ -28,52 +29,81 @@ class ListMounts extends Command {
|
|||
}
|
||||
|
||||
protected function configure(): void {
|
||||
parent::configure();
|
||||
$this
|
||||
->setName('files:mount:list')
|
||||
->setDescription('List of mounts for a user')
|
||||
->addArgument('user', InputArgument::REQUIRED, 'User to list mounts for');
|
||||
->addArgument('user', InputArgument::REQUIRED, 'User to list mounts for')
|
||||
->addOption('cached-only', null, InputOption::VALUE_NONE, 'Only return cached mounts, prevents filesystem setup');
|
||||
}
|
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$userId = $input->getArgument('user');
|
||||
$cachedOnly = $input->getOption('cached-only');
|
||||
$user = $this->userManager->get($userId);
|
||||
if (!$user) {
|
||||
$output->writeln("<error>User $userId not found</error>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$mounts = $this->mountProviderCollection->getMountsForUser($user);
|
||||
$mounts[] = $this->mountProviderCollection->getHomeMountForUser($user);
|
||||
/** @var array<string, IMountPoint> $cachedByMountpoint */
|
||||
$mountsByMountpoint = array_combine(array_map(fn (IMountPoint $mount) => $mount->getMountPoint(), $mounts), $mounts);
|
||||
if ($cachedOnly) {
|
||||
$mounts = [];
|
||||
} else {
|
||||
$mounts = $this->mountProviderCollection->getMountsForUser($user);
|
||||
$mounts[] = $this->mountProviderCollection->getHomeMountForUser($user);
|
||||
}
|
||||
/** @var array<string, IMountPoint> $cachedByMountPoint */
|
||||
$mountsByMountPoint = array_combine(array_map(fn (IMountPoint $mount) => $mount->getMountPoint(), $mounts), $mounts);
|
||||
usort($mounts, fn (IMountPoint $a, IMountPoint $b) => $a->getMountPoint() <=> $b->getMountPoint());
|
||||
|
||||
$cachedMounts = $this->userMountCache->getMountsForUser($user);
|
||||
usort($cachedMounts, fn (ICachedMountInfo $a, ICachedMountInfo $b) => $a->getMountPoint() <=> $b->getMountPoint());
|
||||
/** @var array<string, ICachedMountInfo> $cachedByMountpoint */
|
||||
$cachedByMountpoint = array_combine(array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts), $cachedMounts);
|
||||
$cachedByMountPoint = array_combine(array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts), $cachedMounts);
|
||||
|
||||
foreach ($mounts as $mount) {
|
||||
$output->writeln('<info>' . $mount->getMountPoint() . '</info>: ' . $mount->getStorageId());
|
||||
if (isset($cachedByMountpoint[$mount->getMountPoint()])) {
|
||||
$cached = $cachedByMountpoint[$mount->getMountPoint()];
|
||||
$output->writeln("\t- provider: " . $cached->getMountProvider());
|
||||
$output->writeln("\t- storage id: " . $cached->getStorageId());
|
||||
$output->writeln("\t- root id: " . $cached->getRootId());
|
||||
} else {
|
||||
$output->writeln("\t<error>not registered</error>");
|
||||
}
|
||||
}
|
||||
foreach ($cachedMounts as $cachedMount) {
|
||||
if (!isset($mountsByMountpoint[$cachedMount->getMountPoint()])) {
|
||||
$output->writeln('<info>' . $cachedMount->getMountPoint() . '</info>:');
|
||||
$output->writeln("\t<error>registered but no longer provided</error>");
|
||||
$output->writeln("\t- provider: " . $cachedMount->getMountProvider());
|
||||
$output->writeln("\t- storage id: " . $cachedMount->getStorageId());
|
||||
$output->writeln("\t- root id: " . $cachedMount->getRootId());
|
||||
}
|
||||
}
|
||||
$format = $input->getOption('output');
|
||||
|
||||
if ($format === self::OUTPUT_FORMAT_PLAIN) {
|
||||
foreach ($mounts as $mount) {
|
||||
$output->writeln('<info>' . $mount->getMountPoint() . '</info>: ' . $mount->getStorageId());
|
||||
if (isset($cachedByMountPoint[$mount->getMountPoint()])) {
|
||||
$cached = $cachedByMountPoint[$mount->getMountPoint()];
|
||||
$output->writeln("\t- provider: " . $cached->getMountProvider());
|
||||
$output->writeln("\t- storage id: " . $cached->getStorageId());
|
||||
$output->writeln("\t- root id: " . $cached->getRootId());
|
||||
} else {
|
||||
$output->writeln("\t<error>not registered</error>");
|
||||
}
|
||||
}
|
||||
foreach ($cachedMounts as $cachedMount) {
|
||||
if ($cachedOnly || !isset($mountsByMountPoint[$cachedMount->getMountPoint()])) {
|
||||
$output->writeln('<info>' . $cachedMount->getMountPoint() . '</info>:');
|
||||
if (!$cachedOnly) {
|
||||
$output->writeln("\t<error>registered but no longer provided</error>");
|
||||
}
|
||||
$output->writeln("\t- provider: " . $cachedMount->getMountProvider());
|
||||
$output->writeln("\t- storage id: " . $cachedMount->getStorageId());
|
||||
$output->writeln("\t- root id: " . $cachedMount->getRootId());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$cached = array_map(fn (ICachedMountInfo $cachedMountInfo) => [
|
||||
'mountpoint' => $cachedMountInfo->getMountPoint(),
|
||||
'provider' => $cachedMountInfo->getMountProvider(),
|
||||
'storage_id' => $cachedMountInfo->getStorageId(),
|
||||
'root_id' => $cachedMountInfo->getRootId(),
|
||||
], $cachedMounts);
|
||||
$provided = array_map(fn (IMountPoint $cachedMountInfo) => [
|
||||
'mountpoint' => $cachedMountInfo->getMountPoint(),
|
||||
'provider' => $cachedMountInfo->getMountProvider(),
|
||||
'storage_id' => $cachedMountInfo->getStorageId(),
|
||||
'root_id' => $cachedMountInfo->getStorageRootId(),
|
||||
], $mounts);
|
||||
$this->writeArrayInOutputFormat($input, $output, array_filter([
|
||||
'cached' => $cached,
|
||||
'provided' => $cachedOnly ? null : $provided,
|
||||
]));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ use OCA\Files\Exception\TransferOwnershipException;
|
|||
use OCA\Files_External\Config\ConfigAdapter;
|
||||
use OCA\GroupFolders\Mount\GroupMountPoint;
|
||||
use OCP\Encryption\IManager as IEncryptionManager;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Config\IHomeMountProvider;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\File;
|
||||
|
|
@ -31,6 +32,7 @@ use OCP\IUser;
|
|||
use OCP\IUserManager;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Events\ShareTransferredEvent;
|
||||
use OCP\Share\IManager as IShareManager;
|
||||
use OCP\Share\IShare;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
|
|
@ -53,6 +55,7 @@ class OwnershipTransferService {
|
|||
private IUserManager $userManager,
|
||||
private IFactory $l10nFactory,
|
||||
private IRootFolder $rootFolder,
|
||||
private IEventDispatcher $eventDispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -567,20 +570,23 @@ class OwnershipTransferService {
|
|||
} catch (\Throwable $e) {
|
||||
$output->writeln('<error>Could not restore share with id ' . $share->getId() . ':' . $e->getMessage() . ' : ' . $e->getTraceAsString() . '</error>');
|
||||
}
|
||||
$this->eventDispatcher->dispatchTyped(new ShareTransferredEvent($share));
|
||||
$progress->advance();
|
||||
}
|
||||
$progress->finish();
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
private function transferIncomingShares(string $sourceUid,
|
||||
private function transferIncomingShares(
|
||||
string $sourceUid,
|
||||
string $destinationUid,
|
||||
array $sourceShares,
|
||||
array $destinationShares,
|
||||
OutputInterface $output,
|
||||
string $path,
|
||||
string $finalTarget,
|
||||
bool $move): void {
|
||||
bool $move,
|
||||
): void {
|
||||
$output->writeln('Restoring incoming shares ...');
|
||||
$progress = new ProgressBar($output, count($sourceShares));
|
||||
$prefix = "$destinationUid/files";
|
||||
|
|
@ -619,8 +625,11 @@ class OwnershipTransferService {
|
|||
if ($move) {
|
||||
continue;
|
||||
}
|
||||
$oldMountPoint = $this->getShareMountPoint($destinationUid, $share->getTarget());
|
||||
$newMountPoint = $this->getShareMountPoint($destinationUid, $shareTarget);
|
||||
$share->setTarget($shareTarget);
|
||||
$this->shareManager->moveShare($share, $destinationUid);
|
||||
$this->mountManager->moveMount($oldMountPoint, $newMountPoint);
|
||||
continue;
|
||||
}
|
||||
$this->shareManager->deleteShare($share);
|
||||
|
|
@ -638,8 +647,11 @@ class OwnershipTransferService {
|
|||
if ($move) {
|
||||
continue;
|
||||
}
|
||||
$oldMountPoint = $this->getShareMountPoint($destinationUid, $share->getTarget());
|
||||
$newMountPoint = $this->getShareMountPoint($destinationUid, $shareTarget);
|
||||
$share->setTarget($shareTarget);
|
||||
$this->shareManager->moveShare($share, $destinationUid);
|
||||
$this->mountManager->moveMount($oldMountPoint, $newMountPoint);
|
||||
continue;
|
||||
}
|
||||
} catch (NotFoundException $e) {
|
||||
|
|
@ -652,4 +664,8 @@ class OwnershipTransferService {
|
|||
$progress->finish();
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
private function getShareMountPoint(string $uid, string $target): string {
|
||||
return '/' . $uid . '/files/' . trim($target, '/') . '/';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class MountCacheService implements IEventListener {
|
|||
|
||||
public function handleDeletedStorage(StorageConfig $storage): void {
|
||||
foreach ($this->applicableHelper->getUsersForStorage($storage) as $user) {
|
||||
$this->userMountCache->removeMount($storage->getMountPointForUser($user));
|
||||
$this->userMountCache->removeMount($storage->getMountPointForUser($user), $user);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ class MountCacheService implements IEventListener {
|
|||
|
||||
public function handleUpdatedStorage(StorageConfig $oldStorage, StorageConfig $newStorage): void {
|
||||
foreach ($this->applicableHelper->diffApplicable($oldStorage, $newStorage) as $user) {
|
||||
$this->userMountCache->removeMount($oldStorage->getMountPointForUser($user));
|
||||
$this->userMountCache->removeMount($oldStorage->getMountPointForUser($user), $user);
|
||||
}
|
||||
foreach ($this->applicableHelper->diffApplicable($newStorage, $oldStorage) as $user) {
|
||||
$this->registerForUser($user, $newStorage);
|
||||
|
|
@ -156,7 +156,7 @@ class MountCacheService implements IEventListener {
|
|||
$storages = $this->storagesService->getAllStoragesForGroup($group);
|
||||
foreach ($storages as $storage) {
|
||||
if (!$this->applicableHelper->isApplicableForUser($storage, $user)) {
|
||||
$this->userMountCache->removeMount($storage->getMountPointForUser($user));
|
||||
$this->userMountCache->removeMount($storage->getMountPointForUser($user), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ class MountCacheService implements IEventListener {
|
|||
private function removeGroupFromStorage(StorageConfig $storage, IGroup $group): void {
|
||||
foreach ($group->searchUsers('') as $user) {
|
||||
if (!$this->applicableHelper->isApplicableForUser($storage, $user)) {
|
||||
$this->userMountCache->removeMount($storage->getMountPointForUser($user));
|
||||
$this->userMountCache->removeMount($storage->getMountPointForUser($user), $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,9 @@ return array(
|
|||
'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\\UserHomeSetupListener' => $baseDir . '/../lib/Listener/UserHomeSetupListener.php',
|
||||
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => $baseDir . '/../lib/Listener/UserShareAcceptanceListener.php',
|
||||
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => $baseDir . '/../lib/Middleware/OCSShareAPIMiddleware.php',
|
||||
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => $baseDir . '/../lib/Middleware/ShareInfoMiddleware.php',
|
||||
|
|
@ -97,6 +99,7 @@ return array(
|
|||
'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\\ShareRecipientUpdater' => $baseDir . '/../lib/ShareRecipientUpdater.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',
|
||||
|
|
|
|||
|
|
@ -85,7 +85,9 @@ 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\\UserHomeSetupListener' => __DIR__ . '/..' . '/../lib/Listener/UserHomeSetupListener.php',
|
||||
'OCA\\Files_Sharing\\Listener\\UserShareAcceptanceListener' => __DIR__ . '/..' . '/../lib/Listener/UserShareAcceptanceListener.php',
|
||||
'OCA\\Files_Sharing\\Middleware\\OCSShareAPIMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/OCSShareAPIMiddleware.php',
|
||||
'OCA\\Files_Sharing\\Middleware\\ShareInfoMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/ShareInfoMiddleware.php',
|
||||
|
|
@ -112,6 +114,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\\ShareRecipientUpdater' => __DIR__ . '/..' . '/../lib/ShareRecipientUpdater.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',
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use OCA\Files\Event\LoadAdditionalScriptsEvent;
|
|||
use OCA\Files\Event\LoadSidebar;
|
||||
use OCA\Files_Sharing\Capabilities;
|
||||
use OCA\Files_Sharing\Config\ConfigLexicon;
|
||||
use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent;
|
||||
use OCA\Files_Sharing\External\Manager;
|
||||
use OCA\Files_Sharing\External\MountProvider as ExternalMountProvider;
|
||||
use OCA\Files_Sharing\Helper;
|
||||
|
|
@ -24,7 +25,9 @@ use OCA\Files_Sharing\Listener\LoadAdditionalListener;
|
|||
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\UserHomeSetupListener;
|
||||
use OCA\Files_Sharing\Listener\UserShareAcceptanceListener;
|
||||
use OCA\Files_Sharing\Middleware\OCSShareAPIMiddleware;
|
||||
use OCA\Files_Sharing\Middleware\ShareInfoMiddleware;
|
||||
|
|
@ -46,13 +49,19 @@ use OCP\Files\Config\IMountProviderCollection;
|
|||
use OCP\Files\Events\BeforeDirectFileDownloadEvent;
|
||||
use OCP\Files\Events\BeforeZipCreatedEvent;
|
||||
use OCP\Files\Events\Node\BeforeNodeReadEvent;
|
||||
use OCP\Files\Events\UserHomeSetupEvent;
|
||||
use OCP\Group\Events\BeforeGroupDeletedEvent;
|
||||
use OCP\Group\Events\GroupChangedEvent;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\Group\Events\UserAddedEvent;
|
||||
use OCP\Group\Events\UserRemovedEvent;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroup;
|
||||
use OCP\Share\Events\BeforeShareDeletedEvent;
|
||||
use OCP\Share\Events\ShareCreatedEvent;
|
||||
use OCP\Share\Events\ShareMovedEvent;
|
||||
use OCP\Share\Events\ShareTransferredEvent;
|
||||
use OCP\User\Events\UserChangedEvent;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
use OCP\Util;
|
||||
|
|
@ -111,6 +120,18 @@ class Application extends App implements IBootstrap {
|
|||
// File request auth
|
||||
$context->registerEventListener(BeforeTemplateRenderedEvent::class, LoadPublicFileRequestAuthListener::class);
|
||||
|
||||
// Update mounts
|
||||
$context->registerEventListener(ShareCreatedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(BeforeShareDeletedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(ShareTransferredEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(UserAddedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(UserRemovedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(BeforeGroupDeletedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(GroupDeletedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(UserShareAccessUpdatedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(ShareMovedEvent::class, SharesUpdatedListener::class);
|
||||
$context->registerEventListener(UserHomeSetupEvent::class, UserHomeSetupListener::class);
|
||||
|
||||
$context->registerConfigLexicon(ConfigLexicon::class);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ class ConfigLexicon implements ILexicon {
|
|||
public const SHOW_FEDERATED_AS_INTERNAL = 'show_federated_shares_as_internal';
|
||||
public const SHOW_FEDERATED_TO_TRUSTED_AS_INTERNAL = 'show_federated_shares_to_trusted_servers_as_internal';
|
||||
public const EXCLUDE_RESHARE_FROM_EDIT = 'shareapi_exclude_reshare_from_edit';
|
||||
public const UPDATE_CUTOFF_TIME = 'update_cutoff_time';
|
||||
public const USER_NEEDS_SHARE_REFRESH = 'user_needs_share_refresh';
|
||||
|
||||
public function getStrictness(): Strictness {
|
||||
return Strictness::IGNORE;
|
||||
|
|
@ -34,10 +36,14 @@ class ConfigLexicon implements ILexicon {
|
|||
new Entry(self::SHOW_FEDERATED_AS_INTERNAL, ValueType::BOOL, false, 'shows federated shares as internal shares', true),
|
||||
new Entry(self::SHOW_FEDERATED_TO_TRUSTED_AS_INTERNAL, ValueType::BOOL, false, 'shows federated shares to trusted servers as internal shares', true),
|
||||
new Entry(self::EXCLUDE_RESHARE_FROM_EDIT, ValueType::BOOL, false, 'Exclude reshare permission from "Allow editing" bundled permissions'),
|
||||
|
||||
new Entry(self::UPDATE_CUTOFF_TIME, ValueType::FLOAT, 3.0, 'For how how long do we update the share data immediately before switching to only marking the user'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getUserConfigs(): array {
|
||||
return [];
|
||||
return [
|
||||
new Entry(self::USER_NEEDS_SHARE_REFRESH, ValueType::BOOL, true, 'whether a user needs to have the receiving share data refreshed for possible changes'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
163
apps/files_sharing/lib/Listener/SharesUpdatedListener.php
Normal file
163
apps/files_sharing/lib/Listener/SharesUpdatedListener.php
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Listener;
|
||||
|
||||
use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCA\Files_Sharing\Config\ConfigLexicon;
|
||||
use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent;
|
||||
use OCA\Files_Sharing\ShareRecipientUpdater;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Group\Events\BeforeGroupDeletedEvent;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\Group\Events\UserAddedEvent;
|
||||
use OCP\Group\Events\UserRemovedEvent;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\Events\BeforeShareDeletedEvent;
|
||||
use OCP\Share\Events\ShareCreatedEvent;
|
||||
use OCP\Share\Events\ShareMovedEvent;
|
||||
use OCP\Share\Events\ShareTransferredEvent;
|
||||
use OCP\Share\IManager;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Listen to various events that can change what shares a user has access to
|
||||
*
|
||||
* @psalm-type GroupEvents = UserAddedEvent|UserRemovedEvent|GroupDeletedEvent|BeforeGroupDeletedEvent
|
||||
* @template-implements IEventListener<GroupEvents|ShareCreatedEvent|ShareTransferredEvent|BeforeShareDeletedEvent|UserShareAccessUpdatedEvent|ShareMovedEvent>
|
||||
*/
|
||||
class SharesUpdatedListener implements IEventListener {
|
||||
/**
|
||||
* for how long do we update the share date immediately,
|
||||
* before just marking the other users
|
||||
*/
|
||||
private float $cutOffMarkTime;
|
||||
|
||||
/**
|
||||
* The total amount of time we've spent so far processing updates
|
||||
*/
|
||||
private float $updatedTime = 0.0;
|
||||
private bool $inUpdate = false;
|
||||
|
||||
public function __construct(
|
||||
private readonly IManager $shareManager,
|
||||
private readonly ShareRecipientUpdater $shareUpdater,
|
||||
private readonly IUserConfig $userConfig,
|
||||
private readonly ClockInterface $clock,
|
||||
private readonly LoggerInterface $logger,
|
||||
IAppConfig $appConfig,
|
||||
private readonly UserHomeSetupListener $homeSetupListener,
|
||||
) {
|
||||
$this->cutOffMarkTime = $appConfig->getValueFloat(Application::APP_ID, ConfigLexicon::UPDATE_CUTOFF_TIME, 3.0);
|
||||
}
|
||||
|
||||
public function handle(Event $event): void {
|
||||
// prevent recursive updates
|
||||
if ($this->inUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't trigger the on-setup checks if this handler triggers an fs setup
|
||||
$oldState = $this->homeSetupListener->setDisabled(true);
|
||||
|
||||
if ($event instanceof UserShareAccessUpdatedEvent) {
|
||||
foreach ($event->getUsers() as $user) {
|
||||
$this->updateOrMarkUser($user);
|
||||
}
|
||||
}
|
||||
if ($event instanceof BeforeGroupDeletedEvent) {
|
||||
// ensure the group users are loaded before the group is deleted
|
||||
$event->getGroup()->getUsers();
|
||||
}
|
||||
if ($event instanceof GroupDeletedEvent) {
|
||||
// so we can iterate them after the group is deleted
|
||||
foreach ($event->getGroup()->getUsers() as $user) {
|
||||
$this->updateOrMarkUser($user);
|
||||
}
|
||||
}
|
||||
if ($event instanceof UserAddedEvent || $event instanceof UserRemovedEvent) {
|
||||
$this->updateOrMarkUser($event->getUser());
|
||||
}
|
||||
if ($event instanceof ShareCreatedEvent || $event instanceof ShareTransferredEvent) {
|
||||
$share = $event->getShare();
|
||||
$shareTarget = $share->getTarget();
|
||||
foreach ($this->shareManager->getUsersForShare($share) as $user) {
|
||||
if ($share->getShareOwner() === $user->getUID() || $share->getSharedBy() === $user->getUID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($share->getSharedBy() !== $user->getUID()) {
|
||||
$this->markOrRun($user, function () use ($user, $share) {
|
||||
$this->inUpdate = true;
|
||||
$this->shareUpdater->updateForAddedShare($user, $share);
|
||||
$this->inUpdate = false;
|
||||
});
|
||||
// Share target validation might have changed the target, restore it for the next user
|
||||
$share->setTarget($shareTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($event instanceof ShareMovedEvent) {
|
||||
$share = $event->getShare();
|
||||
$user = $event->getUser();
|
||||
|
||||
// don't trigger if the share is moved as part of the conflict resolution
|
||||
if (!$this->shareUpdater->isInUpdate($user)) {
|
||||
$this->markOrRun($user, function () use ($user, $share) {
|
||||
$this->shareUpdater->updateForMovedShare($user, $share);
|
||||
});
|
||||
}
|
||||
}
|
||||
if ($event instanceof BeforeShareDeletedEvent) {
|
||||
$share = $event->getShare();
|
||||
foreach ($this->shareManager->getUsersForShare($share) as $user) {
|
||||
if ($share->getShareOwner() === $user->getUID() || $share->getSharedBy() === $user->getUID()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->markOrRun($user, function () use ($user, $share) {
|
||||
$this->shareUpdater->updateForDeletedShare($user, $share);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$this->homeSetupListener->setDisabled($oldState);
|
||||
}
|
||||
|
||||
private function markOrRun(IUser $user, callable $callback): void {
|
||||
$start = floatval($this->clock->now()->format('U.u'));
|
||||
if ($this->cutOffMarkTime === -1.0 || $this->updatedTime < $this->cutOffMarkTime) {
|
||||
$callback();
|
||||
} else {
|
||||
$this->markUserForRefresh($user);
|
||||
}
|
||||
$end = floatval($this->clock->now()->format('U.u'));
|
||||
$this->updatedTime += $end - $start;
|
||||
}
|
||||
|
||||
private function updateOrMarkUser(IUser $user): void {
|
||||
$this->markOrRun($user, function () use ($user) {
|
||||
$this->shareUpdater->updateForUser($user);
|
||||
});
|
||||
}
|
||||
|
||||
private function markUserForRefresh(IUser $user): void {
|
||||
// log with exception to capture the trace
|
||||
$ex = new \Exception('Marking ' . $user->getUID() . ' as needing the share mounts refreshed');
|
||||
$this->logger->debug($ex->getMessage(), ['exception' => $ex]);
|
||||
$this->userConfig->setValueBool($user->getUID(), Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, true);
|
||||
}
|
||||
|
||||
public function setCutOffMarkTime(float|int $cutOffMarkTime): void {
|
||||
$this->cutOffMarkTime = (float)$cutOffMarkTime;
|
||||
}
|
||||
}
|
||||
53
apps/files_sharing/lib/Listener/UserHomeSetupListener.php
Normal file
53
apps/files_sharing/lib/Listener/UserHomeSetupListener.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Listener;
|
||||
|
||||
use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCA\Files_Sharing\Config\ConfigLexicon;
|
||||
use OCA\Files_Sharing\ShareRecipientUpdater;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventListener;
|
||||
use OCP\Files\Events\UserHomeSetupEvent;
|
||||
|
||||
/**
|
||||
* Listen to the users filesystem setup being started, to perform any receiving share
|
||||
* work that was postponed.
|
||||
*
|
||||
* @template-implements IEventListener<UserHomeSetupEvent>
|
||||
*/
|
||||
class UserHomeSetupListener implements IEventListener {
|
||||
private bool $disabled = false;
|
||||
public function __construct(
|
||||
private readonly ShareRecipientUpdater $updater,
|
||||
private readonly IUserConfig $userConfig,
|
||||
) {
|
||||
}
|
||||
|
||||
public function setDisabled(bool $disabled): bool {
|
||||
$previous = $this->disabled;
|
||||
$this->disabled = $disabled;
|
||||
return $previous;
|
||||
}
|
||||
public function handle(Event $event): void {
|
||||
if (!$event instanceof UserHomeSetupEvent) {
|
||||
return;
|
||||
}
|
||||
if ($this->disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $event->getUser();
|
||||
if ($this->userConfig->getValueBool($user->getUID(), Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, true)) {
|
||||
$this->updater->updateForUser($user);
|
||||
$this->userConfig->setValueBool($user->getUID(), Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ use InvalidArgumentException;
|
|||
use OC\Files\View;
|
||||
use OCA\Files_Sharing\Event\ShareMountedEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Config\IAuthoritativeMountProvider;
|
||||
use OCP\Files\Config\IMountProvider;
|
||||
use OCP\Files\Config\IPartialMountProvider;
|
||||
use OCP\Files\Mount\IMountManager;
|
||||
|
|
@ -27,7 +28,7 @@ use Psr\Log\LoggerInterface;
|
|||
|
||||
use function count;
|
||||
|
||||
class MountProvider implements IMountProvider, IPartialMountProvider {
|
||||
class MountProvider implements IMountProvider, IAuthoritativeMountProvider, IPartialMountProvider {
|
||||
|
||||
/**
|
||||
* @param IConfig $config
|
||||
|
|
@ -53,6 +54,15 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
* @return IMountPoint[]
|
||||
*/
|
||||
public function getMountsForUser(IUser $user, IStorageFactory $loader) {
|
||||
return array_values($this->getMountsFromSuperShares($user, $this->getSuperSharesForUser($user), $loader));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IUser $user
|
||||
* @param list<IShare> $excludeShares
|
||||
* @return list<array{IShare, array<IShare>}> Tuple of [superShare, groupedShares]
|
||||
*/
|
||||
public function getSuperSharesForUser(IUser $user, array $excludeShares = []): array {
|
||||
$userId = $user->getUID();
|
||||
$shares = $this->mergeIterables(
|
||||
$this->shareManager->getSharedWith($userId, IShare::TYPE_USER, null, -1),
|
||||
|
|
@ -62,17 +72,9 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
$this->shareManager->getSharedWith($userId, IShare::TYPE_DECK, null, -1),
|
||||
);
|
||||
|
||||
$shares = $this->filterShares($shares, $userId);
|
||||
$superShares = $this->buildSuperShares($shares, $user);
|
||||
|
||||
return array_values(
|
||||
$this->getMountsFromSuperShares(
|
||||
$userId,
|
||||
$superShares,
|
||||
$loader,
|
||||
$user,
|
||||
),
|
||||
);
|
||||
$excludeShareIds = array_map(fn (IShare $share) => $share->getFullId(), $excludeShares);
|
||||
$shares = $this->filterShares($shares, $userId, $excludeShareIds);
|
||||
return $this->buildSuperShares($shares, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -254,18 +256,18 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
}
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param array $superShares
|
||||
* @param list<array{IShare, array<IShare>}> $superShares
|
||||
* @param IStorageFactory $loader
|
||||
* @param IUser $user
|
||||
* @return array IMountPoint indexed by mount point
|
||||
* @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');
|
||||
|
|
@ -293,12 +295,6 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
}
|
||||
|
||||
$shareId = (int)$parentShare->getId();
|
||||
$absMountPoint = '/' . $user->getUID() . '/files/' . trim($parentShare->getTarget(), '/') . '/';
|
||||
|
||||
// after the mountpoint is verified for the first time, only new mountpoints (e.g. groupfolders can overwrite the target)
|
||||
if ($shareId > $maxValidatedShare || isset($allMounts[$absMountPoint])) {
|
||||
$this->shareTargetValidator->verifyMountPoint($user, $parentShare, $allMounts, $groupedShares);
|
||||
}
|
||||
|
||||
$mount = new SharedMount(
|
||||
'\OCA\Files_Sharing\SharedStorage',
|
||||
|
|
@ -312,7 +308,6 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
'sharingDisabledForUser' => $sharingDisabledForUser
|
||||
],
|
||||
$loader,
|
||||
$view,
|
||||
$this->eventDispatcher,
|
||||
$user,
|
||||
);
|
||||
|
|
@ -349,14 +344,16 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
* user has no permissions.
|
||||
*
|
||||
* @param iterable<IShare> $shares
|
||||
* @param list<string> $excludeShareIds
|
||||
* @return iterable<IShare>
|
||||
*/
|
||||
private function filterShares(iterable $shares, string $userId): iterable {
|
||||
private function filterShares(iterable $shares, string $userId, array $excludeShareIds = []): iterable {
|
||||
foreach ($shares as $share) {
|
||||
if (
|
||||
$share->getPermissions() > 0
|
||||
&& $share->getShareOwner() !== $userId
|
||||
&& $share->getSharedBy() !== $userId
|
||||
&& !in_array($share->getFullId(), $excludeShareIds)
|
||||
) {
|
||||
yield $share;
|
||||
}
|
||||
|
|
@ -399,7 +396,7 @@ class MountProvider implements IMountProvider, IPartialMountProvider {
|
|||
$shares = $this->filterShares($shares, $userId);
|
||||
$superShares = $this->buildSuperShares($shares, $user);
|
||||
|
||||
return $this->getMountsFromSuperShares($userId, $superShares, $loader, $user);
|
||||
return $this->getMountsFromSuperShares($user, $superShares, $loader);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ namespace OCA\Files_Sharing\Repair;
|
|||
use OC\Files\SetupManager;
|
||||
use OCA\Files_Sharing\ShareTargetValidator;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Mount\IMountManager;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IDBConnection;
|
||||
|
|
@ -42,7 +43,7 @@ class CleanupShareTarget implements IRepairStep {
|
|||
private readonly ShareTargetValidator $shareTargetValidator,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly SetupManager $setupManager,
|
||||
private readonly IMountManager $mountManager,
|
||||
private readonly IUserMountCache $userMountCache,
|
||||
private readonly IRootFolder $rootFolder,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly ICacheFactory $cacheFactory,
|
||||
|
|
@ -85,7 +86,9 @@ class CleanupShareTarget implements IRepairStep {
|
|||
|
||||
$this->setupManager->tearDown();
|
||||
$this->setupManager->setupForUser($recipient);
|
||||
$userMounts = $this->mountManager->getAll();
|
||||
$mounts = $this->userMountCache->getMountsForUser($recipient);
|
||||
$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $mounts);
|
||||
$userMounts = array_combine($mountPoints, $mounts);
|
||||
$userFolder = $this->rootFolder->getUserFolder($recipient->getUID());
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +107,7 @@ class CleanupShareTarget implements IRepairStep {
|
|||
(int)$shareInfo['file_source'],
|
||||
$absoluteNewTarget,
|
||||
$targetParentNode->getMountPoint(),
|
||||
$userMounts,
|
||||
fn ($path) => $userMounts[$path] ?? null,
|
||||
);
|
||||
$newTarget = $userFolder->getRelativePath($absoluteNewTarget);
|
||||
|
||||
|
|
|
|||
113
apps/files_sharing/lib/ShareRecipientUpdater.php
Normal file
113
apps/files_sharing/lib/ShareRecipientUpdater.php
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing;
|
||||
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\Storage\IStorageFactory;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
class ShareRecipientUpdater {
|
||||
private array $inUpdate = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly IUserMountCache $userMountCache,
|
||||
private readonly MountProvider $shareMountProvider,
|
||||
private readonly ShareTargetValidator $shareTargetValidator,
|
||||
private readonly IStorageFactory $storageFactory,
|
||||
private readonly IManager $shareManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate all received shares for a user
|
||||
*/
|
||||
public function updateForUser(IUser $user): void {
|
||||
// prevent recursion
|
||||
if ($this->isInUpdate($user)) {
|
||||
return;
|
||||
}
|
||||
$this->inUpdate[$user->getUID()] = true;
|
||||
|
||||
$cachedMounts = $this->userMountCache->getMountsForUser($user);
|
||||
$shareMounts = array_filter($cachedMounts, fn (ICachedMountInfo $mount) => $mount->getMountProvider() === MountProvider::class);
|
||||
$mountPoints = array_map(fn (ICachedMountInfo $mount) => $mount->getMountPoint(), $cachedMounts);
|
||||
$mountsByPath = array_combine($mountPoints, $cachedMounts);
|
||||
|
||||
$shares = $this->shareMountProvider->getSuperSharesForUser($user);
|
||||
|
||||
// the share mounts have changed if either the number of shares doesn't matched the number of share mounts
|
||||
// or there is a share for which we don't have a mount yet.
|
||||
$mountsChanged = count($shares) !== count($shareMounts);
|
||||
foreach ($shares as $share) {
|
||||
[$parentShare, $groupedShares] = $share;
|
||||
$mountPoint = $this->getMountPointFromTarget($user, $parentShare->getTarget());
|
||||
$mountKey = $parentShare->getNodeId() . '::' . $mountPoint;
|
||||
if (!isset($cachedMounts[$mountKey])) {
|
||||
$mountsChanged = true;
|
||||
$this->shareTargetValidator->verifyMountPoint($user, $parentShare, fn ($path) => $mountsByPath[$path] ?? null, $groupedShares);
|
||||
}
|
||||
}
|
||||
|
||||
if ($mountsChanged) {
|
||||
$newMounts = $this->shareMountProvider->getMountsFromSuperShares($user, $shares, $this->storageFactory);
|
||||
$this->userMountCache->registerMounts($user, $newMounts, [MountProvider::class]);
|
||||
}
|
||||
|
||||
unset($this->inUpdate[$user->getUID()]);
|
||||
}
|
||||
|
||||
public function isInUpdate(IUser $user): bool {
|
||||
return isset($this->inUpdate[$user->getUID()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a single received share for a user
|
||||
*/
|
||||
public function updateForAddedShare(IUser $user, IShare $share): void {
|
||||
$target = $this->shareTargetValidator->verifyMountPoint($user, $share, fn ($path) => $this->userMountCache->getMountAtPath($user, $path), [$share]);
|
||||
$mountPoint = $this->getMountPointFromTarget($user, $target);
|
||||
|
||||
$this->userMountCache->addMount($user, $mountPoint, $share->getNode()->getData(), MountProvider::class);
|
||||
}
|
||||
|
||||
private function getMountPointFromTarget(IUser $user, string $target): string {
|
||||
return '/' . $user->getUID() . '/files/' . trim($target, '/') . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single deleted share for a user
|
||||
*/
|
||||
public function updateForDeletedShare(IUser $user, IShare $share): void {
|
||||
try {
|
||||
$userShare = $this->shareManager->getShareById($share->getFullId(), $user->getUID(), false);
|
||||
$this->userMountCache->removeMount($this->getMountPointFromTarget($user, $userShare->getTarget()), $user);
|
||||
} catch (ShareNotFound) {
|
||||
// user doesn't actually have access to the share
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a single moved share for a user
|
||||
*/
|
||||
public function updateForMovedShare(IUser $user, IShare $share): void {
|
||||
$originalTarget = $share->getOriginalTarget();
|
||||
if ($originalTarget != null) {
|
||||
$newMountPoint = $this->getMountPointFromTarget($user, $share->getTarget());
|
||||
$oldMountPoint = $this->getMountPointFromTarget($user, $originalTarget);
|
||||
$this->userMountCache->removeMount($oldMountPoint, $user);
|
||||
$this->userMountCache->addMount($user, $newMountPoint, $share->getNode()->getData(), MountProvider::class);
|
||||
} else {
|
||||
$this->updateForUser($user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,11 +9,11 @@ declare(strict_types=1);
|
|||
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\Mount\IMountManager;
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\Events\VerifyMountPointEvent;
|
||||
|
|
@ -29,8 +29,7 @@ class ShareTargetValidator {
|
|||
public function __construct(
|
||||
private readonly IManager $shareManager,
|
||||
private readonly IEventDispatcher $eventDispatcher,
|
||||
private readonly SetupManager $setupManager,
|
||||
private readonly IMountManager $mountManager,
|
||||
private readonly IRootFolder $rootFolder,
|
||||
) {
|
||||
$this->folderExistsCache = new CappedMemoryCache();
|
||||
}
|
||||
|
|
@ -46,28 +45,30 @@ class ShareTargetValidator {
|
|||
/**
|
||||
* check if the parent folder exists otherwise move the mount point up
|
||||
*
|
||||
* @param array<string, IMountPoint> $allCachedMounts Other mounts for the user, indexed by path
|
||||
* @param callable(string):?ICachedMountInfo $getMountByPath
|
||||
* @param IShare[] $childShares
|
||||
* @return string
|
||||
*/
|
||||
public function verifyMountPoint(
|
||||
IUser $user,
|
||||
IShare &$share,
|
||||
array $allCachedMounts,
|
||||
callable $getMountByPath,
|
||||
array $childShares,
|
||||
): string {
|
||||
$mountPoint = basename($share->getTarget());
|
||||
$parent = dirname($share->getTarget());
|
||||
|
||||
$recipientView = $this->getViewForUser($user);
|
||||
$event = new VerifyMountPointEvent($share, $recipientView, $parent);
|
||||
$event = new VerifyMountPointEvent($share, $recipientView, $parent, $user);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
$parent = $event->getParent();
|
||||
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$absoluteParent = $recipientView->getAbsolutePath($parent);
|
||||
$this->setupManager->setupForPath($absoluteParent);
|
||||
$parentMount = $this->mountManager->find($absoluteParent);
|
||||
|
||||
// the share target always has to be in the users home
|
||||
$userFolder = $this->rootFolder->getUserFolder($user->getUID());
|
||||
$parentMount = $userFolder->getMountPoint();
|
||||
|
||||
$cached = $this->folderExistsCache->get($parent);
|
||||
if ($cached !== null) {
|
||||
|
|
@ -78,16 +79,22 @@ class ShareTargetValidator {
|
|||
$this->folderExistsCache->set($parent, $parentExists);
|
||||
}
|
||||
if (!$parentExists) {
|
||||
$parent = Helper::getShareFolder($recipientView, $user->getUID());
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$absoluteParent = $recipientView->getAbsolutePath($parent);
|
||||
if ($event->createParent()) {
|
||||
$internalPath = $parentMount->getInternalPath($absoluteParent);
|
||||
$parentMount->getStorage()->mkdir($internalPath);
|
||||
$parentMount->getStorage()->getUpdater()->update($internalPath);
|
||||
} else {
|
||||
$parent = Helper::getShareFolder($recipientView, $user->getUID());
|
||||
/** @psalm-suppress InternalMethod */
|
||||
$absoluteParent = $recipientView->getAbsolutePath($parent);
|
||||
}
|
||||
}
|
||||
|
||||
$newAbsoluteMountPoint = $this->generateUniqueTarget(
|
||||
$share->getNodeId(),
|
||||
Filesystem::normalizePath($absoluteParent . '/' . $mountPoint),
|
||||
$parentMount,
|
||||
$allCachedMounts,
|
||||
$getMountByPath,
|
||||
);
|
||||
|
||||
/** @psalm-suppress InternalMethod */
|
||||
|
|
@ -105,13 +112,13 @@ class ShareTargetValidator {
|
|||
|
||||
|
||||
/**
|
||||
* @param IMountPoint[] $allCachedMounts
|
||||
* @param callable(string):?ICachedMountInfo $getMountByPath
|
||||
*/
|
||||
public function generateUniqueTarget(
|
||||
int $shareNodeId,
|
||||
string $absolutePath,
|
||||
IMountPoint $parentMount,
|
||||
array $allCachedMounts,
|
||||
callable $getMountByPath,
|
||||
): string {
|
||||
$pathInfo = pathinfo($absolutePath);
|
||||
$ext = isset($pathInfo['extension']) ? '.' . $pathInfo['extension'] : '';
|
||||
|
|
@ -121,7 +128,7 @@ class ShareTargetValidator {
|
|||
$i = 2;
|
||||
$parentCache = $parentMount->getStorage()->getCache();
|
||||
$internalPath = $parentMount->getInternalPath($absolutePath);
|
||||
while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($shareNodeId, $allCachedMounts, $absolutePath)) {
|
||||
while ($parentCache->inCache($internalPath) || $this->hasConflictingMount($shareNodeId, $getMountByPath, $absolutePath)) {
|
||||
$absolutePath = Filesystem::normalizePath($dir . '/' . $name . ' (' . $i . ')' . $ext);
|
||||
$internalPath = $parentMount->getInternalPath($absolutePath);
|
||||
$i++;
|
||||
|
|
@ -131,15 +138,15 @@ class ShareTargetValidator {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param IMountPoint[] $allCachedMounts
|
||||
* @param callable(string):?ICachedMountInfo $getMountByPath
|
||||
*/
|
||||
private function hasConflictingMount(int $shareNodeId, array $allCachedMounts, string $absolutePath): bool {
|
||||
if (!isset($allCachedMounts[$absolutePath . '/'])) {
|
||||
private function hasConflictingMount(int $shareNodeId, callable $getMountByPath, string $absolutePath): bool {
|
||||
$mount = $getMountByPath($absolutePath . '/');
|
||||
if ($mount === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mount = $allCachedMounts[$absolutePath . '/'];
|
||||
if ($mount instanceof SharedMount && $mount->getShare()->getNodeId() === $shareNodeId) {
|
||||
if ($mount->getMountProvider() === MountProvider::class && $mount->getRootId() === $shareNodeId) {
|
||||
// "conflicting" mount is a mount for the current share
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ namespace OCA\Files_Sharing;
|
|||
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\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Events\InvalidateMountCacheEvent;
|
||||
|
|
@ -41,7 +40,6 @@ class SharedMount extends MountPoint implements MoveableMount, ISharedMountPoint
|
|||
$storage,
|
||||
$arguments,
|
||||
IStorageFactory $loader,
|
||||
private View $recipientView,
|
||||
private IEventDispatcher $eventDispatcher,
|
||||
private IUser $user,
|
||||
) {
|
||||
|
|
@ -188,4 +186,8 @@ class SharedMount extends MountPoint implements MoveableMount, ISharedMountPoint
|
|||
public function getMountType() {
|
||||
return 'shared';
|
||||
}
|
||||
|
||||
public function getUser(): IUser {
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -826,6 +826,8 @@ class ApiTest extends TestCase {
|
|||
$share3->setStatus(IShare::STATUS_ACCEPTED);
|
||||
$this->shareManager->updateShare($share3);
|
||||
|
||||
$this->logout();
|
||||
|
||||
// $request = $this->createRequest(['path' => $this->subfolder]);
|
||||
$ocs = $this->createOCS(self::TEST_FILES_SHARING_API_USER2);
|
||||
$result1 = $ocs->getShares('false', 'false', 'false', $this->subfolder);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Tests\Listener;
|
||||
|
||||
use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCA\Files_Sharing\Config\ConfigLexicon;
|
||||
use OCA\Files_Sharing\Listener\UserHomeSetupListener;
|
||||
use OCA\Files_Sharing\ShareRecipientUpdater;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\Files\Events\UserHomeSetupEvent;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\IUser;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\Mock\Config\MockUserConfig;
|
||||
use Test\TestCase;
|
||||
|
||||
class UserHomeSetupListenerTest extends TestCase {
|
||||
private ShareRecipientUpdater&MockObject $updater;
|
||||
private IUserConfig $userConfig;
|
||||
private UserHomeSetupListener $listener;
|
||||
private IUser $user;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->updater = $this->createMock(ShareRecipientUpdater::class);
|
||||
$this->userConfig = new MockUserConfig([]);
|
||||
$this->listener = new UserHomeSetupListener($this->updater, $this->userConfig);
|
||||
$this->user = $this->createMock(IUser::class);
|
||||
$this->user->method('getUID')
|
||||
->willReturn('test');
|
||||
}
|
||||
|
||||
private function getEvent(): UserHomeSetupEvent {
|
||||
$homeMount = $this->createMock(IMountPoint::class);
|
||||
return new UserHomeSetupEvent($this->user, $homeMount);
|
||||
}
|
||||
|
||||
public function testClearNeedsUpdate(): void {
|
||||
$this->userConfig->setValueBool('test', Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, true);
|
||||
$this->updater->expects($this->once())
|
||||
->method('updateForUser');
|
||||
|
||||
$this->listener->handle($this->getEvent());
|
||||
$this->assertFalse($this->userConfig->getValueBool('test', Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, true));
|
||||
}
|
||||
|
||||
public function testNoUpdateIfNotNeeded(): void {
|
||||
$this->userConfig->setValueBool('test', Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, false);
|
||||
$this->updater->expects($this->never())
|
||||
->method('updateForUser');
|
||||
|
||||
$this->listener->handle($this->getEvent());
|
||||
$this->assertFalse($this->userConfig->getValueBool('test', Application::APP_ID, ConfigLexicon::USER_NEEDS_SHARE_REFRESH, true));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
namespace OCA\Files_Sharing\Tests\Repair;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Migration\NullOutput;
|
||||
use OCA\Files_Sharing\Repair\CleanupShareTarget;
|
||||
use OCA\Files_Sharing\Tests\TestCase;
|
||||
|
|
@ -49,6 +50,7 @@ class CleanupShareTargetTest extends TestCase {
|
|||
|
||||
$share->setTarget($target);
|
||||
$this->shareManager->moveShare($share, self::TEST_FILES_SHARING_API_USER2);
|
||||
Filesystem::getMountManager()->moveMount('/' . self::TEST_FILES_SHARING_API_USER2 . '/files' . self::TEST_FOLDER_NAME . '/', '/' . self::TEST_FILES_SHARING_API_USER2 . '/files' . $target . '/');
|
||||
|
||||
$share = $this->shareManager->getShareById($share->getFullId());
|
||||
$this->assertEquals($target, $share->getTarget());
|
||||
|
|
|
|||
216
apps/files_sharing/tests/ShareRecipientUpdaterTest.php
Normal file
216
apps/files_sharing/tests/ShareRecipientUpdaterTest.php
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Tests;
|
||||
|
||||
use OCA\Files_Sharing\MountProvider;
|
||||
use OCA\Files_Sharing\ShareRecipientUpdater;
|
||||
use OCA\Files_Sharing\ShareTargetValidator;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\Config\IUserMountCache;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\Node;
|
||||
use OCP\Files\Storage\IStorageFactory;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\Traits\UserTrait;
|
||||
|
||||
class ShareRecipientUpdaterTest extends \Test\TestCase {
|
||||
use UserTrait;
|
||||
|
||||
private IUserMountCache&MockObject $userMountCache;
|
||||
private MountProvider&MockObject $shareMountProvider;
|
||||
private ShareTargetValidator&MockObject $shareTargetValidator;
|
||||
private IStorageFactory&MockObject $storageFactory;
|
||||
private ShareRecipientUpdater $updater;
|
||||
private IManager $shareManager;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->userMountCache = $this->createMock(IUserMountCache::class);
|
||||
$this->shareMountProvider = $this->createMock(MountProvider::class);
|
||||
$this->shareTargetValidator = $this->createMock(ShareTargetValidator::class);
|
||||
$this->storageFactory = $this->createMock(IStorageFactory::class);
|
||||
$this->shareManager = $this->createMock(IManager::class);
|
||||
|
||||
$this->updater = new ShareRecipientUpdater(
|
||||
$this->userMountCache,
|
||||
$this->shareMountProvider,
|
||||
$this->shareTargetValidator,
|
||||
$this->storageFactory,
|
||||
$this->shareManager,
|
||||
);
|
||||
}
|
||||
|
||||
public function testUpdateForShare() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$node = $this->createMock(Node::class);
|
||||
$cacheEntry = $this->createMock(ICacheEntry::class);
|
||||
$share->method('getNode')
|
||||
->willReturn($node);
|
||||
$node->method('getData')
|
||||
->willReturn($cacheEntry);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
|
||||
$this->userMountCache->method('getMountsForUser')
|
||||
->with($user1)
|
||||
->willReturn([]);
|
||||
|
||||
$this->shareTargetValidator->method('verifyMountPoint')
|
||||
->with($user1, $share, fn ($path) => null, [$share])
|
||||
->willReturn('/new-target');
|
||||
|
||||
$this->userMountCache->expects($this->exactly(1))
|
||||
->method('addMount')
|
||||
->with($user1, '/user1/files/new-target/', $cacheEntry, MountProvider::class);
|
||||
|
||||
$this->updater->updateForAddedShare($user1, $share);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IUser $user
|
||||
* @param list<array{fileid: int, mount_point: string, provider: string}> $mounts
|
||||
* @return void
|
||||
*/
|
||||
private function setCachedMounts(IUser $user, array $mounts) {
|
||||
$cachedMounts = array_map(function (array $mount): ICachedMountInfo {
|
||||
$cachedMount = $this->createMock(ICachedMountInfo::class);
|
||||
$cachedMount->method('getRootId')
|
||||
->willReturn($mount['fileid']);
|
||||
$cachedMount->method('getMountPoint')
|
||||
->willReturn($mount['mount_point']);
|
||||
$cachedMount->method('getMountProvider')
|
||||
->willReturn($mount['provider']);
|
||||
return $cachedMount;
|
||||
}, $mounts);
|
||||
$mountKeys = array_map(function (array $mount): string {
|
||||
return $mount['fileid'] . '::' . $mount['mount_point'];
|
||||
}, $mounts);
|
||||
|
||||
$this->userMountCache->method('getMountsForUser')
|
||||
->with($user)
|
||||
->willReturn(array_combine($mountKeys, $cachedMounts));
|
||||
}
|
||||
|
||||
public function testUpdateForUserAddedNoExisting() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$share->method('getTarget')
|
||||
->willReturn('/target');
|
||||
$share->method('getNodeId')
|
||||
->willReturn(111);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
$newMount = $this->createMock(IMountPoint::class);
|
||||
|
||||
$this->shareMountProvider->method('getSuperSharesForUser')
|
||||
->with($user1, [])
|
||||
->willReturn([[
|
||||
$share,
|
||||
[$share],
|
||||
]]);
|
||||
|
||||
$this->shareMountProvider->method('getMountsFromSuperShares')
|
||||
->with($user1, [[
|
||||
$share,
|
||||
[$share],
|
||||
]], $this->storageFactory)
|
||||
->willReturn([$newMount]);
|
||||
|
||||
$this->setCachedMounts($user1, []);
|
||||
|
||||
$this->shareTargetValidator->method('verifyMountPoint')
|
||||
->with($user1, $share, fn ($path) => null, [$share])
|
||||
->willReturn('/new-target');
|
||||
|
||||
$this->userMountCache->expects($this->exactly(1))
|
||||
->method('registerMounts')
|
||||
->with($user1, [$newMount], [MountProvider::class]);
|
||||
|
||||
$this->updater->updateForUser($user1);
|
||||
}
|
||||
|
||||
public function testUpdateForUserNoChanges() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$share->method('getTarget')
|
||||
->willReturn('/target');
|
||||
$share->method('getNodeId')
|
||||
->willReturn(111);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
|
||||
$this->shareMountProvider->method('getSuperSharesForUser')
|
||||
->with($user1, [])
|
||||
->willReturn([[
|
||||
$share,
|
||||
[$share],
|
||||
]]);
|
||||
|
||||
$this->setCachedMounts($user1, [
|
||||
['fileid' => 111, 'mount_point' => '/user1/files/target/', 'provider' => MountProvider::class],
|
||||
]);
|
||||
|
||||
$this->shareTargetValidator->expects($this->never())
|
||||
->method('verifyMountPoint');
|
||||
|
||||
$this->userMountCache->expects($this->never())
|
||||
->method('registerMounts');
|
||||
|
||||
$this->updater->updateForUser($user1);
|
||||
}
|
||||
|
||||
public function testUpdateForUserRemoved() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$share->method('getTarget')
|
||||
->willReturn('/target');
|
||||
$share->method('getNodeId')
|
||||
->willReturn(111);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
|
||||
$this->shareMountProvider->method('getSuperSharesForUser')
|
||||
->with($user1, [])
|
||||
->willReturn([]);
|
||||
|
||||
$this->setCachedMounts($user1, [
|
||||
['fileid' => 111, 'mount_point' => '/user1/files/target/', 'provider' => MountProvider::class],
|
||||
]);
|
||||
|
||||
$this->shareTargetValidator->expects($this->never())
|
||||
->method('verifyMountPoint');
|
||||
|
||||
$this->userMountCache->expects($this->exactly(1))
|
||||
->method('registerMounts')
|
||||
->with($user1, [], [MountProvider::class]);
|
||||
|
||||
$this->updater->updateForUser($user1);
|
||||
}
|
||||
|
||||
public function testDeletedShare() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$share->method('getTarget')
|
||||
->willReturn('/target');
|
||||
$share->method('getNodeId')
|
||||
->willReturn(111);
|
||||
$share->method('getFullId')
|
||||
->willReturn('id');
|
||||
$user1 = $this->createUser('user1', '');
|
||||
|
||||
$this->shareManager->method('getShareById')
|
||||
->with('id')
|
||||
->willReturn($share);
|
||||
|
||||
$this->shareTargetValidator->expects($this->never())
|
||||
->method('verifyMountPoint');
|
||||
|
||||
$this->userMountCache->expects($this->exactly(1))
|
||||
->method('removeMount')
|
||||
->with('/user1/files/target/');
|
||||
|
||||
$this->updater->updateForDeletedShare($user1, $share);
|
||||
}
|
||||
}
|
||||
193
apps/files_sharing/tests/ShareTargetValidatorTest.php
Normal file
193
apps/files_sharing/tests/ShareTargetValidatorTest.php
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Robin Appelman <robin@icewind.nl>
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Tests;
|
||||
|
||||
use OC\EventDispatcher\EventDispatcher;
|
||||
use OCA\Files_Sharing\ShareTargetValidator;
|
||||
use OCP\Constants;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Config\ICachedMountInfo;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IUser;
|
||||
use OCP\Server;
|
||||
use OCP\Share\Events\VerifyMountPointEvent;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\Group('DB')]
|
||||
class ShareTargetValidatorTest extends TestCase {
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
private ShareTargetValidator $targetValidator;
|
||||
|
||||
private IUser $user2;
|
||||
protected string $folder2;
|
||||
|
||||
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->eventDispatcher = new EventDispatcher(
|
||||
new SymfonyEventDispatcher(),
|
||||
Server::get(ContainerInterface::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
);
|
||||
$this->targetValidator = new ShareTargetValidator(
|
||||
Server::get(IManager::class),
|
||||
$this->eventDispatcher,
|
||||
Server::get(IRootFolder::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, fn ($path) => null, [$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, fn ($path) => null, [$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);
|
||||
$conflictingMounts = [
|
||||
'/' . $this->user2->getUID() . '/files' . $this->folder2 . '/' => $conflictingMount
|
||||
];
|
||||
$this->targetValidator->verifyMountPoint($this->user2, $share2, fn ($path) => $conflictingMounts[$path] ?? null, [$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);
|
||||
}
|
||||
/**
|
||||
* test if the parent folder is created if asked for
|
||||
*/
|
||||
public function testShareMountCreateParentFolder(): 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->eventDispatcher->addListener(VerifyMountPointEvent::class, function (VerifyMountPointEvent $event) {
|
||||
$event->setCreateParent(true);
|
||||
});
|
||||
$this->targetValidator->verifyMountPoint($this->user2, $share, fn ($path) => null, [$share]);
|
||||
|
||||
$share = $this->shareManager->getShareById($share->getFullId());
|
||||
$this->assertSame('/foo/bar' . $this->folder, $share->getTarget());
|
||||
$userFolder = $this->rootFolder->getUserFolder(self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($userFolder->nodeExists('/foo/bar'));
|
||||
|
||||
//cleanup
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$this->shareManager->deleteShare($share);
|
||||
$this->view->unlink($this->folder);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 @@ use OCP\Share\IShare;
|
|||
*/
|
||||
#[\PHPUnit\Framework\Attributes\Group(name: '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 @@ class SharedMountTest extends TestCase {
|
|||
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 @@ class SharedMountTest extends TestCase {
|
|||
$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 {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ namespace OCA\Files_Sharing\Tests;
|
|||
use OC\Files\Cache\FailedCache;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Storage\FailedStorage;
|
||||
use OC\Files\Storage\Storage;
|
||||
use OC\Files\Storage\Temporary;
|
||||
use OC\Files\View;
|
||||
use OCA\Files_Sharing\SharedStorage;
|
||||
|
|
@ -60,51 +59,6 @@ class SharedStorageTest extends TestCase {
|
|||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* if the parent of the mount point is gone then the mount point should move up
|
||||
*/
|
||||
public function testParentOfMountPointIsGone(): 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
|
||||
/** @var Storage $storage */
|
||||
[$storage, $internalPath] = Filesystem::resolvePath('/' . self::TEST_FILES_SHARING_API_USER2 . '/files/localfolder');
|
||||
$storage->rmdir($internalPath);
|
||||
|
||||
//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 testRenamePartFile(): void {
|
||||
|
||||
// share to user
|
||||
|
|
@ -466,57 +420,6 @@ class SharedStorageTest extends TestCase {
|
|||
$this->shareManager->deleteShare($share);
|
||||
}
|
||||
|
||||
public function testNameConflict(): void {
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$view1 = new View('/' . self::TEST_FILES_SHARING_API_USER1 . '/files');
|
||||
$view1->mkdir('foo');
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER3);
|
||||
$view3 = new View('/' . self::TEST_FILES_SHARING_API_USER3 . '/files');
|
||||
$view3->mkdir('foo');
|
||||
|
||||
// share a folder with the same name from two different users to the same user
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
|
||||
$share1 = $this->share(
|
||||
IShare::TYPE_GROUP,
|
||||
'foo',
|
||||
self::TEST_FILES_SHARING_API_USER1,
|
||||
self::TEST_FILES_SHARING_API_GROUP1,
|
||||
Constants::PERMISSION_ALL
|
||||
);
|
||||
$this->shareManager->acceptShare($share1, self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER3);
|
||||
|
||||
$share2 = $this->share(
|
||||
IShare::TYPE_GROUP,
|
||||
'foo',
|
||||
self::TEST_FILES_SHARING_API_USER3,
|
||||
self::TEST_FILES_SHARING_API_GROUP1,
|
||||
Constants::PERMISSION_ALL
|
||||
);
|
||||
$this->shareManager->acceptShare($share2, self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
$view2 = new View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
|
||||
$this->assertTrue($view2->file_exists('/foo'));
|
||||
$this->assertTrue($view2->file_exists('/foo (2)'));
|
||||
|
||||
$mount = $view2->getMount('/foo');
|
||||
$this->assertInstanceOf('\OCA\Files_Sharing\SharedMount', $mount);
|
||||
/** @var SharedStorage $storage */
|
||||
$storage = $mount->getStorage();
|
||||
|
||||
$this->assertEquals(self::TEST_FILES_SHARING_API_USER1, $storage->getOwner(''));
|
||||
|
||||
$this->shareManager->deleteShare($share1);
|
||||
$this->shareManager->deleteShare($share2);
|
||||
}
|
||||
|
||||
public function testOwnerPermissions(): void {
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
|
||||
|
|
|
|||
191
apps/files_sharing/tests/SharesUpdatedListenerTest.php
Normal file
191
apps/files_sharing/tests/SharesUpdatedListenerTest.php
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Tests;
|
||||
|
||||
use OCA\Files_Sharing\Config\ConfigLexicon;
|
||||
use OCA\Files_Sharing\Event\UserShareAccessUpdatedEvent;
|
||||
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
|
||||
use OCA\Files_Sharing\Listener\UserHomeSetupListener;
|
||||
use OCA\Files_Sharing\ShareRecipientUpdater;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\Events\BeforeShareDeletedEvent;
|
||||
use OCP\Share\Events\ShareCreatedEvent;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Clock\ClockInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\Mock\Config\MockAppConfig;
|
||||
use Test\Mock\Config\MockUserConfig;
|
||||
use Test\Traits\UserTrait;
|
||||
|
||||
class SharesUpdatedListenerTest extends \Test\TestCase {
|
||||
use UserTrait;
|
||||
|
||||
private SharesUpdatedListener $sharesUpdatedListener;
|
||||
private ShareRecipientUpdater&MockObject $shareRecipientUpdater;
|
||||
private IManager&MockObject $manager;
|
||||
private IUserConfig $userConfig;
|
||||
private IAppConfig $appConfig;
|
||||
private ClockInterface&MockObject $clock;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
private $clockFn;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->shareRecipientUpdater = $this->createMock(ShareRecipientUpdater::class);
|
||||
$this->manager = $this->createMock(IManager::class);
|
||||
$this->appConfig = new MockAppConfig([
|
||||
ConfigLexicon::UPDATE_CUTOFF_TIME => -1,
|
||||
]);
|
||||
$this->userConfig = new MockUserConfig();
|
||||
$this->clock = $this->createMock(ClockInterface::class);
|
||||
$this->clockFn = function () {
|
||||
return new \DateTimeImmutable('@0');
|
||||
};
|
||||
$this->clock->method('now')
|
||||
->willReturnCallback(function () {
|
||||
// extra wrapper so we can modify clockFn
|
||||
return ($this->clockFn)();
|
||||
});
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$homeSetupListener = new UserHomeSetupListener($this->shareRecipientUpdater, $this->userConfig);
|
||||
|
||||
$this->sharesUpdatedListener = new SharesUpdatedListener(
|
||||
$this->manager,
|
||||
$this->shareRecipientUpdater,
|
||||
$this->userConfig,
|
||||
$this->clock,
|
||||
$this->logger,
|
||||
$this->appConfig,
|
||||
$homeSetupListener,
|
||||
);
|
||||
}
|
||||
|
||||
public function testShareAdded() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
$user2 = $this->createUser('user2', '');
|
||||
|
||||
$this->manager->method('getUsersForShare')
|
||||
->willReturn([$user1, $user2]);
|
||||
|
||||
$event = new ShareCreatedEvent($share);
|
||||
|
||||
$this->shareRecipientUpdater
|
||||
->expects($this->exactly(2))
|
||||
->method('updateForAddedShare')
|
||||
->willReturnCallback(function (IUser $user, IShare $eventShare) use ($user1, $user2, $share) {
|
||||
$this->assertContains($user, [$user1, $user2]);
|
||||
$this->assertEquals($share, $eventShare);
|
||||
});
|
||||
|
||||
$this->sharesUpdatedListener->handle($event);
|
||||
}
|
||||
|
||||
public function testShareAddedFilterOwner() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
$user2 = $this->createUser('user2', '');
|
||||
$share->method('getSharedBy')
|
||||
->willReturn($user1->getUID());
|
||||
|
||||
$this->manager->method('getUsersForShare')
|
||||
->willReturn([$user1, $user2]);
|
||||
|
||||
$event = new ShareCreatedEvent($share);
|
||||
|
||||
$this->shareRecipientUpdater
|
||||
->expects($this->exactly(1))
|
||||
->method('updateForAddedShare')
|
||||
->willReturnCallback(function (IUser $user, IShare $eventShare) use ($user2, $share) {
|
||||
$this->assertEquals($user, $user2);
|
||||
$this->assertEquals($share, $eventShare);
|
||||
});
|
||||
|
||||
$this->sharesUpdatedListener->handle($event);
|
||||
}
|
||||
|
||||
public function testShareAccessUpdated() {
|
||||
$user1 = $this->createUser('user1', '');
|
||||
$user2 = $this->createUser('user2', '');
|
||||
|
||||
$event = new UserShareAccessUpdatedEvent([$user1, $user2]);
|
||||
|
||||
$this->shareRecipientUpdater
|
||||
->expects($this->exactly(2))
|
||||
->method('updateForUser')
|
||||
->willReturnCallback(function (IUser $user) use ($user1, $user2) {
|
||||
$this->assertContains($user, [$user1, $user2]);
|
||||
});
|
||||
|
||||
$this->sharesUpdatedListener->handle($event);
|
||||
}
|
||||
|
||||
public function testShareDeleted() {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
$user2 = $this->createUser('user2', '');
|
||||
|
||||
$this->manager->method('getUsersForShare')
|
||||
->willReturn([$user1, $user2]);
|
||||
|
||||
$event = new BeforeShareDeletedEvent($share);
|
||||
|
||||
$this->shareRecipientUpdater
|
||||
->expects($this->exactly(2))
|
||||
->method('updateForDeletedShare')
|
||||
->willReturnCallback(function (IUser $user) use ($user1, $user2, $share) {
|
||||
$this->assertContains($user, [$user1, $user2]);
|
||||
});
|
||||
|
||||
$this->sharesUpdatedListener->handle($event);
|
||||
}
|
||||
|
||||
public static function shareMarkAfterTimeProvider(): array {
|
||||
// note that each user will take exactly 1s in this test
|
||||
return [
|
||||
[0, 0],
|
||||
[0.9, 1],
|
||||
[1.1, 2],
|
||||
[-1, 2],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('shareMarkAfterTimeProvider')]
|
||||
public function testShareMarkAfterTime(float $cutOff, int $expectedCount) {
|
||||
$share = $this->createMock(IShare::class);
|
||||
$user1 = $this->createUser('user1', '');
|
||||
$user2 = $this->createUser('user2', '');
|
||||
|
||||
$this->manager->method('getUsersForShare')
|
||||
->willReturn([$user1, $user2]);
|
||||
|
||||
$event = new ShareCreatedEvent($share);
|
||||
|
||||
$this->sharesUpdatedListener->setCutOffMarkTime($cutOff);
|
||||
$time = 0;
|
||||
$this->clockFn = function () use (&$time) {
|
||||
$time++;
|
||||
return new \DateTimeImmutable('@' . $time);
|
||||
};
|
||||
|
||||
$this->shareRecipientUpdater
|
||||
->expects($this->exactly($expectedCount))
|
||||
->method('updateForAddedShare');
|
||||
|
||||
$this->sharesUpdatedListener->handle($event);
|
||||
|
||||
$this->assertEquals($expectedCount < 1, $this->userConfig->getValueBool($user1->getUID(), 'files_sharing', ConfigLexicon::USER_NEEDS_SHARE_REFRESH));
|
||||
$this->assertEquals($expectedCount < 2, $this->userConfig->getValueBool($user2->getUID(), 'files_sharing', ConfigLexicon::USER_NEEDS_SHARE_REFRESH));
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ use OC\SystemConfig;
|
|||
use OC\User\DisplayNameCache;
|
||||
use OCA\Files_Sharing\AppInfo\Application;
|
||||
use OCA\Files_Sharing\External\MountProvider as ExternalMountProvider;
|
||||
use OCA\Files_Sharing\Listener\SharesUpdatedListener;
|
||||
use OCA\Files_Sharing\MountProvider;
|
||||
use OCP\Files\Config\IMountProviderCollection;
|
||||
use OCP\Files\IRootFolder;
|
||||
|
|
@ -99,6 +100,8 @@ abstract class TestCase extends \Test\TestCase {
|
|||
$groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER4, 'group3');
|
||||
$groupBackend->addToGroup(self::TEST_FILES_SHARING_API_USER2, self::TEST_FILES_SHARING_API_GROUP1);
|
||||
Server::get(IGroupManager::class)->addBackend($groupBackend);
|
||||
|
||||
Server::get(SharesUpdatedListener::class)->setCutOffMarkTime(-1);
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
namespace OCA\Files_Trashbin\Trash;
|
||||
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\IUser;
|
||||
|
||||
|
|
@ -173,4 +174,8 @@ class TrashItem implements ITrashItem {
|
|||
public function getMetadata(): array {
|
||||
return $this->fileInfo->getMetadata();
|
||||
}
|
||||
|
||||
public function getData(): ICacheEntry {
|
||||
return $this->fileInfo->getData();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ class VersioningTest extends \Test\TestCase {
|
|||
\OC::registerShareHooks(Server::get(SystemConfig::class));
|
||||
\OC::$server->boot();
|
||||
|
||||
// ensure both users have an up-to-date state
|
||||
self::loginHelper(self::TEST_VERSIONS_USER2);
|
||||
self::loginHelper(self::TEST_VERSIONS_USER);
|
||||
$this->rootView = new View();
|
||||
if (!$this->rootView->file_exists(self::USERS_VERSIONS_ROOT)) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Client;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
|
@ -13,7 +14,6 @@ use Psr\Http\Message\ResponseInterface;
|
|||
require __DIR__ . '/autoload.php';
|
||||
|
||||
|
||||
|
||||
trait Sharing {
|
||||
use Provisioning;
|
||||
|
||||
|
|
@ -575,17 +575,17 @@ trait Sharing {
|
|||
$expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
|
||||
|
||||
if (!array_key_exists('uid_file_owner', $expectedFields)
|
||||
&& array_key_exists('uid_owner', $expectedFields)) {
|
||||
&& array_key_exists('uid_owner', $expectedFields)) {
|
||||
$expectedFields['uid_file_owner'] = $expectedFields['uid_owner'];
|
||||
}
|
||||
if (!array_key_exists('displayname_file_owner', $expectedFields)
|
||||
&& array_key_exists('displayname_owner', $expectedFields)) {
|
||||
&& array_key_exists('displayname_owner', $expectedFields)) {
|
||||
$expectedFields['displayname_file_owner'] = $expectedFields['displayname_owner'];
|
||||
}
|
||||
|
||||
if (array_key_exists('share_type', $expectedFields)
|
||||
&& $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
|
||||
&& array_key_exists('share_with', $expectedFields)) {
|
||||
&& $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
|
||||
&& array_key_exists('share_with', $expectedFields)) {
|
||||
if ($expectedFields['share_with'] === 'private_conversation') {
|
||||
$expectedFields['share_with'] = 'REGEXP /^private_conversation_[0-9a-f]{6}$/';
|
||||
} else {
|
||||
|
|
@ -791,4 +791,34 @@ trait Sharing {
|
|||
}
|
||||
return $sharees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^Share mounts for "([^"]*)" match$/
|
||||
*/
|
||||
public function checkShareMounts(string $user, ?TableNode $body) {
|
||||
if ($body instanceof TableNode) {
|
||||
$fd = $body->getRows();
|
||||
|
||||
$expected = [];
|
||||
foreach ($fd as $row) {
|
||||
$expected[] = $row[0];
|
||||
}
|
||||
$this->runOcc(['files:mount:list', '--output', 'json', '--cached-only', $user]);
|
||||
$mounts = json_decode($this->lastStdOut, true)['cached'];
|
||||
$shareMounts = array_filter($mounts, fn (array $data) => $data['provider'] === \OCA\Files_Sharing\MountProvider::class);
|
||||
$actual = array_values(array_map(fn (array $data) => $data['mountpoint'], $shareMounts));
|
||||
Assert::assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^Share mounts for "([^"]*)" are empty$/
|
||||
*/
|
||||
public function checkShareMountsEmpty(string $user) {
|
||||
$this->runOcc(['files:mount:list', '--output', 'json', '--cached-only', $user]);
|
||||
$mounts = json_decode($this->lastStdOut, true)['cached'];
|
||||
$shareMounts = array_filter($mounts, fn (array $data) => $data['provider'] === \OCA\Files_Sharing\MountProvider::class);
|
||||
$actual = array_values(array_map(fn (array $data) => $data['mountpoint'], $shareMounts));
|
||||
Assert::assertEquals([], $actual);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class SharingContext implements Context, SnippetAcceptingContext {
|
|||
$this->deleteServerConfig('core', 'shareapi_allow_federation_on_public_shares');
|
||||
$this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
|
||||
$this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
|
||||
$this->deleteServerConfig('files_sharing', 'update_cutoff_time');
|
||||
|
||||
$this->runOcc(['config:system:delete', 'share_folder']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1011,7 +1011,7 @@ trait WebDav {
|
|||
*/
|
||||
public function connectingToDavEndpoint() {
|
||||
try {
|
||||
$this->response = $this->makeDavRequest(null, 'PROPFIND', '', []);
|
||||
$this->response = $this->makeDavRequest($this->currentUser, 'PROPFIND', '', []);
|
||||
} catch (\GuzzleHttp\Exception\ClientException $e) {
|
||||
$this->response = $e->getResponse();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,10 +73,53 @@ Scenario: getting all shares of a file with reshares with link share with less p
|
|||
| item_type | file |
|
||||
| mimetype | text/plain |
|
||||
| storage_id | shared::/textfile0 (2).txt |
|
||||
| file_target | /textfile0.txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| share_with | user2 |
|
||||
| share_with_displayname | user2 |
|
||||
|
||||
Scenario: getting all shares of a file with a received share after revoking the resharing rights with delayed share check
|
||||
Given user "user0" exists
|
||||
And parameter "update_cutoff_time" of app "files_sharing" is set to "0"
|
||||
And user "user1" exists
|
||||
And user "user2" exists
|
||||
And file "textfile0.txt" of user "user1" is shared with user "user0"
|
||||
And user "user0" accepts last share
|
||||
And Updating last share with
|
||||
| permissions | 1 |
|
||||
And file "textfile0.txt" of user "user1" is shared with user "user2"
|
||||
When As an "user0"
|
||||
And sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=/textfile0 (2).txt"
|
||||
Then the list of returned shares has 1 shares
|
||||
And share 0 is returned with
|
||||
| share_type | 0 |
|
||||
| uid_owner | user1 |
|
||||
| displayname_owner | user1 |
|
||||
| path | /textfile0 (2).txt |
|
||||
| item_type | file |
|
||||
| mimetype | text/plain |
|
||||
| storage_id | shared::/textfile0 (2).txt |
|
||||
| file_target | /textfile0.txt |
|
||||
| share_with | user2 |
|
||||
| share_with_displayname | user2 |
|
||||
# After user2 does an FS setup the share is renamed
|
||||
When As an "user2"
|
||||
And Downloading file "/textfile0 (2).txt" with range "bytes=10-18"
|
||||
Then Downloaded content should be "test text"
|
||||
When As an "user0"
|
||||
And sending "GET" to "/apps/files_sharing/api/v1/shares?reshares=true&path=/textfile0 (2).txt"
|
||||
Then the list of returned shares has 1 shares
|
||||
And share 0 is returned with
|
||||
| share_type | 0 |
|
||||
| uid_owner | user1 |
|
||||
| displayname_owner | user1 |
|
||||
| path | /textfile0 (2).txt |
|
||||
| item_type | file |
|
||||
| mimetype | text/plain |
|
||||
| storage_id | shared::/textfile0 (2).txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| share_with | user2 |
|
||||
| share_with_displayname | user2 |
|
||||
|
||||
Scenario: getting all shares of a file with a received share also reshared after revoking the resharing rights
|
||||
Given user "user0" exists
|
||||
And user "user1" exists
|
||||
|
|
@ -114,7 +157,7 @@ Scenario: getting all shares of a file with reshares with link share with less p
|
|||
| item_type | file |
|
||||
| mimetype | text/plain |
|
||||
| storage_id | shared::/textfile0 (2).txt |
|
||||
| file_target | /textfile0.txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| share_with | user2 |
|
||||
| share_with_displayname | user2 |
|
||||
|
||||
|
|
@ -150,7 +193,7 @@ Scenario: getting all shares of a file with reshares with link share with less p
|
|||
| share_type | 0 |
|
||||
| share_with | user1 |
|
||||
| file_source | A_NUMBER |
|
||||
| file_target | /textfile0.txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| path | /textfile0.txt |
|
||||
| permissions | 19 |
|
||||
| stime | A_NUMBER |
|
||||
|
|
@ -431,7 +474,7 @@ Scenario: getting all shares of a file with reshares with link share with less p
|
|||
| item_type | file |
|
||||
| mimetype | text/plain |
|
||||
| storage_id | shared::/FOLDER/textfile0.txt |
|
||||
| file_target | /textfile0.txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| share_with | user2 |
|
||||
| share_with_displayname | user2 |
|
||||
|
||||
|
|
@ -470,7 +513,7 @@ Scenario: getting all shares of a file with reshares with link share with less p
|
|||
| item_type | file |
|
||||
| mimetype | text/plain |
|
||||
| storage_id | shared::/FOLDER/textfile0 (2).txt |
|
||||
| file_target | /textfile0.txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| share_with | user2 |
|
||||
| share_with_displayname | user2 |
|
||||
|
||||
|
|
@ -917,7 +960,7 @@ Scenario: getting all shares of a file with reshares with link share with less p
|
|||
| share_type | 0 |
|
||||
| share_with | user2 |
|
||||
| file_source | A_NUMBER |
|
||||
| file_target | /textfile0.txt |
|
||||
| file_target | /textfile0 (2).txt |
|
||||
| path | /textfile0 (2).txt |
|
||||
| permissions | 19 |
|
||||
| stime | A_NUMBER |
|
||||
|
|
|
|||
|
|
@ -315,3 +315,114 @@ Scenario: Can copy file between shares if share permissions
|
|||
And the OCS status code should be "100"
|
||||
When User "user1" copies file "/share/test.txt" to "/re-share/movetest.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
|
||||
Scenario: Group deletes removes mount without marking
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group0" exists
|
||||
And user "user0" belongs to group "group0"
|
||||
And file "textfile0.txt" of user "user1" is shared with group "group0"
|
||||
And As an "user0"
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
And group "group0" does not exist
|
||||
Then Share mounts for "user0" are empty
|
||||
|
||||
Scenario: Group deletes removes mount with marking
|
||||
Given As an "admin"
|
||||
And parameter "update_cutoff_time" of app "files_sharing" is set to "0"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group0" exists
|
||||
And user "user0" belongs to group "group0"
|
||||
And file "textfile0.txt" of user "user1" is shared with group "group0"
|
||||
And As an "user0"
|
||||
Then Share mounts for "user0" are empty
|
||||
When Connecting to dav endpoint
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
And group "group0" does not exist
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When Connecting to dav endpoint
|
||||
Then Share mounts for "user0" are empty
|
||||
|
||||
Scenario: User share mount without marking
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And file "textfile0.txt" of user "user1" is shared with user "user0"
|
||||
And As an "user0"
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When Deleting last share
|
||||
Then Share mounts for "user0" are empty
|
||||
|
||||
Scenario: User share mount with marking
|
||||
Given As an "admin"
|
||||
And parameter "update_cutoff_time" of app "files_sharing" is set to "0"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And file "textfile0.txt" of user "user1" is shared with user "user0"
|
||||
And As an "user0"
|
||||
Then Share mounts for "user0" are empty
|
||||
When Connecting to dav endpoint
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When Deleting last share
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When Connecting to dav endpoint
|
||||
Then Share mounts for "user0" are empty
|
||||
|
||||
Scenario: User added/removed to group share without marking
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group0" exists
|
||||
And file "textfile0.txt" of user "user1" is shared with group "group0"
|
||||
And As an "user0"
|
||||
Then Share mounts for "user0" are empty
|
||||
When user "user0" belongs to group "group0"
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When As an "admin"
|
||||
Then sending "DELETE" to "/cloud/users/user0/groups" with
|
||||
| groupid | group0 |
|
||||
Then As an "user0"
|
||||
And Share mounts for "user0" are empty
|
||||
|
||||
Scenario: User added/removed to group share with marking
|
||||
Given As an "admin"
|
||||
And parameter "update_cutoff_time" of app "files_sharing" is set to "0"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group0" exists
|
||||
And file "textfile0.txt" of user "user1" is shared with group "group0"
|
||||
And As an "user0"
|
||||
When user "user0" belongs to group "group0"
|
||||
Then Share mounts for "user0" are empty
|
||||
When Connecting to dav endpoint
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When As an "admin"
|
||||
Then sending "DELETE" to "/cloud/users/user0/groups" with
|
||||
| groupid | group0 |
|
||||
Then As an "user0"
|
||||
And Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When Connecting to dav endpoint
|
||||
Then Share mounts for "user0" are empty
|
||||
|
||||
Scenario: Share moved without marking
|
||||
Given As an "admin"
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And file "textfile0.txt" of user "user1" is shared with user "user0"
|
||||
And As an "user0"
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/textfile0 (2).txt/ |
|
||||
When User "user0" moves file "/textfile0 (2).txt" to "/target.txt"
|
||||
Then Share mounts for "user0" match
|
||||
| /user0/files/target.txt/ |
|
||||
|
|
|
|||
|
|
@ -559,6 +559,8 @@ Feature: sharing
|
|||
Scenario: getting all shares of a user using that user
|
||||
Given user "user0" exists
|
||||
And user "user1" exists
|
||||
When User "user1" deletes file "/textfile0.txt"
|
||||
And the HTTP status code should be "204"
|
||||
And file "textfile0.txt" of user "user0" is shared with user "user1"
|
||||
And As an "user0"
|
||||
When sending "GET" to "/apps/files_sharing/api/v1/shares"
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@ return array(
|
|||
'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeRenamedEvent.php',
|
||||
'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeTouchedEvent.php',
|
||||
'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => $baseDir . '/lib/public/Files/Events/Node/NodeWrittenEvent.php',
|
||||
'OCP\\Files\\Events\\UserHomeSetupEvent' => $baseDir . '/lib/public/Files/Events/UserHomeSetupEvent.php',
|
||||
'OCP\\Files\\File' => $baseDir . '/lib/public/Files/File.php',
|
||||
'OCP\\Files\\FileInfo' => $baseDir . '/lib/public/Files/FileInfo.php',
|
||||
'OCP\\Files\\FileNameTooLongException' => $baseDir . '/lib/public/Files/FileNameTooLongException.php',
|
||||
|
|
@ -846,6 +847,8 @@ return array(
|
|||
'OCP\\Share\\Events\\ShareCreatedEvent' => $baseDir . '/lib/public/Share/Events/ShareCreatedEvent.php',
|
||||
'OCP\\Share\\Events\\ShareDeletedEvent' => $baseDir . '/lib/public/Share/Events/ShareDeletedEvent.php',
|
||||
'OCP\\Share\\Events\\ShareDeletedFromSelfEvent' => $baseDir . '/lib/public/Share/Events/ShareDeletedFromSelfEvent.php',
|
||||
'OCP\\Share\\Events\\ShareMovedEvent' => $baseDir . '/lib/public/Share/Events/ShareMovedEvent.php',
|
||||
'OCP\\Share\\Events\\ShareTransferredEvent' => $baseDir . '/lib/public/Share/Events/ShareTransferredEvent.php',
|
||||
'OCP\\Share\\Events\\VerifyMountPointEvent' => $baseDir . '/lib/public/Share/Events/VerifyMountPointEvent.php',
|
||||
'OCP\\Share\\Exceptions\\AlreadySharedException' => $baseDir . '/lib/public/Share/Exceptions/AlreadySharedException.php',
|
||||
'OCP\\Share\\Exceptions\\GenericShareException' => $baseDir . '/lib/public/Share/Exceptions/GenericShareException.php',
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Files\\Events\\Node\\NodeRenamedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeRenamedEvent.php',
|
||||
'OCP\\Files\\Events\\Node\\NodeTouchedEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeTouchedEvent.php',
|
||||
'OCP\\Files\\Events\\Node\\NodeWrittenEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/Node/NodeWrittenEvent.php',
|
||||
'OCP\\Files\\Events\\UserHomeSetupEvent' => __DIR__ . '/../../..' . '/lib/public/Files/Events/UserHomeSetupEvent.php',
|
||||
'OCP\\Files\\File' => __DIR__ . '/../../..' . '/lib/public/Files/File.php',
|
||||
'OCP\\Files\\FileInfo' => __DIR__ . '/../../..' . '/lib/public/Files/FileInfo.php',
|
||||
'OCP\\Files\\FileNameTooLongException' => __DIR__ . '/../../..' . '/lib/public/Files/FileNameTooLongException.php',
|
||||
|
|
@ -887,6 +888,8 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OCP\\Share\\Events\\ShareCreatedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareCreatedEvent.php',
|
||||
'OCP\\Share\\Events\\ShareDeletedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareDeletedEvent.php',
|
||||
'OCP\\Share\\Events\\ShareDeletedFromSelfEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareDeletedFromSelfEvent.php',
|
||||
'OCP\\Share\\Events\\ShareMovedEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareMovedEvent.php',
|
||||
'OCP\\Share\\Events\\ShareTransferredEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/ShareTransferredEvent.php',
|
||||
'OCP\\Share\\Events\\VerifyMountPointEvent' => __DIR__ . '/../../..' . '/lib/public/Share/Events/VerifyMountPointEvent.php',
|
||||
'OCP\\Share\\Exceptions\\AlreadySharedException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/AlreadySharedException.php',
|
||||
'OCP\\Share\\Exceptions\\GenericShareException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/GenericShareException.php',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace OC\Files\Config;
|
||||
|
||||
use OC\User\LazyUser;
|
||||
|
|
@ -32,11 +33,13 @@ class UserMountCache implements IUserMountCache {
|
|||
|
||||
/**
|
||||
* Cached mount info.
|
||||
*
|
||||
* @var CappedMemoryCache<ICachedMountInfo[]>
|
||||
**/
|
||||
private CappedMemoryCache $mountsForUsers;
|
||||
/**
|
||||
* fileid => internal path mapping for cached mount info.
|
||||
*
|
||||
* @var CappedMemoryCache<string>
|
||||
**/
|
||||
private CappedMemoryCache $internalPathCache;
|
||||
|
|
@ -72,7 +75,9 @@ class UserMountCache implements IUserMountCache {
|
|||
|
||||
$cachedMounts = $this->getMountsForUser($user);
|
||||
if (is_array($mountProviderClasses)) {
|
||||
$cachedMounts = array_filter($cachedMounts, function (ICachedMountInfo $mountInfo) use ($mountProviderClasses, $newMounts) {
|
||||
$cachedMounts = array_filter($cachedMounts, function (
|
||||
ICachedMountInfo $mountInfo,
|
||||
) use ($mountProviderClasses, $newMounts) {
|
||||
// for existing mounts that didn't have a mount provider set
|
||||
// we still want the ones that map to new mounts
|
||||
if ($mountInfo->getMountProvider() === '' && isset($newMounts[$mountInfo->getKey()])) {
|
||||
|
|
@ -482,21 +487,13 @@ class UserMountCache implements IUserMountCache {
|
|||
}
|
||||
|
||||
public function getMountForPath(IUser $user, string $path): ICachedMountInfo {
|
||||
$mounts = [];
|
||||
foreach ($this->getMountsForUser($user) as $mount) {
|
||||
$mounts[$mount->getMountPoint()] = $mount;
|
||||
}
|
||||
|
||||
$searchPaths = [];
|
||||
$current = rtrim($path, '/');
|
||||
// walk up the directory tree until we find a path that has a mountpoint set
|
||||
// the loop will return if a mountpoint is found or break if none are found
|
||||
while (true) {
|
||||
// get all paths that we are interested in, $path and all it's parents
|
||||
while ($current !== '') {
|
||||
$mountPoint = $current . '/';
|
||||
if (isset($mounts[$mountPoint])) {
|
||||
return $mounts[$mountPoint];
|
||||
} elseif ($current === '') {
|
||||
break;
|
||||
}
|
||||
|
||||
$searchPaths[] = $mountPoint;
|
||||
|
||||
$current = dirname($current);
|
||||
if ($current === '.' || $current === '/') {
|
||||
|
|
@ -504,6 +501,34 @@ class UserMountCache implements IUserMountCache {
|
|||
}
|
||||
}
|
||||
|
||||
$mounts = [];
|
||||
if (isset($this->mountsForUsers[$user->getUID()])) {
|
||||
foreach ($this->mountsForUsers[$user->getUID()] as $mount) {
|
||||
$mounts[$mount->getMountPoint()] = $mount;
|
||||
}
|
||||
} else {
|
||||
$searchPathHashes = array_map(static fn (string $path) => hash('xxh128', $path), $searchPaths);
|
||||
|
||||
$builder = $this->connection->getQueryBuilder();
|
||||
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
|
||||
->from('mounts', 'm')
|
||||
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
|
||||
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())))
|
||||
->andWhere($builder->expr()->in('mount_point_hash', $builder->createNamedParameter($searchPathHashes, IQueryBuilder::PARAM_STR_ARRAY)));
|
||||
|
||||
foreach ($query->executeQuery()->fetchAll() as $row) {
|
||||
$mount = $this->dbRowToMountInfo($row);
|
||||
$mounts[$mount->getMountPoint()] = $mount;
|
||||
}
|
||||
}
|
||||
|
||||
// note that $searchPaths is sorted deepest path first
|
||||
foreach ($searchPaths as $searchPath) {
|
||||
if (isset($mounts[$searchPath])) {
|
||||
return $mounts[$searchPath];
|
||||
}
|
||||
}
|
||||
|
||||
throw new NotFoundException('No cached mount for path ' . $path);
|
||||
}
|
||||
|
||||
|
|
@ -519,14 +544,29 @@ class UserMountCache implements IUserMountCache {
|
|||
return $result;
|
||||
}
|
||||
|
||||
public function removeMount(string $mountPoint): void {
|
||||
public function removeMount(string $mountPoint, ?IUser $user = null): void {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->delete('mounts')
|
||||
->where($query->expr()->eq('mount_point_hash', $query->createNamedParameter(hash('xxh128', $mountPoint))));
|
||||
if ($user) {
|
||||
$query->andWhere($query->expr()->eq('user_id', $query->createNamedParameter($user->getUID())));
|
||||
}
|
||||
$query->executeStatement();
|
||||
|
||||
$parts = explode('/', $mountPoint);
|
||||
if (count($parts) > 3) {
|
||||
[, $userId] = $parts;
|
||||
unset($this->mountsForUsers[$userId]);
|
||||
}
|
||||
}
|
||||
|
||||
public function addMount(IUser $user, string $mountPoint, ICacheEntry $rootCacheEntry, string $mountProvider, ?int $mountId = null): void {
|
||||
public function addMount(
|
||||
IUser $user,
|
||||
string $mountPoint,
|
||||
ICacheEntry $rootCacheEntry,
|
||||
string $mountProvider,
|
||||
?int $mountId = null,
|
||||
): void {
|
||||
$this->connection->insertIgnoreConflict('mounts', [
|
||||
'storage_id' => $rootCacheEntry->getStorageId(),
|
||||
'root_id' => $rootCacheEntry->getId(),
|
||||
|
|
@ -536,5 +576,37 @@ class UserMountCache implements IUserMountCache {
|
|||
'mount_id' => $mountId,
|
||||
'mount_provider_class' => $mountProvider
|
||||
]);
|
||||
unset($this->mountsForUsers[$user->getUID()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the internal in-memory caches
|
||||
*/
|
||||
public function flush(): void {
|
||||
$this->cacheInfoCache = new CappedMemoryCache();
|
||||
$this->internalPathCache = new CappedMemoryCache();
|
||||
$this->mountsForUsers = new CappedMemoryCache();
|
||||
}
|
||||
|
||||
public function getMountAtPath(IUser $user, string $mountPoint): ?ICachedMountInfo {
|
||||
if (isset($this->mountsForUsers[$user->getUID()])) {
|
||||
foreach ($this->mountsForUsers[$user->getUID()] as $mount) {
|
||||
if ($mount->getMountPoint() === $mountPoint) {
|
||||
return $mount;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$builder = $this->connection->getQueryBuilder();
|
||||
$query = $builder->select('storage_id', 'root_id', 'user_id', 'mount_point', 'mount_id', 'f.path', 'mount_provider_class')
|
||||
->from('mounts', 'm')
|
||||
->innerJoin('m', 'filecache', 'f', $builder->expr()->eq('m.root_id', 'f.fileid'))
|
||||
->where($builder->expr()->eq('user_id', $builder->createNamedParameter($user->getUID())))
|
||||
->andWhere($builder->expr()->eq('mount_point_hash', $builder->createNamedParameter(hash('xxh128', $mountPoint))))
|
||||
->setMaxResults(1);
|
||||
|
||||
$row = $query->executeQuery()->fetch();
|
||||
return $row ? $this->dbRowToMountInfo($row) : null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
namespace OC\Files;
|
||||
|
||||
use OC\Files\Cache\CacheEntry;
|
||||
use OC\Files\Mount\HomeMountPoint;
|
||||
use OCA\Files_Sharing\External\Mount;
|
||||
use OCA\Files_Sharing\ISharedMountPoint;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
|
|
@ -223,8 +223,12 @@ class FileInfo implements \OCP\Files\FileInfo, \ArrayAccess {
|
|||
return $this->data['type'];
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
public function getData(): ICacheEntry {
|
||||
if ($this->data instanceof ICacheEntry) {
|
||||
return $this->data;
|
||||
} else {
|
||||
return new CacheEntry($this->data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -59,11 +59,14 @@ class Manager implements IMountManager {
|
|||
}
|
||||
|
||||
public function moveMount(string $mountPoint, string $target): void {
|
||||
$this->mounts[$target] = $this->mounts[$mountPoint];
|
||||
unset($this->mounts[$mountPoint]);
|
||||
$this->pathCache->clear();
|
||||
$this->inPathCache->clear();
|
||||
$this->areMountsSorted = false;
|
||||
if ($mountPoint !== $target && isset($this->mounts[$mountPoint])) {
|
||||
$this->mounts[$target] = $this->mounts[$mountPoint];
|
||||
$this->mounts[$target]->setMountPoint($target);
|
||||
unset($this->mounts[$mountPoint]);
|
||||
$this->pathCache->clear();
|
||||
$this->inPathCache->clear();
|
||||
$this->areMountsSorted = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace OC\Files\Node;
|
|||
use OC\Files\Filesystem;
|
||||
use OC\Files\Utils\PathHelper;
|
||||
use OCP\Constants;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\Folder;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
|
|
@ -572,6 +573,10 @@ class LazyFolder implements Folder {
|
|||
return $this->data['metadata'] ?? $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
public function getData(): ICacheEntry {
|
||||
return $this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
||||
public function verifyPath($fileName, $readonly = false): void {
|
||||
$this->__call(__FUNCTION__, func_get_args());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use OC\Files\Mount\MoveableMount;
|
|||
use OC\Files\Utils\PathHelper;
|
||||
use OCP\EventDispatcher\GenericEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\IRootFolder;
|
||||
|
|
@ -490,4 +491,8 @@ class Node implements INode {
|
|||
public function getMetadata(): array {
|
||||
return $this->fileInfo->getMetadata();
|
||||
}
|
||||
|
||||
public function getData(): ICacheEntry {
|
||||
return $this->fileInfo->getData();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ use OCP\Files\Events\BeforeFileSystemSetupEvent;
|
|||
use OCP\Files\Events\InvalidateMountCacheEvent;
|
||||
use OCP\Files\Events\Node\BeforeNodeRenamedEvent;
|
||||
use OCP\Files\Events\Node\FilesystemTornDownEvent;
|
||||
use OCP\Files\Events\UserHomeSetupEvent;
|
||||
use OCP\Files\Mount\IMountManager;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\Files\NotFoundException;
|
||||
|
|
@ -69,6 +70,8 @@ class SetupManager {
|
|||
private array $setupUsers = [];
|
||||
// List of users for which all mounts are setup
|
||||
private array $setupUsersComplete = [];
|
||||
// List of users for which we've already refreshed the non-authoritative mounts
|
||||
private array $usersMountsUpdated = [];
|
||||
/**
|
||||
* An array of provider classes that have been set up, indexed by UserUID.
|
||||
*
|
||||
|
|
@ -233,6 +236,10 @@ class SetupManager {
|
|||
* Update the cached mounts for all non-authoritative mount providers for a user.
|
||||
*/
|
||||
private function updateNonAuthoritativeProviders(IUser $user): void {
|
||||
if (isset($this->usersMountsUpdated[$user->getUID()])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent recursion loop from when getting mounts from providers ends up setting up the filesystem
|
||||
static $updatingProviders = false;
|
||||
if ($updatingProviders) {
|
||||
|
|
@ -253,6 +260,7 @@ class SetupManager {
|
|||
$mount = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providerNames);
|
||||
$this->userMountCache->registerMounts($user, $mount, $providerNames);
|
||||
|
||||
$this->usersMountsUpdated[$user->getUID()] = true;
|
||||
$updatingProviders = false;
|
||||
}
|
||||
|
||||
|
|
@ -325,6 +333,9 @@ class SetupManager {
|
|||
$this->eventLogger->end('fs:setup:user:home:scan');
|
||||
}
|
||||
$this->eventLogger->end('fs:setup:user:home');
|
||||
|
||||
$event = new UserHomeSetupEvent($user, $homeMount);
|
||||
$this->eventDispatcher->dispatchTyped($event);
|
||||
} else {
|
||||
$this->mountManager->addMount(new MountPoint(
|
||||
new NullStorage([]),
|
||||
|
|
@ -685,8 +696,13 @@ class SetupManager {
|
|||
}
|
||||
|
||||
if (!$providersAreAuthoritative && $this->fullSetupRequired($user)) {
|
||||
$this->setupForUser($user);
|
||||
return;
|
||||
if ($this->optimizeAuthoritativeProviders) {
|
||||
$this->updateNonAuthoritativeProviders($user);
|
||||
$this->markUserMountsCached($user);
|
||||
} else {
|
||||
$this->setupForUser($user);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
|
||||
|
|
@ -730,6 +746,7 @@ class SetupManager {
|
|||
$this->setupUserMountProviders = [];
|
||||
$this->setupMountProviderPaths = [];
|
||||
$this->fullSetupRequired = [];
|
||||
$this->usersMountsUpdated = [];
|
||||
$this->rootSetup = false;
|
||||
$this->mountManager->clear();
|
||||
$this->userMountCache->clear();
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ use OCP\Share\Events\ShareAcceptedEvent;
|
|||
use OCP\Share\Events\ShareCreatedEvent;
|
||||
use OCP\Share\Events\ShareDeletedEvent;
|
||||
use OCP\Share\Events\ShareDeletedFromSelfEvent;
|
||||
use OCP\Share\Events\ShareMovedEvent;
|
||||
use OCP\Share\Exceptions\AlreadySharedException;
|
||||
use OCP\Share\Exceptions\GenericShareException;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
|
|
@ -1176,13 +1177,16 @@ class Manager implements IManager {
|
|||
if ($share->getShareType() === IShare::TYPE_USER && $share->getSharedWith() !== $recipientId) {
|
||||
throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
|
||||
}
|
||||
$recipient = $this->userManager->get($recipientId);
|
||||
if (!$recipient) {
|
||||
throw new \InvalidArgumentException($this->l->t('Unknown share recipient'));
|
||||
}
|
||||
|
||||
if ($share->getShareType() === IShare::TYPE_GROUP) {
|
||||
$sharedWith = $this->groupManager->get($share->getSharedWith());
|
||||
if (is_null($sharedWith)) {
|
||||
throw new \InvalidArgumentException($this->l->t('Group "%s" does not exist', [$share->getSharedWith()]));
|
||||
}
|
||||
$recipient = $this->userManager->get($recipientId);
|
||||
if (!$sharedWith->inGroup($recipient)) {
|
||||
throw new \InvalidArgumentException($this->l->t('Invalid share recipient'));
|
||||
}
|
||||
|
|
@ -1191,7 +1195,11 @@ class Manager implements IManager {
|
|||
[$providerId,] = $this->splitFullId($share->getFullId());
|
||||
$provider = $this->factory->getProvider($providerId);
|
||||
|
||||
return $provider->move($share, $recipientId);
|
||||
$result = $provider->move($share, $recipientId);
|
||||
|
||||
$this->dispatchEvent(new ShareMovedEvent($share, $recipient), 'share moved');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ class Share implements IShare {
|
|||
private ?int $parent = null;
|
||||
/** @var string */
|
||||
private $target;
|
||||
/** @var string */
|
||||
private ?string $originalTarget = null;
|
||||
/** @var \DateTime */
|
||||
private $shareTime;
|
||||
/** @var bool */
|
||||
|
|
@ -539,10 +541,21 @@ class Share implements IShare {
|
|||
* @inheritdoc
|
||||
*/
|
||||
public function setTarget($target) {
|
||||
// if the target is changed, save the original target
|
||||
if ($this->target && !$this->originalTarget) {
|
||||
$this->originalTarget = $this->target;
|
||||
}
|
||||
$this->target = $target;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the original target, if this share was moved
|
||||
*/
|
||||
public function getOriginalTarget(): ?string {
|
||||
return $this->originalTarget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -113,7 +113,10 @@ interface IUserMountCache {
|
|||
public function clear(): void;
|
||||
|
||||
/**
|
||||
* Get all cached mounts for a user
|
||||
* Get the cached mount for a path
|
||||
*
|
||||
* This walks up the directly tree until a mount is found, if you only want
|
||||
* to get the mount at the specific path, use `getMountAtPath` instead.
|
||||
*
|
||||
* @param IUser $user
|
||||
* @param string $path
|
||||
|
|
@ -139,7 +142,7 @@ interface IUserMountCache {
|
|||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function removeMount(string $mountPoint): void;
|
||||
public function removeMount(string $mountPoint, ?IUser $user = null): void;
|
||||
|
||||
/**
|
||||
* Register a new mountpoint for a user
|
||||
|
|
@ -147,4 +150,11 @@ interface IUserMountCache {
|
|||
* @since 33.0.0
|
||||
*/
|
||||
public function addMount(IUser $user, string $mountPoint, ICacheEntry $rootCacheEntry, string $mountProvider, ?int $mountId = null): void;
|
||||
|
||||
/**
|
||||
* Get the mount at the specified path, if any
|
||||
*
|
||||
* @since 33.0.2
|
||||
*/
|
||||
public function getMountAtPath(IUser $user, string $mountPoint): ?ICachedMountInfo;
|
||||
}
|
||||
|
|
|
|||
46
lib/public/Files/Events/UserHomeSetupEvent.php
Normal file
46
lib/public/Files/Events/UserHomeSetupEvent.php
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCP\Files\Events;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\Files\Mount\IMountPoint;
|
||||
use OCP\IUser;
|
||||
|
||||
/**
|
||||
* Event triggered after the users home mount has been setup, before any other
|
||||
* mounts are setup.
|
||||
*
|
||||
* @since 34.0.0
|
||||
*/
|
||||
class UserHomeSetupEvent extends Event {
|
||||
/**
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly IUser $user,
|
||||
private readonly IMountPoint $homeMount,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getUser(): IUser {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getHomeMount(): IMountPoint {
|
||||
return $this->homeMount;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
namespace OCP\Files;
|
||||
|
||||
use OCP\AppFramework\Attribute\Consumable;
|
||||
use OCP\Files\Cache\ICacheEntry;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
|
||||
/**
|
||||
|
|
@ -308,4 +309,12 @@ interface FileInfo {
|
|||
* @since 28.0.0
|
||||
*/
|
||||
public function getMetadata(): array;
|
||||
|
||||
/**
|
||||
* Get the filecache data for the file
|
||||
*
|
||||
* @return ICacheEntry
|
||||
* @since 34.0.0
|
||||
*/
|
||||
public function getData(): ICacheEntry;
|
||||
}
|
||||
|
|
|
|||
42
lib/public/Share/Events/ShareMovedEvent.php
Normal file
42
lib/public/Share/Events/ShareMovedEvent.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCP\Share\Events;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
class ShareMovedEvent extends Event {
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly IShare $share,
|
||||
private readonly IUser $user,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getShare(): IShare {
|
||||
return $this->share;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getUser(): IUser {
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
33
lib/public/Share/Events/ShareTransferredEvent.php
Normal file
33
lib/public/Share/Events/ShareTransferredEvent.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCP\Share\Events;
|
||||
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
class ShareTransferredEvent extends Event {
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly IShare $share,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getShare(): IShare {
|
||||
return $this->share;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,30 +10,25 @@ namespace OCP\Share\Events;
|
|||
|
||||
use OC\Files\View;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\IUser;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
/**
|
||||
* @since 19.0.0
|
||||
*/
|
||||
class VerifyMountPointEvent extends Event {
|
||||
/** @var IShare */
|
||||
private $share;
|
||||
/** @var View */
|
||||
private $view;
|
||||
/** @var string */
|
||||
private $parent;
|
||||
private bool $createParent = false;
|
||||
|
||||
/**
|
||||
* @since 19.0.0
|
||||
*/
|
||||
public function __construct(IShare $share,
|
||||
View $view,
|
||||
string $parent) {
|
||||
public function __construct(
|
||||
private readonly IShare $share,
|
||||
private readonly View $view,
|
||||
private string $parent,
|
||||
private readonly IUser $user,
|
||||
) {
|
||||
parent::__construct();
|
||||
|
||||
$this->share = $share;
|
||||
$this->view = $view;
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -51,6 +46,8 @@ class VerifyMountPointEvent extends Event {
|
|||
}
|
||||
|
||||
/**
|
||||
* The parent folder where the share is placed, as relative path to the users home directory.
|
||||
*
|
||||
* @since 19.0.0
|
||||
*/
|
||||
public function getParent(): string {
|
||||
|
|
@ -63,4 +60,30 @@ class VerifyMountPointEvent extends Event {
|
|||
public function setParent(string $parent): void {
|
||||
$this->parent = $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 33.0.3
|
||||
*/
|
||||
public function setCreateParent(bool $create): void {
|
||||
$this->createParent = $create;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the parent folder should be created if missing.
|
||||
*
|
||||
* If set for `false` (the default), and the parent folder doesn't exist already,
|
||||
* the share will be moved to the default share folder instead.
|
||||
*
|
||||
* @since 33.0.3
|
||||
*/
|
||||
public function createParent(): bool {
|
||||
return $this->createParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 33.0.3
|
||||
*/
|
||||
public function getUser(): IUser {
|
||||
return $this->user;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -553,6 +553,13 @@ interface IShare {
|
|||
*/
|
||||
public function setTarget($target);
|
||||
|
||||
/**
|
||||
* Return the original target, if this share was moved
|
||||
*
|
||||
* @since 33.0.0
|
||||
*/
|
||||
public function getOriginalTarget(): ?string;
|
||||
|
||||
/**
|
||||
* Get the target path of this share relative to the recipients user folder.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1550,6 +1550,9 @@ class ViewTest extends \Test\TestCase {
|
|||
$storage->method('getStorageCache')->willReturnCallback(function () use ($storage) {
|
||||
return new \OC\Files\Cache\Storage($storage, true, Server::get(IDBConnection::class));
|
||||
});
|
||||
$storage->method('getCache')->willReturnCallback(function () use ($storage) {
|
||||
return new \OC\Files\Cache\Cache($storage);
|
||||
});
|
||||
|
||||
$mounts[] = $this->getMockBuilder(TestMoveableMountPoint::class)
|
||||
->onlyMethods(['moveMount'])
|
||||
|
|
@ -1650,7 +1653,10 @@ class ViewTest extends \Test\TestCase {
|
|||
|
||||
$mount2->expects($this->once())
|
||||
->method('moveMount')
|
||||
->willReturn(true);
|
||||
->willReturnCallback(function ($target) use ($mount2) {
|
||||
$mount2->setMountPoint($target);
|
||||
return true;
|
||||
});
|
||||
|
||||
$view = new View('/' . $this->user . '/files/');
|
||||
$view->mkdir('shareddir');
|
||||
|
|
|
|||
169
tests/lib/Mock/Config/MockAppConfig.php
Normal file
169
tests/lib/Mock/Config/MockAppConfig.php
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Test\Mock\Config;
|
||||
|
||||
use OCP\Exceptions\AppConfigIncorrectTypeException;
|
||||
use OCP\IAppConfig;
|
||||
|
||||
class MockAppConfig implements IAppConfig {
|
||||
public function __construct(
|
||||
public array $config = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function hasKey(string $app, string $key, ?bool $lazy = false): bool {
|
||||
return isset($this->config[$app][$key]);
|
||||
}
|
||||
|
||||
public function getValues($app, $key): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getFilteredValues($app): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getApps(): array {
|
||||
return array_keys($this->config);
|
||||
}
|
||||
|
||||
public function getKeys(string $app): array {
|
||||
return array_keys($this->config[$app] ?? []);
|
||||
}
|
||||
|
||||
public function isSensitive(string $app, string $key, ?bool $lazy = false): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function isLazy(string $app, string $key): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getAllValues(string $app, string $prefix = '', bool $filtered = false): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function searchValues(string $key, bool $lazy = false, ?int $typedAs = null): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getValueString(string $app, string $key, string $default = '', bool $lazy = false): string {
|
||||
return (string)(($this->config[$app] ?? [])[$key] ?? $default);
|
||||
}
|
||||
|
||||
public function getValueInt(string $app, string $key, int $default = 0, bool $lazy = false): int {
|
||||
return (int)(($this->config[$app] ?? [])[$key] ?? $default);
|
||||
}
|
||||
|
||||
public function getValueFloat(string $app, string $key, float $default = 0, bool $lazy = false): float {
|
||||
return (float)(($this->config[$app] ?? [])[$key] ?? $default);
|
||||
}
|
||||
|
||||
public function getValueBool(string $app, string $key, bool $default = false, bool $lazy = false): bool {
|
||||
return (bool)(($this->config[$app] ?? [])[$key] ?? $default);
|
||||
}
|
||||
|
||||
public function getValueArray(string $app, string $key, array $default = [], bool $lazy = false): array {
|
||||
return ($this->config[$app] ?? [])[$key] ?? $default;
|
||||
}
|
||||
|
||||
public function getValueType(string $app, string $key, ?bool $lazy = null): int {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function setValueString(string $app, string $key, string $value, bool $lazy = false, bool $sensitive = false): bool {
|
||||
$this->config[$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueInt(string $app, string $key, int $value, bool $lazy = false, bool $sensitive = false): bool {
|
||||
$this->config[$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueFloat(string $app, string $key, float $value, bool $lazy = false, bool $sensitive = false): bool {
|
||||
$this->config[$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueBool(string $app, string $key, bool $value, bool $lazy = false): bool {
|
||||
$this->config[$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueArray(string $app, string $key, array $value, bool $lazy = false, bool $sensitive = false): bool {
|
||||
$this->config[$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateSensitive(string $app, string $key, bool $sensitive): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function updateLazy(string $app, string $key, bool $lazy): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getDetails(string $app, string $key): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function convertTypeToInt(string $type): int {
|
||||
return match (strtolower($type)) {
|
||||
'mixed' => IAppConfig::VALUE_MIXED,
|
||||
'string' => IAppConfig::VALUE_STRING,
|
||||
'integer' => IAppConfig::VALUE_INT,
|
||||
'float' => IAppConfig::VALUE_FLOAT,
|
||||
'boolean' => IAppConfig::VALUE_BOOL,
|
||||
'array' => IAppConfig::VALUE_ARRAY,
|
||||
default => throw new AppConfigIncorrectTypeException('Unknown type ' . $type)
|
||||
};
|
||||
}
|
||||
|
||||
public function convertTypeToString(int $type): string {
|
||||
$type &= ~self::VALUE_SENSITIVE;
|
||||
|
||||
return match ($type) {
|
||||
IAppConfig::VALUE_MIXED => 'mixed',
|
||||
IAppConfig::VALUE_STRING => 'string',
|
||||
IAppConfig::VALUE_INT => 'integer',
|
||||
IAppConfig::VALUE_FLOAT => 'float',
|
||||
IAppConfig::VALUE_BOOL => 'boolean',
|
||||
IAppConfig::VALUE_ARRAY => 'array',
|
||||
default => throw new AppConfigIncorrectTypeException('Unknown numeric type ' . $type)
|
||||
};
|
||||
}
|
||||
|
||||
public function deleteKey(string $app, string $key): void {
|
||||
if ($this->hasKey($app, $key)) {
|
||||
unset($this->config[$app][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteApp(string $app): void {
|
||||
if (isset($this->config[$app])) {
|
||||
unset($this->config[$app]);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache(bool $reload = false): void {
|
||||
}
|
||||
|
||||
public function searchKeys(string $app, string $prefix = '', bool $lazy = false): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getKeyDetails(string $app, string $key): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getAppInstalledVersions(bool $onlyEnabled = false): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
}
|
||||
209
tests/lib/Mock/Config/MockUserConfig.php
Normal file
209
tests/lib/Mock/Config/MockUserConfig.php
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Test\Mock\Config;
|
||||
|
||||
use Generator;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\Config\ValueType;
|
||||
|
||||
class MockUserConfig implements IUserConfig {
|
||||
public function __construct(
|
||||
public array $config = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUserIds(string $appId = ''): array {
|
||||
return array_keys($this->config);
|
||||
}
|
||||
|
||||
public function getApps(string $userId): array {
|
||||
return array_keys($this->config[$userId] ?? []);
|
||||
}
|
||||
|
||||
public function getKeys(string $userId, string $app): array {
|
||||
if (isset($this->config[$userId][$app])) {
|
||||
return array_keys($this->config[$userId][$app]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public function hasKey(string $userId, string $app, string $key, ?bool $lazy = false): bool {
|
||||
return isset($this->config[$userId][$app][$key]);
|
||||
}
|
||||
|
||||
public function isSensitive(string $userId, string $app, string $key, ?bool $lazy = false): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function isIndexed(string $userId, string $app, string $key, ?bool $lazy = false): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function isLazy(string $userId, string $app, string $key): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getValues(string $userId, string $app, string $prefix = '', bool $filtered = false): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getAllValues(string $userId, bool $filtered = false): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getValuesByApps(string $userId, string $key, bool $lazy = false, ?ValueType $typedAs = null): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getValuesByUsers(string $app, string $key, ?ValueType $typedAs = null, ?array $userIds = null): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function searchUsersByValueString(string $app, string $key, string $value, bool $caseInsensitive = false): Generator {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function searchUsersByValueInt(string $app, string $key, int $value): Generator {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function searchUsersByValues(string $app, string $key, array $values): Generator {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function searchUsersByValueBool(string $app, string $key, bool $value): Generator {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getValueString(string $userId, string $app, string $key, string $default = '', bool $lazy = false): string {
|
||||
if (isset($this->config[$userId][$app])) {
|
||||
return (string)$this->config[$userId][$app][$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function getValueInt(string $userId, string $app, string $key, int $default = 0, bool $lazy = false): int {
|
||||
if (isset($this->config[$userId][$app])) {
|
||||
return (int)$this->config[$userId][$app][$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function getValueFloat(string $userId, string $app, string $key, float $default = 0, bool $lazy = false): float {
|
||||
if (isset($this->config[$userId][$app])) {
|
||||
return (float)$this->config[$userId][$app][$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function getValueBool(string $userId, string $app, string $key, bool $default = false, bool $lazy = false): bool {
|
||||
if (isset($this->config[$userId][$app])) {
|
||||
return (bool)$this->config[$userId][$app][$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function getValueArray(string $userId, string $app, string $key, array $default = [], bool $lazy = false): array {
|
||||
if (isset($this->config[$userId][$app])) {
|
||||
return $this->config[$userId][$app][$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function getValueType(string $userId, string $app, string $key, ?bool $lazy = null): ValueType {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getValueFlags(string $userId, string $app, string $key, bool $lazy = false): int {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function setValueString(string $userId, string $app, string $key, string $value, bool $lazy = false, int $flags = 0): bool {
|
||||
$this->config[$userId][$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueInt(string $userId, string $app, string $key, int $value, bool $lazy = false, int $flags = 0): bool {
|
||||
$this->config[$userId][$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueFloat(string $userId, string $app, string $key, float $value, bool $lazy = false, int $flags = 0): bool {
|
||||
$this->config[$userId][$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueBool(string $userId, string $app, string $key, bool $value, bool $lazy = false): bool {
|
||||
$this->config[$userId][$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setValueArray(string $userId, string $app, string $key, array $value, bool $lazy = false, int $flags = 0): bool {
|
||||
$this->config[$userId][$app][$key] = $value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateSensitive(string $userId, string $app, string $key, bool $sensitive): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function updateGlobalSensitive(string $app, string $key, bool $sensitive): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function updateIndexed(string $userId, string $app, string $key, bool $indexed): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function updateGlobalIndexed(string $app, string $key, bool $indexed): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function updateLazy(string $userId, string $app, string $key, bool $lazy): bool {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function updateGlobalLazy(string $app, string $key, bool $lazy): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function getDetails(string $userId, string $app, string $key): array {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function deleteUserConfig(string $userId, string $app, string $key): void {
|
||||
unset($this->config[$userId][$app][$key]);
|
||||
}
|
||||
|
||||
public function deleteKey(string $app, string $key): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function deleteApp(string $app): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function deleteAllUserConfig(string $userId): void {
|
||||
unset($this->config[$userId]);
|
||||
}
|
||||
|
||||
public function clearCache(string $userId, bool $reload = false): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
|
||||
public function clearCacheAll(): void {
|
||||
throw new \Exception('not implemented');
|
||||
}
|
||||
}
|
||||
|
|
@ -4665,6 +4665,9 @@ class ManagerTest extends \Test\TestCase {
|
|||
$share->setShareType(IShare::TYPE_USER)
|
||||
->setId('42')
|
||||
->setProviderId('foo');
|
||||
$this->userManager->method('get')
|
||||
->with('recipient')
|
||||
->willReturn($this->createMock(IUser::class));
|
||||
|
||||
$share->setSharedWith('recipient');
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use OC\Command\QueueBus;
|
|||
use OC\Files\AppData\Factory;
|
||||
use OC\Files\Cache\Storage;
|
||||
use OC\Files\Config\MountProviderCollection;
|
||||
use OC\Files\Config\UserMountCache;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\Mount\CacheMountProvider;
|
||||
use OC\Files\Mount\LocalHomeMountProvider;
|
||||
|
|
@ -180,6 +181,8 @@ abstract class TestCase extends \PHPUnit\Framework\TestCase {
|
|||
Storage::getGlobalCache()->clearCache();
|
||||
}
|
||||
|
||||
Server::get(UserMountCache::class)->flush();
|
||||
|
||||
// tearDown the traits
|
||||
$traits = $this->getTestTraits();
|
||||
foreach ($traits as $trait) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue