diff options
author | Vincent Petry <vincent@nextcloud.com> | 2022-05-18 15:54:27 +0300 |
---|---|---|
committer | Carl Schwan <carl@carlschwan.eu> | 2022-08-03 14:24:13 +0300 |
commit | f1ab964819603b39442fe20150a982439f688d11 (patch) | |
tree | bdf1fc8e95c3a6f2c3831491b596ce122e08f29b /lib | |
parent | 6088f720bd0e80307268089800b115e706bc664c (diff) |
Add share attributes + prevent download permission
Makes it possible to store download permission
Signed-off-by: Vincent Petry <vincent@nextcloud.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/composer/composer/autoload_classmap.php | 2 | ||||
-rw-r--r-- | lib/composer/composer/autoload_static.php | 2 | ||||
-rw-r--r-- | lib/private/Share20/DefaultShareProvider.php | 66 | ||||
-rw-r--r-- | lib/private/Share20/Manager.php | 14 | ||||
-rw-r--r-- | lib/private/Share20/Share.php | 27 | ||||
-rw-r--r-- | lib/private/Share20/ShareAttributes.php | 73 | ||||
-rw-r--r-- | lib/private/legacy/OC_Files.php | 33 | ||||
-rw-r--r-- | lib/public/Share/IAttributes.php | 68 | ||||
-rw-r--r-- | lib/public/Share/IShare.php | 27 |
9 files changed, 308 insertions, 4 deletions
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 59939b9cb1f..83d1e77b756 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -528,6 +528,7 @@ return array( 'OCP\\Share\\Exceptions\\GenericShareException' => $baseDir . '/lib/public/Share/Exceptions/GenericShareException.php', 'OCP\\Share\\Exceptions\\IllegalIDChangeException' => $baseDir . '/lib/public/Share/Exceptions/IllegalIDChangeException.php', 'OCP\\Share\\Exceptions\\ShareNotFound' => $baseDir . '/lib/public/Share/Exceptions/ShareNotFound.php', + 'OCP\\Share\\IAttributes' => $baseDir . '/lib/public/Share/IAttributes.php', 'OCP\\Share\\IManager' => $baseDir . '/lib/public/Share/IManager.php', 'OCP\\Share\\IProviderFactory' => $baseDir . '/lib/public/Share/IProviderFactory.php', 'OCP\\Share\\IShare' => $baseDir . '/lib/public/Share/IShare.php', @@ -1500,6 +1501,7 @@ return array( 'OC\\Share20\\Manager' => $baseDir . '/lib/private/Share20/Manager.php', 'OC\\Share20\\ProviderFactory' => $baseDir . '/lib/private/Share20/ProviderFactory.php', 'OC\\Share20\\Share' => $baseDir . '/lib/private/Share20/Share.php', + 'OC\\Share20\\ShareAttributes' => $baseDir . '/lib/private/Share20/ShareAttributes.php', 'OC\\Share20\\ShareHelper' => $baseDir . '/lib/private/Share20/ShareHelper.php', 'OC\\Share20\\UserRemovedListener' => $baseDir . '/lib/private/Share20/UserRemovedListener.php', 'OC\\Share\\Constants' => $baseDir . '/lib/private/Share/Constants.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 57ba09046b3..4f7e46a0a5e 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -557,6 +557,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OCP\\Share\\Exceptions\\GenericShareException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/GenericShareException.php', 'OCP\\Share\\Exceptions\\IllegalIDChangeException' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/IllegalIDChangeException.php', 'OCP\\Share\\Exceptions\\ShareNotFound' => __DIR__ . '/../../..' . '/lib/public/Share/Exceptions/ShareNotFound.php', + 'OCP\\Share\\IAttributes' => __DIR__ . '/../../..' . '/lib/public/Share/IAttributes.php', 'OCP\\Share\\IManager' => __DIR__ . '/../../..' . '/lib/public/Share/IManager.php', 'OCP\\Share\\IProviderFactory' => __DIR__ . '/../../..' . '/lib/public/Share/IProviderFactory.php', 'OCP\\Share\\IShare' => __DIR__ . '/../../..' . '/lib/public/Share/IShare.php', @@ -1529,6 +1530,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Share20\\Manager' => __DIR__ . '/../../..' . '/lib/private/Share20/Manager.php', 'OC\\Share20\\ProviderFactory' => __DIR__ . '/../../..' . '/lib/private/Share20/ProviderFactory.php', 'OC\\Share20\\Share' => __DIR__ . '/../../..' . '/lib/private/Share20/Share.php', + 'OC\\Share20\\ShareAttributes' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareAttributes.php', 'OC\\Share20\\ShareHelper' => __DIR__ . '/../../..' . '/lib/private/Share20/ShareHelper.php', 'OC\\Share20\\UserRemovedListener' => __DIR__ . '/../../..' . '/lib/private/Share20/UserRemovedListener.php', 'OC\\Share\\Constants' => __DIR__ . '/../../..' . '/lib/private/Share/Constants.php', diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 9638706025b..5148cf806db 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -52,6 +52,7 @@ use OCP\IUserManager; use OCP\L10N\IFactory; use OCP\Mail\IMailer; use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IAttributes; use OCP\Share\IShare; use OCP\Share\IShareProvider; @@ -193,6 +194,12 @@ class DefaultShareProvider implements IShareProvider { // set the permissions $qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions())); + // set share attributes + $shareAttributes = $this->formatShareAttributes( + $share->getAttributes() + ); + $qb->setValue('attributes', $qb->createNamedParameter($shareAttributes)); + // Set who created this share $qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy())); @@ -248,6 +255,8 @@ class DefaultShareProvider implements IShareProvider { public function update(\OCP\Share\IShare $share) { $originalShare = $this->getShareById($share->getId()); + $shareAttributes = $this->formatShareAttributes($share->getAttributes()); + if ($share->getShareType() === IShare::TYPE_USER) { /* * We allow updating the recipient on user shares. @@ -259,6 +268,7 @@ class DefaultShareProvider implements IShareProvider { ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('attributes', $qb->createNamedParameter($shareAttributes)) ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) @@ -272,6 +282,7 @@ class DefaultShareProvider implements IShareProvider { ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('attributes', $qb->createNamedParameter($shareAttributes)) ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) @@ -301,6 +312,7 @@ class DefaultShareProvider implements IShareProvider { ->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId()))) ->andWhere($qb->expr()->neq('permissions', $qb->createNamedParameter(0))) ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('attributes', $qb->createNamedParameter($shareAttributes)) ->execute(); } elseif ($share->getShareType() === IShare::TYPE_LINK) { $qb = $this->dbConn->getQueryBuilder(); @@ -311,6 +323,7 @@ class DefaultShareProvider implements IShareProvider { ->set('uid_owner', $qb->createNamedParameter($share->getShareOwner())) ->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy())) ->set('permissions', $qb->createNamedParameter($share->getPermissions())) + ->set('attributes', $qb->createNamedParameter($shareAttributes)) ->set('item_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('file_source', $qb->createNamedParameter($share->getNode()->getId())) ->set('token', $qb->createNamedParameter($share->getToken())) @@ -611,6 +624,10 @@ class DefaultShareProvider implements IShareProvider { $data = $stmt->fetch(); $stmt->closeCursor(); + $shareAttributes = $this->formatShareAttributes( + $share->getAttributes() + ); + if ($data === false) { // No usergroup share yet. Create one. $qb = $this->dbConn->getQueryBuilder(); @@ -626,6 +643,7 @@ class DefaultShareProvider implements IShareProvider { 'file_source' => $qb->createNamedParameter($share->getNodeId()), 'file_target' => $qb->createNamedParameter($share->getTarget()), 'permissions' => $qb->createNamedParameter($share->getPermissions()), + 'attributes' => $qb->createNamedParameter($shareAttributes), 'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()), ])->execute(); } else { @@ -1050,6 +1068,8 @@ class DefaultShareProvider implements IShareProvider { $share->setToken($data['token']); } + $share = $this->updateShareAttributes($share, $data['attributes']); + $share->setSharedBy($data['uid_initiator']); $share->setShareOwner($data['uid_owner']); @@ -1517,4 +1537,50 @@ class DefaultShareProvider implements IShareProvider { } $cursor->closeCursor(); } + + /** + * Load from database format (JSON string) to IAttributes + * + * @param IShare $share + * @param string|null $data + * @return IShare modified share + */ + private function updateShareAttributes(IShare $share, $data) { + if ($data !== null) { + $attributes = new ShareAttributes(); + $compressedAttributes = \json_decode($data, true); + foreach ($compressedAttributes as $compressedAttribute) { + $attributes->setAttribute( + $compressedAttribute[0], + $compressedAttribute[1], + $compressedAttribute[2] + ); + } + $share->setAttributes($attributes); + } + + return $share; + } + + /** + * Format IAttributes to database format (JSON string) + * + * @param IAttributes|null $attributes + * @return string|null + */ + private function formatShareAttributes($attributes) { + if ($attributes === null || empty($attributes->toArray())) { + return null; + } + + $compressedAttributes = []; + foreach ($attributes->toArray() as $attribute) { + $compressedAttributes[] = [ + 0 => $attribute['scope'], + 1 => $attribute['key'], + 2 => $attribute['enabled'] + ]; + } + return \json_encode($compressedAttributes); + } } diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index a44a9f99df8..b951384ff46 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -70,6 +70,7 @@ use OCP\Share; use OCP\Share\Exceptions\AlreadySharedException; use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IAttributes; use OCP\Share\IManager; use OCP\Share\IProviderFactory; use OCP\Share\IShare; @@ -1093,6 +1094,7 @@ class Manager implements IManager { 'shareWith' => $share->getSharedWith(), 'uidOwner' => $share->getSharedBy(), 'permissions' => $share->getPermissions(), + 'attributes' => $share->getAttributes(), 'path' => $userFolder->getRelativePath($share->getNode()->getPath()), ]); } @@ -2087,4 +2089,16 @@ class Manager implements IManager { yield from $provider->getAllShares(); } } + + /** + * @param IAttributes|null $perms + * @return string + */ + private function hashAttributes($perms) { + if ($perms === null || empty($perms->toArray())) { + return ""; + } + + return \md5(\json_encode($perms->toArray())); + } } diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 7ed03832e4c..ccf751f0948 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -37,6 +37,7 @@ use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IUserManager; use OCP\Share\Exceptions\IllegalIDChangeException; +use OCP\Share\IAttributes; use OCP\Share\IShare; class Share implements IShare { @@ -65,6 +66,8 @@ class Share implements IShare { private $shareOwner; /** @var int */ private $permissions; + /** @var IAttributes */ + private $attributes; /** @var int */ private $status; /** @var string */ @@ -335,6 +338,28 @@ class Share implements IShare { /** * @inheritdoc */ + public function newAttributes() { + return new ShareAttributes(); + } + + /** + * @inheritdoc + */ + public function setAttributes(IAttributes $attributes) { + $this->attributes = $attributes; + return $this; + } + + /** + * @inheritdoc + */ + public function getAttributes() { + return $this->attributes; + } + + /** + * @inheritdoc + */ public function setStatus(int $status): IShare { $this->status = $status; return $this; @@ -511,7 +536,7 @@ class Share implements IShare { * Set the parent of this share * * @param int parent - * @return \OCP\Share\IShare + * @return IShare * @deprecated The new shares do not have parents. This is just here for legacy reasons. */ public function setParent($parent) { diff --git a/lib/private/Share20/ShareAttributes.php b/lib/private/Share20/ShareAttributes.php new file mode 100644 index 00000000000..92f034e6783 --- /dev/null +++ b/lib/private/Share20/ShareAttributes.php @@ -0,0 +1,73 @@ +<?php +/** + * @author Piotr Mrowczynski <piotr@owncloud.com> + * + * @copyright Copyright (c) 2019, ownCloud GmbH + * @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/> + * + */ +namespace OC\Share20; + +use OCP\Share\IAttributes; + +class ShareAttributes implements IAttributes { + + /** @var array */ + private $attributes; + + public function __construct() { + $this->attributes = []; + } + + /** + * @inheritdoc + */ + public function setAttribute($scope, $key, $enabled) { + if (!\array_key_exists($scope, $this->attributes)) { + $this->attributes[$scope] = []; + } + $this->attributes[$scope][$key] = $enabled; + return $this; + } + + /** + * @inheritdoc + */ + public function getAttribute($scope, $key) { + if (\array_key_exists($scope, $this->attributes) && + \array_key_exists($key, $this->attributes[$scope])) { + return $this->attributes[$scope][$key]; + } + return null; + } + + /** + * @inheritdoc + */ + public function toArray() { + $result = []; + foreach ($this->attributes as $scope => $keys) { + foreach ($keys as $key => $enabled) { + $result[] = [ + "scope" => $scope, + "key" => $key, + "enabled" => $enabled + ]; + } + } + + return $result; + } +} diff --git a/lib/private/legacy/OC_Files.php b/lib/private/legacy/OC_Files.php index 41ac20577b2..08b285c6bf0 100644 --- a/lib/private/legacy/OC_Files.php +++ b/lib/private/legacy/OC_Files.php @@ -44,6 +44,8 @@ use bantu\IniGetWrapper\IniGetWrapper; use OC\Files\View; use OC\Streamer; use OCP\Lock\ILockingProvider; +use OCP\EventDispatcher\GenericEvent; +use OCP\EventDispatcher\IEventDispatcher; /** * Class for file server access @@ -162,6 +164,14 @@ class OC_Files { $numberOfFiles = self::getNumberOfFiles([$fileInfo]); } + //Dispatch an event to see if any apps have problem with download + $event = new GenericEvent(null, ['dir' => $dir, 'files' => $files, 'run' => true]); + $dispatcher = \OC::$server->query(IEventDispatcher::class); + $dispatcher->dispatch('file.beforeCreateZip', $event); + if (($event->getArgument('run') === false) or ($event->hasArgument('errorMessage'))) { + throw new \OC\ForbiddenException($event->getArgument('errorMessage')); + } + $streamer = new Streamer(\OC::$server->getRequest(), $fileSize, $numberOfFiles); OC_Util::obEnd(); @@ -207,6 +217,8 @@ class OC_Files { $streamer->finalize(); set_time_limit($executionTime); self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); + $event = new GenericEvent(null, ['result' => 'success', 'dir' => $dir, 'files' => $files]); + $dispatcher->dispatch('file.afterCreateZip', $event); } catch (\OCP\Lock\LockedException $ex) { self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); OC::$server->getLogger()->logException($ex); @@ -217,13 +229,16 @@ class OC_Files { self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); OC::$server->getLogger()->logException($ex); $l = \OC::$server->getL10N('lib'); - \OC_Template::printErrorPage($l->t('Cannot read file'), $ex->getMessage(), 200); + \OC_Template::printErrorPage($l->t('Cannot download file'), $ex->getMessage(), 200); } catch (\Exception $ex) { self::unlockAllTheFiles($dir, $files, $getType, $view, $filename); OC::$server->getLogger()->logException($ex); $l = \OC::$server->getL10N('lib'); $hint = method_exists($ex, 'getHint') ? $ex->getHint() : ''; - \OC_Template::printErrorPage($l->t('Cannot read file'), $hint, 200); + if ($event && $event->hasArgument('message')) { + $hint .= ' ' . $event->getArgument('message'); + } + \OC_Template::printErrorPage($l->t('Cannot download file'), $hint, 200); } } @@ -282,6 +297,7 @@ class OC_Files { * @param string $name * @param string $dir * @param array $params ; 'head' boolean to only send header of the request ; 'range' http range header + * @throws \OC\ForbiddenException */ private static function getSingleFile($view, $dir, $name, $params) { $filename = $dir . '/' . $name; @@ -317,6 +333,19 @@ class OC_Files { $rangeArray = self::parseHttpRangeHeader(substr($params['range'], 6), $fileSize); } + $dispatcher = \OC::$server->query(IEventDispatcher::class); + $event = new GenericEvent(null, ['path' => $filename]); + $dispatcher->dispatch('file.beforeGetDirect', $event); + + if (!\OC\Files\Filesystem::isReadable($filename) || $event->hasArgument('errorMessage')) { + if (!$event->hasArgument('errorMessage')) { + $msg = $event->getArgument('errorMessage'); + } else { + $msg = 'Access denied'; + } + throw new \OC\ForbiddenException($msg); + } + self::sendHeaders($filename, $name, $rangeArray); if (isset($params['head']) && $params['head']) { diff --git a/lib/public/Share/IAttributes.php b/lib/public/Share/IAttributes.php new file mode 100644 index 00000000000..9f2556e4005 --- /dev/null +++ b/lib/public/Share/IAttributes.php @@ -0,0 +1,68 @@ +<?php +/** + * @author Piotr Mrowczynski <piotr@owncloud.com> + * + * @copyright Copyright (c) 2019, ownCloud GmbH + * @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/> + * + */ +namespace OCP\Share; + +/** + * Interface IAttributes + * + * @package OCP\Share + * @since 10.2.0 + */ +interface IAttributes { + + /** + * Sets an attribute enabled/disabled. If the key did not exist before it will be created. + * + * @param string $scope scope + * @param string $key key + * @param bool $enabled enabled + * @return IAttributes The modified object + * @since 10.2.0 + */ + public function setAttribute($scope, $key, $enabled); + + /** + * Returns if attribute is enabled/disabled for given scope id and key. + * If attribute does not exist, returns null + * + * @param string $scope scope + * @param string $key key + * @return bool|null + * @since 10.2.0 + */ + public function getAttribute($scope, $key); + + /** + * Formats the IAttributes object to array with the following format: + * [ + * 0 => [ + * "scope" => <string>, + * "key" => <string>, + * "enabled" => <bool> + * ], + * ... + * ] + * + * @return array formatted IAttributes + * @since 10.2.0 + */ + public function toArray(); +} diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index 1d3cf9bbbdf..d81f263b464 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -300,7 +300,7 @@ interface IShare { * See \OCP\Constants::PERMISSION_* * * @param int $permissions - * @return \OCP\Share\IShare The modified object + * @return IShare The modified object * @since 9.0.0 */ public function setPermissions($permissions); @@ -315,6 +315,31 @@ interface IShare { public function getPermissions(); /** + * Create share attributes object + * + * @since 25.0.0 + * @return IAttributes + */ + public function newAttributes(); + + /** + * Set share attributes + * + * @param IAttributes $attributes + * @since 25.0.0 + * @return IShare The modified object + */ + public function setAttributes(IAttributes $attributes); + + /** + * Get share attributes + * + * @since 25.0.0 + * @return IAttributes + */ + public function getAttributes(); + + /** * Set the accepted status * See self::STATUS_* * |