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 extraPermissions}}' + + '' + + '' + + '' + + '' + + '{{/each}}' + + '
' + '' + '{{/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 *