diff options
author | Egil Moeller <egil.moller@freecode.no> | 2012-04-19 14:25:12 +0200 |
---|---|---|
committer | Egil Moeller <egil.moller@freecode.no> | 2012-04-19 14:25:12 +0200 |
commit | ac36a99a7226e1092c2e7e28c9b6e32d8f82e6fb (patch) | |
tree | a1de593a6290b66454c7f3684df63441ebb7bd83 /src/node/hooks | |
parent | 4c1d94343fe1b1a8ff02d7cfaef9f9506676d072 (diff) | |
download | etherpad-lite-ac36a99a7226e1092c2e7e28c9b6e32d8f82e6fb.zip |
More general basic auth
Diffstat (limited to 'src/node/hooks')
-rw-r--r-- | src/node/hooks/express/adminplugins.js | 2 | ||||
-rw-r--r-- | src/node/hooks/express/socketio.js | 18 | ||||
-rw-r--r-- | src/node/hooks/express/webaccess.js | 108 |
3 files changed, 95 insertions, 33 deletions
diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 1d1320ab..6cc80cf2 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -21,6 +21,8 @@ exports.expressCreateServer = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) { var io = args.io.of("/pluginfw/installer"); io.on('connection', function (socket) { + if (!socket.handshake.session.user.is_admin) return; + socket.on("load", function (query) { socket.emit("installed-results", {results: plugins.plugins}); }); diff --git a/src/node/hooks/express/socketio.js b/src/node/hooks/express/socketio.js index e040f7ac..6774b653 100644 --- a/src/node/hooks/express/socketio.js +++ b/src/node/hooks/express/socketio.js @@ -7,11 +7,27 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var padMessageHandler = require("../../handler/PadMessageHandler"); var timesliderMessageHandler = require("../../handler/TimesliderMessageHandler"); - +var connect = require('connect'); + exports.expressCreateServer = function (hook_name, args, cb) { //init socket.io and redirect all requests to the MessageHandler 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 //we should remove this when the new socket.io version is more stable io.set('transports', ['xhr-polling']); diff --git a/src/node/hooks/express/webaccess.js b/src/node/hooks/express/webaccess.js index 48b5edae..499451d8 100644 --- a/src/node/hooks/express/webaccess.js +++ b/src/node/hooks/express/webaccess.js @@ -2,55 +2,99 @@ var express = require('express'); var log4js = require('log4js'); var httpLogger = log4js.getLogger("http"); 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 exports.basicAuth = function (req, res, next) { - - // When handling HTTP-Auth, an undefined password will lead to no authorization at all - var pass = settings.httpAuth || ''; - - if (req.path.indexOf('/admin') == 0) { - var pass = settings.adminHttpAuth; - - } - - // Just pass if password is an empty string - if (pass === '') { - return next(); - } - - - // If a password has been set and auth headers are present... - if (pass && req.headers.authorization && req.headers.authorization.search('Basic ') === 0) { - // ...check login and password - if (new Buffer(req.headers.authorization.split(' ')[1], 'base64').toString() === pass) { - return next(); + var authorize = function (cb) { + // Do not require auth for static paths...this could be a bit brittle + if (req.path.match(/^\/(static|javascripts|pluginfw)/)) return cb(true); + + if (req.path.indexOf('/admin') != 0) { + 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); } - // Do not require auth for static paths...this could be a bit brittle - else if (req.path.match(/^\/(static|javascripts|pluginfw)/)) { - return next(); + + var authenticate = function (cb) { + // If auth headers are present use them to authenticate... + 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); } - // Otherwise return Auth required Headers, delayed for 1 second, if auth failed. - res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); - if (req.headers.authorization) { - setTimeout(function () { + var failure = function () { + /* Authentication OR authorization failed. Return Auth required + * Headers, delayed for 1 second, if authentication failed. */ + res.header('WWW-Authenticate', 'Basic realm="Protected Area"'); + if (req.headers.authorization) { + setTimeout(function () { + res.send('Authentication required', 401); + }, 1000); + } else { res.send('Authentication required', 401); - }, 1000); - } else { - res.send('Authentication required', 401); + } } + + + /* This is the actual authentication/authorization hoop. It is done in four steps: + + 1) Try to just access the thing + 2) If not allowed using whatever creds are in the current session already, try to authenticate + 3) If authentication using already supplied credentials succeeds, try to access the thing again + 4) If all els fails, give the user a 401 to request new credentials + + 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) { - 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. // 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")) args.app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'})); 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); } |