mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-01-31 19:02:59 +01:00
security: Don't require express_sid if authn not required
This should make it possible to embed a pad in an iframe from another site as long as `settings.requireAuthentication` is false.
This commit is contained in:
parent
53fd0b4f98
commit
94f944160d
2 changed files with 31 additions and 22 deletions
|
@ -8,6 +8,7 @@ var padMessageHandler = require("../../handler/PadMessageHandler");
|
||||||
|
|
||||||
var cookieParser = require('cookie-parser');
|
var cookieParser = require('cookie-parser');
|
||||||
var sessionModule = require('express-session');
|
var sessionModule = require('express-session');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
exports.expressCreateServer = function (hook_name, args, cb) {
|
exports.expressCreateServer = function (hook_name, args, cb) {
|
||||||
//init socket.io and redirect all requests to the MessageHandler
|
//init socket.io and redirect all requests to the MessageHandler
|
||||||
|
@ -48,32 +49,34 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
||||||
// check whether the user has authenticated, then any random person on the Internet can read,
|
// check whether the user has authenticated, then any random person on the Internet can read,
|
||||||
// modify, or create any pad (unless the pad is password protected or an HTTP API session is
|
// modify, or create any pad (unless the pad is password protected or an HTTP API session is
|
||||||
// required).
|
// required).
|
||||||
var cookieParserFn = cookieParser(webaccess.secret, {});
|
const cookieParserFn = util.promisify(cookieParser(webaccess.secret, {}));
|
||||||
io.use((socket, next) => {
|
const getSession = util.promisify(args.app.sessionStore.get).bind(args.app.sessionStore);
|
||||||
var data = socket.request;
|
io.use(async (socket, next) => {
|
||||||
if (!data.headers.cookie) {
|
const req = socket.request;
|
||||||
|
if (!req.headers.cookie) {
|
||||||
// socketio.js-client on node.js doesn't support cookies (see https://git.io/JU8u9), so the
|
// socketio.js-client on node.js doesn't support cookies (see https://git.io/JU8u9), so the
|
||||||
// token and express_sid cookies have to be passed via a query parameter for unit tests.
|
// token and express_sid cookies have to be passed via a query parameter for unit tests.
|
||||||
data.headers.cookie = socket.handshake.query.cookie;
|
req.headers.cookie = socket.handshake.query.cookie;
|
||||||
}
|
}
|
||||||
if (!data.headers.cookie && settings.loadTest) {
|
if (!req.headers.cookie && settings.loadTest) {
|
||||||
console.warn('bypassing socket.io authentication check due to settings.loadTest');
|
console.warn('bypassing socket.io authentication check due to settings.loadTest');
|
||||||
return next(null, true);
|
return next(null, true);
|
||||||
}
|
}
|
||||||
const fail = (msg) => { return next(new Error(msg), false); };
|
try {
|
||||||
cookieParserFn(data, {}, function(err) {
|
await cookieParserFn(req, {});
|
||||||
if (err) return fail('access denied: unable to parse express_sid cookie');
|
const expressSid = req.signedCookies.express_sid;
|
||||||
const expressSid = data.signedCookies.express_sid;
|
const needAuthn = settings.requireAuthentication;
|
||||||
if (!expressSid) return fail ('access denied: signed express_sid cookie is required');
|
if (needAuthn && !expressSid) throw new Error('signed express_sid cookie is required');
|
||||||
args.app.sessionStore.get(expressSid, (err, session) => {
|
if (expressSid) {
|
||||||
if (err || !session) return fail('access denied: bad session or session has expired');
|
const session = await getSession(expressSid);
|
||||||
data.session = new sessionModule.Session(data, session);
|
if (!session) throw new Error('bad session or session has expired');
|
||||||
if (settings.requireAuthentication && data.session.user == null) {
|
req.session = new sessionModule.Session(req, session);
|
||||||
return fail('access denied: authentication required');
|
if (needAuthn && req.session.user == null) throw new Error('authentication required');
|
||||||
}
|
}
|
||||||
next(null, true);
|
} catch (err) {
|
||||||
});
|
return next(new Error(`access denied: ${err}`), false);
|
||||||
});
|
}
|
||||||
|
return next(null, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// var socketIOLogger = log4js.getLogger("socket.io");
|
// var socketIOLogger = log4js.getLogger("socket.io");
|
||||||
|
|
|
@ -130,13 +130,19 @@ describe('socket.io access checks', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Normal accesses.
|
// Normal accesses.
|
||||||
it('!authn anonymous /p/pad -> 200, ok', async () => {
|
it('!authn anonymous cookie /p/pad -> 200, ok', async () => {
|
||||||
const res = await client.get('/p/pad').expect(200);
|
const res = await client.get('/p/pad').expect(200);
|
||||||
// Should not throw.
|
// Should not throw.
|
||||||
socket = await connect(res);
|
socket = await connect(res);
|
||||||
const clientVars = await handshake(socket, 'pad');
|
const clientVars = await handshake(socket, 'pad');
|
||||||
assert.equal(clientVars.type, 'CLIENT_VARS');
|
assert.equal(clientVars.type, 'CLIENT_VARS');
|
||||||
});
|
});
|
||||||
|
it('!authn !cookie -> ok', async () => {
|
||||||
|
// 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 () => {
|
it('!authn user /p/pad -> 200, ok', async () => {
|
||||||
const res = await client.get('/p/pad').auth('user', 'user-password').expect(200);
|
const res = await client.get('/p/pad').auth('user', 'user-password').expect(200);
|
||||||
// Should not throw.
|
// Should not throw.
|
||||||
|
@ -160,7 +166,7 @@ describe('socket.io access checks', () => {
|
||||||
// Despite the 401, try to create the pad via a socket.io connection anyway.
|
// Despite the 401, try to create the pad via a socket.io connection anyway.
|
||||||
await assert.rejects(connect(res), {message: /authentication required/i});
|
await assert.rejects(connect(res), {message: /authentication required/i});
|
||||||
});
|
});
|
||||||
it('socket.io connection without express-session cookie -> error', async () => {
|
it('authn !cookie -> error', async () => {
|
||||||
settings.requireAuthentication = true;
|
settings.requireAuthentication = true;
|
||||||
await assert.rejects(connect(null), {message: /signed express_sid cookie is required/i});
|
await assert.rejects(connect(null), {message: /signed express_sid cookie is required/i});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue