/** * Controls the security of pad access */ /* * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var ERR = require("async-stacktrace"); var async = require("async"); var authorManager = require("./AuthorManager"); var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); var padManager = require("./PadManager"); var sessionManager = require("./SessionManager"); var settings = require("../utils/Settings"); var log4js = require('log4js'); var authLogger = log4js.getLogger("auth"); /** * This function controlls the access to a pad, it checks if the user can access a pad. * @param padID the pad the user wants to access * @param sessionCookie the session the user has (set via api) * @param token the token of the author (randomly generated at client side, used for public pads) * @param password the password the user has given to access this pad, can be null * @param callback will be called with (err, {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx}) */ exports.checkAccess = function(padID, sessionCookie, token, password, callback) { var statusObject; if (!padID) { callback(null, {accessStatus: "deny"}); return; } // allow plugins to deny access var deniedByHook = hooks.callAll("onAccessCheck", {'padID': padID, 'password': password, 'token': token, 'sessionCookie': sessionCookie}).indexOf(false) > -1; if (deniedByHook) { callback(null, {accessStatus: "deny"}); return; } if (settings.requireSession) { // a valid session is required (api-only mode) if (!sessionCookie) { // without sessionCookie, access is denied callback(null, {accessStatus: "deny"}); return; } } else { // a session is not required, so we'll check if it's a public pad if (padID.indexOf("$") == -1) { // it's not a group pad, means we can grant access // get author for this token authorManager.getAuthor4Token(token, function(err, author) { if (ERR(err, callback)) return; // assume user has access statusObject = { accessStatus: "grant", authorID: author }; if (settings.editOnly) { // user can't create pads // check if pad exists padManager.doesPadExists(padID, function(err, exists) { if (ERR(err, callback)) return; if (!exists) { // pad doesn't exist - user can't have access statusObject.accessStatus = "deny"; } // grant or deny access, with author of token callback(null, statusObject); }); return; } // user may create new pads - no need to check anything // grant access, with author of token callback(null, statusObject); }); // don't continue return; } } var groupID = padID.split("$")[0]; var padExists = false; var validSession = false; var sessionAuthor; var tokenAuthor; var isPublic; var isPasswordProtected; var passwordStatus = password == null ? "notGiven" : "wrong"; // notGiven, correct, wrong async.series([ // get basic informations from the database function(callback) { async.parallel([ // does pad exist function(callback) { padManager.doesPadExists(padID, function(err, exists) { if (ERR(err, callback)) return; padExists = exists; callback(); }); }, // get information about all sessions contained in this cookie function(callback) { if (!sessionCookie) { callback(); return; } var sessionIDs = sessionCookie.split(','); async.forEach(sessionIDs, function(sessionID, callback) { sessionManager.getSessionInfo(sessionID, function(err, sessionInfo) { // skip session if it doesn't exist if (err && err.message == "sessionID does not exist") { authLogger.debug("Auth failed: unknown session"); callback(); return; } if (ERR(err, callback)) return; var now = Math.floor(Date.now()/1000); // is it for this group? if (sessionInfo.groupID != groupID) { authLogger.debug("Auth failed: wrong group"); callback(); return; } // is validUntil still ok? if (sessionInfo.validUntil <= now) { authLogger.debug("Auth failed: validUntil"); callback(); return; } // There is a valid session validSession = true; sessionAuthor = sessionInfo.authorID; callback(); }); }, callback); }, // get author for token function(callback) { // get author for this token authorManager.getAuthor4Token(token, function(err, author) { if (ERR(err, callback)) return; tokenAuthor = author; callback(); }); } ], callback); }, // get more informations of this pad, if avaiable function(callback) { // skip this if the pad doesn't exist if (padExists == false) { callback(); return; } padManager.getPad(padID, function(err, pad) { if (ERR(err, callback)) return; // is it a public pad? isPublic = pad.getPublicStatus(); // is it password protected? isPasswordProtected = pad.isPasswordProtected(); // is password correct? if (isPasswordProtected && password && pad.isCorrectPassword(password)) { passwordStatus = "correct"; } callback(); }); }, function(callback) { if (validSession && padExists) { // - a valid session for this group is avaible AND pad exists if (!isPasswordProtected) { // - the pad is not password protected // --> grant access statusObject = { accessStatus: "grant", authorID: sessionAuthor }; } else if (settings.sessionNoPassword) { // - the setting to bypass password validation is set // --> grant access statusObject = { accessStatus: "grant", authorID: sessionAuthor }; } else if (isPasswordProtected && passwordStatus == "correct") { // - the pad is password protected and password is correct // --> grant access statusObject = { accessStatus: "grant", authorID: sessionAuthor }; } else if (isPasswordProtected && passwordStatus == "wrong") { // - the pad is password protected but wrong password given // --> deny access, ask for new password and tell them that the password is wrong statusObject = { accessStatus: "wrongPassword" }; } else if (isPasswordProtected && passwordStatus == "notGiven") { // - the pad is password protected but no password given // --> ask for password statusObject = { accessStatus: "needPassword" }; } else { throw new Error("Ops, something wrong happend"); } } else if (validSession && !padExists) { // - a valid session for this group avaible but pad doesn't exist // --> grant access statusObject = {accessStatus: "grant", authorID: sessionAuthor}; if (settings.editOnly) { // --> deny access if user isn't allowed to create the pad authLogger.debug("Auth failed: valid session & pad does not exist"); statusObject.accessStatus = "deny"; } } else if (!validSession && padExists) { // there is no valid session avaiable AND pad exists // -- it's public and not password protected if (isPublic && !isPasswordProtected) { // --> grant access, with author of token statusObject = {accessStatus: "grant", authorID: tokenAuthor}; } else if (isPublic && isPasswordProtected && passwordStatus == "correct") { // - it's public and password protected and password is correct // --> grant access, with author of token statusObject = {accessStatus: "grant", authorID: tokenAuthor}; } else if (isPublic && isPasswordProtected && passwordStatus == "wrong") { // - it's public and the pad is password protected but wrong password given // --> deny access, ask for new password and tell them that the password is wrong statusObject = {accessStatus: "wrongPassword"}; } else if (isPublic && isPasswordProtected && passwordStatus == "notGiven") { // - it's public and the pad is password protected but no password given // --> ask for password statusObject = {accessStatus: "needPassword"}; } else if (!isPublic) { // - it's not public authLogger.debug("Auth failed: invalid session & pad is not public"); // --> deny access statusObject = {accessStatus: "deny"}; } else { throw new Error("Ops, something wrong happend"); } } else { // there is no valid session avaiable AND pad doesn't exist authLogger.debug("Auth failed: invalid session & pad does not exist"); // --> deny access statusObject = {accessStatus: "deny"}; } callback(); } ], function(err) { if (ERR(err, callback)) return; callback(null, statusObject); }); };