From 2c57a2d8721089cef820ad3e47118c7411062097 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 17 Jun 2020 17:40:29 +0200 Subject: [PATCH 1/6] Add allow lists for shared folders --- customize.dist/src/less2/include/drive.less | 4 ++ www/common/drive-ui.js | 56 ++++++++++++++------- www/common/inner/access.js | 3 +- www/common/outer/async-store.js | 2 + www/common/outer/sharedfolder.js | 1 + www/common/proxy-manager.js | 2 +- www/drive/inner.js | 7 ++- www/teams/inner.js | 6 ++- 8 files changed, 59 insertions(+), 22 deletions(-) diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 0eb400587..28d539e48 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -158,6 +158,10 @@ user-select: none; } + .cp-app-drive-element-restricted { + background: rgba(0,0,0,0.1); + } + .cp-app-drive-element-droppable { background-color: @drive_droppable-bg; color: #222; diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 93c36597a..f3960f3b5 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -130,6 +130,7 @@ define([ //var $ownerIcon = $('', {"class": "fa fa-id-card"}); var $tagsIcon = $('', {"class": "fa " + faTags}); var $passwordIcon = $('', {"class": "fa fa-lock"}); + var $restrictedIcon = $('', {"class": "fa fa-ban"}); var $expirableIcon = $('', {"class": "fa fa-clock-o"}); var $separator = $('
', {"class": "dropdown-divider"}); @@ -1211,6 +1212,9 @@ define([ if (!$element.is('.cp-app-drive-element-owned')) { hide.push('deleteowned'); } + if ($element.is('.cp-app-drive-element-restricted')) { + hide.push('rename', 'download', 'share', 'access', 'color'); + } if ($element.is('.cp-app-drive-element-notrash')) { // We can't delete elements in virtual categories hide.push('delete'); @@ -1966,7 +1970,8 @@ define([ var $ro; if (manager.isSharedFolder(element)) { var data = manager.getSharedFolderData(element); - key = data && data.title ? data.title : key; + var fId = element; + key = data.title || data.lastTitle; element = manager.folders[element].proxy[manager.user.userObject.ROOT]; $span.addClass('cp-app-drive-element-sharedf'); _addOwnership($span, $state, data); @@ -1981,6 +1986,11 @@ define([ $ro.attr('title', Messages.readonly); } + if (files.restrictedFolders[fId]) { + var $restricted = $restrictedIcon.clone().appendTo($state); + $restricted.attr('title', Messages.fm_restricted); + } + var $shared = $sharedIcon.clone().appendTo($state); $shared.attr('title', Messages.fm_canBeShared); } else if ($content.data('readOnlyFolder') || APP.readOnly) { @@ -1989,14 +1999,14 @@ define([ } var sf = manager.hasSubfolder(element); - var files = manager.hasFile(element); + var hasFiles = manager.hasFile(element); var $name = $('', {'class': 'cp-app-drive-element-name'}).text(key); var $subfolders = $('', { 'class': 'cp-app-drive-element-folders cp-app-drive-element-list' }).text(sf); var $files = $('', { 'class': 'cp-app-drive-element-files cp-app-drive-element-list' - }).text(files); + }).text(hasFiles); var $filler = $('', { 'class': 'cp-app-drive-element-filler cp-app-drive-element-list' }); @@ -2051,6 +2061,7 @@ define([ return $shareBlock; }; + Messages.fm_restricted = "Forbidden access"; // XXX // Create the "li" element corresponding to the file/folder located in "path" var createElement = function (path, elPath, root, isFolder) { // Forbid drag&drop inside the trash @@ -2068,32 +2079,38 @@ define([ element = root[key]; } + var restricted = files.restrictedFolders[element]; var isSharedFolder = manager.isSharedFolder(element); var $icon = !isFolder ? getFileIcon(element) : undefined; var ro = manager.isReadOnlyFile(element); // ro undefined means it's an old hash which doesn't support read-only - var roClass = typeof(ro) === 'undefined' ?' cp-app-drive-element-noreadonly' : - ro ? ' cp-app-drive-element-readonly' : ''; - var liClass = 'cp-app-drive-element-file cp-app-drive-element' + roClass; + var roClass = typeof(ro) === 'undefined' ? '.cp-app-drive-element-noreadonly' : + ro ? '.cp-app-drive-element-readonly' : ''; + var liClass = '.cp-app-drive-element-file'; + var restrictedClass = restricted ? '.cp-app-drive-element-restricted' : ''; if (isSharedFolder) { - liClass = 'cp-app-drive-element-folder cp-app-drive-element'; + liClass = '.cp-app-drive-element-folder'; $icon = $sharedFolderIcon.clone(); $icon.css("color", getFolderColor(path.concat(elPath))); } else if (isFolder) { - liClass = 'cp-app-drive-element-folder cp-app-drive-element'; + liClass = '.cp-app-drive-element-folder'; $icon = manager.isFolderEmpty(root[key]) ? $folderEmptyIcon.clone() : $folderIcon.clone(); $icon.css("color", getFolderColor(path.concat(elPath))); } - var $element = $('
  • ', { - draggable: true, - 'class': 'cp-app-drive-element-row' - }); + var classes = restrictedClass + roClass + liClass; + var $element = $(h('li.cp-app-drive-element.cp-app-drive-element-row' + classes, { + draggable: true + })); $element.data('path', newPath); if (isElementSelected($element)) { selectElement($element); } $element.prepend($icon).dblclick(function () { + if (restricted) { + UI.warn(Messages.fm_restricted); + return; + } if (isFolder) { APP.displayDirectory(newPath); return; @@ -2117,8 +2134,7 @@ define([ } e.stopPropagation(); }); - $element.addClass(liClass); - var droppable = !isTrash && !APP.$content.data('readOnlyFolder'); + var droppable = !isTrash && !APP.$content.data('readOnlyFolder') && !restricted; addDragAndDropHandlers($element, newPath, isFolder, droppable); $element.click(function(e) { e.stopPropagation(); @@ -3697,6 +3713,9 @@ define([ e.stopPropagation(); APP.displayDirectory(path); }); + if (files.restrictedFolders[isSharedFolder]) { + $elementRow.addClass('cp-app-drive-element-restricted'); + } var $element = $('
  • ').append($elementRow); if (draggable) { $elementRow.attr('draggable', true); } if (collapsable) { @@ -3775,7 +3794,7 @@ define([ if (!manager.isFolder(root[key])) { return; } var newPath = path.slice(); newPath.push(key); - var isSharedFolder = manager.isSharedFolder(root[key]); + var isSharedFolder = manager.isSharedFolder(root[key]) && root[key]; var sfId = manager.isInSharedFolder(newPath) || (isSharedFolder && root[key]); var $icon, isCurrentFolder, subfolder; if (isSharedFolder) { @@ -4068,6 +4087,10 @@ define([ else if ($this.hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { var el = manager.find(p.path); + if (files.restrictedFolders[el]) { + UI.warn(Messages.fm_restricted); + return; + } openFile(el, false, true); }); } @@ -4655,7 +4678,6 @@ define([ }); } */ - var deprecated = files.sharedFoldersTemp; var nt = nThen; var passwordModal = function (fId, data, cb) { var content = []; @@ -4711,6 +4733,7 @@ define([ onClose: cb }); }; + var deprecated = files.sharedFoldersTemp; if (typeof (deprecated) === "object" && APP.editable && Object.keys(deprecated).length) { Object.keys(deprecated).forEach(function (fId) { var data = deprecated[fId]; @@ -4727,7 +4750,6 @@ define([ }); } - return { refresh: refresh, close: function () { diff --git a/www/common/inner/access.js b/www/common/inner/access.js index 190af4279..d3084dbe6 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -371,8 +371,7 @@ define([ var parsed = Hash.parsePadUrl(data.href || data.roHref); var owned = Modal.isOwned(Env, data); var disabled = !owned || !parsed.hashData || parsed.hashData.type !== 'pad'; - var allowDisabled = parsed.type === 'drive'; - if (disabled || allowDisabled) { return void cb(); } + if (disabled) { return void cb(); } opts = opts || {}; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index c87edc17d..eb04c7310 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2188,6 +2188,8 @@ define([ }); }; registerProxyEvents = function (proxy, fId) { + if (!proxy) { return; } + if (proxy.deprecated || proxy.restricted) { return; } if (!fId) { // Listen for shared folder password change proxy.on('change', ['drive', UserObject.SHARED_FOLDERS], function (o, n, p) { diff --git a/www/common/outer/sharedfolder.js b/www/common/outer/sharedfolder.js index cbeae1cbd..3c85492f9 100644 --- a/www/common/outer/sharedfolder.js +++ b/www/common/outer/sharedfolder.js @@ -225,6 +225,7 @@ define([ sf.teams.forEach(function (obj) { obj.store.manager.restrictedProxy(obj.id, secret.channel); }); + delete allSharedFolders[secret.channel]; return void cb(); } } diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index df46cb884..7e458df23 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -68,7 +68,7 @@ define([ }; var restrictedProxy = function (Env, id) { - var lm = { proxy: { deprecated: true } }; + var lm = { proxy: { restricted: true, root: {}, filesData: {} } }; removeProxy(Env, id); addProxy(Env, id, lm, function () {}); return void Env.Store.refreshDriveUI(); diff --git a/www/drive/inner.js b/www/drive/inner.js index 5d60e9a4c..553454bc5 100644 --- a/www/drive/inner.js +++ b/www/drive/inner.js @@ -42,6 +42,7 @@ define([ if (!drive || !drive.sharedFolders) { return void cb(); } + var r = drive.restrictedFolders = drive.restrictedFolders || {}; var oldIds = Object.keys(folders); nThen(function (waitFor) { Object.keys(drive.sharedFolders).forEach(function (fId) { @@ -60,7 +61,11 @@ define([ APP.newSharedFolder = null; } } - if (newObj && newObj.deprecated) { + if (newObj && newObj.restricted) { + r[fId] = drive.sharedFolders[fId]; + if (!r[fId].title) { r[fId].title = r[fId].lastTitle; } + } + if (newObj && (newObj.deprecated /*|| newObj.restricted*/)) { delete folders[fId]; delete drive.sharedFolders[fId]; if (manager && manager.folders) { diff --git a/www/teams/inner.js b/www/teams/inner.js index 86b8acf47..2d9690b99 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -55,6 +55,7 @@ define([ if (!drive || !drive.sharedFolders) { return void cb(); } + var r = drive.restrictedFolders = drive.restrictedFolders || {}; var oldIds = Object.keys(folders); nThen(function (waitFor) { Object.keys(drive.sharedFolders).forEach(function (fId) { @@ -65,7 +66,10 @@ define([ sframeChan.query('Q_DRIVE_GETOBJECT', { sharedFolder: fId }, waitFor(function (err, newObj) { - if (newObj && newObj.deprecated) { + if (newObj && newObj.restricted) { + r[fId] = drive.sharedFolders[fId]; + } + if (newObj && (newObj.deprecated || newObj.restricted)) { delete folders[fId]; delete drive.sharedFolders[fId]; if (manager && manager.folders) { From e25756ceca35411191ca9efbe864e1aeb174e71e Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 24 Jun 2020 11:20:43 +0200 Subject: [PATCH 2/6] Don't add the shared folder to your drive is there is an access list --- www/common/outer/async-store.js | 3 +++ www/common/proxy-manager.js | 13 ++++++++++++- www/common/sframe-common-outer.js | 2 +- www/common/sframe-common.js | 4 ++++ www/drive/main.js | 7 ++++++- 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index eb04c7310..d49353b01 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2083,6 +2083,9 @@ define([ Store.addSharedFolder = function (clientId, data, cb) { var s = getStore(data.teamId); s.manager.addSharedFolder(data, function (id) { + if (id && typeof(id) == "object" && id.error) { + return void cb(id); + } var send = data.teamId ? s.sendEvent : sendDriveEvent; send('DRIVE_CHANGE', { path: ['drive', UserObject.FILES_DATA] diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 7e458df23..95bab2c24 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -214,7 +214,7 @@ define([ if (!Env.folders[id]) { return {}; } var obj = Env.folders[id].proxy.metadata || {}; for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) { - var data = JSON.parse(JSON.stringify(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k])); + var data = JSON.parse(JSON.stringify(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k] || {})); if (k === "href" && data.indexOf('#') === -1) { try { data = Env.user.userObject.cryptor.decrypt(data); @@ -503,6 +503,17 @@ define([ }; if (data.password) { folderData.password = data.password; } if (data.owned) { folderData.owners = [Env.edPublic]; } + }).nThen(function (waitFor) { + Env.Store.getPadMetadata(null, { + channel: folderData.channel + }, waitFor(function (obj) { + if (obj && (obj.error || obj.rejected)) { + waitFor.abort(); + return void cb({ + error: obj.error || 'ERESTRICTED' + }); + } + })); }).nThen(function (waitFor) { Env.pinPads([folderData.channel], waitFor()); }).nThen(function (waitFor) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index ef983503c..df4139200 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -363,7 +363,7 @@ define([ } }).nThen(function (waitFor) { if (cfg.afterSecrets) { - cfg.afterSecrets(Cryptpad, Utils, secret, waitFor()); + cfg.afterSecrets(Cryptpad, Utils, secret, waitFor(), sframeChan); } }).nThen(function (waitFor) { // Check if the pad exists on server diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 48e6782ef..26a9ae66d 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -671,6 +671,10 @@ define([ UIElements.displayPasswordPrompt(funcs, cfg); }); + ctx.sframeChan.on("EV_RESTRICTED_ERROR", function () { + UI.errorLoadingScreen(Messages.restrictedError); + }); + ctx.sframeChan.on("EV_PAD_PASSWORD_ERROR", function () { UI.errorLoadingScreen(Messages.password_error_seed); }); diff --git a/www/drive/main.js b/www/drive/main.js index 4f5b9daba..8db013c0f 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -45,11 +45,16 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var afterSecrets = function (Cryptpad, Utils, secret, cb) { + var afterSecrets = function (Cryptpad, Utils, secret, cb, sframeChan) { var _hash = hash.slice(1); if (_hash && Utils.LocalStore.isLoggedIn()) { // Add a shared folder! Cryptpad.addSharedFolder(null, secret, function (id) { + if (id && typeof(id) === "object" && id.error) { + sframeChan.event("EV_RESTRICTED_ERROR"); + return; + } + window.CryptPad_newSharedFolder = id; // Clear the hash now that the secrets have been generated From b301825d670bc794b413d5e3f9e4e73bd64168e6 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 24 Jun 2020 11:29:18 +0200 Subject: [PATCH 3/6] Display drive ROOT when the current folder is restricted --- www/common/drive-ui.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index c8a78e054..d9761e7d1 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -3559,6 +3559,13 @@ define([ var $list = $('
      ').appendTo($dirContent); var sfId = manager.isInSharedFolder(currentPath); + + // Restricted folder? display ROOT instead + if (sfId && files.restrictedFolders[sfId]) { + _displayDirectory([ROOT], true); + return; + } + var readOnlyFolder = false; if (APP.readOnly) { // Read-only drive (team?) From 36777852664562c19e0ec2a51cd5d136e955f2a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Benqu=C3=A9?= Date: Wed, 24 Jun 2020 13:44:05 +0100 Subject: [PATCH 4/6] use grey text for forbidden shared-folders --- customize.dist/src/less2/include/drive.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 28d539e48..6678260ff 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -159,7 +159,7 @@ } .cp-app-drive-element-restricted { - background: rgba(0,0,0,0.1); + color: #939393; } .cp-app-drive-element-droppable { From a44c19f99616c102b59c4e881e03b087d6bd93cf Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 2 Jul 2020 17:05:23 +0200 Subject: [PATCH 5/6] Add missing error logs --- www/common/drive-ui.js | 4 ++++ www/teams/inner.js | 1 + 2 files changed, 5 insertions(+) diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index d9761e7d1..15a888e5a 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -3719,6 +3719,10 @@ define([ } var $elementRow = $('', {'class': 'cp-app-drive-element-row'}).append($collapse).append($icon).append($name).click(function (e) { e.stopPropagation(); + if (files.restrictedFolders[isSharedFolder]) { + UI.warn(Messages.fm_restricted); + return; + } APP.displayDirectory(path); }); if (files.restrictedFolders[isSharedFolder]) { diff --git a/www/teams/inner.js b/www/teams/inner.js index 6904afa06..0be36596f 100644 --- a/www/teams/inner.js +++ b/www/teams/inner.js @@ -68,6 +68,7 @@ define([ }, waitFor(function (err, newObj) { if (newObj && newObj.restricted) { r[fId] = drive.sharedFolders[fId]; + if (!r[fId].title) { r[fId].title = r[fId].lastTitle; } } if (newObj && (newObj.deprecated || newObj.restricted)) { delete folders[fId]; From 27124997a5972cbbb56671b06f981aad92091432 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 2 Jul 2020 17:07:57 +0200 Subject: [PATCH 6/6] lint compliance --- www/common/outer/async-store.js | 2 +- www/common/proxy-manager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index d49353b01..266a75946 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -2083,7 +2083,7 @@ define([ Store.addSharedFolder = function (clientId, data, cb) { var s = getStore(data.teamId); s.manager.addSharedFolder(data, function (id) { - if (id && typeof(id) == "object" && id.error) { + if (id && typeof(id) === "object" && id.error) { return void cb(id); } var send = data.teamId ? s.sendEvent : sendDriveEvent; diff --git a/www/common/proxy-manager.js b/www/common/proxy-manager.js index 95bab2c24..71b01f36f 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -214,7 +214,7 @@ define([ if (!Env.folders[id]) { return {}; } var obj = Env.folders[id].proxy.metadata || {}; for (var k in Env.user.proxy[UserObject.SHARED_FOLDERS][id] || {}) { - var data = JSON.parse(JSON.stringify(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k] || {})); + var data = Util.clone(Env.user.proxy[UserObject.SHARED_FOLDERS][id][k] || {}); if (k === "href" && data.indexOf('#') === -1) { try { data = Env.user.userObject.cryptor.decrypt(data);