mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-23 00:46:16 -04:00
PadMessageHandler: Allow handleMessageSecurity
to grant one-time write access
This commit is contained in:
parent
31b025bd9d
commit
02a56dc58c
5 changed files with 94 additions and 35 deletions
|
@ -172,14 +172,14 @@ exports.connect = async (res = null) => {
|
|||
* @param {string} padId - Which pad to join.
|
||||
* @returns The CLIENT_VARS message from the server.
|
||||
*/
|
||||
exports.handshake = async (socket, padId) => {
|
||||
exports.handshake = async (socket, padId, token = 't.12345') => {
|
||||
logger.debug('sending CLIENT_READY...');
|
||||
socket.send({
|
||||
component: 'pad',
|
||||
type: 'CLIENT_READY',
|
||||
padId,
|
||||
sessionID: null,
|
||||
token: 't.12345',
|
||||
token,
|
||||
});
|
||||
logger.debug('waiting for CLIENT_VARS response...');
|
||||
const msg = await exports.waitForSocketEvent(socket, 'message');
|
||||
|
|
|
@ -1,103 +1,141 @@
|
|||
'use strict';
|
||||
|
||||
const AttributePool = require('../../../static/js/AttributePool');
|
||||
const assert = require('assert').strict;
|
||||
const common = require('../common');
|
||||
const padManager = require('../../../node/db/PadManager');
|
||||
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
||||
const readOnlyManager = require('../../../node/db/ReadOnlyManager');
|
||||
|
||||
describe(__filename, function () {
|
||||
let agent;
|
||||
let pad;
|
||||
let padId;
|
||||
let roPadId;
|
||||
let rev;
|
||||
let socket;
|
||||
let roSocket;
|
||||
const backups = {};
|
||||
|
||||
before(async function () {
|
||||
agent = await common.init();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
backups.hooks = {handleMessageSecurity: plugins.hooks.handleMessageSecurity};
|
||||
plugins.hooks.handleMessageSecurity = [];
|
||||
padId = common.randomString();
|
||||
assert(!await padManager.doesPadExist(padId));
|
||||
pad = await padManager.getPad(padId, '');
|
||||
pad = await padManager.getPad(padId, 'dummy text');
|
||||
await pad.setText('\n'); // Make sure the pad is created.
|
||||
assert.equal(pad.text(), '\n');
|
||||
const res = await agent.get(`/p/${padId}`).expect(200);
|
||||
let res = await agent.get(`/p/${padId}`).expect(200);
|
||||
socket = await common.connect(res);
|
||||
const {type, data: clientVars} = await common.handshake(socket, padId);
|
||||
assert.equal(type, 'CLIENT_VARS');
|
||||
rev = clientVars.collab_client_vars.rev;
|
||||
|
||||
roPadId = await readOnlyManager.getReadOnlyId(padId);
|
||||
res = await agent.get(`/p/${roPadId}`).expect(200);
|
||||
roSocket = await common.connect(res);
|
||||
await common.handshake(roSocket, roPadId, `t.${common.randomString(8)}`);
|
||||
});
|
||||
|
||||
afterEach(async function () {
|
||||
Object.assign(plugins.hooks, backups.hooks);
|
||||
if (socket != null) socket.close();
|
||||
socket = null;
|
||||
if (roSocket != null) roSocket.close();
|
||||
roSocket = null;
|
||||
if (pad != null) await pad.remove();
|
||||
pad = null;
|
||||
});
|
||||
|
||||
describe('USER_CHANGES', function () {
|
||||
const sendUserChanges =
|
||||
async (changeset) => await common.sendUserChanges(socket, {baseRev: rev, changeset});
|
||||
const assertAccepted = async (wantRev) => {
|
||||
async (socket, cs) => await common.sendUserChanges(socket, {baseRev: rev, changeset: cs});
|
||||
const assertAccepted = async (socket, wantRev) => {
|
||||
await common.waitForAcceptCommit(socket, wantRev);
|
||||
rev = wantRev;
|
||||
};
|
||||
const assertRejected = async () => {
|
||||
const assertRejected = async (socket) => {
|
||||
const msg = await common.waitForSocketEvent(socket, 'message');
|
||||
assert.deepEqual(msg, {disconnect: 'badChangeset'});
|
||||
};
|
||||
|
||||
it('changes are applied', async function () {
|
||||
await Promise.all([
|
||||
assertAccepted(rev + 1),
|
||||
sendUserChanges('Z:1>5+5$hello'),
|
||||
assertAccepted(socket, rev + 1),
|
||||
sendUserChanges(socket, 'Z:1>5+5$hello'),
|
||||
]);
|
||||
assert.equal(pad.text(), 'hello\n');
|
||||
});
|
||||
|
||||
it('bad changeset is rejected', async function () {
|
||||
await Promise.all([
|
||||
assertRejected(),
|
||||
sendUserChanges('this is not a valid changeset'),
|
||||
assertRejected(socket),
|
||||
sendUserChanges(socket, 'this is not a valid changeset'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('retransmission is accepted, has no effect', async function () {
|
||||
const cs = 'Z:1>5+5$hello';
|
||||
await Promise.all([
|
||||
assertAccepted(rev + 1),
|
||||
sendUserChanges(cs),
|
||||
assertAccepted(socket, rev + 1),
|
||||
sendUserChanges(socket, cs),
|
||||
]);
|
||||
--rev;
|
||||
await Promise.all([
|
||||
assertAccepted(rev + 1),
|
||||
sendUserChanges(cs),
|
||||
assertAccepted(socket, rev + 1),
|
||||
sendUserChanges(socket, cs),
|
||||
]);
|
||||
assert.equal(pad.text(), 'hello\n');
|
||||
});
|
||||
|
||||
it('identity changeset is accepted, has no effect', async function () {
|
||||
await Promise.all([
|
||||
assertAccepted(rev + 1),
|
||||
sendUserChanges('Z:1>5+5$hello'),
|
||||
assertAccepted(socket, rev + 1),
|
||||
sendUserChanges(socket, 'Z:1>5+5$hello'),
|
||||
]);
|
||||
await Promise.all([
|
||||
assertAccepted(rev),
|
||||
sendUserChanges('Z:6>0$'),
|
||||
assertAccepted(socket, rev),
|
||||
sendUserChanges(socket, 'Z:6>0$'),
|
||||
]);
|
||||
assert.equal(pad.text(), 'hello\n');
|
||||
});
|
||||
|
||||
it('non-identity changeset with no net change is accepted, has no effect', async function () {
|
||||
await Promise.all([
|
||||
assertAccepted(rev + 1),
|
||||
sendUserChanges('Z:1>5+5$hello'),
|
||||
assertAccepted(socket, rev + 1),
|
||||
sendUserChanges(socket, 'Z:1>5+5$hello'),
|
||||
]);
|
||||
await Promise.all([
|
||||
assertAccepted(rev),
|
||||
sendUserChanges('Z:6>0-5+5$hello'),
|
||||
assertAccepted(socket, rev),
|
||||
sendUserChanges(socket, 'Z:6>0-5+5$hello'),
|
||||
]);
|
||||
assert.equal(pad.text(), 'hello\n');
|
||||
});
|
||||
|
||||
it('handleMessageSecurity can grant one-time write access', async function () {
|
||||
const cs = 'Z:1>5+5$hello';
|
||||
// First try to send a change and verify that it was dropped.
|
||||
await sendUserChanges(roSocket, cs);
|
||||
// sendUserChanges() waits for message ack, so if the message was accepted then head should
|
||||
// have already incremented by the time we get here.
|
||||
assert.equal(pad.head, rev); // Not incremented.
|
||||
|
||||
// Now allow the change.
|
||||
plugins.hooks.handleMessageSecurity.push({hook_fn: () => 'permitOnce'});
|
||||
await Promise.all([
|
||||
assertAccepted(roSocket, rev + 1),
|
||||
sendUserChanges(roSocket, cs),
|
||||
]);
|
||||
assert.equal(pad.text(), 'hello\n');
|
||||
|
||||
// The next change should be dropped.
|
||||
plugins.hooks.handleMessageSecurity = [];
|
||||
await sendUserChanges(roSocket, 'Z:6>6=5+6$ world');
|
||||
assert.equal(pad.head, rev); // Not incremented.
|
||||
assert.equal(pad.text(), 'hello\n');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue