aboutsummaryrefslogtreecommitdiffstats
path: root/apps/files_sharing/lib
diff options
context:
space:
mode:
Diffstat (limited to 'apps/files_sharing/lib')
-rw-r--r--apps/files_sharing/lib/AppInfo/Application.php73
-rw-r--r--apps/files_sharing/lib/Controller/PublicPreviewController.php2
-rw-r--r--apps/files_sharing/lib/Controller/ShareAPIController.php94
-rw-r--r--apps/files_sharing/lib/MountProvider.php30
-rw-r--r--apps/files_sharing/lib/ViewOnly.php121
5 files changed, 305 insertions, 15 deletions
diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php
index 6f1d72f9115..63fdced9011 100644
--- a/apps/files_sharing/lib/AppInfo/Application.php
+++ b/apps/files_sharing/lib/AppInfo/Application.php
@@ -50,16 +50,22 @@ use OCA\Files_Sharing\Notification\Listener;
use OCA\Files_Sharing\Notification\Notifier;
use OCA\Files\Event\LoadAdditionalScriptsEvent;
use OCA\Files\Event\LoadSidebar;
+use OCP\Files\Event\BeforeDirectGetEvent;
use OCA\Files_Sharing\ShareBackend\File;
use OCA\Files_Sharing\ShareBackend\Folder;
+use OCA\Files_Sharing\ViewOnly;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as ResourcesLoadAdditionalScriptsEvent;
use OCP\EventDispatcher\IEventDispatcher;
+use OCP\EventDispatcher\GenericEvent;
use OCP\Federation\ICloudIdManager;
use OCP\Files\Config\IMountProviderCollection;
+use OCP\Files\Events\BeforeDirectFileDownloadEvent;
+use OCP\Files\Events\BeforeZipCreatedEvent;
+use OCP\Files\IRootFolder;
use OCP\Group\Events\UserAddedEvent;
use OCP\IDBConnection;
use OCP\IGroup;
@@ -71,7 +77,7 @@ use OCP\User\Events\UserChangedEvent;
use OCP\Util;
use Psr\Container\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
-use Symfony\Component\EventDispatcher\GenericEvent;
+use Symfony\Component\EventDispatcher\GenericEvent as OldGenericEvent;
class Application extends App implements IBootstrap {
public const APP_ID = 'files_sharing';
@@ -107,6 +113,7 @@ class Application extends App implements IBootstrap {
public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerMountProviders']);
$context->injectFn([$this, 'registerEventsScripts']);
+ $context->injectFn([$this, 'registerDownloadEvents']);
$context->injectFn([$this, 'setupSharingMenus']);
Helper::registerHooks();
@@ -121,12 +128,12 @@ class Application extends App implements IBootstrap {
}
- public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider) {
+ public function registerMountProviders(IMountProviderCollection $mountProviderCollection, MountProvider $mountProvider, ExternalMountProvider $externalMountProvider): void {
$mountProviderCollection->registerProvider($mountProvider);
$mountProviderCollection->registerProvider($externalMountProvider);
}
- public function registerEventsScripts(IEventDispatcher $dispatcher, EventDispatcherInterface $oldDispatcher) {
+ public function registerEventsScripts(IEventDispatcher $dispatcher, EventDispatcherInterface $oldDispatcher): void {
// sidebar and files scripts
$dispatcher->addServiceListener(LoadAdditionalScriptsEvent::class, LoadAdditionalListener::class);
$dispatcher->addServiceListener(BeforeTemplateRenderedEvent::class, LegacyBeforeTemplateRenderedListener::class);
@@ -139,19 +146,73 @@ class Application extends App implements IBootstrap {
});
// notifications api to accept incoming user shares
- $oldDispatcher->addListener('OCP\Share::postShare', function (GenericEvent $event) {
+ $oldDispatcher->addListener('OCP\Share::postShare', function (OldGenericEvent $event) {
/** @var Listener $listener */
$listener = $this->getContainer()->query(Listener::class);
$listener->shareNotification($event);
});
- $oldDispatcher->addListener(IGroup::class . '::postAddUser', function (GenericEvent $event) {
+ $oldDispatcher->addListener(IGroup::class . '::postAddUser', function (OldGenericEvent $event) {
/** @var Listener $listener */
$listener = $this->getContainer()->query(Listener::class);
$listener->userAddedToGroup($event);
});
}
- public function setupSharingMenus(IManager $shareManager, IFactory $l10nFactory, IUserSession $userSession) {
+ public function registerDownloadEvents(
+ IEventDispatcher $dispatcher,
+ IUserSession $userSession,
+ IRootFolder $rootFolder
+ ): void {
+
+ $dispatcher->addListener(
+ BeforeDirectFileDownloadEvent::class,
+ function (BeforeDirectFileDownloadEvent $event) use ($userSession, $rootFolder): void {
+ $pathsToCheck = [$event->getPath()];
+ // Check only for user/group shares. Don't restrict e.g. share links
+ $user = $userSession->getUser();
+ if ($user) {
+ $viewOnlyHandler = new ViewOnly(
+ $rootFolder->getUserFolder($user->getUID())
+ );
+ if (!$viewOnlyHandler->check($pathsToCheck)) {
+ $event->setSuccessful(false);
+ $event->setErrorMessage('Access to this resource or one of its sub-items has been denied.');
+ }
+ }
+ }
+ );
+
+ $dispatcher->addListener(
+ BeforeZipCreatedEvent::class,
+ function (BeforeZipCreatedEvent $event) use ($userSession, $rootFolder): void {
+ $dir = $event->getDirectory();
+ $files = $event->getFiles();
+
+ $pathsToCheck = [];
+ foreach ($files as $file) {
+ $pathsToCheck[] = $dir . '/' . $file;
+ }
+
+ // Check only for user/group shares. Don't restrict e.g. share links
+ $user = $userSession->getUser();
+ if ($user) {
+ $viewOnlyHandler = new ViewOnly(
+ $rootFolder->getUserFolder($user->getUID())
+ );
+ if (!$viewOnlyHandler->check($pathsToCheck)) {
+ $event->setErrorMessage('Access to this resource or one of its sub-items has been denied.');
+ $event->setSuccessful(false);
+ } else {
+ $event->setSuccessful(true);
+ }
+ } else {
+ $event->setSuccessful(true);
+ }
+ }
+ );
+ }
+
+ public function setupSharingMenus(IManager $shareManager, IFactory $l10nFactory, IUserSession $userSession): void {
if (!$shareManager->shareApiEnabled() || !class_exists('\OCA\Files\App')) {
return;
}
diff --git a/apps/files_sharing/lib/Controller/PublicPreviewController.php b/apps/files_sharing/lib/Controller/PublicPreviewController.php
index 4a16afa7ac0..98c4d8cafb4 100644
--- a/apps/files_sharing/lib/Controller/PublicPreviewController.php
+++ b/apps/files_sharing/lib/Controller/PublicPreviewController.php
@@ -136,7 +136,7 @@ class PublicPreviewController extends PublicShareController {
* @param $token
* @return DataResponse|FileDisplayResponse
*/
- public function directLink($token) {
+ public function directLink(string $token) {
// No token no image
if ($token === '') {
return new DataResponse([], Http::STATUS_BAD_REQUEST);
diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php
index fafdb1a64cd..59089390667 100644
--- a/apps/files_sharing/lib/Controller/ShareAPIController.php
+++ b/apps/files_sharing/lib/Controller/ShareAPIController.php
@@ -45,8 +45,10 @@ declare(strict_types=1);
namespace OCA\Files_Sharing\Controller;
use OC\Files\FileInfo;
+use OC\Files\Storage\Wrapper\Wrapper;
use OCA\Files_Sharing\Exceptions\SharingRightsException;
use OCA\Files_Sharing\External\Storage;
+use OCA\Files_Sharing\SharedStorage;
use OCA\Files\Helper;
use OCP\App\IAppManager;
use OCP\AppFramework\Http\DataResponse;
@@ -324,6 +326,11 @@ class ShareAPIController extends OCSController {
$result['mail_send'] = $share->getMailSend() ? 1 : 0;
$result['hide_download'] = $share->getHideDownload() ? 1 : 0;
+ $result['attributes'] = null;
+ if ($attributes = $share->getAttributes()) {
+ $result['attributes'] = \json_encode($attributes->toArray());
+ }
+
return $result;
}
@@ -436,6 +443,7 @@ class ShareAPIController extends OCSController {
* @param string $sendPasswordByTalk
* @param string $expireDate
* @param string $label
+ * @param string $attributes
*
* @return DataResponse
* @throws NotFoundException
@@ -456,7 +464,8 @@ class ShareAPIController extends OCSController {
string $sendPasswordByTalk = null,
string $expireDate = '',
string $note = '',
- string $label = ''
+ string $label = '',
+ string $attributes = null
): DataResponse {
$share = $this->shareManager->newShare();
@@ -516,6 +525,8 @@ class ShareAPIController extends OCSController {
$permissions &= ~($permissions & ~$node->getPermissions());
}
+ $this->checkInheritedAttributes($share);
+
if ($shareType === IShare::TYPE_USER) {
// Valid user is required to share
if ($shareWith === null || !$this->userManager->userExists($shareWith)) {
@@ -674,6 +685,10 @@ class ShareAPIController extends OCSController {
$share->setNote($note);
}
+ if ($attributes !== null) {
+ $share = $this->setShareAttributes($share, $attributes);
+ }
+
try {
$share = $this->shareManager->createShare($share);
} catch (GenericShareException $e) {
@@ -1035,6 +1050,7 @@ class ShareAPIController extends OCSController {
* @param string $note
* @param string $label
* @param string $hideDownload
+ * @param string $attributes
* @return DataResponse
* @throws LockedException
* @throws NotFoundException
@@ -1051,7 +1067,8 @@ class ShareAPIController extends OCSController {
string $expireDate = null,
string $note = null,
string $label = null,
- string $hideDownload = null
+ string $hideDownload = null,
+ string $attributes = null
): DataResponse {
try {
$share = $this->getShareById($id);
@@ -1077,7 +1094,8 @@ class ShareAPIController extends OCSController {
$expireDate === null &&
$note === null &&
$label === null &&
- $hideDownload === null
+ $hideDownload === null &&
+ $attributes === null
) {
throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given'));
}
@@ -1086,6 +1104,25 @@ class ShareAPIController extends OCSController {
$share->setNote($note);
}
+ $userFolder = $this->rootFolder->getUserFolder($this->currentUser);
+
+ // get the node with the point of view of the current user
+ $nodes = $userFolder->getById($share->getNode()->getId());
+ if (count($nodes) > 0) {
+ $node = $nodes[0];
+ $storage = $node->getStorage();
+ if ($storage && $storage->instanceOfStorage(SharedStorage::class)) {
+ /** @var \OCA\Files_Sharing\SharedStorage $storage */
+ $inheritedAttributes = $storage->getShare()->getAttributes();
+ if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
+ if ($hideDownload === 'false') {
+ throw new OCSBadRequestException($this->l->t('Cannot increase permissions'));
+ }
+ $share->setHideDownload(true);
+ }
+ }
+ }
+
/**
* expirationdate, password and publicUpload only make sense for link shares
*/
@@ -1216,6 +1253,10 @@ class ShareAPIController extends OCSController {
}
}
+ if ($attributes !== null) {
+ $share = $this->setShareAttributes($share, $attributes);
+ }
+
try {
$share = $this->shareManager->updateShare($share);
} catch (GenericShareException $e) {
@@ -1832,4 +1873,51 @@ class ShareAPIController extends OCSController {
}
}
}
+
+ /**
+ * @param IShare $share
+ * @param string|null $attributesString
+ * @return IShare modified share
+ */
+ private function setShareAttributes(IShare $share, ?string $attributesString) {
+ $newShareAttributes = null;
+ if ($attributesString !== null) {
+ $newShareAttributes = $this->shareManager->newShare()->newAttributes();
+ $formattedShareAttributes = \json_decode($attributesString, true);
+ if (is_array($formattedShareAttributes)) {
+ foreach ($formattedShareAttributes as $formattedAttr) {
+ $newShareAttributes->setAttribute(
+ $formattedAttr['scope'],
+ $formattedAttr['key'],
+ is_string($formattedAttr['enabled']) ? (bool) \json_decode($formattedAttr['enabled']) : $formattedAttr['enabled']
+ );
+ }
+ } else {
+ throw new OCSBadRequestException('Invalid share attributes provided: \"' . $attributesString . '\"');
+ }
+ }
+ $share->setAttributes($newShareAttributes);
+
+ return $share;
+ }
+
+ private function checkInheritedAttributes(IShare $share): void {
+ if ($share->getNode()->getStorage()->instanceOfStorage(SharedStorage::class)) {
+ $storage = $share->getNode()->getStorage();
+ if ($storage instanceof Wrapper) {
+ $storage = $storage->getInstanceOfStorage(SharedStorage::class);
+ if ($storage === null) {
+ throw new \RuntimeException('Should not happen, instanceOfStorage but getInstanceOfStorage return null');
+ }
+ } else {
+ throw new \RuntimeException('Should not happen, instanceOfStorage but not a wrapper');
+ }
+ /** @var \OCA\Files_Sharing\SharedStorage $storage */
+ $inheritedAttributes = $storage->getShare()->getAttributes();
+ if ($inheritedAttributes !== null && $inheritedAttributes->getAttribute('permissions', 'download') === false) {
+ $share->setHideDownload(true);
+ }
+ }
+
+ }
}
diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php
index 5817ece6809..954c9cf70e6 100644
--- a/apps/files_sharing/lib/MountProvider.php
+++ b/apps/files_sharing/lib/MountProvider.php
@@ -38,6 +38,7 @@ use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\ILogger;
use OCP\IUser;
+use OCP\Share\IAttributes;
use OCP\Share\IManager;
use OCP\Share\IShare;
@@ -229,14 +230,32 @@ class MountProvider implements IMountProvider {
->setTarget($shares[0]->getTarget());
// use most permissive permissions
- $permissions = 0;
+ // this covers the case where there are multiple shares for the same
+ // file e.g. from different groups and different permissions
+ $superPermissions = 0;
+ $superAttributes = $this->shareManager->newShare()->newAttributes();
$status = IShare::STATUS_PENDING;
foreach ($shares as $share) {
- $permissions |= $share->getPermissions();
+ $superPermissions |= $share->getPermissions();
$status = max($status, $share->getStatus());
+ // update permissions
+ $superPermissions |= $share->getPermissions();
+
+ // update share permission attributes
+ $attributes = $share->getAttributes();
+ if ($attributes !== null) {
+ foreach ($attributes->toArray() as $attribute) {
+ if ($superAttributes->getAttribute($attribute['scope'], $attribute['key']) === true) {
+ // if super share attribute is already enabled, it is most permissive
+ continue;
+ }
+ // update supershare attributes with subshare attribute
+ $superAttributes->setAttribute($attribute['scope'], $attribute['key'], $attribute['enabled']);
+ }
+ }
+ // adjust target, for database consistency if needed
if ($share->getTarget() !== $superShare->getTarget()) {
- // adjust target, for database consistency
$share->setTarget($superShare->getTarget());
try {
$this->shareManager->moveShare($share, $user->getUID());
@@ -261,8 +280,9 @@ class MountProvider implements IMountProvider {
}
}
- $superShare->setPermissions($permissions)
- ->setStatus($status);
+ $superShare->setPermissions($superPermissions);
+ $superShare->setStatus($status);
+ $superShare->setAttributes($superAttributes);
$result[] = [$superShare, $shares];
}
diff --git a/apps/files_sharing/lib/ViewOnly.php b/apps/files_sharing/lib/ViewOnly.php
new file mode 100644
index 00000000000..26e8e43a871
--- /dev/null
+++ b/apps/files_sharing/lib/ViewOnly.php
@@ -0,0 +1,121 @@
+<?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 OCA\Files_Sharing;
+
+use OCP\Files\File;
+use OCP\Files\Folder;
+use OCP\Files\Node;
+use OCP\Files\NotFoundException;
+
+/**
+ * Handles restricting for download of files
+ */
+class ViewOnly {
+
+ /** @var Folder */
+ private $userFolder;
+
+ public function __construct(Folder $userFolder) {
+ $this->userFolder = $userFolder;
+ }
+
+ /**
+ * @param string[] $pathsToCheck
+ * @return bool
+ */
+ public function check(array $pathsToCheck): bool {
+ // If any of elements cannot be downloaded, prevent whole download
+ foreach ($pathsToCheck as $file) {
+ try {
+ $info = $this->userFolder->get($file);
+ if ($info instanceof File) {
+ // access to filecache is expensive in the loop
+ if (!$this->checkFileInfo($info)) {
+ return false;
+ }
+ } elseif ($info instanceof Folder) {
+ // get directory content is rather cheap query
+ if (!$this->dirRecursiveCheck($info)) {
+ return false;
+ }
+ }
+ } catch (NotFoundException $e) {
+ continue;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @param Folder $dirInfo
+ * @return bool
+ * @throws NotFoundException
+ */
+ private function dirRecursiveCheck(Folder $dirInfo): bool {
+ if (!$this->checkFileInfo($dirInfo)) {
+ return false;
+ }
+ // If any of elements cannot be downloaded, prevent whole download
+ $files = $dirInfo->getDirectoryListing();
+ foreach ($files as $file) {
+ if ($file instanceof File) {
+ if (!$this->checkFileInfo($file)) {
+ return false;
+ }
+ } elseif ($file instanceof Folder) {
+ return $this->dirRecursiveCheck($file);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @param Node $fileInfo
+ * @return bool
+ * @throws NotFoundException
+ */
+ private function checkFileInfo(Node $fileInfo): bool {
+ // Restrict view-only to nodes which are shared
+ $storage = $fileInfo->getStorage();
+ if (!$storage->instanceOfStorage(SharedStorage::class)) {
+ return true;
+ }
+
+ // Extract extra permissions
+ /** @var \OCA\Files_Sharing\SharedStorage $storage */
+ $share = $storage->getShare();
+
+ $canDownload = true;
+
+ // Check if read-only and on whether permission can download is both set and disabled.
+ $attributes = $share->getAttributes();
+ if ($attributes !== null) {
+ $canDownload = $attributes->getAttribute('permissions', 'download');
+ }
+
+ if ($canDownload !== null && !$canDownload) {
+ return false;
+ }
+ return true;
+ }
+}