diff options
Diffstat (limited to 'apps/files_sharing/lib/External/Manager.php')
-rw-r--r-- | apps/files_sharing/lib/External/Manager.php | 293 |
1 files changed, 150 insertions, 143 deletions
diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index f18d8346dc4..ff4781eba0f 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -1,35 +1,9 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Bjoern Schiessle <bjoern@schiessle.org> - * @author Björn Schießle <bjoern@schiessle.org> - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Daniel Hansson <daniel@techandme.se> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @author Julius Härtl <jus@bitgrid.net> - * @author Lukas Reschke <lukas@statuscode.ch> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <robin@icewind.nl> - * @author Roeland Jago Douma <roeland@famdouma.nl> - * @author Stefan Weil <sw@weilnetz.de> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only */ namespace OCA\Files_Sharing\External; @@ -38,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; @@ -56,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; } /** @@ -146,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); @@ -195,7 +126,7 @@ class Manager { 'mountpoint' => $mountPoint, 'owner' => $owner ]; - return $this->mountShare($options); + return $this->mountShare($options, $user); } /** @@ -226,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; @@ -257,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 @@ -278,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; } } @@ -307,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); @@ -326,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) { @@ -344,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]); @@ -358,7 +344,7 @@ class Manager { $share['password'], $share['name'], $share['owner'], - $this->uid, + $user, $mountPoint, $hash, 1, $share['remote_id'], $id, @@ -370,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; } @@ -391,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']; @@ -409,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) { @@ -428,7 +421,7 @@ class Manager { $share['password'], $share['name'], $share['owner'], - $this->uid, + $user, $share['mountpoint'], $share['mountpoint_hash'], 0, @@ -441,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); } @@ -550,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); @@ -562,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; } @@ -594,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; } @@ -733,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']); @@ -756,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); @@ -765,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); @@ -777,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 = []; |