diff options
Diffstat (limited to 'apps/files_sharing/lib/External')
-rw-r--r-- | apps/files_sharing/lib/External/Cache.php | 19 | ||||
-rw-r--r-- | apps/files_sharing/lib/External/Manager.php | 260 | ||||
-rw-r--r-- | apps/files_sharing/lib/External/Mount.php | 18 | ||||
-rw-r--r-- | apps/files_sharing/lib/External/MountProvider.php | 25 | ||||
-rw-r--r-- | apps/files_sharing/lib/External/Scanner.php | 9 | ||||
-rw-r--r-- | apps/files_sharing/lib/External/Storage.php | 122 | ||||
-rw-r--r-- | apps/files_sharing/lib/External/Watcher.php | 1 |
7 files changed, 244 insertions, 210 deletions
diff --git a/apps/files_sharing/lib/External/Cache.php b/apps/files_sharing/lib/External/Cache.php index 7fad71b9084..027f682d818 100644 --- a/apps/files_sharing/lib/External/Cache.php +++ b/apps/files_sharing/lib/External/Cache.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -9,23 +10,21 @@ namespace OCA\Files_Sharing\External; use OCP\Federation\ICloudId; class Cache extends \OC\Files\Cache\Cache { - /** @var ICloudId */ - private $cloudId; private $remote; private $remoteUser; - private $storage; /** - * @param \OCA\Files_Sharing\External\Storage $storage + * @param Storage $storage * @param ICloudId $cloudId */ - public function __construct($storage, ICloudId $cloudId) { - $this->cloudId = $cloudId; - $this->storage = $storage; - [, $remote] = explode('://', $cloudId->getRemote(), 2); + public function __construct( + private $storage, + private ICloudId $cloudId, + ) { + [, $remote] = explode('://', $this->cloudId->getRemote(), 2); $this->remote = $remote; - $this->remoteUser = $cloudId->getUser(); - parent::__construct($storage); + $this->remoteUser = $this->cloudId->getUser(); + parent::__construct($this->storage); } public function get($file) { diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index abddcde4dae..ff4781eba0f 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -11,11 +12,13 @@ use Doctrine\DBAL\Driver\Exception; use OC\Files\Filesystem; use OCA\FederatedFileSharing\Events\FederatedShareAddedEvent; use OCA\Files_Sharing\Helper; +use OCA\Files_Sharing\ResponseDefinitions; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; use OCP\Federation\ICloudFederationFactory; use OCP\Federation\ICloudFederationProviderManager; use OCP\Files; +use OCP\Files\Events\InvalidateMountCacheEvent; use OCP\Files\NotFoundException; use OCP\Files\Storage\IStorageFactory; use OCP\Http\Client\IClientService; @@ -29,77 +32,32 @@ use OCP\Share; use OCP\Share\IShare; use Psr\Log\LoggerInterface; +/** + * @psalm-import-type Files_SharingRemoteShare from ResponseDefinitions + */ class Manager { public const STORAGE = '\OCA\Files_Sharing\External\Storage'; /** @var string|null */ private $uid; - /** @var IDBConnection */ - private $connection; - - /** @var \OC\Files\Mount\Manager */ - private $mountManager; - - /** @var IStorageFactory */ - private $storageLoader; - - /** @var IClientService */ - private $clientService; - - /** @var IManager */ - private $notificationManager; - - /** @var IDiscoveryService */ - private $discoveryService; - - /** @var ICloudFederationProviderManager */ - private $cloudFederationProviderManager; - - /** @var ICloudFederationFactory */ - private $cloudFederationFactory; - - /** @var IGroupManager */ - private $groupManager; - - /** @var IUserManager */ - private $userManager; - - /** @var IEventDispatcher */ - private $eventDispatcher; - - /** @var LoggerInterface */ - private $logger; - public function __construct( - IDBConnection $connection, - \OC\Files\Mount\Manager $mountManager, - IStorageFactory $storageLoader, - IClientService $clientService, - IManager $notificationManager, - IDiscoveryService $discoveryService, - ICloudFederationProviderManager $cloudFederationProviderManager, - ICloudFederationFactory $cloudFederationFactory, - IGroupManager $groupManager, - IUserManager $userManager, - IUserSession $userSession, - IEventDispatcher $eventDispatcher, - LoggerInterface $logger + private IDBConnection $connection, + private \OC\Files\Mount\Manager $mountManager, + private IStorageFactory $storageLoader, + private IClientService $clientService, + private IManager $notificationManager, + private IDiscoveryService $discoveryService, + private ICloudFederationProviderManager $cloudFederationProviderManager, + private ICloudFederationFactory $cloudFederationFactory, + private IGroupManager $groupManager, + private IUserManager $userManager, + IUserSession $userSession, + private IEventDispatcher $eventDispatcher, + private LoggerInterface $logger, ) { $user = $userSession->getUser(); - $this->connection = $connection; - $this->mountManager = $mountManager; - $this->storageLoader = $storageLoader; - $this->clientService = $clientService; $this->uid = $user ? $user->getUID() : null; - $this->notificationManager = $notificationManager; - $this->discoveryService = $discoveryService; - $this->cloudFederationProviderManager = $cloudFederationProviderManager; - $this->cloudFederationFactory = $cloudFederationFactory; - $this->groupManager = $groupManager; - $this->userManager = $userManager; - $this->eventDispatcher = $eventDispatcher; - $this->logger = $logger; } /** @@ -119,7 +77,7 @@ class Manager { * @throws \Doctrine\DBAL\Exception */ public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) { - $user = $user ? $user : $this->uid; + $user = $user ?? $this->uid; $accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING; $name = Filesystem::normalizePath('/' . $name); @@ -168,7 +126,7 @@ class Manager { 'mountpoint' => $mountPoint, 'owner' => $owner ]; - return $this->mountShare($options); + return $this->mountShare($options, $user); } /** @@ -199,18 +157,29 @@ class Manager { $query->execute([$remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType]); } + private function fetchShare(int $id): array|false { + $getShare = $this->connection->prepare(' + SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash` + FROM `*PREFIX*share_external` + WHERE `id` = ?'); + $result = $getShare->execute([$id]); + $share = $result->fetch(); + $result->closeCursor(); + return $share; + } + /** - * get share + * get share by token * - * @param int $id share id + * @param string $token * @return mixed share of false */ - private function fetchShare($id) { + private function fetchShareByToken($token) { $getShare = $this->connection->prepare(' SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash` FROM `*PREFIX*share_external` - WHERE `id` = ?'); - $result = $getShare->execute([$id]); + WHERE `share_token` = ?'); + $result = $getShare->execute([$token]); $share = $result->fetch(); $result->closeCursor(); return $share; @@ -230,20 +199,54 @@ class Manager { return null; } - /** - * get share - * - * @param int $id share id - * @return mixed share of false - */ - public function getShare($id) { + public function getShare(int $id, ?string $user = null): array|false { + $user = $user ?? $this->uid; $share = $this->fetchShare($id); - $validShare = is_array($share) && isset($share['share_type']) && isset($share['user']); + if ($share === false) { + return false; + } // check if the user is allowed to access it - if ($validShare && (int)$share['share_type'] === IShare::TYPE_USER && $share['user'] === $this->uid) { + if ($this->canAccessShare($share, $user)) { return $share; - } elseif ($validShare && (int)$share['share_type'] === IShare::TYPE_GROUP) { + } + + return false; + } + + /** + * Get share by token + * + * @param string $token + * @return array|false + */ + public function getShareByToken(string $token): array|false { + $share = $this->fetchShareByToken($token); + + // We do not check if the user is allowed to access it here, + // as this is not used from a user context. + if ($share === false) { + return false; + } + + return $share; + } + + private function canAccessShare(array $share, string $user): bool { + $validShare = isset($share['share_type']) && isset($share['user']); + + if (!$validShare) { + return false; + } + + // If the share is a user share, check if the user is the recipient + if ((int)$share['share_type'] === IShare::TYPE_USER + && $share['user'] === $user) { + return true; + } + + // If the share is a group share, check if the user is in the group + if ((int)$share['share_type'] === IShare::TYPE_GROUP) { $parentId = (int)$share['parent']; if ($parentId !== -1) { // we just retrieved a sub-share, switch to the parent entry for verification @@ -251,9 +254,10 @@ class Manager { } else { $groupShare = $share; } - $user = $this->userManager->get($this->uid); + + $user = $this->userManager->get($user); if ($this->groupManager->get($groupShare['user'])->inGroup($user)) { - return $share; + return true; } } @@ -280,13 +284,22 @@ class Manager { * @param int $id * @return bool True if the share could be accepted, false otherwise */ - public function acceptShare($id) { - $share = $this->getShare($id); + public function acceptShare(int $id, ?string $user = null) { + // If we're auto-accepting a share, we need to know the user id + // as there is no session available while processing the share + // from the remote server request. + $user = $user ?? $this->uid; + if ($user === null) { + $this->logger->error('No user specified for accepting share'); + return false; + } + + $share = $this->getShare($id, $user); $result = false; if ($share) { - \OC_Util::setupFS($this->uid); - $shareFolder = Helper::getShareFolder(null, $this->uid); + \OC_Util::setupFS($user); + $shareFolder = Helper::getShareFolder(null, $user); $mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']); $mountPoint = Filesystem::normalizePath($mountPoint); $hash = md5($mountPoint); @@ -299,14 +312,14 @@ class Manager { `mountpoint` = ?, `mountpoint_hash` = ? WHERE `id` = ? AND `user` = ?'); - $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $this->uid]); + $userShareAccepted = $acceptShare->execute([1, $mountPoint, $hash, $id, $user]); } else { $parentId = (int)$share['parent']; if ($parentId !== -1) { // this is the sub-share $subshare = $share; } else { - $subshare = $this->fetchUserShare($id, $this->uid); + $subshare = $this->fetchUserShare($id, $user); } if ($subshare !== null) { @@ -317,7 +330,7 @@ class Manager { `mountpoint` = ?, `mountpoint_hash` = ? WHERE `id` = ? AND `user` = ?'); - $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $this->uid]); + $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $user]); $result = true; } catch (Exception $e) { $this->logger->emergency('Could not update share', ['exception' => $e]); @@ -331,7 +344,7 @@ class Manager { $share['password'], $share['name'], $share['owner'], - $this->uid, + $user, $mountPoint, $hash, 1, $share['remote_id'], $id, @@ -343,17 +356,18 @@ class Manager { } } } + if ($userShareAccepted !== false) { $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept'); $event = new FederatedShareAddedEvent($share['remote']); $this->eventDispatcher->dispatchTyped($event); - $this->eventDispatcher->dispatchTyped(new Files\Events\InvalidateMountCacheEvent($this->userManager->get($this->uid))); + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->userManager->get($user))); $result = true; } } // Make sure the user has no notification for something that does not exist anymore. - $this->processNotification($id); + $this->processNotification($id, $user); return $result; } @@ -364,17 +378,23 @@ class Manager { * @param int $id * @return bool True if the share could be declined, false otherwise */ - public function declineShare($id) { - $share = $this->getShare($id); + public function declineShare(int $id, ?string $user = null) { + $user = $user ?? $this->uid; + if ($user === null) { + $this->logger->error('No user specified for declining share'); + return false; + } + + $share = $this->getShare($id, $user); $result = false; if ($share && (int)$share['share_type'] === IShare::TYPE_USER) { $removeShare = $this->connection->prepare(' DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?'); - $removeShare->execute([$id, $this->uid]); + $removeShare->execute([$id, $user]); $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); - $this->processNotification($id); + $this->processNotification($id, $user); $result = true; } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) { $parentId = (int)$share['parent']; @@ -382,7 +402,7 @@ class Manager { // this is the sub-share $subshare = $share; } else { - $subshare = $this->fetchUserShare($id, $this->uid); + $subshare = $this->fetchUserShare($id, $user); } if ($subshare !== null) { @@ -401,7 +421,7 @@ class Manager { $share['password'], $share['name'], $share['owner'], - $this->uid, + $user, $share['mountpoint'], $share['mountpoint_hash'], 0, @@ -414,16 +434,27 @@ class Manager { $result = false; } } - $this->processNotification($id); + $this->processNotification($id, $user); } return $result; } - public function processNotification(int $remoteShare): void { + public function processNotification(int $remoteShare, ?string $user = null): void { + $user = $user ?? $this->uid; + if ($user === null) { + $this->logger->error('No user specified for processing notification'); + return; + } + + $share = $this->fetchShare($remoteShare); + if ($share === false) { + return; + } + $filter = $this->notificationManager->createNotification(); $filter->setApp('files_sharing') - ->setUser($this->uid) + ->setUser($user) ->setObject('remote_share', (string)$remoteShare); $this->notificationManager->markProcessed($filter); } @@ -523,9 +554,10 @@ class Manager { return rtrim(substr($path, strlen($prefix)), '/'); } - public function getMount($data) { + public function getMount($data, ?string $user = null) { + $user = $user ?? $this->uid; $data['manager'] = $this; - $mountPoint = '/' . $this->uid . '/files' . $data['mountpoint']; + $mountPoint = '/' . $user . '/files' . $data['mountpoint']; $data['mountpoint'] = $mountPoint; $data['certificateManager'] = \OC::$server->getCertificateManager(); return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader); @@ -535,8 +567,8 @@ class Manager { * @param array $data * @return Mount */ - protected function mountShare($data) { - $mount = $this->getMount($data); + protected function mountShare($data, ?string $user = null) { + $mount = $this->getMount($data, $user); $this->mountManager->addMount($mount); return $mount; } @@ -567,7 +599,7 @@ class Manager { '); $result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]); - $this->eventDispatcher->dispatchTyped(new Files\Events\InvalidateMountCacheEvent($this->userManager->get($this->uid))); + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->userManager->get($this->uid))); return $result; } @@ -706,12 +738,12 @@ class Manager { $qb = $this->connection->getQueryBuilder(); // delete group share entry and matching sub-entries $qb->delete('share_external') - ->where( - $qb->expr()->orX( - $qb->expr()->eq('id', $qb->createParameter('share_id')), - $qb->expr()->eq('parent', $qb->createParameter('share_parent_id')) - ) - ); + ->where( + $qb->expr()->orX( + $qb->expr()->eq('id', $qb->createParameter('share_id')), + $qb->expr()->eq('parent', $qb->createParameter('share_parent_id')) + ) + ); foreach ($shares as $share) { $qb->setParameter('share_id', $share['id']); @@ -729,7 +761,7 @@ class Manager { /** * return a list of shares which are not yet accepted by the user * - * @return array list of open server-to-server shares + * @return list<Files_SharingRemoteShare> list of open server-to-server shares */ public function getOpenShares() { return $this->getShares(false); @@ -738,7 +770,7 @@ class Manager { /** * return a list of shares which are accepted by the user * - * @return array list of accepted server-to-server shares + * @return list<Files_SharingRemoteShare> list of accepted server-to-server shares */ public function getAcceptedShares() { return $this->getShares(true); @@ -750,9 +782,11 @@ class Manager { * @param bool|null $accepted True for accepted only, * false for not accepted, * null for all shares of the user - * @return array list of open server-to-server shares + * @return list<Files_SharingRemoteShare> list of open server-to-server shares */ private function getShares($accepted) { + // Not allowing providing a user here, + // as we only want to retrieve shares for the current user. $user = $this->userManager->get($this->uid); $groups = $this->groupManager->getUserGroups($user); $userGroups = []; diff --git a/apps/files_sharing/lib/External/Mount.php b/apps/files_sharing/lib/External/Mount.php index 685e931e3bc..f50c379f85f 100644 --- a/apps/files_sharing/lib/External/Mount.php +++ b/apps/files_sharing/lib/External/Mount.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,25 +9,26 @@ namespace OCA\Files_Sharing\External; use OC\Files\Mount\MountPoint; use OC\Files\Mount\MoveableMount; +use OC\Files\Storage\Storage; use OCA\Files_Sharing\ISharedMountPoint; class Mount extends MountPoint implements MoveableMount, ISharedMountPoint { /** - * @var \OCA\Files_Sharing\External\Manager - */ - protected $manager; - - /** - * @param string|\OC\Files\Storage\Storage $storage + * @param string|Storage $storage * @param string $mountpoint * @param array $options * @param \OCA\Files_Sharing\External\Manager $manager * @param \OC\Files\Storage\StorageFactory $loader */ - public function __construct($storage, $mountpoint, $options, $manager, $loader = null) { + public function __construct( + $storage, + $mountpoint, + $options, + protected $manager, + $loader = null, + ) { parent::__construct($storage, $mountpoint, $options, $loader, null, null, MountProvider::class); - $this->manager = $manager; } /** diff --git a/apps/files_sharing/lib/External/MountProvider.php b/apps/files_sharing/lib/External/MountProvider.php index 2c639543a87..a5781d5d35a 100644 --- a/apps/files_sharing/lib/External/MountProvider.php +++ b/apps/files_sharing/lib/External/MountProvider.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -10,36 +11,30 @@ use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Federation\ICloudIdManager; use OCP\Files\Config\IMountProvider; use OCP\Files\Storage\IStorageFactory; +use OCP\Http\Client\IClientService; use OCP\IDBConnection; use OCP\IUser; +use OCP\Server; class MountProvider implements IMountProvider { public const STORAGE = '\OCA\Files_Sharing\External\Storage'; /** - * @var \OCP\IDBConnection - */ - private $connection; - - /** * @var callable */ private $managerProvider; /** - * @var ICloudIdManager - */ - private $cloudIdManager; - - /** - * @param \OCP\IDBConnection $connection + * @param IDBConnection $connection * @param callable $managerProvider due to setup order we need a callable that return the manager instead of the manager itself * @param ICloudIdManager $cloudIdManager */ - public function __construct(IDBConnection $connection, callable $managerProvider, ICloudIdManager $cloudIdManager) { - $this->connection = $connection; + public function __construct( + private IDBConnection $connection, + callable $managerProvider, + private ICloudIdManager $cloudIdManager, + ) { $this->managerProvider = $managerProvider; - $this->cloudIdManager = $cloudIdManager; } public function getMount(IUser $user, $data, IStorageFactory $storageFactory) { @@ -50,7 +45,7 @@ class MountProvider implements IMountProvider { $data['mountpoint'] = $mountPoint; $data['cloudId'] = $this->cloudIdManager->getCloudId($data['owner'], $data['remote']); $data['certificateManager'] = \OC::$server->getCertificateManager(); - $data['HttpClientService'] = \OC::$server->getHTTPClientService(); + $data['HttpClientService'] = Server::get(IClientService::class); return new Mount(self::STORAGE, $mountPoint, $data, $manager, $storageFactory); } diff --git a/apps/files_sharing/lib/External/Scanner.php b/apps/files_sharing/lib/External/Scanner.php index 63757d44f46..0d57248595b 100644 --- a/apps/files_sharing/lib/External/Scanner.php +++ b/apps/files_sharing/lib/External/Scanner.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -6,13 +7,14 @@ */ namespace OCA\Files_Sharing\External; +use OC\Files\Cache\CacheEntry; use OC\ForbiddenException; use OCP\Files\NotFoundException; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; class Scanner extends \OC\Files\Cache\Scanner { - /** @var \OCA\Files_Sharing\External\Storage */ + /** @var Storage */ protected $storage; public function scan($path, $recursive = self::SCAN_RECURSIVE, $reuse = -1, $lock = true) { @@ -29,9 +31,10 @@ class Scanner extends \OC\Files\Cache\Scanner { * @param string $file file to scan * @param int $reuseExisting * @param int $parentId - * @param array | null $cacheData existing data in the cache for the file to be scanned + * @param CacheEntry|array|null|false $cacheData existing data in the cache for the file to be scanned * @param bool $lock set to false to disable getting an additional read lock during scanning - * @return array | null an array of metadata of the scanned file + * @param array|null $data the metadata for the file, as returned by the storage + * @return array|null an array of metadata of the scanned file */ public function scanFile($file, $reuseExisting = 0, $parentId = -1, $cacheData = null, $lock = true, $data = null) { try { diff --git a/apps/files_sharing/lib/External/Storage.php b/apps/files_sharing/lib/External/Storage.php index 7af22beda52..a9781b91a6c 100644 --- a/apps/files_sharing/lib/External/Storage.php +++ b/apps/files_sharing/lib/External/Storage.php @@ -13,14 +13,19 @@ use GuzzleHttp\Exception\ConnectException; use GuzzleHttp\Exception\RequestException; use OC\Files\Storage\DAV; use OC\ForbiddenException; +use OC\Share\Share; use OCA\Files_Sharing\External\Manager as ExternalShareManager; use OCA\Files_Sharing\ISharedStorage; use OCP\AppFramework\Http; use OCP\Constants; use OCP\Federation\ICloudId; +use OCP\Files\Cache\ICache; +use OCP\Files\Cache\IScanner; +use OCP\Files\Cache\IWatcher; use OCP\Files\NotFoundException; use OCP\Files\Storage\IDisableEncryptionStorage; use OCP\Files\Storage\IReliableEtagStorage; +use OCP\Files\Storage\IStorage; use OCP\Files\StorageInvalidException; use OCP\Files\StorageNotAvailableException; use OCP\Http\Client\IClientService; @@ -31,6 +36,7 @@ use OCP\OCM\Exceptions\OCMArgumentException; use OCP\OCM\Exceptions\OCMProviderException; use OCP\OCM\IOCMDiscoveryService; use OCP\Server; +use OCP\Util; use Psr\Log\LoggerInterface; class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, IReliableEtagStorage { @@ -47,7 +53,7 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, * @param array{HttpClientService: IClientService, manager: ExternalShareManager, cloudId: ICloudId, mountpoint: string, token: string, password: ?string}|array $options */ public function __construct($options) { - $this->memcacheFactory = \OC::$server->getMemCacheFactory(); + $this->memcacheFactory = Server::get(ICacheFactory::class); $this->httpClient = $options['HttpClientService']; $this->manager = $options['manager']; $this->cloudId = $options['cloudId']; @@ -82,6 +88,7 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, parent::__construct( [ 'secure' => ((parse_url($remote, PHP_URL_SCHEME) ?? 'https') === 'https'), + 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false), 'host' => $host, 'root' => $webDavEndpoint, 'user' => $options['token'], @@ -91,7 +98,7 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, ); } - public function getWatcher($path = '', $storage = null) { + public function getWatcher(string $path = '', ?IStorage $storage = null): IWatcher { if (!$storage) { $storage = $this; } @@ -122,46 +129,29 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, return $this->password; } - /** - * Get id of the mount point. - * @return string - */ - public function getId() { + public function getId(): string { return 'shared::' . md5($this->token . '@' . $this->getRemote()); } - public function getCache($path = '', $storage = null) { + public function getCache(string $path = '', ?IStorage $storage = null): ICache { if (is_null($this->cache)) { $this->cache = new Cache($this, $this->cloudId); } return $this->cache; } - /** - * @param string $path - * @param \OC\Files\Storage\Storage $storage - * @return \OCA\Files_Sharing\External\Scanner - */ - public function getScanner($path = '', $storage = null) { + public function getScanner(string $path = '', ?IStorage $storage = null): IScanner { if (!$storage) { $storage = $this; } if (!isset($this->scanner)) { $this->scanner = new Scanner($storage); } + /** @var Scanner */ return $this->scanner; } - /** - * Check if a file or folder has been updated since $time - * - * @param string $path - * @param int $time - * @throws \OCP\Files\StorageNotAvailableException - * @throws \OCP\Files\StorageInvalidException - * @return bool - */ - public function hasUpdated($path, $time) { + public function hasUpdated(string $path, int $time): bool { // since for owncloud webdav servers we can rely on etag propagation we only need to check the root of the storage // because of that we only do one check for the entire storage per request if ($this->updateChecked) { @@ -181,7 +171,7 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, } } - public function test() { + public function test(): bool { try { return parent::test(); } catch (StorageInvalidException $e) { @@ -199,8 +189,8 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, * Check whether this storage is permanently or temporarily * unavailable * - * @throws \OCP\Files\StorageNotAvailableException - * @throws \OCP\Files\StorageInvalidException + * @throws StorageNotAvailableException + * @throws StorageInvalidException */ public function checkStorageAvailability() { // see if we can find out why the share is unavailable @@ -214,24 +204,24 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, // we remove the invalid storage $this->manager->removeShare($this->mountPoint); $this->manager->getMountManager()->removeMount($this->mountPoint); - throw new StorageInvalidException("Remote share not found", 0, $e); + throw new StorageInvalidException('Remote share not found', 0, $e); } else { // Nextcloud instance is gone, likely to be a temporary server configuration error - throw new StorageNotAvailableException("No nextcloud instance found at remote", 0, $e); + throw new StorageNotAvailableException('No nextcloud instance found at remote', 0, $e); } } catch (ForbiddenException $e) { // auth error, remove share for now (provide a dialog in the future) $this->manager->removeShare($this->mountPoint); $this->manager->getMountManager()->removeMount($this->mountPoint); - throw new StorageInvalidException("Auth error when getting remote share"); + throw new StorageInvalidException('Auth error when getting remote share'); } catch (\GuzzleHttp\Exception\ConnectException $e) { - throw new StorageNotAvailableException("Failed to connect to remote instance", 0, $e); + throw new StorageNotAvailableException('Failed to connect to remote instance', 0, $e); } catch (\GuzzleHttp\Exception\RequestException $e) { - throw new StorageNotAvailableException("Error while sending request to remote instance", 0, $e); + throw new StorageNotAvailableException('Error while sending request to remote instance', 0, $e); } } - public function file_exists($path) { + public function file_exists(string $path): bool { if ($path === '') { return true; } else { @@ -263,19 +253,12 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, $client = $this->httpClient->newClient(); try { - $result = $client->get($url, [ - 'timeout' => 10, - 'connect_timeout' => 10, - 'verify' => !$this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates', false), - ])->getBody(); + $result = $client->get($url, $this->getDefaultRequestOptions())->getBody(); $data = json_decode($result); $returnValue = (is_object($data) && !empty($data->version)); - } catch (ConnectException $e) { - $returnValue = false; - } catch (ClientException $e) { - $returnValue = false; - } catch (RequestException $e) { + } catch (ConnectException|ClientException|RequestException $e) { $returnValue = false; + $this->logger->warning('Failed to test remote URL', ['exception' => $e]); } $cache->set($url, $returnValue, 60 * 60 * 24); @@ -321,14 +304,13 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, $url = rtrim($remote, '/') . '/index.php/apps/files_sharing/shareinfo?t=' . $token; // TODO: DI - $client = \OC::$server->getHTTPClientService()->newClient(); + $client = Server::get(IClientService::class)->newClient(); try { - $response = $client->post($url, [ + $response = $client->post($url, array_merge($this->getDefaultRequestOptions(), [ 'body' => ['password' => $password, 'depth' => $depth], - 'timeout' => 10, - 'connect_timeout' => 10, - ]); + ])); } catch (\GuzzleHttp\Exception\RequestException $e) { + $this->logger->warning('Failed to fetch share info', ['exception' => $e]); if ($e->getCode() === Http::STATUS_UNAUTHORIZED || $e->getCode() === Http::STATUS_FORBIDDEN) { throw new ForbiddenException(); } @@ -344,27 +326,34 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, return json_decode($response->getBody(), true); } - public function getOwner($path) { + public function getOwner(string $path): string|false { return $this->cloudId->getDisplayId(); } - 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) & Constants::PERMISSION_SHARE); } - public function getPermissions($path): int { + public function getPermissions(string $path): int { $response = $this->propfind($path); + if ($response === false) { + return 0; + } + + $ocsPermissions = $response['{http://open-collaboration-services.org/ns}share-permissions'] ?? null; + $ocmPermissions = $response['{http://open-cloud-mesh.org/ns}share-permissions'] ?? null; + $ocPermissions = $response['{http://owncloud.org/ns}permissions'] ?? null; // old federated sharing permissions - if (isset($response['{http://open-collaboration-services.org/ns}share-permissions'])) { - $permissions = (int)$response['{http://open-collaboration-services.org/ns}share-permissions']; - } elseif (isset($response['{http://open-cloud-mesh.org/ns}share-permissions'])) { + if ($ocsPermissions !== null) { + $permissions = (int)$ocsPermissions; + } elseif ($ocmPermissions !== null) { // permissions provided by the OCM API - $permissions = $this->ocmPermissions2ncPermissions($response['{http://open-collaboration-services.org/ns}share-permissions'], $path); - } elseif (isset($response['{http://owncloud.org/ns}permissions'])) { - return $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); + $permissions = $this->ocmPermissions2ncPermissions($ocmPermissions, $path); + } elseif ($ocPermissions !== null) { + return $this->parsePermissions($ocPermissions); } else { // use default permission if remote server doesn't provide the share permissions $permissions = $this->getDefaultPermissions($path); @@ -373,7 +362,7 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, return $permissions; } - public function needsPartFile() { + public function needsPartFile(): bool { return false; } @@ -423,7 +412,18 @@ class Storage extends DAV implements ISharedStorage, IDisableEncryptionStorage, return $permissions; } - public function free_space($path) { - return parent::free_space(""); + public function free_space(string $path): int|float|false { + return parent::free_space(''); + } + + private function getDefaultRequestOptions(): array { + $options = [ + 'timeout' => 10, + 'connect_timeout' => 10, + ]; + if ($this->config->getSystemValueBool('sharing.federation.allowSelfSignedCertificates')) { + $options['verify'] = false; + } + return $options; } } diff --git a/apps/files_sharing/lib/External/Watcher.php b/apps/files_sharing/lib/External/Watcher.php index ccd6bd179b3..f3616feabba 100644 --- a/apps/files_sharing/lib/External/Watcher.php +++ b/apps/files_sharing/lib/External/Watcher.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. |