More general basic auth

pull/651/head
Egil Moeller 2012-04-19 14:25:12 +02:00
parent 4c1d94343f
commit ac36a99a72
6 changed files with 123 additions and 48 deletions

View File

@ -46,12 +46,27 @@
/* This is the path to the Abiword executable. Setting it to null, disables abiword. /* This is the path to the Abiword executable. Setting it to null, disables abiword.
Abiword is needed to enable the import/export of pads*/ Abiword is needed to enable the import/export of pads*/
"abiword" : null, "abiword" : null,
/* This setting is used if you need http basic auth */ /* This setting is used if you require authentication of all users.
// "httpAuth" : "user:pass", Note: /admin always requires authentication. */
"requireAuthentication": false,
/* This setting is used for http basic auth for admin pages. If not set, the admin page won't be accessible from web*/ /* Require authorization by a module, or a user with is_admin set,
// "adminHttpAuth" : "user:pass", see below. Access to /admin allways requires either, regardless
of this setting. */
"requireAuthorization": false,
/* Users for basic authentication. is_admin = true gives access to /admin */
"users": {
"admin": {
"password": "changeme",
"is_admin": true
},
"user": {
"password": "changeme",
"is_admin": false
}
},
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */ /* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO" "loglevel": "INFO"

View File

@ -21,6 +21,8 @@ exports.expressCreateServer = function (hook_name, args, cb) {
exports.socketio = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) {
var io = args.io.of("/pluginfw/installer"); var io = args.io.of("/pluginfw/installer");
io.on('connection', function (socket) { io.on('connection', function (socket) {
if (!socket.handshake.session.user.is_admin) return;
socket.on("load", function (query) { socket.on("load", function (query) {
socket.emit("installed-results", {results: plugins.plugins}); socket.emit("installed-results", {results: plugins.plugins});
}); });

View File

@ -7,11 +7,27 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks");
var padMessageHandler = require("../../handler/PadMessageHandler"); var padMessageHandler = require("../../handler/PadMessageHandler");
var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler"); var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler");
var connect = require('connect');
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
var io = socketio.listen(args.app); var io = socketio.listen(args.app);
/* Require an express session cookie to be present, and load the
* session. See http://www.danielbaulig.de/socket-ioexpress for more
* info */
io.set('authorization', function (data, accept) {
if (!data.headers.cookie) return accept('No session cookie transmitted.', false);
data.cookie = connect.utils.parseCookie(data.headers.cookie);
data.sessionID = data.cookie.express_sid;
args.app.sessionStore.get(data.sessionID, function (err, session) {
if (err || !session) return accept('Bad session / session has expired', false);
data.session = new connect.middleware.session.Session(data, session);
accept(null, true);
});
});
//this is only a workaround to ensure it works with all browers behind a proxy //this is only a workaround to ensure it works with all browers behind a proxy
//we should remove this when the new socket.io version is more stable //we should remove this when the new socket.io version is more stable
io.set('transports', ['xhr-polling']); io.set('transports', ['xhr-polling']);

View File

