diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2019-06-25 19:34:38 -0100 |
---|---|---|
committer | Roeland Jago Douma <roeland@famdouma.nl> | 2019-12-03 08:10:27 +0100 |
commit | 5794f14df905380cb179cc8eb13dec60d96dfcc6 (patch) | |
tree | 4ea2fd5baf57dc6f51d139c44d5831d7f9db665d | |
parent | 1e91b6a716ab85f402e4b9dbdb4107ff133e1597 (diff) | |
download | nextcloud-server-5794f14df905380cb179cc8eb13dec60d96dfcc6.tar.gz nextcloud-server-5794f14df905380cb179cc8eb13dec60d96dfcc6.zip |
Inherited Shares
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
working on users with resharing rights
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
-getLogger()
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
cleaning
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
fix type
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Update SharingRightsException.php
5 files changed, 318 insertions, 49 deletions
diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index ab5e829f86b..fa59cf936c9 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -54,6 +54,11 @@ return [ 'verb' => 'GET', ], [ + 'name' => 'ShareAPI#getInheritedShares', + 'url' => '/api/v1/shares/inherited', + 'verb' => 'GET', + ], + [ 'name' => 'ShareAPI#createShare', 'url' => '/api/v1/shares', 'verb' => 'POST', diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index af60a18718c..3a2b1159320 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -34,6 +34,7 @@ return array( 'OCA\\Files_Sharing\\DeleteOrphanedSharesJob' => $baseDir . '/../lib/DeleteOrphanedSharesJob.php', 'OCA\\Files_Sharing\\Exceptions\\BrokenPath' => $baseDir . '/../lib/Exceptions/BrokenPath.php', 'OCA\\Files_Sharing\\Exceptions\\S2SException' => $baseDir . '/../lib/Exceptions/S2SException.php', + 'OCA\\Files_Sharing\\Exceptions\\SharingRightsException' => $baseDir . '/../lib/Exceptions/SharingRightsException.php', 'OCA\\Files_Sharing\\ExpireSharesJob' => $baseDir . '/../lib/ExpireSharesJob.php', 'OCA\\Files_Sharing\\External\\Cache' => $baseDir . '/../lib/External/Cache.php', 'OCA\\Files_Sharing\\External\\Manager' => $baseDir . '/../lib/External/Manager.php', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index 67f8ce47f72..1c1322f6c3e 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -49,6 +49,7 @@ class ComposerStaticInitFiles_Sharing 'OCA\\Files_Sharing\\DeleteOrphanedSharesJob' => __DIR__ . '/..' . '/../lib/DeleteOrphanedSharesJob.php', 'OCA\\Files_Sharing\\Exceptions\\BrokenPath' => __DIR__ . '/..' . '/../lib/Exceptions/BrokenPath.php', 'OCA\\Files_Sharing\\Exceptions\\S2SException' => __DIR__ . '/..' . '/../lib/Exceptions/S2SException.php', + 'OCA\\Files_Sharing\\Exceptions\\SharingRightsException' => __DIR__ . '/..' . '/../lib/Exceptions/SharingRightsException.php', 'OCA\\Files_Sharing\\ExpireSharesJob' => __DIR__ . '/..' . '/../lib/ExpireSharesJob.php', 'OCA\\Files_Sharing\\External\\Cache' => __DIR__ . '/..' . '/../lib/External/Cache.php', 'OCA\\Files_Sharing\\External\\Manager' => __DIR__ . '/..' . '/../lib/External/Manager.php', diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index db92b8f3818..b470061c96b 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -12,6 +12,7 @@ declare(strict_types=1); * @author Roeland Jago Douma <roeland@famdouma.nl> * @author Vincent Petry <pvince81@owncloud.com> * @author John Molakvoæ <skjnldsv@protonmail.com> + * @author Maxence Lange <maxence@artificial-owl.com> * * @license AGPL-3.0 * @@ -31,6 +32,8 @@ declare(strict_types=1); namespace OCA\Files_Sharing\Controller; +use OCA\Files_Sharing\Exceptions\SharingRightsException; +use OCA\Files_Sharing\External\Storage; use OCA\Files\Helper; use OCA\Files_Sharing\External\Storage; use OCP\App\IAppManager; @@ -42,6 +45,7 @@ use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; use OCP\AppFramework\QueryException; use OCP\Constants; +use OCP\Files\InvalidPathException; use OCP\Files\IRootFolder; use OCP\Files\Node; use OCP\Files\NotFoundException; @@ -383,7 +387,7 @@ class ShareAPIController extends OCSController { * @throws OCSException * @throws OCSForbiddenException * @throws OCSNotFoundException - * @throws \OCP\Files\InvalidPathException + * @throws InvalidPathException * @suppress PhanUndeclaredClassMethod */ public function createShare( @@ -579,11 +583,12 @@ class ShareAPIController extends OCSController { } /** - * @param \OCP\Files\File|\OCP\Files\Folder $node + * @param null|Node $node * @param boolean $includeTags - * @return DataResponse + * + * @return array */ - private function getSharedWithMe($node = null, bool $includeTags): DataResponse { + private function getSharedWithMe($node, bool $includeTags): array { $userShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_USER, $node, -1, 0); $groupShares = $this->shareManager->getSharedWith($this->currentUser, Share::SHARE_TYPE_GROUP, $node, -1, 0); @@ -592,7 +597,7 @@ class ShareAPIController extends OCSController { $shares = array_merge($userShares, $groupShares, $circleShares, $roomShares); - $shares = array_filter($shares, function (IShare $share) { + $shares = array_filter($shares, function(IShare $share) { return $share->getShareOwner() !== $this->currentUser; }); @@ -611,13 +616,15 @@ class ShareAPIController extends OCSController { $formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager()); } - return new DataResponse($formatted); + return $formatted; } /** - * @param \OCP\Files\Folder $folder + * @param \OCP\Files\Node $folder + * * @return array * @throws OCSBadRequestException + * @throws NotFoundException */ private function getSharesInDir(Node $folder): array { if (!($folder instanceof \OCP\Files\Folder)) { @@ -638,15 +645,35 @@ class ShareAPIController extends OCSController { if (in_array($share->getId(), $known)) { return false; } - $known[] = $share->getId(); - return true; - }); + + try { + $format = $this->formatShare($share); + + $known[] = $share->getId(); + $formatted[] = $format; + if ($share->getSharedBy() === $this->currentUser) { + $miniFormatted[] = $format; + } + if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $folder)) { + $resharingRight = true; + } + } catch (\Exception $e) { + //Ignore this share + } + } + + if (!$resharingRight) { + $formatted = $miniFormatted; + } + + return $formatted; } /** * The getShares function. * * @NoAdminRequired + * @NoCSRFRequired * * @param string $shared_with_me * @param string $reshares @@ -659,64 +686,82 @@ class ShareAPIController extends OCSController { * - Get shares for a specific path (?path=...) * - Get all shares in a folder (?subfiles=true&path=..) * + * @param string $include_tags + * * @return DataResponse + * @throws NotFoundException + * @throws OCSBadRequestException * @throws OCSNotFoundException */ public function getShares( string $shared_with_me = 'false', string $reshares = 'false', string $subfiles = 'false', - string $path = null, + string $path = '', string $include_tags = 'false' ): DataResponse { - if ($path !== null) { + $node = null; + if ($path !== '') { $userFolder = $this->rootFolder->getUserFolder($this->currentUser); try { - $path = $userFolder->get($path); - $this->lock($path); - } catch (\OCP\Files\NotFoundException $e) { - throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist')); + $node = $userFolder->get($path); + $this->lock($node); + } catch (NotFoundException $e) { + throw new OCSNotFoundException( + $this->l->t('Wrong path, file/folder doesn\'t exist') + ); } catch (LockedException $e) { - throw new OCSNotFoundException($this->l->t('Could not lock path')); + throw new OCSNotFoundException($this->l->t('Could not lock node')); } } - $include_tags = $include_tags === 'true'; + $shares = $this->getFormattedShares( + $this->currentUser, + $node, + ($shared_with_me === 'true'), + ($reshares === 'true'), + ($subfiles === 'true'), + ($include_tags === 'true') + ); - if ($shared_with_me === 'true') { - $result = $this->getSharedWithMe($path, $include_tags); - return $result; - } + return new DataResponse($shares); + } - if ($reshares === 'true') { - $reshares = true; - } else { - $reshares = false; + + /** + * @param string $viewer + * @param Node $node + * @param bool $sharedWithMe + * @param bool $reShares + * @param bool $subFiles + * @param bool $includeTags + * + * @return array + * @throws NotFoundException + * @throws OCSBadRequestException + */ + private function getFormattedShares( + string $viewer, $node = null, bool $sharedWithMe = false, bool $reShares = false, + bool $subFiles = false, bool $includeTags = false + ): array { + + if ($sharedWithMe) { + return $this->getSharedWithMe($node, $includeTags); } - if ($subfiles === 'true') { - $shares = $this->getSharesInDir($path); - $recipientNode = null; - } else { - // get all shares - $shares = $this->getAllShares($path, $reshares); - $recipientNode = $path; + if ($subFiles) { + return $this->getSharesInDir($node); } - // process all shares + $shares = $this->getSharesFromNode($viewer, $node, $reShares); + $formatted = $miniFormatted = []; $resharingRight = false; foreach ($shares as $share) { - /** @var IShare $share */ - - // do not list the shares of the current user - if ($share->getSharedWith() === $this->currentUser) { - continue; - } - try { - $format = $this->formatShare($share, $recipientNode); + /** @var IShare $share */ + $format = $this->formatShare($share, $node); $formatted[] = $format; // let's also build a list of shares created @@ -731,8 +776,7 @@ class ShareAPIController extends OCSController { if (!$resharingRight && $this->shareProviderResharingRights($this->currentUser, $share, $path)) { $resharingRight = true; } - } catch (\Exception $e) { - //Ignore share + } catch (InvalidPathException | NotFoundException $e) { } } @@ -740,13 +784,89 @@ class ShareAPIController extends OCSController { $formatted = $miniFormatted; } - if ($include_tags) { - $formatted = Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager()); + if ($includeTags) { + $formatted = + Helper::populateTags($formatted, 'file_source', \OC::$server->getTagManager()); } - return new DataResponse($formatted); + return $formatted; } + + /** + * The getInheritedShares function. + * returns all shares relative to a file, including parent folders shares rights. + * + * @NoAdminRequired + * + * @param string $path + * + * - Get shares by the current user + * - Get shares by the current user and reshares (?reshares=true) + * - Get shares with the current user (?shared_with_me=true) + * - Get shares for a specific path (?path=...) + * - Get all shares in a folder (?subfiles=true&path=..) + * + * @return DataResponse + * @throws InvalidPathException + * @throws NotFoundException + * @throws OCSNotFoundException + * @throws OCSBadRequestException + * @throws SharingRightsException + */ + public function getInheritedShares(string $path): DataResponse { + + // get Node from (string) path. + $userFolder = $this->rootFolder->getUserFolder($this->currentUser); + try { + $node = $userFolder->get($path); + $this->lock($node); + } catch (\OCP\Files\NotFoundException $e) { + throw new OCSNotFoundException($this->l->t('Wrong path, file/folder doesn\'t exist')); + } catch (LockedException $e) { + throw new OCSNotFoundException($this->l->t('Could not lock path')); + } + + // current User has resharing rights ? + $this->confirmSharingRights($node); + + // initiate real owner. + $owner = $node->getOwner() + ->getUID(); + if (!$this->userManager->userExists($owner)) { + return new DataResponse([]); + } + + // get node based on the owner, fix owner in case of external storage + $userFolder = $this->rootFolder->getUserFolder($owner); + if ($node->getId() !== $userFolder->getId() && !$userFolder->isSubNode($node)) { + $owner = $node->getOwner() + ->getUID(); + $userFolder = $this->rootFolder->getUserFolder($owner); + $nodes = $userFolder->getById($node->getId()); + $node = array_shift($nodes); + } + $basePath = $userFolder->getPath(); + + // generate node list for each parent folders + /** @var Node[] $nodes */ + $nodes = []; + while ($node->getPath() !== $basePath) { + $nodes[] = $node; + $node = $node->getParent(); + } + + // for each nodes, retrieve shares. + $shares = []; + foreach ($nodes as $node) { + $getShares = $this->getFormattedShares($owner, $node, false, true); + $this->mergeFormattedShares($shares, $getShares); + } + + return new DataResponse(array_values($shares)); + } + + /** * @NoAdminRequired * @@ -1262,6 +1382,90 @@ class ShareAPIController extends OCSController { /** + * @param string $viewer + * @param Node $node + * @param bool $reShares + * + * @return IShare[] + */ + private function getSharesFromNode(string $viewer, $node, bool $reShares): array { + + $providers = [ + Share::SHARE_TYPE_USER, + Share::SHARE_TYPE_GROUP, + Share::SHARE_TYPE_LINK, + Share::SHARE_TYPE_EMAIL, + Share::SHARE_TYPE_EMAIL, + Share::SHARE_TYPE_CIRCLE, + Share::SHARE_TYPE_ROOM + ]; + + // Should we assume that the (currentUser) viewer is the owner of the node !? + $shares = []; + foreach ($providers as $provider) { + if (!$this->shareManager->shareProviderExists($provider)) { + continue; + } + + $providerShares = + $this->shareManager->getSharesBy($viewer, $provider, $node, $reShares, -1, 0); + $shares = array_merge($shares, $providerShares); + } + + if ($this->shareManager->outgoingServer2ServerSharesAllowed()) { + $federatedShares = $this->shareManager->getSharesBy( + $this->currentUser, Share::SHARE_TYPE_REMOTE, $node, $reShares, -1, 0 + ); + $shares = array_merge($shares, $federatedShares); + } + + if ($this->shareManager->outgoingServer2ServerGroupSharesAllowed()) { + $federatedShares = $this->shareManager->getSharesBy( + $this->currentUser, Share::SHARE_TYPE_REMOTE_GROUP, $node, $reShares, -1, 0 + ); + $shares = array_merge($shares, $federatedShares); + } + + return $shares; + } + + + /** + * @param Node $node + * + * @throws SharingRightsException + */ + private function confirmSharingRights(Node $node): void { + if (!$this->hasResharingRights($this->currentUser, $node)) { + throw new SharingRightsException('no sharing rights on this item'); + } + } + + + /** + * @param string $viewer + * @param Node $node + * + * @return bool + */ + private function hasResharingRights($viewer, $node): bool { + foreach ([$node, $node->getParent()] as $node) { + $shares = $this->getSharesFromNode($viewer, $node, true); + foreach ($shares as $share) { + try { + if ($this->shareProviderResharingRights($viewer, $share, $node)) { + return true; + } + } catch (InvalidPathException | NotFoundException $e) { + } + } + } + + return false; + } + + + /** * Returns if we can find resharing rights in an IShare object for a specific user. * * @suppress PhanUndeclaredClassMethod @@ -1269,9 +1473,10 @@ class ShareAPIController extends OCSController { * @param string $userId * @param IShare $share * @param Node $node + * * @return bool * @throws NotFoundException - * @throws \OCP\Files\InvalidPathException + * @throws InvalidPathException */ private function shareProviderResharingRights(string $userId, IShare $share, $node): bool { @@ -1280,7 +1485,7 @@ class ShareAPIController extends OCSController { } // we check that current user have parent resharing rights on the current file - if ($node !== null && ($node->getPermissions() & \OCP\Constants::PERMISSION_SHARE) !== 0) { + if ($node !== null && ($node->getPermissions() & Constants::PERMISSION_SHARE) !== 0) { return true; } @@ -1357,4 +1562,21 @@ class ShareAPIController extends OCSController { return array_merge($userShares, $groupShares, $linkShares, $mailShares, $circleShares, $roomShares, $federatedShares, $federatedGroupShares); } + + /** + * merging already formatted shares. + * We'll make an associative array to easily detect duplicate Ids. + * Keys _needs_ to be removed after all shares are retrieved and merged. + * + * @param array $shares + * @param array $newShares + */ + private function mergeFormattedShares(array &$shares, array $newShares) { + foreach ($newShares as $newShare) { + if (!array_key_exists($newShare['id'], $shares)) { + $shares[$newShare['id']] = $newShare; + } + } + } + } diff --git a/apps/files_sharing/lib/Exceptions/SharingRightsException.php b/apps/files_sharing/lib/Exceptions/SharingRightsException.php new file mode 100644 index 00000000000..2ec496c0421 --- /dev/null +++ b/apps/files_sharing/lib/Exceptions/SharingRightsException.php @@ -0,0 +1,40 @@ +<?php + +/** + * @copyright Copyright (c) 2019, Maxence Lange <maxence@artificial-owl.com> + * + * @author Maxence Lange <maxence@artificial-owl.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Sharing\Exceptions; + +use Exception; + + +/** + * Sharing and Resharing rights. + * + * Class SharingRightsException + * + * @package OCA\Files_Sharing\Exceptions + */ +class SharingRightsException extends Exception { + +} + |