diff options
Diffstat (limited to 'apps/files_sharing/lib/External/Manager.php')
-rw-r--r-- | apps/files_sharing/lib/External/Manager.php | 777 |
1 files changed, 573 insertions, 204 deletions
diff --git a/apps/files_sharing/lib/External/Manager.php b/apps/files_sharing/lib/External/Manager.php index 0a57324c32f..ff4781eba0f 100644 --- a/apps/files_sharing/lib/External/Manager.php +++ b/apps/files_sharing/lib/External/Manager.php @@ -1,98 +1,63 @@ <?php + /** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Björn Schießle <bjoern@schiessle.org> - * @author Joas Schilling <coding@schilljs.com> - * @author Jörn Friedrich Dreyer <jfd@butonic.de> - * @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> - * - * @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; +use Doctrine\DBAL\Driver\Exception; use OC\Files\Filesystem; -use OCA\FederatedFileSharing\DiscoveryManager; +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; +use OCP\IDBConnection; +use OCP\IGroupManager; +use OCP\IUserManager; +use OCP\IUserSession; use OCP\Notification\IManager; +use OCP\OCS\IDiscoveryService; +use OCP\Share; +use OCP\Share\IShare; +use Psr\Log\LoggerInterface; +/** + * @psalm-import-type Files_SharingRemoteShare from ResponseDefinitions + */ class Manager { - const STORAGE = '\OCA\Files_Sharing\External\Storage'; + public const STORAGE = '\OCA\Files_Sharing\External\Storage'; - /** - * @var string - */ + /** @var string|null */ private $uid; - /** - * @var \OCP\IDBConnection - */ - private $connection; - - /** - * @var \OC\Files\Mount\Manager - */ - private $mountManager; - - /** - * @var \OCP\Files\Storage\IStorageFactory - */ - private $storageLoader; - - /** - * @var IClientService - */ - private $clientService; - - /** - * @var IManager - */ - private $notificationManager; - /** @var DiscoveryManager */ - private $discoveryManager; - - /** - * @param \OCP\IDBConnection $connection - * @param \OC\Files\Mount\Manager $mountManager - * @param \OCP\Files\Storage\IStorageFactory $storageLoader - * @param IClientService $clientService - * @param IManager $notificationManager - * @param DiscoveryManager $discoveryManager - * @param string $uid - */ - public function __construct(\OCP\IDBConnection $connection, - \OC\Files\Mount\Manager $mountManager, - \OCP\Files\Storage\IStorageFactory $storageLoader, - IClientService $clientService, - IManager $notificationManager, - DiscoveryManager $discoveryManager, - $uid) { - $this->connection = $connection; - $this->mountManager = $mountManager; - $this->storageLoader = $storageLoader; - $this->clientService = $clientService; - $this->uid = $uid; - $this->notificationManager = $notificationManager; - $this->discoveryManager = $discoveryManager; + public function __construct( + 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->uid = $user ? $user->getUID() : null; } /** @@ -103,18 +68,20 @@ class Manager { * @param string $password * @param string $name * @param string $owner + * @param int $shareType * @param boolean $accepted * @param string $user - * @param int $remoteId + * @param string $remoteId + * @param int $parent * @return Mount|null + * @throws \Doctrine\DBAL\Exception */ - public function addShare($remote, $token, $password, $name, $owner, $accepted=false, $user = null, $remoteId = -1) { - - $user = $user ? $user : $this->uid; - $accepted = $accepted ? 1 : 0; + public function addShare($remote, $token, $password, $name, $owner, $shareType, $accepted = false, $user = null, $remoteId = '', $parent = -1) { + $user = $user ?? $this->uid; + $accepted = $accepted ? IShare::STATUS_ACCEPTED : IShare::STATUS_PENDING; $name = Filesystem::normalizePath('/' . $name); - if (!$accepted) { + if ($accepted !== IShare::STATUS_ACCEPTED) { // To avoid conflicts with the mount point generation later, // we only use a temporary mount point name here. The real // mount point name will be generated when accepting the share, @@ -123,16 +90,17 @@ class Manager { $mountPoint = $tmpMountPointName; $hash = md5($tmpMountPointName); $data = [ - 'remote' => $remote, - 'share_token' => $token, - 'password' => $password, - 'name' => $name, - 'owner' => $owner, - 'user' => $user, - 'mountpoint' => $mountPoint, - 'mountpoint_hash' => $hash, - 'accepted' => $accepted, - 'remote_id' => $remoteId, + 'remote' => $remote, + 'share_token' => $token, + 'password' => $password, + 'name' => $name, + 'owner' => $owner, + 'user' => $user, + 'mountpoint' => $mountPoint, + 'mountpoint_hash' => $hash, + 'accepted' => $accepted, + 'remote_id' => $remoteId, + 'share_type' => $shareType, ]; $i = 1; @@ -149,37 +117,165 @@ class Manager { $mountPoint = Filesystem::normalizePath('/' . $mountPoint); $hash = md5($mountPoint); + $this->writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType); + + $options = [ + 'remote' => $remote, + 'token' => $token, + 'password' => $password, + 'mountpoint' => $mountPoint, + 'owner' => $owner + ]; + return $this->mountShare($options, $user); + } + + /** + * write remote share to the database + * + * @param $remote + * @param $token + * @param $password + * @param $name + * @param $owner + * @param $user + * @param $mountPoint + * @param $hash + * @param $accepted + * @param $remoteId + * @param $parent + * @param $shareType + * + * @return void + * @throws \Doctrine\DBAL\Driver\Exception + */ + private function writeShareToDb($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId, $parent, $shareType): void { $query = $this->connection->prepare(' INSERT INTO `*PREFIX*share_external` - (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + (`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`, `parent`, `share_type`) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) '); - $query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId)); - - $options = array( - 'remote' => $remote, - 'token' => $token, - 'password' => $password, - 'mountpoint' => $mountPoint, - 'owner' => $owner - ); - return $this->mountShare($options); + $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 */ - public function getShare($id) { + private function fetchShareByToken($token) { $getShare = $this->connection->prepare(' - SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted` + SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted`, `parent`, `share_type`, `password`, `mountpoint_hash` FROM `*PREFIX*share_external` - WHERE `id` = ? AND `user` = ?'); - $result = $getShare->execute(array($id, $this->uid)); + WHERE `share_token` = ?'); + $result = $getShare->execute([$token]); + $share = $result->fetch(); + $result->closeCursor(); + return $share; + } - return $result ? $getShare->fetch() : false; + private function fetchUserShare($parentId, $uid) { + $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 `parent` = ? AND `user` = ?'); + $result = $getShare->execute([$parentId, $uid]); + $share = $result->fetch(); + $result->closeCursor(); + if ($share !== false) { + return $share; + } + return null; + } + + public function getShare(int $id, ?string $user = null): array|false { + $user = $user ?? $this->uid; + $share = $this->fetchShare($id); + if ($share === false) { + return false; + } + + // check if the user is allowed to access it + if ($this->canAccessShare($share, $user)) { + return $share; + } + + 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 + $groupShare = $this->fetchShare($parentId); + } else { + $groupShare = $share; + } + + $user = $this->userManager->get($user); + if ($this->groupManager->get($groupShare['user'])->inGroup($user)) { + return true; + } + } + + return false; + } + + /** + * Updates accepted flag in the database + * + * @param int $id + */ + private function updateAccepted(int $shareId, bool $accepted) : void { + $query = $this->connection->prepare(' + UPDATE `*PREFIX*share_external` + SET `accepted` = ? + WHERE `id` = ?'); + $updateResult = $query->execute([$accepted ? 1 : 0, $shareId]); + $updateResult->closeCursor(); } /** @@ -188,31 +284,92 @@ class Manager { * @param int $id * @return bool True if the share could be accepted, false otherwise */ - public function acceptShare($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); + $share = $this->getShare($id, $user); + $result = false; if ($share) { - $mountPoint = Files::buildNotExistingFileName('/', $share['name']); - $mountPoint = Filesystem::normalizePath('/' . $mountPoint); + \OC_Util::setupFS($user); + $shareFolder = Helper::getShareFolder(null, $user); + $mountPoint = Files::buildNotExistingFileName($shareFolder, $share['name']); + $mountPoint = Filesystem::normalizePath($mountPoint); $hash = md5($mountPoint); + $userShareAccepted = false; - $acceptShare = $this->connection->prepare(' + if ((int)$share['share_type'] === IShare::TYPE_USER) { + $acceptShare = $this->connection->prepare(' UPDATE `*PREFIX*share_external` SET `accepted` = ?, `mountpoint` = ?, `mountpoint_hash` = ? WHERE `id` = ? AND `user` = ?'); - $acceptShare->execute(array(1, $mountPoint, $hash, $id, $this->uid)); - $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept'); - - \OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $share['remote']]); + $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, $user); + } + + if ($subshare !== null) { + try { + $acceptShare = $this->connection->prepare(' + UPDATE `*PREFIX*share_external` + SET `accepted` = ?, + `mountpoint` = ?, + `mountpoint_hash` = ? + WHERE `id` = ? AND `user` = ?'); + $acceptShare->execute([1, $mountPoint, $hash, $subshare['id'], $user]); + $result = true; + } catch (Exception $e) { + $this->logger->emergency('Could not update share', ['exception' => $e]); + $result = false; + } + } else { + try { + $this->writeShareToDb( + $share['remote'], + $share['share_token'], + $share['password'], + $share['name'], + $share['owner'], + $user, + $mountPoint, $hash, 1, + $share['remote_id'], + $id, + $share['share_type']); + $result = true; + } catch (Exception $e) { + $this->logger->emergency('Could not create share', ['exception' => $e]); + $result = false; + } + } + } - $this->processNotification($id); - return true; + 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 InvalidateMountCacheEvent($this->userManager->get($user))); + $result = true; + } } - return false; + // Make sure the user has no notification for something that does not exist anymore. + $this->processNotification($id, $user); + + return $result; } /** @@ -221,31 +378,84 @@ class Manager { * @param int $id * @return bool True if the share could be declined, false otherwise */ - public function declineShare($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); + $share = $this->getShare($id, $user); + $result = false; - if ($share) { + if ($share && (int)$share['share_type'] === IShare::TYPE_USER) { $removeShare = $this->connection->prepare(' DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?'); - $removeShare->execute(array($id, $this->uid)); + $removeShare->execute([$id, $user]); $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); - $this->processNotification($id); - return true; + $this->processNotification($id, $user); + $result = true; + } elseif ($share && (int)$share['share_type'] === IShare::TYPE_GROUP) { + $parentId = (int)$share['parent']; + if ($parentId !== -1) { + // this is the sub-share + $subshare = $share; + } else { + $subshare = $this->fetchUserShare($id, $user); + } + + if ($subshare !== null) { + try { + $this->updateAccepted((int)$subshare['id'], false); + $result = true; + } catch (Exception $e) { + $this->logger->emergency('Could not update share', ['exception' => $e]); + $result = false; + } + } else { + try { + $this->writeShareToDb( + $share['remote'], + $share['share_token'], + $share['password'], + $share['name'], + $share['owner'], + $user, + $share['mountpoint'], + $share['mountpoint_hash'], + 0, + $share['remote_id'], + $id, + $share['share_type']); + $result = true; + } catch (Exception $e) { + $this->logger->emergency('Could not create share', ['exception' => $e]); + $result = false; + } + } + $this->processNotification($id, $user); } - return false; + return $result; } - /** - * @param int $remoteShare - */ - public function processNotification($remoteShare) { + 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) - ->setObject('remote_share', (int) $remoteShare); + ->setUser($user) + ->setObject('remote_share', (string)$remoteShare); $this->notificationManager->markProcessed($filter); } @@ -254,14 +464,22 @@ class Manager { * * @param string $remote * @param string $token - * @param int $remoteId Share id on the remote host + * @param string $remoteId Share id on the remote host * @param string $feedback * @return boolean */ private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) { + $result = $this->tryOCMEndPoint($remote, $token, $remoteId, $feedback); + + if (is_array($result)) { + return true; + } + + $federationEndpoints = $this->discoveryService->discover($remote, 'FEDERATED_SHARING'); + $endpoint = $federationEndpoints['share'] ?? '/ocs/v2.php/cloud/shares'; - $url = rtrim($remote, '/') . $this->discoveryManager->getShareEndpoint($remote) . '/' . $remoteId . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT; - $fields = array('token' => $token); + $url = rtrim($remote, '/') . $endpoint . '/' . $remoteId . '/' . $feedback . '?format=' . Share::RESPONSE_FORMAT; + $fields = ['token' => $token]; $client = $this->clientService->newClient(); @@ -283,6 +501,49 @@ class Manager { } /** + * try send accept message to ocm end-point + * + * @param string $remoteDomain + * @param string $token + * @param string $remoteId id of the share + * @param string $feedback + * @return array|false + */ + protected function tryOCMEndPoint($remoteDomain, $token, $remoteId, $feedback) { + switch ($feedback) { + case 'accept': + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + 'SHARE_ACCEPTED', + 'file', + $remoteId, + [ + 'sharedSecret' => $token, + 'message' => 'Recipient accept the share' + ] + + ); + return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification); + case 'decline': + $notification = $this->cloudFederationFactory->getCloudFederationNotification(); + $notification->setMessage( + 'SHARE_DECLINED', + 'file', + $remoteId, + [ + 'sharedSecret' => $token, + 'message' => 'Recipient declined the share' + ] + + ); + return $this->cloudFederationProviderManager->sendNotification($remoteDomain, $notification); + } + + return false; + } + + + /** * remove '/user/files' from the path and trailing slashes * * @param string $path @@ -293,11 +554,12 @@ 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($this->uid); + $data['certificateManager'] = \OC::$server->getCertificateManager(); return new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader); } @@ -305,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; } @@ -335,48 +597,67 @@ class Manager { WHERE `mountpoint_hash` = ? AND `user` = ? '); - $result = (bool)$query->execute(array($target, $targetHash, $sourceHash, $this->uid)); + $result = (bool)$query->execute([$target, $targetHash, $sourceHash, $this->uid]); + + $this->eventDispatcher->dispatchTyped(new InvalidateMountCacheEvent($this->userManager->get($this->uid))); return $result; } - public function removeShare($mountPoint) { - - $mountPointObj = $this->mountManager->find($mountPoint); + public function removeShare($mountPoint): bool { + try { + $mountPointObj = $this->mountManager->find($mountPoint); + } catch (NotFoundException $e) { + $this->logger->error('Mount point to remove share not found', ['mountPoint' => $mountPoint]); + return false; + } + if (!$mountPointObj instanceof Mount) { + $this->logger->error('Mount point to remove share is not an external share, share probably doesn\'t exist', ['mountPoint' => $mountPoint]); + return false; + } $id = $mountPointObj->getStorage()->getCache()->getId(''); $mountPoint = $this->stripPath($mountPoint); $hash = md5($mountPoint); - $getShare = $this->connection->prepare(' - SELECT `remote`, `share_token`, `remote_id` - FROM `*PREFIX*share_external` - WHERE `mountpoint_hash` = ? AND `user` = ?'); - $result = $getShare->execute(array($hash, $this->uid)); - - if ($result) { - $share = $getShare->fetch(); - $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); - } - $getShare->closeCursor(); - - $query = $this->connection->prepare(' - DELETE FROM `*PREFIX*share_external` - WHERE `mountpoint_hash` = ? - AND `user` = ? - '); - $result = (bool)$query->execute(array($hash, $this->uid)); + try { + $getShare = $this->connection->prepare(' + SELECT `remote`, `share_token`, `remote_id`, `share_type`, `id` + FROM `*PREFIX*share_external` + WHERE `mountpoint_hash` = ? AND `user` = ?'); + $result = $getShare->execute([$hash, $this->uid]); + $share = $result->fetch(); + $result->closeCursor(); + if ($share !== false && (int)$share['share_type'] === IShare::TYPE_USER) { + try { + $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); + } catch (\Throwable $e) { + // if we fail to notify the remote (probably cause the remote is down) + // we still want the share to be gone to prevent undeletable remotes + } + + $query = $this->connection->prepare(' + DELETE FROM `*PREFIX*share_external` + WHERE `id` = ? + '); + $deleteResult = $query->execute([(int)$share['id']]); + $deleteResult->closeCursor(); + } elseif ($share !== false && (int)$share['share_type'] === IShare::TYPE_GROUP) { + $this->updateAccepted((int)$share['id'], false); + } - if($result) { $this->removeReShares($id); + } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->emergency('Could not update share', ['exception' => $ex]); + return false; } - return $result; + return true; } /** * remove re-shares from share table and mapping in the federated_reshares table - * + * * @param $mountPointId */ protected function removeReShares($mountPointId) { @@ -388,7 +669,7 @@ class Manager { $query->delete('federated_reshares') - ->where($query->expr()->in('share_id', $query->createFunction('(' . $select . ')'))); + ->where($query->expr()->in('share_id', $query->createFunction($select))); $query->execute(); $deleteReShares = $this->connection->getQueryBuilder(); @@ -401,33 +682,86 @@ class Manager { * remove all shares for user $uid if the user was deleted * * @param string $uid - * @return bool */ - public function removeUserShares($uid) { - $getShare = $this->connection->prepare(' - SELECT `remote`, `share_token`, `remote_id` - FROM `*PREFIX*share_external` - WHERE `user` = ?'); - $result = $getShare->execute(array($uid)); - - if ($result) { - $shares = $getShare->fetchAll(); - foreach($shares as $share) { + public function removeUserShares($uid): bool { + try { + // TODO: use query builder + $getShare = $this->connection->prepare(' + SELECT `id`, `remote`, `share_type`, `share_token`, `remote_id` + FROM `*PREFIX*share_external` + WHERE `user` = ? + AND `share_type` = ?'); + $result = $getShare->execute([$uid, IShare::TYPE_USER]); + $shares = $result->fetchAll(); + $result->closeCursor(); + + foreach ($shares as $share) { $this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline'); } + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share_external') + // user field can specify a user or a group + ->where($qb->expr()->eq('user', $qb->createNamedParameter($uid))) + ->andWhere( + $qb->expr()->orX( + // delete direct shares + $qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_USER)), + // delete sub-shares of group shares for that user + $qb->expr()->andX( + $qb->expr()->eq('share_type', $qb->expr()->literal(IShare::TYPE_GROUP)), + $qb->expr()->neq('parent', $qb->expr()->literal(-1)), + ) + ) + ); + $qb->execute(); + } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->emergency('Could not delete user shares', ['exception' => $ex]); + return false; } - $query = $this->connection->prepare(' - DELETE FROM `*PREFIX*share_external` - WHERE `user` = ? - '); - return (bool)$query->execute(array($uid)); + return true; + } + + public function removeGroupShares($gid): bool { + try { + $getShare = $this->connection->prepare(' + SELECT `id`, `remote`, `share_type`, `share_token`, `remote_id` + FROM `*PREFIX*share_external` + WHERE `user` = ? + AND `share_type` = ?'); + $result = $getShare->execute([$gid, IShare::TYPE_GROUP]); + $shares = $result->fetchAll(); + $result->closeCursor(); + + $deletedGroupShares = []; + $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')) + ) + ); + + foreach ($shares as $share) { + $qb->setParameter('share_id', $share['id']); + $qb->setParameter('share_parent_id', $share['id']); + $qb->execute(); + } + } catch (\Doctrine\DBAL\Exception $ex) { + $this->logger->emergency('Could not delete user shares', ['exception' => $ex]); + return false; + } + + return true; } /** * 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); @@ -436,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); @@ -448,22 +782,57 @@ 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) { - $query = 'SELECT `id`, `remote`, `remote_id`, `share_token`, `name`, `owner`, `user`, `mountpoint`, `accepted` - FROM `*PREFIX*share_external` - WHERE `user` = ?'; - $parameters = [$this->uid]; - if (!is_null($accepted)) { - $query .= ' AND `accepted` = ?'; - $parameters[] = (int) $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 = []; + foreach ($groups as $group) { + $userGroups[] = $group->getGID(); } - $query .= ' ORDER BY `id` ASC'; - $shares = $this->connection->prepare($query); - $result = $shares->execute($parameters); + $qb = $this->connection->getQueryBuilder(); + $qb->select('id', 'share_type', 'parent', 'remote', 'remote_id', 'share_token', 'name', 'owner', 'user', 'mountpoint', 'accepted') + ->from('share_external') + ->where( + $qb->expr()->orX( + $qb->expr()->eq('user', $qb->createNamedParameter($this->uid)), + $qb->expr()->in( + 'user', + $qb->createNamedParameter($userGroups, IQueryBuilder::PARAM_STR_ARRAY) + ) + ) + ) + ->orderBy('id', 'ASC'); - return $result ? $shares->fetchAll() : []; + try { + $result = $qb->execute(); + $shares = $result->fetchAll(); + $result->closeCursor(); + + // remove parent group share entry if we have a specific user share entry for the user + $toRemove = []; + foreach ($shares as $share) { + if ((int)$share['share_type'] === IShare::TYPE_GROUP && (int)$share['parent'] > 0) { + $toRemove[] = $share['parent']; + } + } + $shares = array_filter($shares, function ($share) use ($toRemove) { + return !in_array($share['id'], $toRemove, true); + }); + + if (!is_null($accepted)) { + $shares = array_filter($shares, function ($share) use ($accepted) { + return (bool)$share['accepted'] === $accepted; + }); + } + return array_values($shares); + } catch (\Doctrine\DBAL\Exception $e) { + $this->logger->emergency('Error when retrieving shares', ['exception' => $e]); + return []; + } } } |