Skip to content

Commit

Permalink
View-only dav plugin using IShare extra share permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
mrow4a committed Jan 12, 2019
1 parent 395c0a2 commit e477d52
Show file tree
Hide file tree
Showing 14 changed files with 638 additions and 68 deletions.
4 changes: 4 additions & 0 deletions apps/dav/lib/Connector/Sabre/ServerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

use OCA\DAV\DAV\FileCustomPropertiesBackend;
use OCA\DAV\DAV\FileCustomPropertiesPlugin;
use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\FileLocksBackend;
use OCP\Files\Mount\IMountManager;
Expand Down Expand Up @@ -154,6 +155,9 @@ public function createServer($baseUri,
);
$server->addPlugin(new \OCA\DAV\Connector\Sabre\QuotaPlugin($view));

// Allow view-only plugin for webdav requests
$server->addPlugin(new ViewOnlyPlugin());

if ($this->userSession->isLoggedIn()) {
$server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager));
$server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin(
Expand Down
106 changes: 106 additions & 0 deletions apps/dav/lib/DAV/ViewOnlyPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
/**
* @author Piotr Mrowczynski piotr@owncloud.com
*
* @copyright Copyright (c) 2018, 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\DAV\DAV;

use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\File;
use OCP\Files\InvalidPathException;
use Sabre\DAV\Exception\ServiceUnavailable;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\DAV\Exception\NotFound;
use \OCP\Files\NotFoundException;

/**
* Sabre plugin for the the file secure-view:
*/
class ViewOnlyPlugin extends ServerPlugin {

/** @var \Sabre\DAV\Server $server */
private $server;

/**
* ViewOnlyPlugin plugin
*/
public function __construct() {
}

/**
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param Server $server
* @return void
*/
public function initialize(Server $server) {
$this->server = $server;
//priority 90 to make sure the plugin is called before
//Sabre\DAV\CorePlugin::httpGet
$this->server->on('method:GET', [$this, 'checkViewOnly'], 90);
}

/**
*
* @param RequestInterface $request request object
* @return boolean
* @throws Forbidden
* @throws ServiceUnavailable
*/
public function checkViewOnly(
RequestInterface $request
) {
$path = $request->getPath();

try {
$node = $this->server->tree->getNodeForPath($path);

// Restrict view-only only to files
if (!($node instanceof File)) {
return true;
}

// Restrict view-only to files which are shared
$file = $node->getNode();
$storage = $file->getStorage();
if (!$storage->instanceOfStorage('\OCA\Files_Sharing\SharedStorage')) {
return true;
}

// Extract extra permissions
/** @var \OCA\Files_Sharing\SharedStorage $storage */
$share = $storage->getShare();
if (!$file->isUpdateable() && !$share->getExtraPermissions()->getPermission('dav', 'can-download')) {
throw new Forbidden('File is in secure-view mode and cannot be directly downloaded.');
}
} catch (NotFound $e) {
} catch (NotFoundException $e) {
} catch (InvalidPathException $e) {
}

return true;
}
}
4 changes: 4 additions & 0 deletions apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use OCA\DAV\DAV\LazyOpsPlugin;
use OCA\DAV\DAV\MiscCustomPropertiesBackend;
use OCA\DAV\DAV\PublicAuth;
use OCA\DAV\DAV\ViewOnlyPlugin;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\FileLocksBackend;
use OCA\DAV\Files\PreviewPlugin;
Expand Down Expand Up @@ -189,6 +190,9 @@ public function __construct(IRequest $request, $baseUri) {
$this->server->addPlugin(new CopyEtagHeaderPlugin());
$this->server->addPlugin(new ChunkingPlugin());

// Allow view-only plugin for webdav requests
$this->server->addPlugin(new ViewOnlyPlugin());

if (BrowserErrorPagePlugin::isBrowserRequest($request)) {
$this->server->addPlugin(new BrowserErrorPagePlugin());
}
Expand Down
42 changes: 42 additions & 0 deletions apps/files_sharing/lib/Controller/Share20OcsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
namespace OCA\Files_Sharing\Controller;

use OC\OCS\Result;
use OC\Share20\ExtraPermissions;
use OCP\AppFramework\OCSController;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
Expand Down Expand Up @@ -125,6 +126,43 @@ private function getAdditionalUserInfo(IUser $user) {
return null;
}

/**
* @param IShare $share
* @param string[][] $formattedShareExtraPermissions
* @return IShare modified share
*/
private function extractExtraPermissions(IShare $share, $formattedShareExtraPermissions) {
$shareExtraPermissions = $share->getExtraPermissions();
foreach($formattedShareExtraPermissions as $formattedPermission) {
$shareExtraPermissions->setPermission(
$formattedPermission["app"],
$formattedPermission["name"],
(bool) json_decode($formattedPermission["enabled"])
);
}

$share->setExtraPermissions($shareExtraPermissions);
return $share;
}

/**
* @param IShare $share
* @return array
*/
private function formatExtraPermissions(IShare $share) {
$permissions = $share->getExtraPermissions();
$formattedShareExtraPermissions = [];
foreach($permissions->getApps() as $app) {
foreach($permissions->getKeys($app) as $key) {
$formattedPermission['app'] = $app;
$formattedPermission['name'] = $key;
$formattedPermission['enabled'] = $permissions->getPermission($app, $key);
$formattedShareExtraPermissions[] = $formattedPermission;
}
}
return json_encode($formattedShareExtraPermissions);
}

/**
* Convert an IShare to an array for OCS output
*
Expand All @@ -143,6 +181,7 @@ protected function formatShare(IShare $share, $received = false) {
'uid_owner' => $share->getSharedBy(),
'displayname_owner' => $sharedBy !== null ? $sharedBy->getDisplayName() : $share->getSharedBy(),
'permissions' => $share->getPermissions(),
'extra_permissions' => $this->formatExtraPermissions($share),
'stime' => $share->getShareTime() ? $share->getShareTime()->getTimestamp() : null,
'parent' => null,
'expiration' => null,
Expand Down Expand Up @@ -836,6 +875,9 @@ public function updateShare($id) {
return new Result(null, 400, $this->l->t('Cannot remove all permissions'));
}

$formattedExtraPermissions = $this->request->getParam('extraPermissions', []);
$share = $this->extractExtraPermissions($share, $formattedExtraPermissions);

try {
$share = $this->shareManager->updateShare($share);
} catch (\Exception $e) {
Expand Down
20 changes: 16 additions & 4 deletions apps/files_sharing/lib/MountProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,32 @@ private function buildSuperShares(array $allShares, \OCP\IUser $user) {
continue;
}

$superShare = $this->shareManager->newShare();

// compute super share based on first entry of the group
$superShare = $this->shareManager->newShare();
$superShare->setId($shares[0]->getId())
->setShareOwner($shares[0]->getShareOwner())
->setNodeId($shares[0]->getNodeId())
->setTarget($shares[0]->getTarget());

// use most permissive permissions
// this covers the case where there are multiple shares for the same
// file e.g. from different groups and different permissions
$permissions = 0;
$extraPermissions = $superShare->getExtraPermissions();
foreach ($shares as $share) {
// update permissions
$permissions |= $share->getPermissions();

//update extra permissions
foreach($share->getExtraPermissions()->getApps() as $app) {
foreach($share->getExtraPermissions()->getKeys($app) as $key) {
$enabled = $share->getExtraPermissions()->getPermission($app, $key);
$extraPermissions->setPermission($app, $key, $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());
Expand All @@ -191,8 +203,8 @@ private function buildSuperShares(array $allShares, \OCP\IUser $user) {
}
}
}

$superShare->setPermissions($permissions);
$superShare->setExtraPermissions($extraPermissions);

$result[] = [$superShare, $shares];
}
Expand Down
33 changes: 33 additions & 0 deletions core/Migrations/Version20181220085457.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
namespace OC\Migrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type;
use OCP\Migration\ISchemaMigration;

/**
* Auto-generated migration step: Please modify to your needs!
*/
class Version20181220085457 implements ISchemaMigration {

public function changeSchema(Schema $schema, array $options) {
$prefix = $options['tablePrefix'];

// FIXME: Should Type::TEXT or Type::JSON be used? For now use STRING
if ($schema->hasTable("${prefix}share")) {
$shareTable = $schema->getTable("${prefix}share");

if (!$shareTable->hasColumn('extra_permissions')) {
$shareTable->addColumn(
'extra_permissions',
Type::STRING,
[
'default' => null,
'length' => 4096,
'notnull' => false
]
);
}
}
}
}
Loading

0 comments on commit e477d52

Please sign in to comment.