diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php
index c8c4b5608d57..0269efbe9a02 100644
--- a/apps/dav/lib/Connector/Sabre/ServerFactory.php
+++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php
@@ -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;
@@ -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(
diff --git a/apps/dav/lib/DAV/ViewOnlyPlugin.php b/apps/dav/lib/DAV/ViewOnlyPlugin.php
new file mode 100644
index 000000000000..67dd6c8d057c
--- /dev/null
+++ b/apps/dav/lib/DAV/ViewOnlyPlugin.php
@@ -0,0 +1,109 @@
+
+ *
+ */
+
+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();
+
+ // Check if read-only and on whether permission can download is both set and disabled.
+ $canDownload = $share->getExtraPermissions()->getPermission('dav', 'can-download');
+ if (!$file->isUpdateable() && $canDownload !== null && !$canDownload) {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php
index e1e5a0d984b9..95dc5339d4f6 100644
--- a/apps/dav/lib/Server.php
+++ b/apps/dav/lib/Server.php
@@ -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;
@@ -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());
}
diff --git a/apps/files/js/fileinfomodel.js b/apps/files/js/fileinfomodel.js
index e31997617ea7..bd0da262568f 100644
--- a/apps/files/js/fileinfomodel.js
+++ b/apps/files/js/fileinfomodel.js
@@ -79,6 +79,15 @@
return OC.joinPaths(this.get('path'), this.get('name'));
},
+ /**
+ * Returns the mimetype of the file
+ *
+ * @return {string} mimetype
+ */
+ getMimeType: function() {
+ return this.get('mimetype');
+ },
+
/**
* Reloads missing properties from server and set them in the model.
* @param properties array of properties to be reloaded
diff --git a/apps/files_sharing/lib/Controller/Share20OcsController.php b/apps/files_sharing/lib/Controller/Share20OcsController.php
index 8d1ee1c7645a..d934196ec34a 100644
--- a/apps/files_sharing/lib/Controller/Share20OcsController.php
+++ b/apps/files_sharing/lib/Controller/Share20OcsController.php
@@ -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;
@@ -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
*
@@ -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,
@@ -489,6 +528,10 @@ public function createShare() {
return new Result(null, 400, $this->l->t('Unknown share type'));
}
+
+ $formattedExtraPermissions = $this->request->getParam('extraPermissions', []);
+ $share = $this->extractExtraPermissions($share, $formattedExtraPermissions);
+
$share->setShareType($shareType);
$share->setSharedBy($this->currentUser->getUID());
@@ -836,6 +879,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) {
diff --git a/apps/files_sharing/lib/MountProvider.php b/apps/files_sharing/lib/MountProvider.php
index 7ae71a10356d..b4b230721414 100644
--- a/apps/files_sharing/lib/MountProvider.php
+++ b/apps/files_sharing/lib/MountProvider.php
@@ -157,20 +157,36 @@ 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) {
+ // if permission is already enabled, it is most permissive
+ if ($extraPermissions->getPermission($app, $key) === true) {
+ continue;
+ }
+ $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());
@@ -191,8 +207,8 @@ private function buildSuperShares(array $allShares, \OCP\IUser $user) {
}
}
}
-
$superShare->setPermissions($permissions);
+ $superShare->setExtraPermissions($extraPermissions);
$result[] = [$superShare, $shares];
}
diff --git a/core/Migrations/Version20181220085457.php b/core/Migrations/Version20181220085457.php
new file mode 100644
index 000000000000..afe9a2e206e0
--- /dev/null
+++ b/core/Migrations/Version20181220085457.php
@@ -0,0 +1,33 @@
+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
+ ]
+ );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/css/share.css b/core/css/share.css
index 1436b868cb37..94f4f91a1690 100644
--- a/core/css/share.css
+++ b/core/css/share.css
@@ -99,6 +99,10 @@
white-space: normal;
}
+#shareWithList .extraPermission {
+ display: block;
+}
+
#shareWithList .shareOption {
white-space: nowrap;
display: inline-block;
diff --git a/core/js/sharedialogshareelistview.js b/core/js/sharedialogshareelistview.js
index 6012055e5330..d244885215c8 100644
--- a/core/js/sharedialogshareelistview.js
+++ b/core/js/sharedialogshareelistview.js
@@ -66,10 +66,18 @@
'' +
'{{/if}}' +
'' +
+ '
' +
'' +
'{{/each}}' +
''
- ;
+ ;
/**
* @class OCA.Share.ShareDialogShareeListView
@@ -94,6 +102,7 @@
events: {
'click .unshare': 'onUnshare',
'click .permissions': 'onPermissionChange',
+ 'click .extra-permissions': 'onPermissionChange',
'click .showCruds': 'onCrudsToggle',
'click .mailNotification': 'onSendMailNotification'
},
@@ -112,8 +121,31 @@
},
/**
- *
- * @param {OC.Share.Types.ShareInfo} shareInfo
+ * @param shareIndex
+ * @returns {object}
+ */
+ getExtraPermissionsObject: function(shareIndex) {
+ var shareWith = this.model.getShareWith(shareIndex);
+
+ // Returns OC.Share.Types.ShareExtraPermission[]
+ var permissions = this.model.getShareExtraPermissions(shareIndex);
+
+ var list = [];
+ permissions.map(function(permission) {
+ list.push(_.extend(
+ {
+ cid: this.cid,
+ shareWith: shareWith
+ },
+ permission)
+ );
+ });
+
+ return list;
+ },
+
+ /**
+ * @param shareIndex
* @returns {object}
*/
getShareeObject: function(shareIndex) {
@@ -136,6 +168,7 @@
hasCreatePermission: this.model.hasCreatePermission(shareIndex),
hasUpdatePermission: this.model.hasUpdatePermission(shareIndex),
hasDeletePermission: this.model.hasDeletePermission(shareIndex),
+ extraPermissions: this.getExtraPermissionsObject(shareIndex),
wasMailSent: this.model.notificationMailWasSent(shareIndex),
shareWith: shareWith,
shareWithDisplayName: shareWithDisplayName,
@@ -261,7 +294,7 @@
var shareType = $li.data('share-type');
var shareWith = $li.attr('data-share-with');
- // adjust checkbox states
+ // adjust share permissions and their required checkbox states
var $checkboxes = $('.permissions', $li).not('input[name="edit"]').not('input[name="share"]');
var checked;
if ($element.attr('name') === 'edit') {
@@ -279,7 +312,19 @@
permissions |= $(checkbox).data('permissions');
});
- this.model.updateShare(shareId, {permissions: permissions});
+ // Check extra share permissions
+ var extraPermissions = [];
+ $('.extra-permissions', $li).each(function(index, checkbox) {
+ var checked = $(checkbox).is(':checked');
+ $(checkbox).prop('enabled', checked);
+ extraPermissions.push({
+ app : $(checkbox).data('app'),
+ name: $(checkbox).attr('name'),
+ enabled: checked
+ });
+ });
+
+ this.model.updateShare(shareId, {permissions: permissions, extraPermissions: extraPermissions});
},
onCrudsToggle: function(event) {
diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js
index c181d7abb8e4..320b5f6eff73 100644
--- a/core/js/shareitemmodel.js
+++ b/core/js/shareitemmodel.js
@@ -38,6 +38,7 @@
* @typedef {object} OC.Share.Types.ShareInfo
* @property {number} share_type
* @property {number} permissions
+ * @property {string} extra_permissions
* @property {number} file_source optional
* @property {number} item_source
* @property {string} token
@@ -56,6 +57,14 @@
* @property {OC.Share.Types.LinkShareInfo|undefined} linkShare
*/
+ /**
+ * @typedef {object} OC.Share.Types.ShareExtraPermission
+ * @property {string} name
+ * @property {bool} enabled
+ * @property {string} app
+ * @property {string} label
+ */
+
/**
* These properties are sometimes returned by the server as strings instead
* of integers, so we need to convert them accordingly...
@@ -84,6 +93,11 @@
_linkSharesCollection: null,
+ /**
+ * @type {object} available extra permissions for this file/folder share
+ */
+ _availableExtraPermissions: {},
+
initialize: function(attributes, options) {
if(!_.isUndefined(options.configModel)) {
this.configModel = options.configModel;
@@ -96,6 +110,7 @@
this._linkSharesCollection = new OC.Share.SharesCollection();
_.bindAll(this, 'addShare');
+ OC.Plugins.attach('OC.Share.ShareItemModel', this);
},
defaults: {
@@ -126,10 +141,9 @@
options = options || {};
attributes = _.extend({}, attributes);
- var defaultPermissions = OC.getCapabilities()['files_sharing']['default_permissions'] || OC.PERMISSION_ALL;
-
// Default permissions are Edit (CRUD) and Share
// Check if these permissions are possible
+ var defaultPermissions = OC.getCapabilities()['files_sharing']['default_permissions'] || OC.PERMISSION_ALL;
var possiblePermissions = OC.PERMISSION_READ;
if (this.updatePermissionPossible()) {
possiblePermissions = possiblePermissions | OC.PERMISSION_UPDATE;
@@ -143,8 +157,18 @@
if (this.configModel.get('isResharingAllowed') && (this.sharePermissionPossible())) {
possiblePermissions = possiblePermissions | OC.PERMISSION_SHARE;
}
-
attributes.permissions = defaultPermissions & possiblePermissions;
+
+ var extraPermissions = [];
+ _.map(this._getCompatibleExtraPermissions(attributes.permissions), function(extraPermission) {
+ extraPermissions.push({
+ app : extraPermission.app,
+ name: extraPermission.id,
+ enabled: extraPermission.meta.default
+ });
+ });
+ attributes.extraPermissions = extraPermissions;
+
if (_.isUndefined(attributes.path)) {
attributes.path = this.fileInfoModel.getFullPath();
}
@@ -394,6 +418,18 @@
return share.share_type;
},
+ /**
+ * whether permission is in permission bitmap
+ *
+ * @param {number} permissions
+ * @param {number} permission
+ * @returns {boolean}
+ * @private
+ */
+ _hasPermission: function(permissions, permission) {
+ return (permissions & permission) === permission;
+ },
+
/**
* whether a share from shares has the requested permission
*
@@ -408,7 +444,7 @@
if(!_.isObject(share)) {
throw "Unknown Share";
}
- return (share.permissions & permission) === permission;
+ return this._hasPermission(share.permissions, permission);
},
notificationMailWasSent: function(shareIndex) {
@@ -752,7 +788,105 @@
result.push(OC.Share.SHARE_TYPE_LINK);
}
return _.uniq(result);
+ },
+
+ /**
+ *
+ * @param {number} permissions
+ * @returns {Array}
+ * @private
+ */
+ _getCompatibleExtraPermissions: function(permissions) {
+ var result = [];
+ for (var appId in this._availableExtraPermissions) {
+ if (!this._availableExtraPermissions.hasOwnProperty(appId)) {
+ continue;
+ }
+ for (var permissionId in this._availableExtraPermissions[appId]) {
+ if (!this._availableExtraPermissions[appId].hasOwnProperty(permissionId)) {
+ continue;
+ }
+ var permissionMeta = this._availableExtraPermissions[appId][permissionId];
+
+ var compatible = true;
+
+ for (var i in permissionMeta.incompatiblePermissions) {
+ if (this._hasPermission(permissions, permissionMeta.incompatiblePermissions[i])) {
+ compatible = false;
+ }
+ }
+
+ if (compatible) {
+ result.push({
+ app: appId,
+ id: permissionId,
+ meta: permissionMeta
+ }); }
+ }
+ }
+
+ return result
+ },
+
+ /**
+ * @param shareIndex
+ * @returns OC.Share.Types.ShareExtraPermission[]
+ */
+ getShareExtraPermissions: function(shareIndex) {
+ /** @type OC.Share.Types.ShareInfo **/
+ var share = this.get('shares')[shareIndex];
+ if(!_.isObject(share)) {
+ throw "Unknown Share";
+ }
+
+ if (_.isUndefined(share.extra_permissions) || _.isUndefined(share.permissions)) {
+ return [];
+ }
+
+ // Mark available permissions as enabled if share has extra permission
+ var formattedPermissions = [];
+ var shareExtraPermissions = JSON.parse(share.extra_permissions);
+ _.map(this._getCompatibleExtraPermissions(share.permissions), function(extraPermission) {
+ var enabled = extraPermission.meta.default;
+ shareExtraPermissions.map(function(permission) {
+ if (permission.app === extraPermission.app &&
+ permission.name === extraPermission.id) {
+ enabled = permission.enabled;
+ }
+ });
+
+ var shareExtraPermission = {
+ app: extraPermission.app,
+ name: extraPermission.id,
+ label: extraPermission.meta.label,
+ enabled: enabled
+ };
+ formattedPermissions.push(shareExtraPermission);
+ });
+
+ return formattedPermissions;
+ },
+
+ /**
+ * Apps can register their extra share permissions
+ *
+ * @param {string} $appId
+ * @param {string} $permissionName
+ * @param {string} $permissionLabel
+ * @param {boolean} $permissionDefault
+ * @param {number[]} $incompatiblePermissions
+ */
+ registerExtraSharePermission: function($appId, $permissionName, $permissionLabel, $permissionDefault, $incompatiblePermissions) {
+ if (!this._availableExtraPermissions.hasOwnProperty($appId)) {
+ this._availableExtraPermissions[$appId] = {};
+ }
+ this._availableExtraPermissions[$appId][$permissionName] = {
+ incompatiblePermissions: $incompatiblePermissions,
+ default: $permissionDefault,
+ label: $permissionLabel
+ };
}
+
});
OC.Share.ShareItemModel = ShareItemModel;
diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php
index c05b7d768796..5e2162784df0 100644
--- a/lib/private/Share20/DefaultShareProvider.php
+++ b/lib/private/Share20/DefaultShareProvider.php
@@ -26,6 +26,7 @@
namespace OC\Share20;
use OCP\Files\File;
+use OCP\Share\IExtraPermissions;
use OCP\Share\IShare;
use OCP\Share\IShareProvider;
use OC\Share20\Exception\InvalidShare;
@@ -153,6 +154,12 @@ public function create(\OCP\Share\IShare $share) {
// set the permissions
$qb->setValue('permissions', $qb->createNamedParameter($share->getPermissions()));
+ // set extra permissions
+ $extraSharePermissions = $this->formatExtraPermissions(
+ $share->getExtraPermissions()
+ );
+ $qb->setValue('extra_permissions', $qb->createNamedParameter($extraSharePermissions));
+
// Set who created this share
$qb->setValue('uid_initiator', $qb->createNamedParameter($share->getSharedBy()));
@@ -198,6 +205,11 @@ public function create(\OCP\Share\IShare $share) {
*/
public function update(\OCP\Share\IShare $share) {
$this->validate($share);
+
+ $extraSharePermissions = $this->formatExtraPermissions(
+ $share->getExtraPermissions()
+ );
+
if ($share->getShareType() === \OCP\Share::SHARE_TYPE_USER) {
/*
* We allow updating the recipient on user shares.
@@ -209,6 +221,7 @@ public function update(\OCP\Share\IShare $share) {
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+ ->set('extra_permissions', $qb->createNamedParameter($extraSharePermissions))
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('accepted', $qb->createNamedParameter($share->getState()))
@@ -221,6 +234,7 @@ public function update(\OCP\Share\IShare $share) {
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+ ->set('extra_permissions', $qb->createNamedParameter($extraSharePermissions))
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('accepted', $qb->createNamedParameter($share->getState()))
@@ -246,6 +260,7 @@ public function update(\OCP\Share\IShare $share) {
$qb->update('share')
->where($qb->expr()->eq('parent', $qb->createNamedParameter($share->getId())))
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+ ->set('extra_permissions', $qb->createNamedParameter($extraSharePermissions))
->execute();
} elseif ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK) {
$qb = $this->dbConn->getQueryBuilder();
@@ -255,6 +270,7 @@ public function update(\OCP\Share\IShare $share) {
->set('uid_owner', $qb->createNamedParameter($share->getShareOwner()))
->set('uid_initiator', $qb->createNamedParameter($share->getSharedBy()))
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+ ->set('extra_permissions', $qb->createNamedParameter($extraSharePermissions))
->set('item_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('file_source', $qb->createNamedParameter($share->getNode()->getId()))
->set('token', $qb->createNamedParameter($share->getToken()))
@@ -396,6 +412,10 @@ public function updateForRecipient(\OCP\Share\IShare $share, $recipient) {
$data = $stmt->fetch();
$stmt->closeCursor();
+ $extraSharePermissions = $this->formatExtraPermissions(
+ $share->getExtraPermissions()
+ );
+
if ($data === false) {
// No usergroup share yet. Create one.
$qb = $this->dbConn->getQueryBuilder();
@@ -411,6 +431,7 @@ public function updateForRecipient(\OCP\Share\IShare $share, $recipient) {
'file_source' => $qb->createNamedParameter($share->getNode()->getId()),
'file_target' => $qb->createNamedParameter($share->getTarget()),
'permissions' => $qb->createNamedParameter($share->getPermissions()),
+ 'extra_permissions' => $qb->createNamedParameter($extraSharePermissions),
'stime' => $qb->createNamedParameter($share->getShareTime()->getTimestamp()),
'accepted' => $qb->createNamedParameter($share->getState()),
])->execute();
@@ -423,6 +444,7 @@ public function updateForRecipient(\OCP\Share\IShare $share, $recipient) {
// make sure to reset the permissions to the one of the parent share,
// as legacy entries with zero permissions might still exist
->set('permissions', $qb->createNamedParameter($share->getPermissions()))
+ ->set('extra_permissions', $qb->createNamedParameter($extraSharePermissions))
->where($qb->expr()->eq('id', $qb->createNamedParameter($data['id'])))
->execute();
}
@@ -963,6 +985,9 @@ private function createShare($data) {
$share->setToken($data['token']);
}
+ $extraPermissions = $this->loadExtraPermissions($data['extra_permissions']);
+ $share->setExtraPermissions($extraPermissions);
+
$share->setSharedBy($data['uid_initiator']);
$share->setShareOwner($data['uid_owner']);
@@ -1238,4 +1263,45 @@ private function validate($share) {
// TODO: add more early validation for fields instead of relying on the DB
}
+
+ /**
+ * Load from database format (JSON string) to IExtraPermissions
+ *
+ * @param string $data
+ * @return IExtraPermissions
+ */
+ private function loadExtraPermissions($data) {
+ $extraPermissions = new ExtraPermissions();
+ if (!is_null($data)) {
+ $extraPermissionsJson = json_decode($data, true);
+ foreach ($extraPermissionsJson as $app => $keys) {
+ foreach($keys as $key => $enabled) {
+ $extraPermissions->setPermission($app, $key, $enabled);
+ }
+ }
+ }
+
+ return $extraPermissions;
+ }
+
+ /**
+ * Format IExtraPermissions to database format (JSON string)
+ *
+ * @param IExtraPermissions $permissions
+ * @return string|null
+ */
+ private function formatExtraPermissions($permissions) {
+ $formattedPermissions = [];
+ foreach($permissions->getApps() as $app) {
+ $formattedPermissions[$app] = [];
+ foreach($permissions->getKeys($app) as $key) {
+ $formattedPermissions[$app][$key] = $permissions->getPermission($app, $key);
+ }
+ }
+
+ if (empty($formattedPermissions)) {
+ return null;
+ }
+ return json_encode($formattedPermissions);
+ }
}
diff --git a/lib/private/Share20/ExtraPermissions.php b/lib/private/Share20/ExtraPermissions.php
new file mode 100644
index 000000000000..38b0affaa136
--- /dev/null
+++ b/lib/private/Share20/ExtraPermissions.php
@@ -0,0 +1,71 @@
+
+ *
+ * @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
+ *
+ */
+namespace OC\Share20;
+
+use OCP\Share\IExtraPermissions;
+
+class ExtraPermissions implements IExtraPermissions {
+
+ /** @var array */
+ private $permissions;
+
+ public function __construct() {
+ $this->permissions = [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setPermission($app, $key, $enabled) {
+ if (!array_key_exists($app, $this->permissions)) {
+ $this->permissions[$app] = [];
+ }
+ $this->permissions[$app][$key] = $enabled;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getPermission($app, $key) {
+ if (array_key_exists($app, $this->permissions) &&
+ array_key_exists($key, $this->permissions[$app])) {
+ return $this->permissions[$app][$key];
+ }
+ return null;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getApps() {
+ return array_keys($this->permissions);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getKeys($app) {
+ if (!array_key_exists($app, $this->permissions)) {
+ return [];
+ }
+ return array_keys($this->permissions[$app]);
+ }
+}
\ No newline at end of file
diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php
index a98401c53fab..a1b8bd297a33 100644
--- a/lib/private/Share20/Manager.php
+++ b/lib/private/Share20/Manager.php
@@ -641,6 +641,7 @@ public function createShare(\OCP\Share\IShare $share) {
'shareType' => $share->getShareType(),
'uidOwner' => $share->getSharedBy(),
'permissions' => $share->getPermissions(),
+ 'extraPermissions' => $share->getExtraPermissions(),
'fileSource' => $share->getNode()->getId(),
'expiration' => $share->getExpirationDate(),
'token' => $share->getToken(),
@@ -668,6 +669,7 @@ public function createShare(\OCP\Share\IShare $share) {
'shareType' => $share->getShareType(),
'uidOwner' => $share->getSharedBy(),
'permissions' => $share->getPermissions(),
+ 'extraPermissions' => $share->getExtraPermissions(),
'fileSource' => $share->getNode()->getId(),
'expiration' => $share->getExpirationDate(),
'token' => $share->getToken(),
@@ -1382,7 +1384,10 @@ public function getAccessList(\OCP\Files\Node $path) {
* @return \OCP\Share\IShare;
*/
public function newShare() {
- return new \OC\Share20\Share($this->rootFolder, $this->userManager);
+ $extraPermissions = new ExtraPermissions();
+ $share = new Share($this->rootFolder, $this->userManager);
+ $share->setExtraPermissions($extraPermissions);
+ return $share;
}
/**
diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php
index 4fa7e4ccfe93..1d416e537e1f 100644
--- a/lib/private/Share20/Share.php
+++ b/lib/private/Share20/Share.php
@@ -28,8 +28,10 @@
use OCP\IUserManager;
use OCP\Share\Exceptions\IllegalIDChangeException;
use OC\Share\Constants;
+use OCP\Share\IExtraPermissions;
+use OCP\Share\IShare;
-class Share implements \OCP\Share\IShare {
+class Share implements IShare {
/** @var string */
private $id;
@@ -51,6 +53,8 @@ class Share implements \OCP\Share\IShare {
private $shareOwner;
/** @var int */
private $permissions;
+ /** @var IExtraPermissions */
+ private $extraPermissions;
/** @var \DateTime */
private $expireDate;
/** @var string */
@@ -269,6 +273,20 @@ public function getPermissions() {
return $this->permissions;
}
+ /**
+ * @inheritdoc
+ */
+ public function setExtraPermissions($permissions) {
+ $this->extraPermissions = $permissions;
+ return $this;
+ }
+ /**
+ * @inheritdoc
+ */
+ public function getExtraPermissions() {
+ return $this->extraPermissions;
+ }
+
/**
* @inheritdoc
*/
@@ -362,7 +380,7 @@ public function getToken() {
* 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/public/Share/IExtraPermissions.php b/lib/public/Share/IExtraPermissions.php
new file mode 100644
index 000000000000..44ce15460b2d
--- /dev/null
+++ b/lib/public/Share/IExtraPermissions.php
@@ -0,0 +1,69 @@
+
+ *
+ * @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
+ *
+ */
+namespace OCP\Share;
+
+/**
+ * Interface IExtraPermission
+ *
+ * @package OCP\Share
+ * @since 10.2.0
+ */
+interface IExtraPermissions {
+
+ /**
+ * Sets a permission. If the key did not exist before it will be created.
+ *
+ * @param string $app app
+ * @param string $key key
+ * @param bool $enabled enabled
+ * @since 10.2.0
+ */
+ public function setPermission($app, $key, $enabled);
+
+ /**
+ * Checks if permission for given app and key is enabled.
+ * If permission does not exist, returns null
+ *
+ * @param string $app app
+ * @param string $key key
+ * @return bool|null
+ * @since 10.2.0
+ */
+ public function getPermission($app, $key);
+
+ /**
+ * Get all apps for which extra permissions are set
+ *
+ * @return string[] apps
+ * @since 10.2.0
+ */
+ public function getApps();
+
+ /**
+ * Get all permission keys for specific app
+ *
+ * @param string $app
+ * @return string[]
+ * @since 10.2.0
+ */
+ public function getKeys($app);
+
+}
\ No newline at end of file
diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php
index ae8d90489661..fbb65e127fa6 100644
--- a/lib/public/Share/IShare.php
+++ b/lib/public/Share/IShare.php
@@ -184,6 +184,22 @@ public function setPermissions($permissions);
*/
public function getPermissions();
+ /**
+ * Set extra permissions
+ *
+ * @param IExtraPermissions $permissions
+ * @since 10.2.0
+ * @return \OCP\Share\IShare The modified object
+ */
+ public function setExtraPermissions($permissions);
+ /**
+ * Get extra permissions
+ *
+ * @return IExtraPermissions
+ * @since 10.2.0
+ */
+ public function getExtraPermissions();
+
/**
* Set the expiration date
*