diff --git a/CHANGELOG.md b/CHANGELOG.md index 324c03785..f7a78c0fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +# WIP + +* OnlyOffice + * inform OnlyOffice of userlist changes + * rename doc and slide editors + * handle different lock formats for docs and slides + * relative to sheets + * handle some cursor logic outside of sheets + * handle locks when integrating remote checkpoints in strict mode + * OnlyOffice renamed buttons in slides and docs and we need to hardcode CSS that hides them by their randomly generated IDs + * support CryptPad cursor colors in OnlyOffice by adding opacity value + * use the appropriate APIs to detect if the document is modified + * display users cursor colors in the toolbar next to their name + * handle errors when migrating in embed mode + * change the method we use to lock the whole sheet since OnlyOffice changed their internal API's behaviour +* bad channel IDs stored in your drive or accessed via bad links (corrupted somehow) + * don't try to join invalid channels + * don't try to get their metadata +* prompt premium users to cancel their subscriptions before deleting their accounts + # 4.3.1 This minor release addresses some bugs discovered after deploying and tagging 4.3.0 diff --git a/lib/commands/metadata.js b/lib/commands/metadata.js index 564c4f00d..2fdb41c8a 100644 --- a/lib/commands/metadata.js +++ b/lib/commands/metadata.js @@ -12,6 +12,17 @@ Data.getMetadataRaw = function (Env, channel /* channelName */, _cb) { if (channel.length !== HK.STANDARD_CHANNEL_LENGTH && channel.length !== HK.ADMIN_CHANNEL_LENGTH) { return cb("INVALID_CHAN_LENGTH"); } + // return synthetic metadata for admin broadcast channels as a safety net + // in case anybody manages to write metadata + /* + if (channel.length === HK.ADMIN_CHANNEL_LENGTH) { // XXX + return void cb(void 0, { + channel: channel, + creation: +new Date(), + owners: Env.admins, + }); + } */ + var cached = Env.metadata_cache[channel]; if (HK.isMetadataMessage(cached)) { Env.checkCache(channel); @@ -141,7 +152,7 @@ Data.setMetadata = function (Env, safeKey, data, cb, Server) { const metadata_cache = Env.metadata_cache; // update the cached metadata - metadata_cache[channel] = metadata; + metadata_cache[channel] = metadata; // XXX guard against malicious takeover of the broadcast channel // it's easy to check if the channel is restricted const isRestricted = metadata.restricted; diff --git a/lib/decrees.js b/lib/decrees.js index 0b5e8572e..9ee294608 100644 --- a/lib/decrees.js +++ b/lib/decrees.js @@ -27,6 +27,7 @@ DISABLE_INTEGRATED_EVICTION // BROADCAST SET_LAST_BROADCAST_HASH SET_SURVEY_URL +SET_MAINTENANCE NOT IMPLEMENTED: @@ -129,10 +130,10 @@ var args_isString = function (args) { return Array.isArray(args) && typeof(args[0]) === "string"; }; var args_isMaintenance = function (args) { - return Array.isArray(args) && args[0] && args[0].end && args[0].start; + return Array.isArray(args) && args[0] && args[0].end && args[0].start; // XXX we could validate that these are numbers && !isNaN }; -var makeBroadcastSetter = function (attr) { +var makeBroadcastSetter = function (attr) { // XXX could pass extra validation here? return function (Env, args) { if (!args_isString(args) && !args_isMaintenance(args)) { throw new Error('INVALID_ARGS'); @@ -149,7 +150,7 @@ var makeBroadcastSetter = function (attr) { commands.SET_LAST_BROADCAST_HASH = makeBroadcastSetter('lastBroadcastHash'); // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_SURVEY_URL', [url]]], console.log) -commands.SET_SURVEY_URL = makeBroadcastSetter('surveyURL'); +commands.SET_SURVEY_URL = makeBroadcastSetter('surveyURL'); // XXX anticipate language-specific surveys // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAINTENANCE', [{start: +Date, end: +Date}]]], console.log) // CryptPad_AsyncStore.rpc.send('ADMIN', [ 'ADMIN_DECREE', ['SET_MAINTENANCE', [""]]], console.log) diff --git a/server.js b/server.js index 7f4c1fcbf..62e144efa 100644 --- a/server.js +++ b/server.js @@ -113,8 +113,7 @@ var setHeaders = (function () { // Don't set CSP headers on /api/config because they aren't necessary and they cause problems // when duplicated by NGINX in production environments - // XXX /api/broadcast too? - if (/^\/api\/config/.test(req.url)) { return; } + if (/^\/api\/(broadcast|config)/.test(req.url)) { return; } // targeted CSP, generic policies, maybe custom headers const h = [ /^\/common\/onlyoffice\/.*\/index\.html.*/, @@ -273,7 +272,7 @@ var serveConfig = (function () { }; }()); -var serveBroadcast = (function () { +var serveBroadcast = (function () { // XXX deduplicate var cacheString = function () { return (Env.FRESH_KEY? '-' + Env.FRESH_KEY: '') + (Env.DEV_MODE? '-' + (+new Date()): ''); }; @@ -284,7 +283,7 @@ var serveBroadcast = (function () { maintenance = undefined; } return [ - 'define(function(){', + 'define(function(){', // XXX maybe this could just be JSON 'return ' + JSON.stringify({ lastBroadcastHash: Env.lastBroadcastHash, surveyURL: Env.surveyURL, diff --git a/www/admin/app-admin.less b/www/admin/app-admin.less index c3556c869..17450361c 100644 --- a/www/admin/app-admin.less +++ b/www/admin/app-admin.less @@ -14,6 +14,10 @@ display: flex; flex-flow: column; + a { + color: @cryptpad_color_link; + text-decoration: underline; + } .cp-admin-setlimit-form, .cp-admin-broadcast-form { label { diff --git a/www/admin/inner.js b/www/admin/inner.js index a7517e8d3..4642296db 100644 --- a/www/admin/inner.js +++ b/www/admin/inner.js @@ -970,6 +970,7 @@ define([ var getApi = function (cb) { return function () { require(['/api/broadcast?'+ (+new Date())], function (Broadcast) { + // XXX require.s.contexts._ can be used to erase old loaded objects cb(Broadcast); }); }; @@ -1060,7 +1061,7 @@ define([ var form = h('div.cp-admin-broadcast-form'); var $form = $(form).appendTo($div); - var refresh = getApi(function (Broadcast) { + var refresh = getApi(function (/* Broadcast */) { // XXX unused argument var button = h('button.btn.btn-primary', Messages.admin_broadcastButton); var $button = $(button); var removeButton = h('button.btn.btn-danger', Messages.admin_broadcastCancel); @@ -1219,7 +1220,7 @@ define([ } if (error) { console.error('One of the selected languages has no data'); - return false; + return false; // XXX better error handling? } return { defaultLanguage: defaultLanguage, @@ -1229,8 +1230,8 @@ define([ var send = function (data) { $button.prop('disabled', 'disabled'); - data.time = +new Date(); - common.mailbox.sendTo('BROADCAST_CUSTOM', data, {}, function (err, data) { + data.time = +new Date(); // XXX not used anymore? + common.mailbox.sendTo('BROADCAST_CUSTOM', data, {}, function (err /*, data */) { // XXX unused argument if (err) { $button.prop('disabled', ''); console.error(err); @@ -1249,13 +1250,13 @@ define([ send(data); }); - UI.confirmButton(removeButton, { + UI.confirmButton(removeButton, { // XXX table jank classes: 'btn-danger', }, function () { if (!activeUid) { return; } common.mailbox.sendTo('BROADCAST_DELETE', { uid: activeUid - }, {}, function (err, data) { + }, {}, function (err /* , data */) { // XXX unused argument if (err) { return UI.warn(Messages.error); } UI.log(Messages.saved); refresh(); @@ -1311,7 +1312,8 @@ define([ var end = h('input'); var $start = $(start); var $end = $(end); - var endPickr = Flatpickr(end, { + // XXX new Date().toLocaleString('fr-fr', {month: 'long'}).replace(/./, c => c.toUpperCase()) + var endPickr = Flatpickr(end, { // XXX translations? enableTime: true, minDate: new Date() }); @@ -1349,7 +1351,7 @@ define([ return; } // Maintenance applied, send notification - common.mailbox.sendTo('BROADCAST_MAINTENANCE', {}, {}, function (err, data) { + common.mailbox.sendTo('BROADCAST_MAINTENANCE', {}, {}, function (/* err, data */) { // XXX unused arguments refresh(); checkLastBroadcastHash(); }); @@ -1411,7 +1413,7 @@ define([ common.openUnsafeURL(Broadcast.surveyURL); }); active = h('div.cp-broadcast-active', [ - h('p', a), + h('p', a), // XXX spacing around this element is really cramped removeButton ]); } @@ -1445,7 +1447,7 @@ define([ // Maintenance applied, send notification common.mailbox.sendTo('BROADCAST_SURVEY', { url: data - }, {}, function (err, data) { + }, {}, function (/* err, data */) { // XXX unused arguments refresh(); checkLastBroadcastHash(); }); diff --git a/www/common/application_config_internal.js b/www/common/application_config_internal.js index 48fa1f845..2ee4d2849 100644 --- a/www/common/application_config_internal.js +++ b/www/common/application_config_internal.js @@ -162,7 +162,7 @@ define(function() { // making it much faster to open new tabs. config.disableWorkers = false; - //config.surveyURL = ""; + //config.surveyURL = ""; // XXX remove this? // Teams are always loaded during the initial loading screen (for the first tab only if // SharedWorkers are available). Allowing users to be members of multiple teams can diff --git a/www/common/onlyoffice/inner.js b/www/common/onlyoffice/inner.js index 486fcb3af..5775eb4f6 100644 --- a/www/common/onlyoffice/inner.js +++ b/www/common/onlyoffice/inner.js @@ -1409,7 +1409,7 @@ define([ // Migration required but read-only: continue... if (readOnly) { setEditable(true); - getEditor().setViewModeDisconnect(); + try { getEditor().asc_setRestriction(true); } catch (e) {} } else { // No changes after the cp: migrate now onMigrateRdy.fire(); @@ -1434,12 +1434,9 @@ define([ return; } - if (lock) { - getEditor().setViewModeDisconnect(); - } else if (readOnly) { - try { - getEditor().asc_setRestriction(true); - } catch (e) {} + if (lock || readOnly) { + try { getEditor().asc_setRestriction(true); } catch (e) {} + //getEditor().setViewModeDisconnect(); // can't be used anymore, display an OO error popup } else { setEditable(true); deleteOfflineLocks(); @@ -1459,7 +1456,6 @@ define([ } } - if (isLockedModal.modal && force) { isLockedModal.modal.closeModal(); delete isLockedModal.modal; @@ -1469,7 +1465,8 @@ define([ } if (APP.template) { - getEditor().setViewModeDisconnect(); + try { getEditor().asc_setRestriction(true); } catch (e) {} + //getEditor().setViewModeDisconnect(); UI.removeLoadingScreen(); makeCheckpoint(true); return; @@ -1483,7 +1480,7 @@ define([ } catch (e) {} } - if (APP.migrate && !readOnly) { + if (lock && !readOnly) { onMigrateRdy.fire(); } @@ -2065,7 +2062,7 @@ define([ UI.removeModals(); UI.confirm(Messages.oo_uploaded, function (yes) { try { - getEditor().setViewModeDisconnect(); + getEditor().asc_setRestriction(true); } catch (e) {} if (!yes) { return; } common.gotoURL(); @@ -2232,7 +2229,9 @@ define([ APP.history = true; APP.template = true; var editor = getEditor(); - if (editor) { editor.setViewModeDisconnect(); } + if (editor) { + try { getEditor().asc_setRestriction(true); } catch (e) {} + } var content = parsed.content; // Get checkpoint @@ -2372,7 +2371,7 @@ define([ var setHistoryMode = function (bool) { if (bool) { APP.history = true; - getEditor().setViewModeDisconnect(); + try { getEditor().asc_setRestriction(true); } catch (e) {} return; } // Cancel button: redraw from lastCp @@ -2579,7 +2578,11 @@ define([ APP.onLocal(); } else { msg = h('div.alert.alert-warning.cp-burn-after-reading', Messages.oo_sheetMigration_anonymousEditor); - $(APP.helpMenu.menu).after(msg); + if (APP.helpMenu) { + $(APP.helpMenu.menu).after(msg); + } else { + $('#cp-app-oo-editor').prepend(msg); + } readOnly = true; } } else if (content && content.version <= 3) { // V2 or V3 @@ -2591,7 +2594,11 @@ define([ APP.onLocal(); } else { msg = h('div.alert.alert-warning.cp-burn-after-reading', Messages.oo_sheetMigration_anonymousEditor); - $(APP.helpMenu.menu).after(msg); + if (APP.helpMenu) { + $(APP.helpMenu.menu).after(msg); + } else { + $('#cp-app-oo-editor').prepend(msg); + } readOnly = true; } } diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 5c7f3623c..3ce97a254 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -3178,7 +3178,7 @@ define([ }); }; - Store.newVersionReload = function () { + Store.newVersionReload = function () { // XXX not used anymore? broadcast([], "NETWORK_RECONNECT"); }; Store.disconnect = function () { diff --git a/www/common/outer/mailbox-handlers.js b/www/common/outer/mailbox-handlers.js index f9cfd994d..7db5a3119 100644 --- a/www/common/outer/mailbox-handlers.js +++ b/www/common/outer/mailbox-handlers.js @@ -687,7 +687,7 @@ define([ // Broadcast - var broadcasts = {}; + //var broadcasts = {}; // XXX defined but never used ? handlers['BROADCAST_MAINTENANCE'] = function (ctx, box, data, cb) { var msg = data.msg; var uid = msg.uid; @@ -708,7 +708,7 @@ define([ var dismiss = !content.url; cb(dismiss, old); }; - var activeCustom + var activeCustom; handlers['BROADCAST_CUSTOM'] = function (ctx, box, data, cb) { var msg = data.msg; var uid = msg.uid; diff --git a/www/common/outer/mailbox.js b/www/common/outer/mailbox.js index 2ef42409c..6605a55ee 100644 --- a/www/common/outer/mailbox.js +++ b/www/common/outer/mailbox.js @@ -341,7 +341,7 @@ proxy.mailboxes = { }; var notify = box.ready; Handlers.add(ctx, box, message, function (dismissed, toDismiss, setAsLKH) { - if (setAsLKH) { + if (setAsLKH) { // XXX confirm whether this if is used? // Update LKH box.data.lastKnownHash = hash; box.data.viewed = []; diff --git a/www/common/outer/team.js b/www/common/outer/team.js index db6326115..c5423ff7b 100644 --- a/www/common/outer/team.js +++ b/www/common/outer/team.js @@ -1382,15 +1382,15 @@ define([ // Viewer to editor if (user.role === "VIEWER" && data.data.role !== "VIEWER") { - changeEditRights(ctx, teamId, user, true, function (err) { - return void cb({error: err}); + changeEditRights(ctx, teamId, user, true, function (obj) { + return void cb(obj); }); } // Editor to viewer if (user.role !== "VIEWER" && data.data.role === "VIEWER") { - changeEditRights(ctx, teamId, user, false, function (err) { - return void cb({error: err}); + changeEditRights(ctx, teamId, user, false, function (obj) { + return void cb(obj); }); } diff --git a/www/common/toolbar.js b/www/common/toolbar.js index d59c9afa6..07eda5b2e 100644 --- a/www/common/toolbar.js +++ b/www/common/toolbar.js @@ -1032,7 +1032,7 @@ MessengerUI, Messages) { Messages.broadcast_maintenance = "A maintenance is planned between {0} and {1}"; // XXX var createMaintenance = function (toolbar, config) { var $notif = toolbar.$top.find('.'+MAINTENANCE_CLS); - var button = h('button.cp-maintenance-wrench.fa.fa-wrench'); + var button = h('button.cp-maintenance-wrench.fa.fa-wrench'); // XXX might need some color contrast $notif.append(button); diff --git a/www/support/ui.js b/www/support/ui.js index 1a8f3e440..a2b5da4a8 100644 --- a/www/support/ui.js +++ b/www/support/ui.js @@ -445,7 +445,7 @@ define([ }; ctx.FM = common.createFileManager(fmConfig); - ui.send = function (id, type, data, dest) { + ui.send = function (id, type, data, dest) { // XXX confirm that this is actually used return send(ctx, id, type, data, dest); }; ui.sendForm = function (id, form, dest) {