diff options
Diffstat (limited to 'apps/files_sharing/lib/SharedStorage.php')
-rw-r--r-- | apps/files_sharing/lib/SharedStorage.php | 237 |
1 files changed, 106 insertions, 131 deletions
diff --git a/apps/files_sharing/lib/SharedStorage.php b/apps/files_sharing/lib/SharedStorage.php index a67b79542e1..e310c5f3138 100644 --- a/apps/files_sharing/lib/SharedStorage.php +++ b/apps/files_sharing/lib/SharedStorage.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,43 +8,52 @@ namespace OCA\Files_Sharing; use OC\Files\Cache\CacheDependencies; +use OC\Files\Cache\CacheEntry; use OC\Files\Cache\FailedCache; use OC\Files\Cache\NullWatcher; -use OC\Files\Cache\Watcher; use OC\Files\ObjectStore\HomeObjectStoreStorage; use OC\Files\Storage\Common; use OC\Files\Storage\FailedStorage; use OC\Files\Storage\Home; +use OC\Files\Storage\Storage; +use OC\Files\Storage\Wrapper\Jail; use OC\Files\Storage\Wrapper\PermissionsMask; use OC\Files\Storage\Wrapper\Wrapper; +use OC\Files\View; +use OC\Share\Share; use OC\User\NoUserException; -use OCA\Files_External\Config\ConfigAdapter; +use OCA\Files_Sharing\ISharedStorage as LegacyISharedStorage; use OCP\Constants; +use OCP\Files\Cache\ICache; use OCP\Files\Cache\ICacheEntry; -use OCP\Files\Config\IUserMountCache; +use OCP\Files\Cache\IScanner; +use OCP\Files\Cache\IWatcher; use OCP\Files\Folder; use OCP\Files\IHomeStorage; use OCP\Files\IRootFolder; -use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\Files\Storage\IDisableEncryptionStorage; +use OCP\Files\Storage\ILockingStorage; +use OCP\Files\Storage\ISharedStorage; use OCP\Files\Storage\IStorage; use OCP\Lock\ILockingProvider; +use OCP\Server; use OCP\Share\IShare; +use OCP\Util; use Psr\Log\LoggerInterface; /** * Convert target path to source path and pass the function call to the correct storage provider */ -class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedStorage, IDisableEncryptionStorage { - /** @var \OCP\Share\IShare */ +class SharedStorage extends Jail implements LegacyISharedStorage, ISharedStorage, IDisableEncryptionStorage { + /** @var IShare */ private $superShare; - /** @var \OCP\Share\IShare[] */ + /** @var IShare[] */ private $groupedShares; /** - * @var \OC\Files\View + * @var View */ private $ownerView; @@ -59,7 +69,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto private LoggerInterface $logger; - /** @var IStorage */ + /** @var IStorage */ private $nonMaskedStorage; private array $mountOptions = []; @@ -76,20 +86,20 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto /** * @psalm-suppress NonInvariantDocblockPropertyType - * @var ?\OC\Files\Storage\Storage $storage + * @var ?Storage $storage */ protected $storage; - public function __construct($arguments) { - $this->ownerView = $arguments['ownerView']; - $this->logger = \OC::$server->get(LoggerInterface::class); + public function __construct(array $parameters) { + $this->ownerView = $parameters['ownerView']; + $this->logger = Server::get(LoggerInterface::class); - $this->superShare = $arguments['superShare']; - $this->groupedShares = $arguments['groupedShares']; + $this->superShare = $parameters['superShare']; + $this->groupedShares = $parameters['groupedShares']; - $this->user = $arguments['user']; - if (isset($arguments['sharingDisabledForUser'])) { - $this->sharingDisabledForUser = $arguments['sharingDisabledForUser']; + $this->user = $parameters['user']; + if (isset($parameters['sharingDisabledForUser'])) { + $this->sharingDisabledForUser = $parameters['sharingDisabledForUser']; } else { $this->sharingDisabledForUser = false; } @@ -116,7 +126,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto } /** - * @psalm-assert \OC\Files\Storage\Storage $this->storage + * @psalm-assert Storage $this->storage */ private function init() { if ($this->initialized) { @@ -138,25 +148,37 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto try { if (self::$initDepth > 10) { - throw new \Exception("Maximum share depth reached"); + throw new \Exception('Maximum share depth reached'); } /** @var IRootFolder $rootFolder */ - $rootFolder = \OC::$server->get(IRootFolder::class); + $rootFolder = Server::get(IRootFolder::class); $this->ownerUserFolder = $rootFolder->getUserFolder($this->superShare->getShareOwner()); $sourceId = $this->superShare->getNodeId(); - $ownerNode = $this->ownerUserFolder->getFirstNodeById($sourceId); - if (!$ownerNode) { + $ownerNodes = $this->ownerUserFolder->getById($sourceId); + + if (count($ownerNodes) === 0) { $this->storage = new FailedStorage(['exception' => new NotFoundException("File by id $sourceId not found")]); $this->cache = new FailedCache(); $this->rootPath = ''; } else { - if ($this->nonMaskedStorage instanceof Wrapper && $this->nonMaskedStorage->isWrapperOf($this)) { + foreach ($ownerNodes as $ownerNode) { + $nonMaskedStorage = $ownerNode->getStorage(); + + // check if potential source node would lead to a recursive share setup + if ($nonMaskedStorage instanceof Wrapper && $nonMaskedStorage->isWrapperOf($this)) { + continue; + } + $this->nonMaskedStorage = $nonMaskedStorage; + $this->sourcePath = $ownerNode->getPath(); + $this->rootPath = $ownerNode->getInternalPath(); + $this->cache = null; + break; + } + if (!$this->nonMaskedStorage) { + // all potential source nodes would have been recursive throw new \Exception('recursive share detected'); } - $this->nonMaskedStorage = $ownerNode->getStorage(); - $this->sourcePath = $ownerNode->getPath(); - $this->rootPath = $ownerNode->getInternalPath(); $this->storage = new PermissionsMask([ 'storage' => $this->nonMaskedStorage, 'mask' => $this->superShare->getPermissions(), @@ -185,10 +207,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto self::$initDepth--; } - /** - * @inheritdoc - */ - public function instanceOfStorage($class): bool { + public function instanceOfStorage(string $class): bool { if ($class === '\OC\Files\Storage\Common' || $class == Common::class) { return true; } @@ -216,22 +235,11 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $this->getSourceRootInfo() && ($this->getSourceRootInfo()->getPermissions() & Constants::PERMISSION_SHARE) === Constants::PERMISSION_SHARE; } - /** - * get id of the mount point - * - * @return string - */ public function getId(): string { return 'shared::' . $this->getMountPoint(); } - /** - * Get the permissions granted for a shared file - * - * @param string $path Shared target file path - * @return int CRUDS permissions granted - */ - public function getPermissions($path = ''): int { + public function getPermissions(string $path = ''): int { if (!$this->isValid()) { return 0; } @@ -239,21 +247,21 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto // part files and the mount point always have delete permissions if ($path === '' || pathinfo($path, PATHINFO_EXTENSION) === 'part') { - $permissions |= \OCP\Constants::PERMISSION_DELETE; + $permissions |= Constants::PERMISSION_DELETE; } if ($this->sharingDisabledForUser) { - $permissions &= ~\OCP\Constants::PERMISSION_SHARE; + $permissions &= ~Constants::PERMISSION_SHARE; } return $permissions; } - public function isCreatable($path): bool { - return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_CREATE); + public function isCreatable(string $path): bool { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_CREATE); } - public function isReadable($path): bool { + public function isReadable(string $path): bool { if (!$this->isValid()) { return false; } @@ -266,22 +274,22 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $storage->isReadable($internalPath); } - public function isUpdatable($path): bool { - return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_UPDATE); + public function isUpdatable(string $path): bool { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_UPDATE); } - public function isDeletable($path): bool { - return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_DELETE); + public function isDeletable(string $path): bool { + return (bool)($this->getPermissions($path) & Constants::PERMISSION_DELETE); } - public function isSharable($path): bool { - if (\OCP\Util::isSharingDisabledForUser() || !\OC\Share\Share::isResharingAllowed()) { + public function isSharable(string $path): bool { + if (Util::isSharingDisabledForUser() || !Share::isResharingAllowed()) { return false; } - return (bool)($this->getPermissions($path) & \OCP\Constants::PERMISSION_SHARE); + return (bool)($this->getPermissions($path) & Constants::PERMISSION_SHARE); } - public function fopen($path, $mode) { + public function fopen(string $path, string $mode) { $source = $this->getUnjailedPath($path); switch ($mode) { case 'r+': @@ -329,18 +337,11 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto 'source' => $source, 'mode' => $mode, ]; - \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); + Util::emitHook('\OC\Files\Storage\Shared', 'fopen', $info); return $this->nonMaskedStorage->fopen($this->getUnjailedPath($path), $mode); } - /** - * see https://www.php.net/manual/en/function.rename.php - * - * @param string $source - * @param string $target - * @return bool - */ - public function rename($source, $target): bool { + public function rename(string $source, string $target): bool { $this->init(); $isPartFile = pathinfo($source, PATHINFO_EXTENSION) === 'part'; $targetExists = $this->file_exists($target); @@ -368,10 +369,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $this->superShare->getTarget(); } - /** - * @param string $path - */ - public function setMountPoint($path): void { + public function setMountPoint(string $path): void { $this->superShare->setTarget($path); foreach ($this->groupedShares as $share) { @@ -388,9 +386,6 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $this->superShare->getShareOwner(); } - /** - * @return \OCP\Share\IShare - */ public function getShare(): IShare { return $this->superShare; } @@ -404,7 +399,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $this->superShare->getNodeType(); } - public function getCache($path = '', $storage = null) { + public function getCache(string $path = '', ?IStorage $storage = null): ICache { if ($this->cache) { return $this->cache; } @@ -416,44 +411,44 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return new FailedCache(); } - $this->cache = new \OCA\Files_Sharing\Cache( + $this->cache = new Cache( $storage, $sourceRoot, - \OC::$server->get(CacheDependencies::class), + Server::get(CacheDependencies::class), $this->getShare() ); return $this->cache; } - public function getScanner($path = '', $storage = null) { + public function getScanner(string $path = '', ?IStorage $storage = null): IScanner { if (!$storage) { $storage = $this; } - return new \OCA\Files_Sharing\Scanner($storage); + return new Scanner($storage); } - public function getOwner($path): string { + public function getOwner(string $path): string|false { return $this->superShare->getShareOwner(); } - public function getWatcher($path = '', $storage = null): Watcher { + public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher { if ($this->watcher) { return $this->watcher; } // Get node information $node = $this->getShare()->getNodeCacheEntry(); - if ($node) { - /** @var IUserMountCache $userMountCache */ - $userMountCache = \OC::$server->get(IUserMountCache::class); - $mounts = $userMountCache->getMountsForStorageId($node->getStorageId()); - foreach ($mounts as $mount) { - // If the share is originating from an external storage - if ($mount->getMountProvider() === ConfigAdapter::class) { - // Propagate original storage scan - $this->watcher = parent::getWatcher($path, $storage); - return $this->watcher; + if ($node instanceof CacheEntry) { + $storageId = $node->getData()['storage_string_id'] ?? null; + // for shares from the home storage we can rely on the home storage to keep itself up to date + // for other storages we need use the proper watcher + if ($storageId !== null && !(str_starts_with($storageId, 'home::') || str_starts_with($storageId, 'object::user'))) { + $cache = $this->getCache(); + $this->watcher = parent::getWatcher($path, $storage); + if ($cache instanceof Cache) { + $this->watcher->onUpdate($cache->markRootChanged(...)); } + return $this->watcher; } } @@ -469,19 +464,13 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto */ public function unshareStorage(): bool { foreach ($this->groupedShares as $share) { - \OC::$server->getShareManager()->deleteFromSelf($share, $this->user); + Server::get(\OCP\Share\IManager::class)->deleteFromSelf($share, $this->user); } return true; } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - * @throws \OCP\Lock\LockedException - */ - public function acquireLock($path, $type, ILockingProvider $provider) { - /** @var \OCP\Files\Storage $targetStorage */ + public function acquireLock(string $path, int $type, ILockingProvider $provider): void { + /** @var ILockingStorage $targetStorage */ [$targetStorage, $targetInternalPath] = $this->resolvePath($path); $targetStorage->acquireLock($targetInternalPath, $type, $provider); // lock the parent folders of the owner when locking the share as recipient @@ -491,13 +480,8 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto } } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function releaseLock($path, $type, ILockingProvider $provider) { - /** @var \OCP\Files\Storage $targetStorage */ + public function releaseLock(string $path, int $type, ILockingProvider $provider): void { + /** @var ILockingStorage $targetStorage */ [$targetStorage, $targetInternalPath] = $this->resolvePath($path); $targetStorage->releaseLock($targetInternalPath, $type, $provider); // unlock the parent folders of the owner when unlocking the share as recipient @@ -507,21 +491,13 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto } } - /** - * @param string $path - * @param int $type \OCP\Lock\ILockingProvider::LOCK_SHARED or \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE - * @param \OCP\Lock\ILockingProvider $provider - */ - public function changeLock($path, $type, ILockingProvider $provider) { - /** @var \OCP\Files\Storage $targetStorage */ + public function changeLock(string $path, int $type, ILockingProvider $provider): void { + /** @var ILockingStorage $targetStorage */ [$targetStorage, $targetInternalPath] = $this->resolvePath($path); $targetStorage->changeLock($targetInternalPath, $type, $provider); } - /** - * @return array [ available, last_checked ] - */ - public function getAvailability() { + public function getAvailability(): array { // shares do not participate in availability logic return [ 'available' => true, @@ -529,10 +505,7 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto ]; } - /** - * @param bool $isAvailable - */ - public function setAvailability($isAvailable) { + public function setAvailability(bool $isAvailable): void { // shares do not participate in availability logic } @@ -541,14 +514,14 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $this->nonMaskedStorage; } - public function getWrapperStorage() { + public function getWrapperStorage(): Storage { $this->init(); /** * @psalm-suppress DocblockTypeContradiction */ if (!$this->storage) { - $message = "no storage set after init for share " . $this->getShareId(); + $message = 'no storage set after init for share ' . $this->getShareId(); $this->logger->error($message); $this->storage = new FailedStorage(['exception' => new \Exception($message)]); } @@ -556,34 +529,36 @@ class SharedStorage extends \OC\Files\Storage\Wrapper\Jail implements ISharedSto return $this->storage; } - public function file_get_contents($path) { + public function file_get_contents(string $path): string|false { $info = [ 'target' => $this->getMountPoint() . '/' . $path, 'source' => $this->getUnjailedPath($path), ]; - \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); + Util::emitHook('\OC\Files\Storage\Shared', 'file_get_contents', $info); return parent::file_get_contents($path); } - public function file_put_contents($path, $data) { + public function file_put_contents(string $path, mixed $data): int|float|false { $info = [ 'target' => $this->getMountPoint() . '/' . $path, 'source' => $this->getUnjailedPath($path), ]; - \OCP\Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); + Util::emitHook('\OC\Files\Storage\Shared', 'file_put_contents', $info); return parent::file_put_contents($path, $data); } - /** - * @return void - */ - public function setMountOptions(array $options) { + public function setMountOptions(array $options): void { /* Note: This value is never read */ $this->mountOptions = $options; } - public function getUnjailedPath($path) { + public function getUnjailedPath(string $path): string { $this->init(); return parent::getUnjailedPath($path); } + + public function getDirectDownload(string $path): array|false { + // disable direct download for shares + return []; + } } |