@ -2,55 +2,99 @@ var express = require('express');
var log4js = require('log4js'); var log4js = require('log4js');
var httpLogger = log4js.getLogger("http"); var httpLogger = log4js.getLogger("http");
var settings = require('../../utils/Settings'); var settings = require('../../utils/Settings');
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
//checks for basic http auth //checks for basic http auth
exports.basicAuth = function (req, res, next) { exports.basicAuth = function (req, res, next) {
var authorize = function (cb) {
// When handling HTTP-Auth, an undefined password will lead to no authorization at all // Do not require auth for static paths...this could be a bit brittle
var pass = settings.httpAuth || ''; if (req.path.match(/^\/(static|javascripts|pluginfw)/)) return cb(true);
if (req.path.indexOf('/admin') == 0) { if (req.path.indexOf('/admin') != 0) {
var pass = settings.adminHttpAuth; if (!settings.requireAuthentication) return cb(true);
if (!settings.requireAuthorization && req.session && req.session.user) return cb(true);
}
if (req.session && req.session.user && req.session.user.is_admin) return cb(true);
// hooks.aCallFirst("authorize", {resource: req.path, req: req}, cb);
cb(false);
} }
// Just pass if password is an empty string var authenticate = function (cb) {
if (pass === '') { // If auth headers are present use them to authenticate...
return next(); if (req.headers.authorization && req.headers.authorization.search('Basic ') === 0) {
var userpass = new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString().split(":")
var username = userpass[0];
var password = userpass[1];
if (settings.users[username] != undefined && settings.users[username].password == password) {
settings.users[username].username = username;
req.session.user = settings.users[username];
return cb(true);
}
// return hooks.aCallFirst("authenticate", {req: req, username: username, password: password}, cb);
}
// hooks.aCallFirst("authenticate", {req: req}, cb);
cb(false);
} }
// If a password has been set and auth headers are present... var failure = function () {
if (pass && req.headers.authorization && req.headers.authorization.search('Basic ') === 0) { /* Authentication OR authorization failed. Return Auth required
// ...check login and password * Headers, delayed for 1 second, if authentication failed. */
if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() === pass) { res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
return next(); if (req.headers.authorization) {
setTimeout(function () {
res.send('Authentication required', 401);
}, 1000);
} else {
res.send('Authentication required', 401);
} }
} }
// Do not require auth for static paths...this could be a bit brittle
else if (req.path.match(/^\/(static|javascripts|pluginfw)/)) {
return next();
}
// Otherwise return Auth required Headers, delayed for 1 second, if auth failed. /* This is the actual authentication/authorization hoop. It is done in four steps:
res.header('WWW-Authenticate', 'Basic realm="Protected Area"');
if (req.headers.authorization) { 1) Try to just access the thing
setTimeout(function () { 2) If not allowed using whatever creds are in the current session already, try to authenticate
res.send('Authentication required', 401); 3) If authentication using already supplied credentials succeeds, try to access the thing again
}, 1000); 4) If all els fails, give the user a 401 to request new credentials
} else {
res.send('Authentication required', 401); Note that the process could stop already in step 3 with a redirect to login page.
}
*/
authorize(function (ok) {
if (ok) return next();
authenticate(function (ok) {
if (!ok) return failure();
authorize(function (ok) {
if (ok) return next();
failure();
});
});
});
} }
exports.expressConfigure = function (hook_name, args, cb) { exports.expressConfigure = function (hook_name, args, cb) {
args.app.use(exports.basicAuth);
// If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158. // If the log level specified in the config file is WARN or ERROR the application server never starts listening to requests as reported in issue #158.
// Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway. // Not installing the log4js connect logger when the log level has a higher severity than INFO since it would not log at that level anyway.
if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR")) if (!(settings.loglevel === "WARN" || settings.loglevel == "ERROR"))
args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
args.app.use(express.cookieParser()); args.app.use(express.cookieParser());
/* Do not let express create the session, so that we can retain a
* reference to it for socket.io to use. Also, set the key (cookie
* name) to a javascript identifier compatible string. Makes code
* handling it cleaner :) */
args.app.sessionStore = new express.session.MemoryStore();
args.app.use(express.session({store: args.app.sessionStore,
key: 'express_sid',
secret: apikey = randomString(32)}));
args.app.use(exports.basicAuth);
} }

View File

@ -80,15 +80,12 @@ exports.abiword = null;
*/ */
exports.loglevel = "INFO"; exports.loglevel = "INFO";
/** /* This setting is used if you need authentication and/or
* Http basic auth, with "user:password" format * authorization. Note: /admin always requires authentication, and
*/ * either authorization by a module, or a user with is_admin set */
exports.httpAuth = null; exports.requireAuthentication = false;
exports.requireAuthorization = false;
/** exports.users = {};
* Http basic auth, with "user:password" format
*/
exports.adminHttpAuth = null;
//checks if abiword is avaiable //checks if abiword is avaiable
exports.abiwordAvailable = function() exports.abiwordAvailable = function()

View File

@ -17,6 +17,7 @@
"ueberDB" : "0.1.7", "ueberDB" : "0.1.7",
"async" : "0.1.18", "async" : "0.1.18",
"express" : "2.5.8", "express" : "2.5.8",
"connect" : "1.8.7",
"clean-css" : "0.3.2", "clean-css" : "0.3.2",
"uglify-js" : "1.2.5", "uglify-js" : "1.2.5",
"formidable" : "1.0.9", "formidable" : "1.0.9",