mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-23 00:46:16 -04:00
Merge branch 'master' into develop
This commit is contained in:
commit
f00b1ae89b
14 changed files with 606 additions and 348 deletions
170
src/tests/backend/specs/ImportEtherpad.js
Normal file
170
src/tests/backend/specs/ImportEtherpad.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const authorManager = require('../../../node/db/AuthorManager');
|
||||
const db = require('../../../node/db/DB');
|
||||
const importEtherpad = require('../../../node/utils/ImportEtherpad');
|
||||
const padManager = require('../../../node/db/PadManager');
|
||||
const {randomString} = require('../../../static/js/pad_utils');
|
||||
|
||||
describe(__filename, function () {
|
||||
let padId;
|
||||
|
||||
const makeAuthorId = () => `a.${randomString(16)}`;
|
||||
|
||||
const makeExport = (authorId) => ({
|
||||
'pad:testing': {
|
||||
atext: {
|
||||
text: 'foo\n',
|
||||
attribs: '|1+4',
|
||||
},
|
||||
pool: {
|
||||
numToAttrib: {},
|
||||
nextNum: 0,
|
||||
},
|
||||
head: 0,
|
||||
savedRevisions: [],
|
||||
},
|
||||
[`globalAuthor:${authorId}`]: {
|
||||
colorId: '#000000',
|
||||
name: 'new',
|
||||
timestamp: 1598747784631,
|
||||
padIDs: 'testing',
|
||||
},
|
||||
'pad:testing:revs:0': {
|
||||
changeset: 'Z:1>3+3$foo',
|
||||
meta: {
|
||||
author: '',
|
||||
timestamp: 1597632398288,
|
||||
pool: {
|
||||
numToAttrib: {},
|
||||
nextNum: 0,
|
||||
},
|
||||
atext: {
|
||||
text: 'foo\n',
|
||||
attribs: '|1+4',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
padId = randomString(10);
|
||||
assert(!await padManager.doesPadExist(padId));
|
||||
});
|
||||
|
||||
it('unknown db records are ignored', async function () {
|
||||
const badKey = `maliciousDbKey${randomString(10)}`;
|
||||
await importEtherpad.setPadRaw(padId, JSON.stringify({
|
||||
[badKey]: 'value',
|
||||
...makeExport(makeAuthorId()),
|
||||
}));
|
||||
assert(await db.get(badKey) == null);
|
||||
});
|
||||
|
||||
it('changes are all or nothing', async function () {
|
||||
const authorId = makeAuthorId();
|
||||
const data = makeExport(authorId);
|
||||
data['pad:differentPadId:revs:0'] = data['pad:testing:revs:0'];
|
||||
delete data['pad:testing:revs:0'];
|
||||
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
|
||||
assert(!await authorManager.doesAuthorExist(authorId));
|
||||
assert(!await padManager.doesPadExist(padId));
|
||||
});
|
||||
|
||||
describe('author pad IDs', function () {
|
||||
let existingAuthorId;
|
||||
let newAuthorId;
|
||||
|
||||
beforeEach(async function () {
|
||||
existingAuthorId = (await authorManager.createAuthor('existing')).authorID;
|
||||
assert(await authorManager.doesAuthorExist(existingAuthorId));
|
||||
assert.deepEqual((await authorManager.listPadsOfAuthor(existingAuthorId)).padIDs, []);
|
||||
newAuthorId = makeAuthorId();
|
||||
assert.notEqual(newAuthorId, existingAuthorId);
|
||||
assert(!await authorManager.doesAuthorExist(newAuthorId));
|
||||
});
|
||||
|
||||
it('author does not yet exist', async function () {
|
||||
await importEtherpad.setPadRaw(padId, JSON.stringify(makeExport(newAuthorId)));
|
||||
assert(await authorManager.doesAuthorExist(newAuthorId));
|
||||
const author = await authorManager.getAuthor(newAuthorId);
|
||||
assert.equal(author.name, 'new');
|
||||
assert.equal(author.colorId, '#000000');
|
||||
assert.deepEqual((await authorManager.listPadsOfAuthor(newAuthorId)).padIDs, [padId]);
|
||||
});
|
||||
|
||||
it('author already exists, no pads', async function () {
|
||||
newAuthorId = existingAuthorId;
|
||||
await importEtherpad.setPadRaw(padId, JSON.stringify(makeExport(newAuthorId)));
|
||||
assert(await authorManager.doesAuthorExist(newAuthorId));
|
||||
const author = await authorManager.getAuthor(newAuthorId);
|
||||
assert.equal(author.name, 'existing');
|
||||
assert.notEqual(author.colorId, '#000000');
|
||||
assert.deepEqual((await authorManager.listPadsOfAuthor(newAuthorId)).padIDs, [padId]);
|
||||
});
|
||||
|
||||
it('author already exists, on different pad', async function () {
|
||||
const otherPadId = randomString(10);
|
||||
await authorManager.addPad(existingAuthorId, otherPadId);
|
||||
newAuthorId = existingAuthorId;
|
||||
await importEtherpad.setPadRaw(padId, JSON.stringify(makeExport(newAuthorId)));
|
||||
assert(await authorManager.doesAuthorExist(newAuthorId));
|
||||
const author = await authorManager.getAuthor(newAuthorId);
|
||||
assert.equal(author.name, 'existing');
|
||||
assert.notEqual(author.colorId, '#000000');
|
||||
assert.deepEqual(
|
||||
(await authorManager.listPadsOfAuthor(newAuthorId)).padIDs.sort(),
|
||||
[otherPadId, padId].sort());
|
||||
});
|
||||
|
||||
it('author already exists, on same pad', async function () {
|
||||
await authorManager.addPad(existingAuthorId, padId);
|
||||
newAuthorId = existingAuthorId;
|
||||
await importEtherpad.setPadRaw(padId, JSON.stringify(makeExport(newAuthorId)));
|
||||
assert(await authorManager.doesAuthorExist(newAuthorId));
|
||||
const author = await authorManager.getAuthor(newAuthorId);
|
||||
assert.equal(author.name, 'existing');
|
||||
assert.notEqual(author.colorId, '#000000');
|
||||
assert.deepEqual((await authorManager.listPadsOfAuthor(newAuthorId)).padIDs, [padId]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('enforces consistent pad ID', function () {
|
||||
it('pad record has different pad ID', async function () {
|
||||
const data = makeExport(makeAuthorId());
|
||||
data['pad:differentPadId'] = data['pad:testing'];
|
||||
delete data['pad:testing'];
|
||||
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
|
||||
});
|
||||
|
||||
it('globalAuthor record has different pad ID', async function () {
|
||||
const authorId = makeAuthorId();
|
||||
const data = makeExport(authorId);
|
||||
data[`globalAuthor:${authorId}`].padIDs = 'differentPadId';
|
||||
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
|
||||
});
|
||||
|
||||
it('pad rev record has different pad ID', async function () {
|
||||
const data = makeExport(makeAuthorId());
|
||||
data['pad:differentPadId:revs:0'] = data['pad:testing:revs:0'];
|
||||
delete data['pad:testing:revs:0'];
|
||||
assert.rejects(importEtherpad.setPadRaw(padId, JSON.stringify(data)), /unexpected pad ID/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('order of records does not matter', function () {
|
||||
for (const perm of [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]) {
|
||||
it(JSON.stringify(perm), async function () {
|
||||
const authorId = makeAuthorId();
|
||||
const records = Object.entries(makeExport(authorId));
|
||||
assert.equal(records.length, 3);
|
||||
await importEtherpad.setPadRaw(
|
||||
padId, JSON.stringify(Object.fromEntries(perm.map((i) => records[i]))));
|
||||
assert.deepEqual((await authorManager.listPadsOfAuthor(authorId)).padIDs, [padId]);
|
||||
const pad = await padManager.getPad(padId);
|
||||
assert.equal(pad.text(), 'foo\n');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -315,6 +315,118 @@ describe(__filename, function () {
|
|||
});
|
||||
});
|
||||
|
||||
describe('malformed .etherpad files are rejected', function () {
|
||||
const makeGoodExport = () => ({
|
||||
'pad:testing': {
|
||||
atext: {
|
||||
text: 'foo\n',
|
||||
attribs: '|1+4',
|
||||
},
|
||||
pool: {
|
||||
numToAttrib: {
|
||||
0: ['author', 'a.foo'],
|
||||
},
|
||||
nextNum: 1,
|
||||
},
|
||||
head: 0,
|
||||
savedRevisions: [],
|
||||
},
|
||||
'globalAuthor:a.foo': {
|
||||
colorId: '#000000',
|
||||
name: 'author foo',
|
||||
timestamp: 1598747784631,
|
||||
padIDs: 'testing',
|
||||
},
|
||||
'pad:testing:revs:0': {
|
||||
changeset: 'Z:1>3+3$foo',
|
||||
meta: {
|
||||
author: 'a.foo',
|
||||
timestamp: 1597632398288,
|
||||
pool: {
|
||||
numToAttrib: {},
|
||||
nextNum: 0,
|
||||
},
|
||||
atext: {
|
||||
text: 'foo\n',
|
||||
attribs: '|1+4',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const importEtherpad = (records) => agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', Buffer.from(JSON.stringify(records), 'utf8'), {
|
||||
filename: '/test.etherpad',
|
||||
contentType: 'application/etherpad',
|
||||
});
|
||||
|
||||
before(async function () {
|
||||
// makeGoodExport() is assumed to produce good .etherpad records. Verify that assumption so
|
||||
// that a buggy makeGoodExport() doesn't cause checks to accidentally pass.
|
||||
const records = makeGoodExport();
|
||||
await importEtherpad(records)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.deepEqual(res.body, {
|
||||
code: 0,
|
||||
message: 'ok',
|
||||
data: {directDatabaseAccess: true},
|
||||
}));
|
||||
await agent.get(`/p/${testPadId}/export/txt`)
|
||||
.expect(200)
|
||||
.buffer(true).parse(superagent.parse.text)
|
||||
.expect((res) => assert.match(res.text, /foo/));
|
||||
});
|
||||
|
||||
it('missing rev', async function () {
|
||||
const records = makeGoodExport();
|
||||
delete records['pad:testing:revs:0'];
|
||||
await importEtherpad(records).expect(500);
|
||||
});
|
||||
|
||||
it('bad changeset', async function () {
|
||||
const records = makeGoodExport();
|
||||
records['pad:testing:revs:0'].changeset = 'garbage';
|
||||
await importEtherpad(records).expect(500);
|
||||
});
|
||||
|
||||
it('missing attrib in pool', async function () {
|
||||
const records = makeGoodExport();
|
||||
records['pad:testing'].pool.nextNum++;
|
||||
await importEtherpad(records).expect(500);
|
||||
});
|
||||
|
||||
it('extra attrib in pool', async function () {
|
||||
const records = makeGoodExport();
|
||||
const pool = records['pad:testing'].pool;
|
||||
pool.numToAttrib[pool.nextNum] = ['key', 'value'];
|
||||
await importEtherpad(records).expect(500);
|
||||
});
|
||||
|
||||
it('changeset refers to non-existent attrib', async function () {
|
||||
const records = makeGoodExport();
|
||||
records['pad:testing:revs:1'] = {
|
||||
changeset: 'Z:4>4*1+4$asdf',
|
||||
meta: {
|
||||
author: 'a.foo',
|
||||
timestamp: 1597632398288,
|
||||
},
|
||||
};
|
||||
records['pad:testing'].head = 1;
|
||||
records['pad:testing'].atext = {
|
||||
text: 'asdffoo\n',
|
||||
attribs: '*1+4|1+4',
|
||||
};
|
||||
await importEtherpad(records).expect(500);
|
||||
});
|
||||
|
||||
it('pad atext does not match', async function () {
|
||||
const records = makeGoodExport();
|
||||
records['pad:testing'].atext.attribs = `*0${records['pad:testing'].atext.attribs}`;
|
||||
await importEtherpad(records).expect(500);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import authorization checks', function () {
|
||||
let authorize;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue