mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-28 03:16:16 -04:00
Merge branch 'develop' of github.com:ether/etherpad-lite into test-plugins
This commit is contained in:
commit
f823f48607
19 changed files with 542 additions and 402 deletions
|
@ -2,15 +2,14 @@
|
|||
* Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints.
|
||||
*/
|
||||
|
||||
const assert = require('assert');
|
||||
const assert = require('assert').strict;
|
||||
const superagent = require(__dirname+'/../../../../src/node_modules/superagent');
|
||||
const supertest = require(__dirname+'/../../../../src/node_modules/supertest');
|
||||
const fs = require('fs');
|
||||
const settings = require(__dirname+'/../../../../src/node/utils/Settings');
|
||||
const host = 'http://127.0.0.1:'+settings.port;
|
||||
const api = supertest('http://'+settings.ip+":"+settings.port);
|
||||
const agent = supertest(`http://${settings.ip}:${settings.port}`);
|
||||
const path = require('path');
|
||||
const async = require(__dirname+'/../../../../src/node_modules/async');
|
||||
const request = require(__dirname+'/../../../../src/node_modules/request');
|
||||
const padText = fs.readFileSync("../tests/backend/specs/api/test.txt");
|
||||
const etherpadDoc = fs.readFileSync("../tests/backend/specs/api/test.etherpad");
|
||||
const wordDoc = fs.readFileSync("../tests/backend/specs/api/test.doc");
|
||||
|
@ -23,26 +22,20 @@ var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
|||
apiKey = apiKey.replace(/\n$/, "");
|
||||
var apiVersion = 1;
|
||||
var testPadId = makeid();
|
||||
var lastEdited = "";
|
||||
var text = generateLongText();
|
||||
|
||||
describe('Connectivity', function(){
|
||||
it('can connect', function(done) {
|
||||
api.get('/api/')
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
it('can connect', async function() {
|
||||
await agent.get('/api/')
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/);
|
||||
});
|
||||
})
|
||||
|
||||
describe('API Versioning', function(){
|
||||
it('finds the version tag', function(done) {
|
||||
api.get('/api/')
|
||||
.expect(function(res){
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error("No version set in API");
|
||||
return;
|
||||
})
|
||||
.expect(200, done)
|
||||
it('finds the version tag', async function() {
|
||||
await agent.get('/api/')
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.currentVersion));
|
||||
});
|
||||
})
|
||||
|
||||
|
@ -73,289 +66,144 @@ Example Curl command for testing import URI:
|
|||
*/
|
||||
|
||||
describe('Imports and Exports', function(){
|
||||
it('creates a new Pad, imports content to it, checks that content', function(done) {
|
||||
if(!settings.allowAnyoneToImport){
|
||||
console.warn("not anyone can import so not testing -- to include this test set allowAnyoneToImport to true in settings.json");
|
||||
done();
|
||||
}else{
|
||||
api.get(endPoint('createPad')+"&padID="+testPadId)
|
||||
.expect(function(res){
|
||||
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
api.get(endPoint('getText')+"&padID="+testPadId)
|
||||
.expect(function(res){
|
||||
if(res.body.data.text !== padText.toString()){
|
||||
throw new Error("text is wrong on export");
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
|
||||
form.append('file', padText, {
|
||||
filename: '/test.txt',
|
||||
contentType: 'text/plain'
|
||||
});
|
||||
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
before(function() {
|
||||
if (!settings.allowAnyoneToImport) {
|
||||
console.warn('not anyone can import so not testing -- ' +
|
||||
'to include this test set allowAnyoneToImport to true in settings.json');
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
// For some reason word import does not work in testing..
|
||||
// TODO: fix support for .doc files..
|
||||
it('Tries to import .doc that uses soffice or abiword', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
|
||||
throw new Error("Failed DOC import", testPadId);
|
||||
}else{
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
form.append('file', wordDoc, {
|
||||
filename: '/test.doc',
|
||||
contentType: 'application/msword'
|
||||
});
|
||||
it('creates a new Pad, imports content to it, checks that content', async function() {
|
||||
await agent.get(endPoint('createPad') + `&padID=${testPadId}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/)
|
||||
.expect((res) => assert.equal(res.body.code, 0));
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(200);
|
||||
await agent.get(endPoint('getText') + `&padID=${testPadId}`)
|
||||
.expect(200)
|
||||
.expect((res) => assert.equal(res.body.data.text, padText.toString()));
|
||||
});
|
||||
|
||||
it('exports DOC', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
try{
|
||||
request(host + '/p/'+testPadId+'/export/doc', function (err, res, body) {
|
||||
// TODO: At some point checking that the contents is correct would be suitable
|
||||
if(body.length >= 9000){
|
||||
done();
|
||||
}else{
|
||||
throw new Error("Word Document export length is not right");
|
||||
}
|
||||
})
|
||||
}catch(e){
|
||||
throw new Error(e);
|
||||
}
|
||||
})
|
||||
|
||||
it('Tries to import .docx that uses soffice or abiword', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
|
||||
throw new Error("Failed DOCX import");
|
||||
}else{
|
||||
done();
|
||||
}
|
||||
describe('Import/Export tests requiring AbiWord/LibreOffice', function() {
|
||||
before(function() {
|
||||
if ((!settings.abiword || settings.abiword.indexOf('/') === -1) &&
|
||||
(!settings.soffice || settings.soffice.indexOf('/') === -1)) {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
form.append('file', wordXDoc, {
|
||||
filename: '/test.docx',
|
||||
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
});
|
||||
});
|
||||
|
||||
it('exports DOC from imported DOCX', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
request(host + '/p/'+testPadId+'/export/doc', function (err, res, body) {
|
||||
// TODO: At some point checking that the contents is correct would be suitable
|
||||
if(body.length >= 9100){
|
||||
done();
|
||||
}else{
|
||||
throw new Error("Word Document export length is not right");
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('Tries to import .pdf that uses soffice or abiword', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
|
||||
throw new Error("Failed PDF import");
|
||||
}else{
|
||||
done();
|
||||
}
|
||||
}
|
||||
// For some reason word import does not work in testing..
|
||||
// TODO: fix support for .doc files..
|
||||
it('Tries to import .doc that uses soffice or abiword', async function() {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
form.append('file', pdfDoc, {
|
||||
filename: '/test.pdf',
|
||||
contentType: 'application/pdf'
|
||||
});
|
||||
});
|
||||
|
||||
it('exports PDF', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
request(host + '/p/'+testPadId+'/export/pdf', function (err, res, body) {
|
||||
// TODO: At some point checking that the contents is correct would be suitable
|
||||
if(body.length >= 1000){
|
||||
done();
|
||||
}else{
|
||||
throw new Error("PDF Document export length is not right");
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('Tries to import .odt that uses soffice or abiword', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
if(res.body.indexOf("FrameCall('undefined', 'ok');") === -1){
|
||||
throw new Error("Failed ODT import", testPadId);
|
||||
}else{
|
||||
done();
|
||||
}
|
||||
}
|
||||
it('exports DOC', async function() {
|
||||
await agent.get(`/p/${testPadId}/export/doc`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 9000));
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
form.append('file', odtDoc, {
|
||||
filename: '/test.odt',
|
||||
contentType: 'application/odt'
|
||||
});
|
||||
});
|
||||
|
||||
it('exports ODT', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
if((settings.abiword && settings.abiword.indexOf("/" === -1)) && (settings.office && settings.soffice.indexOf("/" === -1))) return done();
|
||||
request(host + '/p/'+testPadId+'/export/odt', function (err, res, body) {
|
||||
// TODO: At some point checking that the contents is correct would be suitable
|
||||
if(body.length >= 7000){
|
||||
done();
|
||||
}else{
|
||||
throw new Error("ODT Document export length is not right");
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('Tries to import .etherpad', function(done) {
|
||||
if(!settings.allowAnyoneToImport) return done();
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
if(res.body.indexOf("FrameCall(\'true\', \'ok\');") === -1){
|
||||
throw new Error("Failed Etherpad import", err, testPadId);
|
||||
}else{
|
||||
done();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
form.append('file', etherpadDoc, {
|
||||
filename: '/test.etherpad',
|
||||
contentType: 'application/etherpad'
|
||||
});
|
||||
});
|
||||
|
||||
it('exports Etherpad', function(done) {
|
||||
request(host + '/p/'+testPadId+'/export/etherpad', function (err, res, body) {
|
||||
// TODO: At some point checking that the contents is correct would be suitable
|
||||
if(body.indexOf("hello") !== -1){
|
||||
done();
|
||||
}else{
|
||||
console.error("body");
|
||||
throw new Error("Etherpad Document does not include hello");
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('exports HTML for this Etherpad file', function(done) {
|
||||
request(host + '/p/'+testPadId+'/export/html', function (err, res, body) {
|
||||
|
||||
// broken pre fix export -- <ul class="bullet"></li><ul class="bullet"></ul></li></ul>
|
||||
var expectedHTML = '<ul class="bullet"><li><ul class="bullet"><li>hello</ul></li></ul>';
|
||||
// expect body to include
|
||||
if(body.indexOf(expectedHTML) !== -1){
|
||||
done();
|
||||
}else{
|
||||
console.error(body);
|
||||
throw new Error("Exported HTML nested list items is not right", body);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to import Plain Text to a pad that does not exist', function(done) {
|
||||
var req = request.post(host + '/p/'+testPadId+testPadId+testPadId+'/import', function (err, res, body) {
|
||||
if (res.statusCode === 200) {
|
||||
throw new Error("Was able to import to a pad that doesn't exist");
|
||||
}else{
|
||||
// Wasn't able to write to a pad that doesn't exist, this is expected behavior
|
||||
api.get(endPoint('getText')+"&padID="+testPadId+testPadId+testPadId)
|
||||
.expect(function(res){
|
||||
if(res.body.code !== 1) throw new Error("Pad Exists");
|
||||
it('Tries to import .docx that uses soffice or abiword', async function() {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', wordXDoc, {
|
||||
filename: '/test.docx',
|
||||
contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
})
|
||||
.expect(200, done)
|
||||
}
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
it('exports DOC from imported DOCX', async function() {
|
||||
await agent.get(`/p/${testPadId}/export/doc`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 9100));
|
||||
});
|
||||
|
||||
form.append('file', padText, {
|
||||
filename: '/test.txt',
|
||||
contentType: 'text/plain'
|
||||
});
|
||||
})
|
||||
it('Tries to import .pdf that uses soffice or abiword', async function() {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports PDF', async function() {
|
||||
await agent.get(`/p/${testPadId}/export/pdf`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 1000));
|
||||
});
|
||||
|
||||
it('Tries to import .odt that uses soffice or abiword', async function() {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('undefined', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('exports ODT', async function() {
|
||||
await agent.get(`/p/${testPadId}/export/odt`)
|
||||
.buffer(true).parse(superagent.parse['application/octet-stream'])
|
||||
.expect(200)
|
||||
.expect((res) => assert(res.body.length >= 7000));
|
||||
});
|
||||
|
||||
}); // End of AbiWord/LibreOffice tests.
|
||||
|
||||
it('Tries to import .etherpad', async function() {
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', etherpadDoc, {
|
||||
filename: '/test.etherpad',
|
||||
contentType: 'application/etherpad',
|
||||
})
|
||||
.expect(200)
|
||||
.expect(/FrameCall\('true', 'ok'\);/);
|
||||
});
|
||||
|
||||
it('Tries to import unsupported file type', function(done) {
|
||||
if(settings.allowUnknownFileEnds === true){
|
||||
console.log("allowing unknown file ends so skipping this test");
|
||||
return done();
|
||||
it('exports Etherpad', async function() {
|
||||
await agent.get(`/p/${testPadId}/export/etherpad`)
|
||||
.buffer(true).parse(superagent.parse.text)
|
||||
.expect(200)
|
||||
.expect(/hello/);
|
||||
});
|
||||
|
||||
it('exports HTML for this Etherpad file', async function() {
|
||||
await agent.get(`/p/${testPadId}/export/html`)
|
||||
.expect(200)
|
||||
.expect('content-type', 'text/html; charset=utf-8')
|
||||
.expect(/<ul class="bullet"><li><ul class="bullet"><li>hello<\/ul><\/li><\/ul>/);
|
||||
});
|
||||
|
||||
it('tries to import Plain Text to a pad that does not exist', async function() {
|
||||
const padId = testPadId + testPadId + testPadId;
|
||||
await agent.post(`/p/${padId}/import`)
|
||||
.attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'})
|
||||
.expect(405);
|
||||
await agent.get(endPoint('getText') + `&padID=${padId}`)
|
||||
.expect(200)
|
||||
.expect((res) => assert.equal(res.body.code, 1));
|
||||
});
|
||||
|
||||
it('Tries to import unsupported file type', async function() {
|
||||
if (settings.allowUnknownFileEnds === true) {
|
||||
console.log('skipping test because allowUnknownFileEnds is true');
|
||||
return this.skip();
|
||||
}
|
||||
|
||||
var req = request.post(host + '/p/'+testPadId+'/import', function (err, res, body) {
|
||||
if (err) {
|
||||
throw new Error("Failed to import", err);
|
||||
} else {
|
||||
if(res.body.indexOf("FrameCall('undefined', 'ok');") !== -1){
|
||||
console.log("worked");
|
||||
throw new Error("You shouldn't be able to import this file", testPadId);
|
||||
}
|
||||
return done();
|
||||
}
|
||||
});
|
||||
|
||||
let form = req.form();
|
||||
form.append('file', padText, {
|
||||
filename: '/test.xasdasdxx',
|
||||
contentType: 'weirdness/jobby'
|
||||
});
|
||||
await agent.post(`/p/${testPadId}/import`)
|
||||
.attach('file', padText, {filename: '/test.xasdasdxx', contentType: 'weirdness/jobby'})
|
||||
.expect(200)
|
||||
.expect((res) => assert.doesNotMatch(res.text, /FrameCall\('undefined', 'ok'\);/));
|
||||
});
|
||||
|
||||
// end of tests
|
||||
})
|
||||
}); // End of tests.
|
||||
|
||||
|
||||
|
||||
|
@ -363,7 +211,7 @@ describe('Imports and Exports', function(){
|
|||
|
||||
var endPoint = function(point, version){
|
||||
version = version || apiVersion;
|
||||
return '/api/'+version+'/'+point+'?apikey='+apiKey;
|
||||
return `/api/${version}/${point}?apikey=${apiKey}`;
|
||||
}
|
||||
|
||||
function makeid()
|
||||
|
@ -376,35 +224,3 @@ function makeid()
|
|||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
function generateLongText(){
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < 80000; i++ ){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
// Need this to compare arrays (listSavedRevisions test)
|
||||
Array.prototype.equals = function (array) {
|
||||
// if the other array is a falsy value, return
|
||||
if (!array)
|
||||
return false;
|
||||
// compare lengths - can save a lot of time
|
||||
if (this.length != array.length)
|
||||
return false;
|
||||
for (var i = 0, l=this.length; i < l; i++) {
|
||||
// Check if we have nested arrays
|
||||
if (this[i] instanceof Array && array[i] instanceof Array) {
|
||||
// recurse into the nested arrays
|
||||
if (!this[i].equals(array[i]))
|
||||
return false;
|
||||
} else if (this[i] != array[i]) {
|
||||
// Warning - two different object instances will never be equal: {x:20} != {x:20}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -11,19 +11,19 @@ const settings = require(m('node/utils/Settings'));
|
|||
const supertest = require(m('node_modules/supertest'));
|
||||
|
||||
const logger = log4js.getLogger('test');
|
||||
let client;
|
||||
let agent;
|
||||
let baseUrl;
|
||||
|
||||
before(async () => {
|
||||
before(async function() {
|
||||
settings.port = 0;
|
||||
settings.ip = 'localhost';
|
||||
const httpServer = await server.start();
|
||||
baseUrl = `http://localhost:${httpServer.address().port}`;
|
||||
logger.debug(`HTTP server at ${baseUrl}`);
|
||||
client = supertest(baseUrl);
|
||||
agent = supertest(baseUrl);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
after(async function() {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
|
@ -107,10 +107,22 @@ const handshake = async (socket, padID) => {
|
|||
return msg;
|
||||
};
|
||||
|
||||
describe('socket.io access checks', () => {
|
||||
describe('socket.io access checks', function() {
|
||||
let authorize;
|
||||
let authorizeHooksBackup;
|
||||
const cleanUpPads = async () => {
|
||||
const padIds = ['pad', 'other-pad', 'päd'];
|
||||
await Promise.all(padIds.map(async (padId) => {
|
||||
if (await padManager.doesPadExist(padId)) {
|
||||
const pad = await padManager.getPad(padId);
|
||||
await pad.remove();
|
||||
}
|
||||
}));
|
||||
};
|
||||
const settingsBackup = {};
|
||||
let socket;
|
||||
beforeEach(async () => {
|
||||
|
||||
beforeEach(async function() {
|
||||
Object.assign(settingsBackup, settings);
|
||||
assert(socket == null);
|
||||
settings.requireAuthentication = false;
|
||||
|
@ -119,61 +131,93 @@ describe('socket.io access checks', () => {
|
|||
admin: {password: 'admin-password', is_admin: true},
|
||||
user: {password: 'user-password'},
|
||||
};
|
||||
Promise.all(['pad', 'other-pad'].map(async (pad) => {
|
||||
if (await padManager.doesPadExist(pad)) (await padManager.getPad(pad)).remove();
|
||||
}));
|
||||
authorize = () => true;
|
||||
authorizeHooksBackup = plugins.hooks.authorize;
|
||||
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => {
|
||||
if (req.session.user == null) return cb([]); // Hasn't authenticated yet.
|
||||
return cb([authorize(req)]);
|
||||
}}];
|
||||
await cleanUpPads();
|
||||
});
|
||||
afterEach(async () => {
|
||||
afterEach(async function() {
|
||||
Object.assign(settings, settingsBackup);
|
||||
if (socket) socket.close();
|
||||
socket = null;
|
||||
plugins.hooks.authorize = authorizeHooksBackup;
|
||||
await cleanUpPads();
|
||||
});
|
||||
|
||||
// Normal accesses.
|
||||
it('!authn anonymous /p/pad -> 200, ok', async () => {
|
||||
const res = await client.get('/p/pad').expect(200);
|
||||
it('!authn anonymous cookie /p/pad -> 200, ok', async function() {
|
||||
const res = await agent.get('/p/pad').expect(200);
|
||||
// Should not throw.
|
||||
socket = await connect(res);
|
||||
const clientVars = await handshake(socket, 'pad');
|
||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||
});
|
||||
it('!authn user /p/pad -> 200, ok', async () => {
|
||||
const res = await client.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
it('!authn !cookie -> ok', async function() {
|
||||
// Should not throw.
|
||||
socket = await connect(null);
|
||||
const clientVars = await handshake(socket, 'pad');
|
||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||
});
|
||||
it('!authn user /p/pad -> 200, ok', async function() {
|
||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
// Should not throw.
|
||||
socket = await connect(res);
|
||||
const clientVars = await handshake(socket, 'pad');
|
||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||
});
|
||||
it('authn user /p/pad -> 200, ok', async () => {
|
||||
it('authn user /p/pad -> 200, ok', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
const res = await client.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
// Should not throw.
|
||||
socket = await connect(res);
|
||||
const clientVars = await handshake(socket, 'pad');
|
||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||
});
|
||||
it('authz user /p/pad -> 200, ok', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
// Should not throw.
|
||||
socket = await connect(res);
|
||||
const clientVars = await handshake(socket, 'pad');
|
||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||
});
|
||||
it('supports pad names with characters that must be percent-encoded', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
// requireAuthorization is set to true here to guarantee that the user's padAuthorizations
|
||||
// object is populated. Technically this isn't necessary because the user's padAuthorizations is
|
||||
// currently populated even if requireAuthorization is false, but setting this to true ensures
|
||||
// the test remains useful if the implementation ever changes.
|
||||
settings.requireAuthorization = true;
|
||||
const encodedPadId = encodeURIComponent('päd');
|
||||
const res = await agent.get(`/p/${encodedPadId}`).auth('user', 'user-password').expect(200);
|
||||
// Should not throw.
|
||||
socket = await connect(res);
|
||||
const clientVars = await handshake(socket, 'päd');
|
||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||
});
|
||||
|
||||
// Abnormal access attempts.
|
||||
it('authn anonymous /p/pad -> 401, error', async () => {
|
||||
it('authn anonymous /p/pad -> 401, error', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
const res = await client.get('/p/pad').expect(401);
|
||||
const res = await agent.get('/p/pad').expect(401);
|
||||
// Despite the 401, try to create the pad via a socket.io connection anyway.
|
||||
await assert.rejects(connect(res), {message: /authentication required/i});
|
||||
});
|
||||
it('socket.io connection without express-session cookie -> error', async () => {
|
||||
it('authn !cookie -> error', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
await assert.rejects(connect(null), {message: /signed express_sid cookie is required/i});
|
||||
});
|
||||
it('authorization bypass attempt -> error', async () => {
|
||||
plugins.hooks.authorize = [{hook_fn: (hookName, {req}, cb) => {
|
||||
if (req.session.user == null) return cb([]); // Hasn't authenticated yet.
|
||||
// Only allowed to access /p/pad.
|
||||
return cb([req.path === '/p/pad']);
|
||||
}}];
|
||||
it('authorization bypass attempt -> error', async function() {
|
||||
// Only allowed to access /p/pad.
|
||||
authorize = (req) => req.path === '/p/pad';
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
// First authenticate and establish a session.
|
||||
const res = await client.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
const res = await agent.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||
// Connecting should work because the user successfully authenticated.
|
||||
socket = await connect(res);
|
||||
// Accessing /p/other-pad should fail, despite the successful fetch of /p/pad.
|
||||
|
|
166
tests/backend/specs/webaccess.js
Normal file
166
tests/backend/specs/webaccess.js
Normal file
|
@ -0,0 +1,166 @@
|
|||
function m(mod) { return __dirname + '/../../../src/' + mod; }
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const log4js = require(m('node_modules/log4js'));
|
||||
const plugins = require(m('static/js/pluginfw/plugin_defs'));
|
||||
const server = require(m('node/server'));
|
||||
const settings = require(m('node/utils/Settings'));
|
||||
const supertest = require(m('node_modules/supertest'));
|
||||
|
||||
let agent;
|
||||
const logger = log4js.getLogger('test');
|
||||
|
||||
before(async function() {
|
||||
settings.port = 0;
|
||||
settings.ip = 'localhost';
|
||||
const httpServer = await server.start();
|
||||
const baseUrl = `http://localhost:${httpServer.address().port}`;
|
||||
logger.debug(`HTTP server at ${baseUrl}`);
|
||||
agent = supertest(baseUrl);
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
describe('webaccess without any plugins', function() {
|
||||
const backup = {};
|
||||
|
||||
before(async function() {
|
||||
Object.assign(backup, settings);
|
||||
settings.users = {
|
||||
admin: {password: 'admin-password', is_admin: true},
|
||||
user: {password: 'user-password'},
|
||||
};
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
Object.assign(settings, backup);
|
||||
});
|
||||
|
||||
it('!authn !authz anonymous / -> 200', async function() {
|
||||
settings.requireAuthentication = false;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/').expect(200);
|
||||
});
|
||||
it('!authn !authz anonymous /admin/ -> 401', async function() {
|
||||
settings.requireAuthentication = false;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/admin/').expect(401);
|
||||
});
|
||||
it('authn !authz anonymous / -> 401', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/').expect(401);
|
||||
});
|
||||
it('authn !authz user / -> 200', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/').auth('user', 'user-password').expect(200);
|
||||
});
|
||||
it('authn !authz user /admin/ -> 403', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
||||
});
|
||||
it('authn !authz admin / -> 200', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
||||
});
|
||||
it('authn !authz admin /admin/ -> 200', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = false;
|
||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
||||
});
|
||||
it('authn authz user / -> 403', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||
});
|
||||
it('authn authz user /admin/ -> 403', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
await agent.get('/admin/').auth('user', 'user-password').expect(403);
|
||||
});
|
||||
it('authn authz admin / -> 200', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
await agent.get('/').auth('admin', 'admin-password').expect(200);
|
||||
});
|
||||
it('authn authz admin /admin/ -> 200', async function() {
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
await agent.get('/admin/').auth('admin', 'admin-password').expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('webaccess with authFailure plugin', function() {
|
||||
let handle, returnUndef, status, called;
|
||||
const authFailure = (hookName, context, cb) => {
|
||||
assert.equal(hookName, 'authFailure');
|
||||
assert(context != null);
|
||||
assert(context.req != null);
|
||||
assert(context.res != null);
|
||||
assert(context.next != null);
|
||||
assert(!called);
|
||||
called = true;
|
||||
if (handle) {
|
||||
context.res.status(status).send('injected content');
|
||||
return cb([true]);
|
||||
}
|
||||
if (returnUndef) return cb();
|
||||
return cb([]);
|
||||
};
|
||||
|
||||
const settingsBackup = {};
|
||||
let authFailureHooksBackup;
|
||||
before(function() {
|
||||
Object.assign(settingsBackup, settings);
|
||||
authFailureHooksBackup = plugins.hooks.authFailure;
|
||||
plugins.hooks.authFailure = [{hook_fn: authFailure}];
|
||||
settings.requireAuthentication = true;
|
||||
settings.requireAuthorization = true;
|
||||
settings.users = {
|
||||
admin: {password: 'admin-password', is_admin: true},
|
||||
user: {password: 'user-password'},
|
||||
};
|
||||
});
|
||||
after(function() {
|
||||
Object.assign(settings, settingsBackup);
|
||||
plugins.hooks.authFailure = authFailureHooksBackup;
|
||||
});
|
||||
|
||||
beforeEach(function() {
|
||||
handle = false;
|
||||
returnUndef = false;
|
||||
status = 200;
|
||||
called = false;
|
||||
});
|
||||
afterEach(function() {
|
||||
assert(called);
|
||||
});
|
||||
|
||||
it('authn fail, hook handles -> 200', async function() {
|
||||
handle = true;
|
||||
await agent.get('/').expect(200, /injected content/);
|
||||
});
|
||||
it('authn fail, hook defers (undefined) -> 401', async function() {
|
||||
returnUndef = true;
|
||||
await agent.get('/').expect(401);
|
||||
});
|
||||
it('authn fail, hook defers (empty list) -> 401', async function() {
|
||||
await agent.get('/').expect(401);
|
||||
});
|
||||
it('authz fail, hook handles -> 200', async function() {
|
||||
handle = true;
|
||||
await agent.get('/').auth('user', 'user-password').expect(200, /injected content/);
|
||||
});
|
||||
it('authz fail, hook defers (undefined) -> 403', async function() {
|
||||
returnUndef = true;
|
||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||
});
|
||||
it('authz fail, hook defers (empty list) -> 403', async function() {
|
||||
await agent.get('/').auth('user', 'user-password').expect(403);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue