2011-08-13 22:07:21 +01:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2011-12-04 16:50:02 +01:00
|
|
|
|
2011-08-13 22:07:21 +01:00
|
|
|
var authorManager = require("./AuthorManager");
|
2017-07-10 20:54:32 +02:00
|
|
|
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
|
2011-08-13 22:07:21 +01:00
|
|
|
var padManager = require("./PadManager");
|
|
|
|
var sessionManager = require("./SessionManager");
|
2012-12-06 02:00:58 +01:00
|
|
|
var settings = require("../utils/Settings");
|
2013-03-30 20:46:56 +01:00
|
|
|
var log4js = require('log4js');
|
|
|
|
var authLogger = log4js.getLogger("auth");
|
2012-01-28 13:24:58 +01:00
|
|
|
|
2011-08-13 22:07:21 +01:00
|
|
|
/**
|
|
|
|
* 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
|
2017-07-10 20:59:08 +02:00
|
|
|
* @param sessionCookie the session the user has (set via api)
|
2011-08-13 22:07:21 +01:00
|
|
|
* @param token the token of the author (randomly generated at client side, used for public pads)
|
2019-02-08 23:20:57 +01:00
|
|
|
* @param password the password the user has given to access this pad, can be null
|
2019-01-28 13:13:24 +00:00
|
|
|
* @return {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx})
|
2019-02-08 23:20:57 +01:00
|
|
|
*/
|
2019-01-28 13:13:24 +00:00
|
|
|
exports.checkAccess = async function(padID, sessionCookie, token, password)
|
2019-02-08 23:20:57 +01:00
|
|
|
{
|
2019-01-28 13:13:24 +00:00
|
|
|
// immutable object
|
|
|
|
let deny = Object.freeze({ accessStatus: "deny" });
|
2019-02-08 23:20:57 +01:00
|
|
|
|
|
|
|
if (!padID) {
|
2019-01-28 13:13:24 +00:00
|
|
|
return deny;
|
2013-10-12 18:41:48 +02:00
|
|
|
}
|
2011-11-21 12:44:33 -05:00
|
|
|
|
2017-07-10 20:54:32 +02:00
|
|
|
// allow plugins to deny access
|
|
|
|
var deniedByHook = hooks.callAll("onAccessCheck", {'padID': padID, 'password': password, 'token': token, 'sessionCookie': sessionCookie}).indexOf(false) > -1;
|
2019-02-08 23:20:57 +01:00
|
|
|
if (deniedByHook) {
|
2019-01-28 13:13:24 +00:00
|
|
|
return deny;
|
2017-07-10 20:54:32 +02:00
|
|
|
}
|
|
|
|
|
2019-02-01 09:57:50 +00:00
|
|
|
// start to get author for this token
|
|
|
|
let p_tokenAuthor = authorManager.getAuthor4Token(token);
|
2019-01-28 13:13:24 +00:00
|
|
|
|
2019-02-01 09:57:50 +00:00
|
|
|
// start to check if pad exists
|
|
|
|
let p_padExists = padManager.doesPadExist(padID);
|
2019-01-28 13:13:24 +00:00
|
|
|
|
2019-02-08 23:20:57 +01:00
|
|
|
if (settings.requireSession) {
|
|
|
|
// a valid session is required (api-only mode)
|
|
|
|
if (!sessionCookie) {
|
|
|
|
// without sessionCookie, access is denied
|
2019-01-28 13:13:24 +00:00
|
|
|
return deny;
|
2011-11-21 01:45:37 -05:00
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
} else {
|
|
|
|
// a session is not required, so we'll check if it's a public pad
|
2019-03-01 09:43:41 +01:00
|
|
|
if (padID.indexOf("$") === -1) {
|
2019-02-08 23:20:57 +01:00
|
|
|
// it's not a group pad, means we can grant access
|
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// assume user has access
|
2019-02-01 09:57:50 +00:00
|
|
|
let authorID = await p_tokenAuthor;
|
|
|
|
let statusObject = { accessStatus: "grant", authorID };
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (settings.editOnly) {
|
|
|
|
// user can't create pads
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-02-01 09:57:50 +00:00
|
|
|
let padExists = await p_padExists;
|
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (!padExists) {
|
|
|
|
// pad doesn't exist - user can't have access
|
|
|
|
statusObject.accessStatus = "deny";
|
|
|
|
}
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// user may create new pads - no need to check anything
|
|
|
|
// grant access, with author of token
|
|
|
|
return statusObject;
|
|
|
|
}
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
let validSession = false;
|
|
|
|
let sessionAuthor;
|
|
|
|
let isPublic;
|
|
|
|
let isPasswordProtected;
|
|
|
|
let passwordStatus = password == null ? "notGiven" : "wrong"; // notGiven, correct, wrong
|
|
|
|
|
|
|
|
// get information about all sessions contained in this cookie
|
|
|
|
if (sessionCookie) {
|
|
|
|
let groupID = padID.split("$")[0];
|
SecurityManager: remove double quotes from session cookie content
Sometimes, RFC 6265-compliant [0] web servers may send back a cookie whose value
is enclosed in double quotes, such as:
Set-Cookie: sessionCookie="s.37cf5299fbf981e14121fba3a588c02b,s.2b21517bf50729d8130ab85736a11346"; Version=1; Path=/; Domain=localhost; Discard
Where the double quotes at the start and the end of the header value are just
delimiters. This is perfectly legal: Etherpad parsing logic should cope with
that, and remove the quotes early in the request phase.
Somehow, this does not happen, and in such cases the actual value that
sessionCookie ends up having is:
sessionCookie = '"s.37cf5299fbf981e14121fba3a588c02b,s.2b21517bf50729d8130ab85736a11346"'
As quick measure, let's strip the double quotes (when present).
Note that here we are being minimal, limiting ourselves to just removing quotes
at the start and the end of the string.
Fixes #3819.
Also, see #3820.
[0] https://tools.ietf.org/html/rfc6265
2020-04-02 10:43:25 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Sometimes, RFC 6265-compliant web servers may send back a cookie whose
|
|
|
|
* value is enclosed in double quotes, such as:
|
|
|
|
*
|
|
|
|
* Set-Cookie: sessionCookie="s.37cf5299fbf981e14121fba3a588c02b,s.2b21517bf50729d8130ab85736a11346"; Version=1; Path=/; Domain=localhost; Discard
|
|
|
|
*
|
|
|
|
* Where the double quotes at the start and the end of the header value are
|
|
|
|
* just delimiters. This is perfectly legal: Etherpad parsing logic should
|
|
|
|
* cope with that, and remove the quotes early in the request phase.
|
|
|
|
*
|
|
|
|
* Somehow, this does not happen, and in such cases the actual value that
|
|
|
|
* sessionCookie ends up having is:
|
|
|
|
*
|
|
|
|
* sessionCookie = '"s.37cf5299fbf981e14121fba3a588c02b,s.2b21517bf50729d8130ab85736a11346"'
|
|
|
|
*
|
|
|
|
* As quick measure, let's strip the double quotes (when present).
|
|
|
|
* Note that here we are being minimal, limiting ourselves to just removing
|
|
|
|
* quotes at the start and the end of the string.
|
|
|
|
*
|
|
|
|
* Fixes #3819.
|
|
|
|
* Also, see #3820.
|
|
|
|
*/
|
|
|
|
let sessionIDs = sessionCookie.replace(/^"|"$/g, '').split(',');
|
2019-01-28 13:13:24 +00:00
|
|
|
|
|
|
|
// was previously iterated in parallel using async.forEach
|
2019-12-26 00:30:43 +01:00
|
|
|
try {
|
|
|
|
let sessionInfos = await Promise.all(sessionIDs.map(sessionID => {
|
|
|
|
return sessionManager.getSessionInfo(sessionID);
|
|
|
|
}));
|
2019-01-28 13:13:24 +00:00
|
|
|
|
2019-12-26 00:30:43 +01:00
|
|
|
// seperated out the iteration of sessioninfos from the (parallel) fetches from the DB
|
|
|
|
for (let sessionInfo of sessionInfos) {
|
2019-01-28 13:13:24 +00:00
|
|
|
// is it for this group?
|
|
|
|
if (sessionInfo.groupID != groupID) {
|
|
|
|
authLogger.debug("Auth failed: wrong group");
|
|
|
|
continue;
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// is validUntil still ok?
|
|
|
|
let now = Math.floor(Date.now() / 1000);
|
|
|
|
if (sessionInfo.validUntil <= now) {
|
|
|
|
authLogger.debug("Auth failed: validUntil");
|
|
|
|
continue;
|
|
|
|
}
|
2018-08-29 02:33:29 +02:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// fall-through - there is a valid session
|
|
|
|
validSession = true;
|
|
|
|
sessionAuthor = sessionInfo.authorID;
|
|
|
|
break;
|
2019-12-26 00:30:43 +01:00
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
// skip session if it doesn't exist
|
|
|
|
if (err.message == "sessionID does not exist") {
|
|
|
|
authLogger.debug("Auth failed: unknown session");
|
|
|
|
} else {
|
|
|
|
throw err;
|
2019-01-28 13:13:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-01 09:57:50 +00:00
|
|
|
let padExists = await p_padExists;
|
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (padExists) {
|
|
|
|
let pad = await padManager.getPad(padID);
|
2018-08-29 02:33:29 +02:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// is it a public pad?
|
|
|
|
isPublic = pad.getPublicStatus();
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// is it password protected?
|
|
|
|
isPasswordProtected = pad.isPasswordProtected();
|
|
|
|
|
|
|
|
// is password correct?
|
|
|
|
if (isPasswordProtected && password && pad.isCorrectPassword(password)) {
|
|
|
|
passwordStatus = "correct";
|
2011-11-21 01:45:37 -05:00
|
|
|
}
|
2011-08-13 22:07:21 +01:00
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// - a valid session for this group is avaible AND pad exists
|
|
|
|
if (validSession && padExists) {
|
|
|
|
let authorID = sessionAuthor;
|
|
|
|
let grant = Object.freeze({ accessStatus: "grant", authorID });
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (!isPasswordProtected) {
|
|
|
|
// - the pad is not password protected
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// --> grant access
|
|
|
|
return grant;
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (settings.sessionNoPassword) {
|
|
|
|
// - the setting to bypass password validation is set
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// --> grant access
|
|
|
|
return grant;
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (isPasswordProtected && passwordStatus === "correct") {
|
|
|
|
// - the pad is password protected and password is correct
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// --> grant access
|
|
|
|
return grant;
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (isPasswordProtected && passwordStatus === "wrong") {
|
|
|
|
// - the pad is password protected but wrong password given
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// --> deny access, ask for new password and tell them that the password is wrong
|
|
|
|
return { accessStatus: "wrongPassword" };
|
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (isPasswordProtected && passwordStatus === "notGiven") {
|
|
|
|
// - the pad is password protected but no password given
|
|
|
|
|
|
|
|
// --> ask for password
|
|
|
|
return { accessStatus: "needPassword" };
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error("Oops, something wrong happend");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (validSession && !padExists) {
|
|
|
|
// - a valid session for this group avaible but pad doesn't exist
|
|
|
|
|
|
|
|
// --> grant access by default
|
|
|
|
let accessStatus = "grant";
|
|
|
|
let authorID = sessionAuthor;
|
|
|
|
|
|
|
|
// --> deny access if user isn't allowed to create the pad
|
|
|
|
if (settings.editOnly) {
|
|
|
|
authLogger.debug("Auth failed: valid session & pad does not exist");
|
|
|
|
accessStatus = "deny";
|
|
|
|
}
|
|
|
|
|
|
|
|
return { accessStatus, authorID };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!validSession && padExists) {
|
|
|
|
// there is no valid session avaiable AND pad exists
|
|
|
|
|
2019-02-01 09:57:50 +00:00
|
|
|
let authorID = await p_tokenAuthor;
|
2019-01-28 13:13:24 +00:00
|
|
|
let grant = Object.freeze({ accessStatus: "grant", authorID });
|
|
|
|
|
|
|
|
if (isPublic && !isPasswordProtected) {
|
|
|
|
// -- it's public and not password protected
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
// --> grant access, with author of token
|
|
|
|
return grant;
|
2011-08-13 22:07:21 +01:00
|
|
|
}
|
2019-02-08 23:20:57 +01:00
|
|
|
|
2019-01-28 13:13:24 +00:00
|
|
|
if (isPublic && isPasswordProtected && passwordStatus === "correct") {
|
|
|
|
// - it's public and password protected and password is correct
|
|
|
|
|
|
|
|
// --> grant access, with author of token
|
|
|
|
return grant;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
return { accessStatus: "wrongPassword" };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isPublic && isPasswordProtected && passwordStatus === "notGiven") {
|
|
|
|
// - it's public and the pad is password protected but no password given
|
|
|
|
|
|
|
|
// --> ask for password
|
|
|
|
return { accessStatus: "needPassword" };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isPublic) {
|
|
|
|
// - it's not public
|
|
|
|
|
|
|
|
authLogger.debug("Auth failed: invalid session & pad is not public");
|
|
|
|
// --> deny access
|
|
|
|
return { accessStatus: "deny" };
|
|
|
|
}
|
|
|
|
|
|
|
|
throw new Error("Oops, something wrong happend");
|
|
|
|
}
|
|
|
|
|
|
|
|
// there is no valid session avaiable AND pad doesn't exist
|
|
|
|
authLogger.debug("Auth failed: invalid session & pad does not exist");
|
|
|
|
return { accessStatus: "deny" };
|
|
|
|
}
|