etherpad-lite/src/node/db/SecurityManager.js

147 lines
5.6 KiB
JavaScript
Raw Normal View History

/**
* 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 authorManager = require("./AuthorManager");
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
var padManager = require("./PadManager");
var sessionManager = require("./SessionManager");
2012-12-06 02:00:58 +01:00
var settings = require("../utils/Settings");
const webaccess = require('../hooks/express/webaccess');
var log4js = require('log4js');
var authLogger = log4js.getLogger("auth");
2012-01-28 13:24:58 +01:00
const DENY = Object.freeze({accessStatus: 'deny'});
const WRONG_PASSWORD = Object.freeze({accessStatus: 'wrongPassword'});
const NEED_PASSWORD = Object.freeze({accessStatus: 'needPassword'});
/**
* Determines whether the user can access a pad.
*
* @param padID identifies the pad the user wants to access.
* @param sessionCookie identifies the sessions the user created via the HTTP API, if any.
* Note: The term "session" used here is unrelated to express-session.
* @param token is a random token of the form t.randomstring_of_length_20 generated by the client
* when using the web UI (not the HTTP API). This token is only used if settings.requireSession
* is false and the user is accessing a public pad. If there is not an author already associated
* with this token then a new author object is created (including generating an author ID) and
* associated with this token.
* @param password is the password the user has given to access this pad. It can be null.
* @param userSettings is the settings.users[username] object (or equivalent from an authn plugin).
* @return {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx}. The caller
* must use the author ID returned in this object when making any changes associated with the
* author.
*
* WARNING: Tokens and session IDs MUST be kept secret, otherwise users will be able to impersonate
* each other (which might allow them to gain privileges).
*/
exports.checkAccess = async function(padID, sessionCookie, token, password, userSettings)
{
if (!padID) {
authLogger.debug('access denied: missing padID');
return DENY;
}
2011-11-21 12:44:33 -05:00
let canCreate = !settings.editOnly;
if (settings.requireAuthentication) {
// Make sure the user has authenticated if authentication is required. The caller should have
// already performed this check, but it is repeated here just in case.
if (userSettings == null) {
authLogger.debug('access denied: authentication is required');
return DENY;
}
// Check whether the user is authorized. Note that userSettings.padAuthorizations will still be
// populated even if settings.requireAuthorization is false.
const padAuthzs = userSettings.padAuthorizations || {};
const level = webaccess.normalizeAuthzLevel(padAuthzs[padID]);
if (!level) {
authLogger.debug('access denied: unauthorized');
return DENY;
}
if (level !== 'create') canCreate = false;
}
// allow plugins to deny access
const isFalse = (x) => x === false;
if (hooks.callAll('onAccessCheck', {padID, password, token, sessionCookie}).some(isFalse)) {
authLogger.debug('access denied: an onAccessCheck hook function returned false');
return DENY;
}
// start fetching the info we may need
const p_sessionAuthorID = sessionManager.findAuthorID(padID.split('$')[0], sessionCookie);
const p_tokenAuthorID = authorManager.getAuthor4Token(token);
const p_padExists = padManager.doesPadExist(padID);
const padExists = await p_padExists;
if (!padExists && !canCreate) {
authLogger.debug('access denied: user attempted to create a pad, which is prohibited');
return DENY;
}
const sessionAuthorID = await p_sessionAuthorID;
if (settings.requireSession && !sessionAuthorID) {
authLogger.debug('access denied: HTTP API session is required');
return DENY;
}
const grant = {
accessStatus: 'grant',
authorID: (sessionAuthorID != null) ? sessionAuthorID : await p_tokenAuthorID,
};
if (!padID.includes('$')) {
// Only group pads can be private or have passwords, so there is nothing more to check for this
// non-group pad.
return grant;
}
if (!padExists) {
if (sessionAuthorID == null) {
authLogger.debug('access denied: must have an HTTP API session to create a group pad');
return DENY;
}
// Creating a group pad, so there is no password or public status to check.
return grant;
}
const pad = await padManager.getPad(padID);
if (!pad.getPublicStatus() && sessionAuthorID == null) {
authLogger.debug('access denied: must have an HTTP API session to access private group pads');
return DENY;
}
const passwordExempt = settings.sessionNoPassword && sessionAuthorID != null;
const requirePassword = pad.isPasswordProtected() && !passwordExempt;
if (requirePassword) {
if (password == null) {
authLogger.debug('access denied: password required');
return NEED_PASSWORD;
}
if (!password || !pad.isCorrectPassword(password)) {
authLogger.debug('access denied: wrong password');
return WRONG_PASSWORD;
}
}
return grant;
};