diff --git a/customize.dist/src/less2/include/drive.less b/customize.dist/src/less2/include/drive.less index 72e34d7c6..3d56b6bf2 100644 --- a/customize.dist/src/less2/include/drive.less +++ b/customize.dist/src/less2/include/drive.less @@ -159,6 +159,10 @@ user-select: none; } + .cp-app-drive-element-restricted { + color: #939393; + } + .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 9643a4b1b..e0385348d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -129,6 +129,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'); @@ -1949,7 +1953,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); @@ -1964,6 +1969,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) { @@ -1972,14 +1982,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' }); @@ -2034,6 +2044,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 @@ -2051,32 +2062,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; @@ -2100,8 +2117,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(); @@ -3600,6 +3616,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?) @@ -3753,8 +3776,15 @@ 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]) { + $elementRow.addClass('cp-app-drive-element-restricted'); + } if (isSharedFolder) { var sfData = manager.getSharedFolderData(isSharedFolder); _addOwnership($elementRow, $(), sfData); @@ -3837,7 +3867,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) { @@ -4130,6 +4160,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); }); } @@ -4715,7 +4749,6 @@ define([ }); } */ - var deprecated = files.sharedFoldersTemp; var nt = nThen; var passwordModal = function (fId, data, cb) { var content = []; @@ -4771,6 +4804,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]; @@ -4787,7 +4821,6 @@ define([ }); } - return { refresh: refresh, close: function () { diff --git a/www/common/inner/access.js b/www/common/inner/access.js index ad9f28a94..84ada6da6 100644 --- a/www/common/inner/access.js +++ b/www/common/inner/access.js @@ -372,8 +372,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 c6e588672..b5be30fa1 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] @@ -2188,6 +2191,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 8054e83f7..7211df88c 100644 --- a/www/common/proxy-manager.js +++ b/www/common/proxy-manager.js @@ -69,7 +69,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(); @@ -215,7 +215,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); @@ -491,6 +491,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 5689f6284..2752d52e7 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -378,7 +378,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 a7cb94910..bc91c5227 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/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/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 diff --git a/www/teams/inner.js b/www/teams/inner.js index 0f3db0829..e0913d1a9 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,11 @@ 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 (!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) {