Merge branch 'staging' into deleteBlock

pull/1/head
ansuz 4 years ago
commit 1f61dbcc02

@ -19,7 +19,7 @@ Since early in the pandemic we've been serving a custom home page on CryptPad.fr
To update from 4.1.0 to 4.2.0: To update from 4.1.0 to 4.2.0:
1. Stop your server 1. Stop your server
2. Get the latest code from the 4.1.0 tag (`git fetch origin && git checkout 4.1.0`, or just `git pull origin main`) 2. Get the latest code from the 4.2.0 tag (`git fetch origin && git checkout 4.2.0`, or just `git pull origin main`)
3. Install the latest dependencies with `bower update` and `npm i` 3. Install the latest dependencies with `bower update` and `npm i`
4. Restart your server 4. Restart your server
@ -35,6 +35,7 @@ To update from 4.1.0 to 4.2.0:
* Server administrators can now refresh the _performance_ table on the admin panel without reloading the page. * Server administrators can now refresh the _performance_ table on the admin panel without reloading the page.
* We've begun working on a _checkup_ page for CryptPad to help administrators identify and fix common misconfigurations of the platform. It's still in a very basic state, but we hope to to make it a core part of the server installation guide that is under development. * We've begun working on a _checkup_ page for CryptPad to help administrators identify and fix common misconfigurations of the platform. It's still in a very basic state, but we hope to to make it a core part of the server installation guide that is under development.
* The kanban app now supports import like the rest of our apps and rejects content of any file-type other than JSON. * The kanban app now supports import like the rest of our apps and rejects content of any file-type other than JSON.
* We've dropped support for a very old migration that handled user accounts that had not been accessed fo several years. This should make everyone else's account slightly faster.
## Bug fixes ## Bug fixes
@ -55,10 +56,12 @@ To update from 4.1.0 to 4.2.0:
* The client will now check whether a file is larger than is allowed by the server before attempting to upload it, rather failing only when the server rejects the upload. * The client will now check whether a file is larger than is allowed by the server before attempting to upload it, rather failing only when the server rejects the upload.
* The drive no longer allows files to be dragged and dropped into locations other than the "Documents" section, as it did not make sense for files to be displayed anywhere else. * The drive no longer allows files to be dragged and dropped into locations other than the "Documents" section, as it did not make sense for files to be displayed anywhere else.
* We identified and fixed a number of issues which caused shared folders that were protected with access lists to fail to load due to race conditions between loading the document and authenticating with the server as a user or member of a team. This could also result in a loss of access to documents stored exclusively in those shared folders. * We identified and fixed a number of issues which caused shared folders that were protected with access lists to fail to load due to race conditions between loading the document and authenticating with the server as a user or member of a team. This could also result in a loss of access to documents stored exclusively in those shared folders.
* There was a similar race condition that could occur when registering an account that could cause some parts of the UI to get stuck offline.
* We've fixed a number of server issues: * We've fixed a number of server issues:
1. A change in a function signature in late December caused the upload of unowned files to fail to complete. 1. A change in a function signature in late December caused the upload of unowned files to fail to complete.
2. Messages sent via websocket are no longer broadcast to other members of a session until they have been validated by the server and stored on the disk. This was not a security issue as clients validate messages anyway, however, it could cause inconsistencies in documents when some members of a session incorrectly believed that a message had been saved. 2. Messages sent via websocket are no longer broadcast to other members of a session until they have been validated by the server and stored on the disk. This was not a security issue as clients validate messages anyway, however, it could cause inconsistencies in documents when some members of a session incorrectly believed that a message had been saved.
3. A subtle race condition in very specific circumstances could cause the server's in-memory index for a given session to become incorrect. This could cause one or two messages to be omitted when requesting the most recent history. We observed this in practice when some clients did not realize they had been kicked from a team. This is unlikely to have affected anyone in practice because it only occurred when reconnecting using cached messages for the document which records team membership, and this functionality is only being introduced in this release. 3. A subtle race condition in very specific circumstances could cause the server's in-memory index for a given session to become incorrect. This could cause one or two messages to be omitted when requesting the most recent history. We observed this in practice when some clients did not realize they had been kicked from a team. This is unlikely to have affected anyone in practice because it only occurred when reconnecting using cached messages for the document which records team membership, and this functionality is only being introduced in this release.
4. Several HTTP headers were set by both our example NGINX configuration and the NodeJS server which is proxied by NGINX for a particular resource. The duplication of certain headers caused unexpected behaviour in Chrome-based browsers, so we've updated the Node process to avoid conflicting.
* We spent a lot of time improving our integration of OnlyOffice's sheet editor: * We spent a lot of time improving our integration of OnlyOffice's sheet editor:
* The editor is now initialized with your CryptPad account's preferred language. * The editor is now initialized with your CryptPad account's preferred language.
* We realized that our peer-to-peer locking system (which replaces the server-based system provided by OnlyOffice's document server) did not correctly handle multiple locks per user. This caused errors when filtering and sorting columns. We've improved our locking system so these features should now work as expected, but old clients will not understand the new format. As mentioned in the "Update notes" section, admins must follow the recommended update steps to ensure that all clients correctly update to the latest version. * We realized that our peer-to-peer locking system (which replaces the server-based system provided by OnlyOffice's document server) did not correctly handle multiple locks per user. This caused errors when filtering and sorting columns. We've improved our locking system so these features should now work as expected, but old clients will not understand the new format. As mentioned in the "Update notes" section, admins must follow the recommended update steps to ensure that all clients correctly update to the latest version.

@ -45,6 +45,14 @@ module.exports = {
* In such a case this should be also handled by NGINX, as documented in * In such a case this should be also handled by NGINX, as documented in
* cryptpad/docs/example.nginx.conf (see the $main_domain variable) * cryptpad/docs/example.nginx.conf (see the $main_domain variable)
* *
* Note: you may provide multiple origins for the purpose of accessing
* a development instance via different URLs, like so:
* httpUnsafeOrigin: 'http://127.0.0.1:3000/ http://localhost:3000/',
*
* Such configuration is not recommended for production instances,
* as the development team does not actively test such configuration
* and it may have unintended consequences in practice.
*
*/ */
httpUnsafeOrigin: 'http://localhost:3000/', httpUnsafeOrigin: 'http://localhost:3000/',
@ -295,6 +303,8 @@ module.exports = {
*/ */
blobStagingPath: './data/blobstage', blobStagingPath: './data/blobstage',
decreePath: './data/decrees',
/* CryptPad supports logging events directly to the disk in a 'logs' directory /* CryptPad supports logging events directly to the disk in a 'logs' directory
* Set its location here, or set it to false (or nothing) if you'd rather not log * Set its location here, or set it to false (or nothing) if you'd rather not log
*/ */

@ -22,6 +22,7 @@ define([
Feedback, LocalStore, Messages, nThen, Block, Hash) { Feedback, LocalStore, Messages, nThen, Block, Hash) {
var Exports = { var Exports = {
Cred: Cred, Cred: Cred,
Block: Block,
// this is depended on by non-customizable files // this is depended on by non-customizable files
// be careful when modifying login.js // be careful when modifying login.js
requiredBytes: 192, requiredBytes: 192,
@ -92,7 +93,7 @@ define([
}; };
var loginOptionsFromBlock = function (blockInfo) { var loginOptionsFromBlock = Exports.loginOptionsFromBlock = function (blockInfo) {
var opt = {}; var opt = {};
var parsed = Hash.getSecrets('pad', blockInfo.User_hash); var parsed = Hash.getSecrets('pad', blockInfo.User_hash);
opt.channelHex = parsed.channel; opt.channelHex = parsed.channel;
@ -102,7 +103,7 @@ define([
return opt; return opt;
}; };
var loadUserObject = function (opt, cb) { var loadUserObject = Exports.loadUserObject = function (opt, cb) {
var config = { var config = {
websocketURL: NetConfig.getWebsocketURL(), websocketURL: NetConfig.getWebsocketURL(),
channel: opt.channelHex, channel: opt.channelHex,

@ -91,7 +91,7 @@
} }
} }
.cp-support-form-container { .cp-support-form-container {
display: none !important; display: none;
} }
} }
button { button {

@ -20,6 +20,12 @@ html, body {
padding-top: 15px; padding-top: 15px;
} }
.pending {
border: 1px solid white;
.fa {
margin-right: 20px;
}
}
.success { .success {
border: 1px solid green; border: 1px solid green;
} }
@ -53,5 +59,9 @@ html, body {
background-color: @cp_alerts-danger-bg; background-color: @cp_alerts-danger-bg;
color: @cp_alerts-danger-text; color: @cp_alerts-danger-text;
} }
iframe {
display: none;
}
} }

@ -122,6 +122,7 @@ module.exports.create = function (Env, cb) {
// create a pin store // create a pin store
Store.create({ Store.create({
filePath: pinPath, filePath: pinPath,
archivePath: Env.paths.archive,
}, w(function (err, s) { }, w(function (err, s) {
if (err) { throw err; } if (err) { throw err; }
Env.pinStore = s; Env.pinStore = s;
@ -130,7 +131,7 @@ module.exports.create = function (Env, cb) {
// create a channel store // create a channel store
Store.create({ Store.create({
filePath: Env.paths.data, filePath: Env.paths.data,
archivepath: Env.paths.archive, archivePath: Env.paths.archive,
}, w(function (err, _store) { }, w(function (err, _store) {
if (err) { throw err; } if (err) { throw err; }
Env.msgStore = _store; // API used by rpc Env.msgStore = _store; // API used by rpc

@ -87,6 +87,7 @@ Logger.create = function (config, cb) {
Store.create({ Store.create({
filePath: config.logPath, filePath: config.logPath,
archivePath: config.archivePath,
}, function (err, store) { }, function (err, store) {
if (err) { if (err) {
throw err; throw err;

@ -63,6 +63,7 @@ const init = function (config, _cb) {
})); }));
Store.create({ Store.create({
filePath: config.pinPath, filePath: config.pinPath,
archivePath: config.archivePath,
}, w(function (err, _pinStore) { }, w(function (err, _pinStore) {
if (err) { if (err) {
w.abort(); w.abort();

@ -8,14 +8,14 @@ var config = require("../../lib/load-config");
// but the API requires it, and I don't feel like changing that // but the API requires it, and I don't feel like changing that
// --ansuz // --ansuz
var FileStorage = require("../../lib/storage/file"); var FileStorage = require("../../lib/storage/file");
var tasks; var tasks;
nThen(function (w) { nThen(function (w) {
Logger.create(config, w(function (_log) { Logger.create(config, w(function (_log) {
config.log = _log; config.log = _log;
})); }));
}).nThen(function (w) { }).nThen(function (w) {
FileStorage.create(config, w(function (_store) { FileStorage.create(config, w(function (err, _store) {
if (err) { throw err; }
config.store = _store; config.store = _store;
})); }));
}).nThen(function (w) { }).nThen(function (w) {

@ -107,6 +107,9 @@ var setHeaders = (function () {
"Cross-Origin-Embedder-Policy": 'require-corp', "Cross-Origin-Embedder-Policy": 'require-corp',
}); });
// Don't set CSP headers on /api/config because they aren't necessary and they cause problems
// when duplicated by NGINX in production environments
if (/^\/api\/config/.test(req.url)) { return; }
// targeted CSP, generic policies, maybe custom headers // targeted CSP, generic policies, maybe custom headers
const h = [ const h = [
/^\/common\/onlyoffice\/.*\/index\.html.*/, /^\/common\/onlyoffice\/.*\/index\.html.*/,

@ -107,6 +107,16 @@
background-color: @cp_admin-premium-bg; background-color: @cp_admin-premium-bg;
} }
} }
&.cp-support-list-closed {
.cp-support-list-actions {
.cp-support-answer {
display: inline !important;
}
}
.cp-support-form-container {
display: block !important;
}
}
} }
.cp-support-list-ticket:not(.cp-support-list-closed) { .cp-support-list-ticket:not(.cp-support-list-closed) {

@ -840,6 +840,7 @@ define([
return; return;
} }
if (msg.type !== 'TICKET') { return; } if (msg.type !== 'TICKET') { return; }
$ticket.removeClass('cp-support-list-closed');
if (!$ticket.length) { if (!$ticket.length) {
$ticket = APP.support.makeTicket($div, content, function (hideButton) { $ticket = APP.support.makeTicket($div, content, function (hideButton) {

@ -21,8 +21,10 @@ define([], function () {
}); });
}; };
assert.run = function (cb) { assert.run = function (cb, progress) {
progress = progress || function () {};
var count = ASSERTS.length; var count = ASSERTS.length;
var total = ASSERTS.length;
var done = function (err) { var done = function (err) {
count--; count--;
if (err) { failMessages.push(err); } if (err) { failMessages.push(err); }
@ -38,6 +40,7 @@ define([], function () {
ASSERTS.forEach(function (f, index) { ASSERTS.forEach(function (f, index) {
f(function (err) { f(function (err) {
//console.log("test " + index); //console.log("test " + index);
progress(index, total);
done(err, index); done(err, index);
}, index); }, index);
}); });

@ -6,4 +6,6 @@
<script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script> <script data-bootload="main.js" data-main="/common/boot.js" src="/bower_components/requirejs/require.js"></script>
</head> </head>
<body> <body>
<div id="cp-progress"></div>
<iframe-placeholder>

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html class="cp-app-noscroll cp-app-print">
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script async data-bootload="/checkup/inner.js" data-main="/common/sframe-boot.js?ver=1.7" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
</head>
<body class="cp-app-checkup">
</body>
</html>

@ -0,0 +1,4 @@
define([
], function () {
console.log('inner loaded');
});

@ -4,12 +4,21 @@ define([
'/assert/assertions.js', '/assert/assertions.js',
'/common/hyperscript.js', '/common/hyperscript.js',
'/customize/messages.js', '/customize/messages.js',
'/common/dom-ready.js',
'/bower_components/nthen/index.js',
'/common/sframe-common-outer.js', '/common/sframe-common-outer.js',
'/customize/login.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/pinpad.js',
'/common/outer/network-config.js',
'/bower_components/tweetnacl/nacl-fast.min.js', '/bower_components/tweetnacl/nacl-fast.min.js',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/pages/page-checkup.less', 'less!/customize/src/less2/pages/page-checkup.less',
], function ($, ApiConfig, Assertions, h, Messages /*, SFCommonO*/) { ], function ($, ApiConfig, Assertions, h, Messages, DomReady,
nThen, SFCommonO, Login, Hash, Util, Pinpad,
NetConfig) {
var assert = Assertions(); var assert = Assertions();
var trimSlashes = function (s) { var trimSlashes = function (s) {
@ -41,7 +50,7 @@ define([
var checkAvailability = function (url, cb) { var checkAvailability = function (url, cb) {
$.ajax({ $.ajax({
url: url, url: url,
date: {}, data: {},
complete: function (xhr) { complete: function (xhr) {
cb(xhr.status === 200); cb(xhr.status === 200);
}, },
@ -52,10 +61,141 @@ define([
checkAvailability(trimmedUnsafe, cb); checkAvailability(trimmedUnsafe, cb);
}, _alert("Main domain is not available")); }, _alert("Main domain is not available"));
// Try loading an iframe on the safe domain
assert(function (cb) {
var to;
nThen(function (waitFor) {
DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
to = setTimeout(function () {
console.error('TIMEOUT loading iframe on the safe domain');
cb(false);
}, 5000);
SFCommonO.initIframe(waitFor);
}).nThen(function () {
// Iframe is loaded
clearTimeout(to);
cb(true);
});
}, _alert("Sandbox domain is not available"));
// Test Websocket
var evWSError = Util.mkEvent(true);
assert(function (cb) { assert(function (cb) {
console.log(trimmedSafe); var ws = new WebSocket(NetConfig.getWebsocketURL());
checkAvailability(trimmedSafe, cb); var to = setTimeout(function () {
}, _alert("Sandbox domain is not available")); // FIXME Blocked by CSP. try loading it via sframe ? console.error('Websocket TIMEOUT');
evWSError.fire();
cb('TIMEOUT (5 seconds)');
}, 5000);
ws.onopen = function () {
clearTimeout(to);
cb(true);
};
ws.onerror = function (err) {
clearTimeout(to);
console.error('Websocket error', err);
evWSError.fire();
cb('WebSocket error: check your console');
};
}, _alert("Websocket is not available"));
// Test login block
assert(function (cb) {
var bytes = new Uint8Array(Login.requiredBytes);
var opt = Login.allocateBytes(bytes);
var blockUrl = Login.Block.getBlockUrl(opt.blockKeys);
var blockRequest = Login.Block.serialize("{}", opt.blockKeys);
var removeRequest = Login.Block.remove(opt.blockKeys);
console.log('Test block URL:', blockUrl);
var userHash = '/2/drive/edit/000000000000000000000000';
var secret = Hash.getSecrets('drive', userHash);
opt.keys = secret.keys;
opt.channelHex = secret.channel;
var RT, rpc, exists;
nThen(function (waitFor) {
Util.fetch(blockUrl, waitFor(function (err) {
if (err) { return; } // No block found
exists = true;
}));
}).nThen(function (waitFor) {
// If WebSockets aren't working, don't wait forever here
evWSError.reg(function () {
waitFor.abort();
cb("No WebSocket (test number 6)");
});
// Create proxy
Login.loadUserObject(opt, waitFor(function (err, rt) {
if (err) {
waitFor.abort();
console.error("Can't create new channel. This may also be a websocket issue.");
return void cb(false);
}
RT = rt;
var proxy = rt.proxy;
proxy.edPublic = opt.edPublic;
proxy.edPrivate = opt.edPrivate;
proxy.curvePublic = opt.curvePublic;
proxy.curvePrivate = opt.curvePrivate;
rt.realtime.onSettle(waitFor());
}));
}).nThen(function (waitFor) {
// Init RPC
Pinpad.create(RT.network, RT.proxy, waitFor(function (e, _rpc) {
if (e) {
waitFor.abort();
console.error("Can't initialize RPC", e); // INVALID_KEYS
return void cb(false);
}
rpc = _rpc;
}));
}).nThen(function (waitFor) {
// Write block
if (exists) { return; }
rpc.writeLoginBlock(blockRequest, waitFor(function (e) {
if (e) {
waitFor.abort();
console.error("Can't write login block", e);
return void cb(false);
}
}));
}).nThen(function (waitFor) {
// Read block
Util.fetch(blockUrl, waitFor(function (e) {
if (e) {
waitFor.abort();
console.error("Can't read login block", e);
return void cb(false);
}
}));
}).nThen(function (waitFor) {
// Remove block
rpc.removeLoginBlock(removeRequest, waitFor(function (e) {
if (e) {
waitFor.abort();
console.error("Can't remove login block", e);
console.error(blockRequest);
return void cb(false);
}
}));
}).nThen(function (waitFor) {
rpc.removeOwnedChannel(secret.channel, waitFor(function (e) {
if (e) {
waitFor.abort();
console.error("Can't remove channel", e);
return void cb(false);
}
}));
}).nThen(function () {
cb(true);
});
}, _alert("Login block is not working (write/read/remove)"));
var row = function (cells) { var row = function (cells) {
return h('tr', cells.map(function (cell) { return h('tr', cells.map(function (cell) {
@ -73,6 +213,8 @@ define([
]); ]);
}; };
var completed = 0;
var $progress = $('#cp-progress');
assert.run(function (state) { assert.run(function (state) {
var errors = state.errors; var errors = state.errors;
var failed = errors.length; var failed = errors.length;
@ -94,6 +236,17 @@ define([
h('div.failures', errors.map(failureReport)), h('div.failures', errors.map(failureReport)),
]); ]);
$progress.remove();
$('body').prepend(report); $('body').prepend(report);
}, function (i, total) {
console.log('test '+ i +' completed');
completed++;
Messages.assert_numberOfTestsCompleted = "{0} / {1} tests completed.";
$progress.html('').append(h('div.report.pending.summary', [
h('p', [
h('i.fa.fa-spinner.fa-pulse'),
h('span', Messages._getKey('assert_numberOfTestsCompleted', [completed, total]))
])
]));
}); });
}); });

@ -836,12 +836,7 @@ define([
.text(Messages.propertiesButton)) .text(Messages.propertiesButton))
.click(common.prepareFeedback(type)) .click(common.prepareFeedback(type))
.click(function () { .click(function () {
common.isPadStored(function (err, data) { sframeChan.event('EV_PROPERTIES_OPEN');
if (!data) {
return void UI.alert(Messages.autostore_notAvailable);
}
sframeChan.event('EV_PROPERTIES_OPEN');
});
}); });
break; break;
case 'save': // OnlyOffice save case 'save': // OnlyOffice save
@ -1092,36 +1087,36 @@ define([
return e; return e;
}; };
UIElements.createHelpMenu = function (common, categories) { UIElements.createHelpMenu = function (common /*, categories */) {
var type = common.getMetadataMgr().getMetadata().type || 'pad'; var type = common.getMetadataMgr().getMetadata().type || 'pad';
var elements = [];
if (Messages.help && Messages.help.generic) { var apps = {
Object.keys(Messages.help.generic).forEach(function (el) { pad: 'richtext',
elements.push(setHTML(h('li'), Messages.help.generic[el])); code: 'code',
}); slide: 'slides',
} sheet: 'sheets',
if (categories) { poll: 'poll',
categories.forEach(function (cat) { kanban: 'kanban',
var msgs = Messages.help[cat]; whiteboard: 'whiteboard',
if (msgs) { };
Object.keys(msgs).forEach(function (el) {
elements.push(setHTML(h('li'), msgs[el])); var href = "https://docs.cryptpad.fr/en/user_guide/applications.html";
}); if (apps[type]) {
} href = "https://docs.cryptpad.fr/en/user_guide/apps/" + apps[type] + ".html";
});
} }
var content = setHTML(h('p'), Messages.help.generic.more);
$(content).find('a').attr('href', href);
var text = h('p.cp-help-text', [ var text = h('p.cp-help-text', [
h('h1', Messages.help.title), content
h('ul', elements)
]); ]);
common.fixLinks(text); common.fixLinks(text);
var closeButton = h('span.cp-help-close.fa.fa-times'); var closeButton = h('span.cp-help-close.fa.fa-times');
var $toolbarButton = common.createButton('', true, { var $toolbarButton = common.createButton('', true, {
title: Messages.hide_help_button,
text: Messages.help_button, text: Messages.help_button,
name: 'help' name: 'help'
}).addClass('cp-toolbar-button-active'); }).addClass('cp-toolbar-button-active');
@ -1130,45 +1125,25 @@ define([
text text
]); ]);
var toggleHelp = function (forceClose) { $toolbarButton.attr('title', Messages.show_help_button);
if ($(help).hasClass('cp-help-hidden')) {
if (forceClose) { return; } var toggleHelp = function () {
common.setAttribute(['hideHelp', type], false);
$toolbarButton.addClass('cp-toolbar-button-active');
$toolbarButton.attr('title', Messages.hide_help_button);
return void $(help).removeClass('cp-help-hidden');
}
$toolbarButton.removeClass('cp-toolbar-button-active'); $toolbarButton.removeClass('cp-toolbar-button-active');
$toolbarButton.attr('title', Messages.show_help_button);
$(help).addClass('cp-help-hidden'); $(help).addClass('cp-help-hidden');
common.setAttribute(['hideHelp', type], true); common.setAttribute(['hideHelp', type], true);
}; };
var showMore = function () {
$(text).addClass("cp-help-small");
var $dot = $('<span>').text('...').appendTo($(text).find('h1'));
$(text).click(function () {
$(text).removeClass('cp-help-small');
$(text).off('click');
$dot.remove();
});
};
$(closeButton).click(function (e) { $(closeButton).click(function (e) {
e.stopPropagation(); e.stopPropagation();
toggleHelp(true); toggleHelp(true);
}); });
$toolbarButton.click(function () { $toolbarButton.click(function () {
toggleHelp(); common.openUnsafeURL(href);
}); });
common.getAttribute(['hideHelp', type], function (err, val) { common.getAttribute(['hideHelp', type], function (err, val) {
//if ($(window).height() < 800 || $(window).width() < 800) { return void toggleHelp(true); } if (val === true || $(window).height() < 800 || $(window).width() < 800) {
if (val === true) { return void toggleHelp(true); } toggleHelp(true);
// Note: Help is always hidden by default now, to avoid displaying to many things in the UI
// This is why we have (true || ...)
if (!val && (true || $(window).height() < 800 || $(window).width() < 800)) {
return void showMore();
} }
}); });
@ -1724,21 +1699,7 @@ define([
}, },
}); });
} }
/*
if (AppConfig.surveyURL) {
options.push({
tag: 'a',
attributes: {
'class': 'cp-toolbar-survey fa fa-graduation-cap'
},
content: h('span', Messages.survey),
action: function () {
Common.openUnsafeURL(AppConfig.surveyURL);
Feedback.send('SURVEY_CLICKED');
},
});
}
*/
options.push({ options.push({
tag: 'a', tag: 'a',
attributes: { attributes: {
@ -1795,6 +1756,20 @@ define([
}); });
} }
if (AppConfig.surveyURL) {
options.push({
tag: 'a',
attributes: {
'class': 'cp-toolbar-survey fa fa-graduation-cap'
},
content: h('span', Messages.survey),
action: function () {
Common.openUnsafeURL(AppConfig.surveyURL);
Feedback.send('SURVEY_CLICKED');
},
});
}
options.push({ tag: 'hr' }); options.push({ tag: 'hr' });
// Add login or logout button depending on the current status // Add login or logout button depending on the current status
if (priv.loggedIn) { if (priv.loggedIn) {

@ -269,9 +269,8 @@
Util.magnitudeOfBytes = function (bytes) { Util.magnitudeOfBytes = function (bytes) {
if (bytes >= oneGigabyte) { return 'GB'; } if (bytes >= oneGigabyte) { return 'GB'; }
// smallest supported format is MB to preserve existing behaviour else if (bytes >= oneMegabyte) { return 'MB'; }
else /* if (bytes >= oneMegabyte) */ { return 'MB'; } else { return 'KB'; }
//else { return 'KB'; }
}; };
// given a path, asynchronously return an arraybuffer // given a path, asynchronously return an arraybuffer

@ -2308,6 +2308,7 @@ define([
var channelIsReady = waitFor(); var channelIsReady = waitFor();
updateLocalVersion();
var msgEv = Util.mkEvent(); var msgEv = Util.mkEvent();
var postMsg, worker; var postMsg, worker;
@ -2541,7 +2542,6 @@ define([
AppConfig.afterLogin(common, waitFor()); AppConfig.afterLogin(common, waitFor());
} }
}).nThen(function () { }).nThen(function () {
updateLocalVersion();
f(void 0, env); f(void 0, env);
if (typeof(window.onhashchange) === 'function') { window.onhashchange(); } if (typeof(window.onhashchange) === 'function') { window.onhashchange(); }
}); });

@ -18,6 +18,16 @@ define([
opts = opts || {}; opts = opts || {};
var $d = $('<div>'); var $d = $('<div>');
if (!data) { return void cb(void 0, $d); } if (!data) { return void cb(void 0, $d); }
data = Util.clone(data);
var privateData = common.getMetadataMgr().getPrivateData();
if (privateData.propChannels) {
var p = privateData.propChannels;
data.channel = data.channel || p.channel;
data.rtChannel = data.rtChannel || p.rtChannel;
data.lastVersion = data.lastVersion || p.lastVersion;
data.lastCpHash = data.lastCpHash || p.lastCpHash;
}
if (data.channel) { if (data.channel) {
$('<label>', { 'for': 'cp-app-prop-id'}).text(Messages.documentID).appendTo($d); $('<label>', { 'for': 'cp-app-prop-id'}).text(Messages.documentID).appendTo($d);
@ -26,18 +36,20 @@ define([
})); }));
} }
if (data.href) { if (!data.fakeHref) {
$('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d); if (data.href) {
$d.append(UI.dialog.selectable(data.href, { $('<label>', {'for': 'cp-app-prop-link'}).text(Messages.editShare).appendTo($d);
id: 'cp-app-prop-link', $d.append(UI.dialog.selectable(data.href, {
})); id: 'cp-app-prop-link',
} }));
}
if (data.roHref && !opts.noReadOnly) { if (data.roHref && !opts.noReadOnly) {
$('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d); $('<label>', {'for': 'cp-app-prop-rolink'}).text(Messages.viewShare).appendTo($d);
$d.append(UI.dialog.selectable(data.roHref, { $d.append(UI.dialog.selectable(data.roHref, {
id: 'cp-app-prop-rolink', id: 'cp-app-prop-rolink',
})); }));
}
} }
if (data.tags && Array.isArray(data.tags)) { if (data.tags && Array.isArray(data.tags)) {
@ -54,7 +66,6 @@ define([
if (!common.isLoggedIn()) { return void cb(void 0, $d); } if (!common.isLoggedIn()) { return void cb(void 0, $d); }
var privateData = common.getMetadataMgr().getPrivateData();
if (privateData.offline) { return void cb(void 0, $d); } if (privateData.offline) { return void cb(void 0, $d); }
// File and history size... // File and history size...
@ -179,6 +190,7 @@ define([
Properties.getPropertiesModal = function (common, opts, cb) { Properties.getPropertiesModal = function (common, opts, cb) {
cb = cb || function () {}; cb = cb || function () {};
opts = opts || {}; opts = opts || {};
opts.access = true;
var tabs = [{ var tabs = [{
getTab: getPadProperties, getTab: getPadProperties,
title: Messages.fc_prop, title: Messages.fc_prop,

@ -26,6 +26,10 @@ define([
obj.ooVersionHash = version; obj.ooVersionHash = version;
obj.ooForceVersion = localStorage.CryptPad_ooVersion || ""; obj.ooForceVersion = localStorage.CryptPad_ooVersion || "";
}; };
var channels = {};
var getPropChannels = function () {
return channels;
};
var addRpc = function (sframeChan, Cryptpad, Utils) { var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('Q_OO_SAVE', function (data, cb) { sframeChan.on('Q_OO_SAVE', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data.url); var chanId = Utils.Hash.hrefToHexChannelId(data.url);
@ -38,14 +42,17 @@ define([
Cryptpad.pinPads([chanId], function (e) { Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
Cryptpad.setPadAttribute('lastVersion', data.url, cb); Cryptpad.setPadAttribute('lastVersion', data.url, cb);
channels.lastVersion = data.url;
}); });
Cryptpad.setPadAttribute('lastCpHash', data.hash, cb); Cryptpad.setPadAttribute('lastCpHash', data.hash, cb);
channels.lastCpHash = data.hash;
}); });
sframeChan.on('Q_OO_OPENCHANNEL', function (data, cb) { sframeChan.on('Q_OO_OPENCHANNEL', function (data, cb) {
Cryptpad.getPadAttribute('rtChannel', function (err, res) { Cryptpad.getPadAttribute('rtChannel', function (err, res) {
// If already stored, don't pin it again // If already stored, don't pin it again
if (res && res === data.channel) { return; } if (res && res === data.channel) { return; }
Cryptpad.pinPads([data.channel], function () { Cryptpad.pinPads([data.channel], function () {
channels.rtChannel = data.channel;
Cryptpad.setPadAttribute('rtChannel', data.channel, function () {}); Cryptpad.setPadAttribute('rtChannel', data.channel, function () {});
}); });
}); });
@ -144,6 +151,7 @@ define([
useCreationScreen: true, useCreationScreen: true,
addData: addData, addData: addData,
addRpc: addRpc, addRpc: addRpc,
getPropChannels: getPropChannels,
messaging: true messaging: true
}); });
}); });

@ -1150,18 +1150,10 @@ define([
obj.userObject.setHref(channel, null, href); obj.userObject.setHref(channel, null, href);
}); });
// Pads owned by us ("us" can be a user or a team) that are not in our "main" drive
// (meaning they are stored in a shared folder) must be added to the "main" drive.
// This is to make sure owners always have control over owned data.
var edPublic = data.teamId ?
Util.find(store.proxy, ['teams', data.teamId, 'keys', 'edPublic']) :
store.proxy.edPublic;
var ownedByMe = Array.isArray(owners) && owners.indexOf(edPublic) !== -1;
// Add the pad if it does not exist in our drive // Add the pad if it does not exist in our drive
if (!contains) { // || (ownedByMe && !inMyDrive)) { if (!contains) { // || (ownedByMe && !inMyDrive)) {
var autoStore = Util.find(store.proxy, ['settings', 'general', 'autostore']); var autoStore = Util.find(store.proxy, ['settings', 'general', 'autostore']);
if (autoStore !== 1 && !data.forceSave && !data.path && !ownedByMe) { if (autoStore !== 1 && !data.forceSave && !data.path) {
// send event to inner to display the corner popup // send event to inner to display the corner popup
postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", { postMessage(clientId, "AUTOSTORE_DISPLAY_POPUP", {
autoStore: autoStore autoStore: autoStore
@ -2644,6 +2636,7 @@ define([
progress: 0 progress: 0
}); });
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (typeof(proxy.version) === "undefined") { proxy.version = 11; }
Migrate(proxy, waitFor(), function (version, progress) { Migrate(proxy, waitFor(), function (version, progress) {
postMessage(clientId, 'LOADING_DRIVE', { postMessage(clientId, 'LOADING_DRIVE', {
type: 'migrate', type: 'migrate',
@ -2691,7 +2684,8 @@ define([
// every user object should have a persistent, random number // every user object should have a persistent, random number
if (typeof(proxy.loginToken) !== 'number') { if (typeof(proxy.loginToken) !== 'number') {
proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); proxy[Constants.tokenKey] = store.data.localToken ||
Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
} }
returned[Constants.tokenKey] = proxy[Constants.tokenKey]; returned[Constants.tokenKey] = proxy[Constants.tokenKey];
@ -2851,12 +2845,15 @@ define([
if (store.ready) { return; } // the store is already ready, it is a reconnection if (store.ready) { return; } // the store is already ready, it is a reconnection
store.driveMetadata = info.metadata; store.driveMetadata = info.metadata;
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
/*
// deprecating localStorage migration as of 4.2.0
var drive = rt.proxy.drive; var drive = rt.proxy.drive;
// Creating a new anon drive: import anon pads from localStorage // Creating a new anon drive: import anon pads from localStorage
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey])) if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
&& !drive['filesData']) { && !drive['filesData']) {
drive[Constants.oldStorageKey] = []; drive[Constants.oldStorageKey] = [];
} }
*/
// Drive already exist: return the existing drive, don't load data from legacy store // Drive already exist: return the existing drive, don't load data from legacy store
if (store.manager) { if (store.manager) {
// If a cache is loading, make sure it is complete before calling onReady // If a cache is loading, make sure it is complete before calling onReady

@ -1378,6 +1378,14 @@ define([
// Secure modal // Secure modal
var SecureModal = {}; var SecureModal = {};
// Create or display the iframe and modal // Create or display the iframe and modal
var getPropChannels = function () {
var channels = {};
if (cfg.getPropChannels) {
channels = Utils.Util.clone(cfg.getPropChannels());
}
channels.channel = secret.channel;
return channels;
};
var initSecureModal = function (type, cfg, cb) { var initSecureModal = function (type, cfg, cb) {
cfg.modal = type; cfg.modal = type;
SecureModal.cb = cb; SecureModal.cb = cb;
@ -1397,7 +1405,8 @@ define([
app: parsed.type, app: parsed.type,
hashes: hashes, hashes: hashes,
password: password, password: password,
isTemplate: isTemplate isTemplate: isTemplate,
getPropChannels: getPropChannels
}; };
config.addCommonRpc = addCommonRpc; config.addCommonRpc = addCommonRpc;
config.modules = { config.modules = {

@ -33,7 +33,7 @@ define([
var metadataMgr = common.getMetadataMgr(); var metadataMgr = common.getMetadataMgr();
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var $body = $('body'); var $body = $('body');
var displayed; var displayed;
var hideIframe = function () { var hideIframe = function () {
if (!displayed) { return; } if (!displayed) { return; }

@ -103,6 +103,7 @@ define([
feedbackAllowed: Utils.Feedback.state, feedbackAllowed: Utils.Feedback.state,
hashes: config.data.hashes, hashes: config.data.hashes,
password: config.data.password, password: config.data.password,
propChannels: config.data.getPropChannels(),
isTemplate: isTemplate, isTemplate: isTemplate,
file: config.data.file, file: config.data.file,
secureIframe: true, secureIframe: true,

@ -119,6 +119,7 @@ define([
return; return;
} }
if (msg.type !== 'TICKET') { return; } if (msg.type !== 'TICKET') { return; }
$ticket.removeClass('cp-support-list-closed');
if (!$ticket.length) { if (!$ticket.length) {
$ticket = APP.support.makeTicket($div, content, function () { $ticket = APP.support.makeTicket($div, content, function () {

@ -52,6 +52,11 @@ define([
}); });
return ret; return ret;
}).filter(Boolean); }).filter(Boolean);
// "dest" is the recipient that is not the admin support mailbox.
// In the support page, make sure dest is always ourselves.
dest.channel = privateData.support;
dest.curvePublic = user.curvePublic;
} }
// Send the message to the admin mailbox and to the user mailbox // Send the message to the admin mailbox and to the user mailbox

Loading…
Cancel
Save