db/SecurityManager.js: converted checkAccess() to pure Promises
Also converted the handler functions that depend on checkAccess() into async functions too. NB: this commit needs specific attention to it because it touches a lot of security related code!pull/3559/head
parent
7709fd46e5
commit
e58da69cfb
|
@ -18,8 +18,6 @@
|
|||
* 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");
|
||||
|
@ -35,270 +33,222 @@ const thenify = require("thenify").withCallback;
|
|||
* @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})
|
||||
* @return {accessStatus: grant|deny|wrongPassword|needPassword, authorID: a.xxxxxx})
|
||||
*/
|
||||
exports.checkAccess = thenify(function(padID, sessionCookie, token, password, callback)
|
||||
exports.checkAccess = async function(padID, sessionCookie, token, password)
|
||||
{
|
||||
var statusObject;
|
||||
// immutable object
|
||||
let deny = Object.freeze({ accessStatus: "deny" });
|
||||
|
||||
if (!padID) {
|
||||
callback(null, {accessStatus: "deny"});
|
||||
return;
|
||||
return deny;
|
||||
}
|
||||
|
||||
// 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;
|
||||
return deny;
|
||||
}
|
||||
|
||||
// get author for this token
|
||||
let tokenAuthor = await authorManager.getAuthor4Token(token);
|
||||
|
||||
// check if pad exists
|
||||
let padExists = await padManager.doesPadExist(padID);
|
||||
|
||||
if (settings.requireSession) {
|
||||
// a valid session is required (api-only mode)
|
||||
if (!sessionCookie) {
|
||||
// without sessionCookie, access is denied
|
||||
callback(null, {accessStatus: "deny"});
|
||||
|
||||
return;
|
||||
return deny;
|
||||
}
|
||||
} 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
|
||||
let statusObject = { accessStatus: "grant", authorID: tokenAuthor };
|
||||
|
||||
// assume user has access
|
||||
statusObject = { accessStatus: "grant", authorID: author };
|
||||
if (settings.editOnly) {
|
||||
// user can't create pads
|
||||
|
||||
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;
|
||||
if (!padExists) {
|
||||
// pad doesn't exist - user can't have access
|
||||
statusObject.accessStatus = "deny";
|
||||
}
|
||||
}
|
||||
|
||||
// user may create new pads - no need to check anything
|
||||
// grant access, with author of token
|
||||
callback(null, statusObject);
|
||||
});
|
||||
|
||||
// don't continue
|
||||
return;
|
||||
// user may create new pads - no need to check anything
|
||||
// grant access, with author of token
|
||||
return statusObject;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
let validSession = false;
|
||||
let sessionAuthor;
|
||||
let isPublic;
|
||||
let isPasswordProtected;
|
||||
let 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;
|
||||
// get information about all sessions contained in this cookie
|
||||
if (sessionCookie) {
|
||||
let groupID = padID.split("$")[0];
|
||||
let sessionIDs = sessionCookie.split(',');
|
||||
|
||||
padExists = exists;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// was previously iterated in parallel using async.forEach
|
||||
for (let sessionID of sessionIDs) {
|
||||
try {
|
||||
let sessionInfo = await sessionManager.getSessionInfo(sessionID);
|
||||
|
||||
// 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";
|
||||
// is it for this group?
|
||||
if (sessionInfo.groupID != groupID) {
|
||||
authLogger.debug("Auth failed: wrong group");
|
||||
continue;
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// is validUntil still ok?
|
||||
let now = Math.floor(Date.now() / 1000);
|
||||
if (sessionInfo.validUntil <= now) {
|
||||
authLogger.debug("Auth failed: validUntil");
|
||||
continue;
|
||||
}
|
||||
|
||||
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" };
|
||||
// fall-through - there is a valid session
|
||||
validSession = true;
|
||||
sessionAuthor = sessionInfo.authorID;
|
||||
break;
|
||||
} catch (err) {
|
||||
// skip session if it doesn't exist
|
||||
if (err.message == "sessionID does not exist") {
|
||||
authLogger.debug("Auth failed: unknown session");
|
||||
} else {
|
||||
throw new Error("Ops, something wrong happend");
|
||||
throw err;
|
||||
}
|
||||
} 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);
|
||||
});
|
||||
});
|
||||
if (padExists) {
|
||||
let pad = await padManager.getPad(padID);
|
||||
|
||||
// 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";
|
||||
}
|
||||
}
|
||||
|
||||
// - a valid session for this group is avaible AND pad exists
|
||||
if (validSession && padExists) {
|
||||
let authorID = sessionAuthor;
|
||||
let grant = Object.freeze({ accessStatus: "grant", authorID });
|
||||
|
||||
if (!isPasswordProtected) {
|
||||
// - the pad is not password protected
|
||||
|
||||
// --> grant access
|
||||
return grant;
|
||||
}
|
||||
|
||||
if (settings.sessionNoPassword) {
|
||||
// - the setting to bypass password validation is set
|
||||
|
||||
// --> grant access
|
||||
return grant;
|
||||
}
|
||||
|
||||
if (isPasswordProtected && passwordStatus === "correct") {
|
||||
// - the pad is password protected and password is correct
|
||||
|
||||
// --> grant access
|
||||
return grant;
|
||||
}
|
||||
|
||||
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
|
||||
return { accessStatus: "wrongPassword" };
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
let authorID = tokenAuthor;
|
||||
let grant = Object.freeze({ accessStatus: "grant", authorID });
|
||||
|
||||
if (isPublic && !isPasswordProtected) {
|
||||
// -- it's public and not password protected
|
||||
|
||||
// --> grant access, with author of token
|
||||
return grant;
|
||||
}
|
||||
|
||||
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" };
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ exports.handleDisconnect = function(client)
|
|||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
exports.handleMessage = function(client, message)
|
||||
exports.handleMessage = async function(client, message)
|
||||
{
|
||||
if (message == null) {
|
||||
return;
|
||||
|
@ -181,35 +181,33 @@ exports.handleMessage = function(client, message)
|
|||
return;
|
||||
}
|
||||
|
||||
var handleMessageHook = function(callback) {
|
||||
async function handleMessageHook() {
|
||||
// Allow plugins to bypass the readonly message blocker
|
||||
hooks.aCallAll("handleMessageSecurity", { client: client, message: message }, function( err, messages ) {
|
||||
if(ERR(err, callback)) return;
|
||||
_.each(messages, function(newMessage){
|
||||
if ( newMessage === true ) {
|
||||
thisSession.readonly = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
let messages = await hooks.aCallAll("handleMessageSecurity", { client: client, message: message });
|
||||
|
||||
for (let message of messages) {
|
||||
if (message === true) {
|
||||
thisSession.readonly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let dropMessage = false;
|
||||
|
||||
var dropMessage = false;
|
||||
// Call handleMessage hook. If a plugin returns null, the message will be dropped. Note that for all messages
|
||||
// handleMessage will be called, even if the client is not authorized
|
||||
hooks.aCallAll("handleMessage", { client: client, message: message }, function( err, messages ) {
|
||||
if(ERR(err, callback)) return;
|
||||
_.each(messages, function(newMessage){
|
||||
if ( newMessage === null ) {
|
||||
dropMessage = true;
|
||||
}
|
||||
});
|
||||
|
||||
// If no plugins explicitly told us to drop the message, its ok to proceed
|
||||
if(!dropMessage){ callback() };
|
||||
});
|
||||
messages = await hooks.aCallAll("handleMessage", { client: client, message: message });
|
||||
for (let message of messages) {
|
||||
if (message === null ) {
|
||||
dropMessage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return dropMessage;
|
||||
}
|
||||
|
||||
var finalHandler = function() {
|
||||
function finalHandler() {
|
||||
// Check what type of message we get and delegate to the other methods
|
||||
if (message.type == "CLIENT_READY") {
|
||||
handleClientReady(client, message);
|
||||
|
@ -256,54 +254,49 @@ exports.handleMessage = function(client, message)
|
|||
return;
|
||||
}
|
||||
|
||||
async.series([
|
||||
handleMessageHook,
|
||||
let dropMessage = await handleMessageHook();
|
||||
if (!dropMessage) {
|
||||
|
||||
// check permissions
|
||||
function(callback) {
|
||||
// client tried to auth for the first time (first msg from the client)
|
||||
if (message.type == "CLIENT_READY") {
|
||||
|
||||
// client tried to auth for the first time (first msg from the client)
|
||||
if (message.type == "CLIENT_READY") {
|
||||
createSessionInfo(client, message);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: message.sessionID is an entirely different kind of
|
||||
// session from the sessions we use here! Beware!
|
||||
// FIXME: Call our "sessions" "connections".
|
||||
// FIXME: Use a hook instead
|
||||
// FIXME: Allow to override readwrite access with readonly
|
||||
// Note: message.sessionID is an entirely different kind of
|
||||
// session from the sessions we use here! Beware!
|
||||
// FIXME: Call our "sessions" "connections".
|
||||
// FIXME: Use a hook instead
|
||||
// FIXME: Allow to override readwrite access with readonly
|
||||
|
||||
// Simulate using the load testing tool
|
||||
if (!sessioninfos[client.id].auth) {
|
||||
console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.")
|
||||
return;
|
||||
}
|
||||
// Simulate using the load testing tool
|
||||
if (!sessioninfos[client.id].auth) {
|
||||
console.error("Auth was never applied to a session. If you are using the stress-test tool then restart Etherpad and the Stress test tool.")
|
||||
return;
|
||||
}
|
||||
|
||||
var auth = sessioninfos[client.id].auth;
|
||||
var checkAccessCallback = function(err, statusObject) {
|
||||
if (ERR(err, callback)) return;
|
||||
let auth = sessioninfos[client.id].auth;
|
||||
|
||||
if (statusObject.accessStatus == "grant") {
|
||||
// access was granted
|
||||
callback();
|
||||
} else {
|
||||
// no access, send the client a message that tells him why
|
||||
client.json.send({accessStatus: statusObject.accessStatus})
|
||||
}
|
||||
};
|
||||
// check if pad is requested via readOnly
|
||||
let padId = auth.padID;
|
||||
|
||||
// check if pad is requested via readOnly
|
||||
if (auth.padID.indexOf("r.") === 0) {
|
||||
// Pad is readOnly, first get the real Pad ID
|
||||
readOnlyManager.getPadId(auth.padID, function(err, value) {
|
||||
ERR(err);
|
||||
securityManager.checkAccess(value, auth.sessionID, auth.token, auth.password, checkAccessCallback);
|
||||
});
|
||||
} else {
|
||||
securityManager.checkAccess(auth.padID, auth.sessionID, auth.token, auth.password, checkAccessCallback);
|
||||
}
|
||||
},
|
||||
finalHandler
|
||||
]);
|
||||
// Pad is readOnly, first get the real Pad ID
|
||||
if (padId.indexOf("r.") === 0) {
|
||||
padId = await readOnlyManager.getPadId(padID);
|
||||
}
|
||||
|
||||
let { accessStatus } = await securityManager.checkAccess(padId, auth.sessionID, auth.token, auth.password);
|
||||
|
||||
// no access, send the client a message that tells him why
|
||||
if (accessStatus !== "grant") {
|
||||
client.json.send({ accessStatus });
|
||||
return;
|
||||
}
|
||||
|
||||
// access was granted
|
||||
finalHandler();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -977,7 +970,7 @@ function createSessionInfo(client, message)
|
|||
* @param client the client that send this message
|
||||
* @param message the message from the client
|
||||
*/
|
||||
function handleClientReady(client, message)
|
||||
async function handleClientReady(client, message)
|
||||
{
|
||||
// check if all ok
|
||||
if (!message.token) {
|
||||
|
@ -1000,434 +993,319 @@ function handleClientReady(client, message)
|
|||
return;
|
||||
}
|
||||
|
||||
var author;
|
||||
var authorName;
|
||||
var authorColorId;
|
||||
var pad;
|
||||
var historicalAuthorData = {};
|
||||
var currentTime;
|
||||
var padIds;
|
||||
|
||||
hooks.callAll("clientReady", message);
|
||||
|
||||
async.series([
|
||||
// Get ro/rw id:s
|
||||
function(callback) {
|
||||
readOnlyManager.getIds(message.padId, function(err, value) {
|
||||
if (ERR(err, callback)) return;
|
||||
// Get ro/rw id:s
|
||||
let padIds = await readOnlyManager.getIds(message.padId);
|
||||
|
||||
padIds = value;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// check permissions
|
||||
|
||||
// check permissions
|
||||
function(callback) {
|
||||
// Note: message.sessionID is an entierly different kind of
|
||||
// session from the sessions we use here! Beware!
|
||||
// FIXME: Call our "sessions" "connections".
|
||||
// FIXME: Use a hook instead
|
||||
// FIXME: Allow to override readwrite access with readonly
|
||||
securityManager.checkAccess(padIds.padId, message.sessionID, message.token, message.password, function(err, statusObject) {
|
||||
if (ERR(err, callback)) return;
|
||||
// Note: message.sessionID is an entierly different kind of
|
||||
// session from the sessions we use here! Beware!
|
||||
// FIXME: Call our "sessions" "connections".
|
||||
// FIXME: Use a hook instead
|
||||
// FIXME: Allow to override readwrite access with readonly
|
||||
let statusObject = await securityManager.checkAccess(padIds.padId, message.sessionID, message.token, message.password);
|
||||
let accessStatus = statusObject.accessStatus;
|
||||
|
||||
if (statusObject.accessStatus == "grant") {
|
||||
// access was granted
|
||||
author = statusObject.authorID;
|
||||
callback();
|
||||
} else {
|
||||
// no access, send the client a message that tells him why
|
||||
client.json.send({accessStatus: statusObject.accessStatus})
|
||||
}
|
||||
});
|
||||
},
|
||||
// no access, send the client a message that tells him why
|
||||
if (accessStatus !== "grant") {
|
||||
client.json.send({ accessStatus });
|
||||
return;
|
||||
}
|
||||
|
||||
// get all authordata of this new user, and load the pad-object from the database
|
||||
function(callback)
|
||||
{
|
||||
async.parallel([
|
||||
// get colorId and name
|
||||
function(callback) {
|
||||
authorManager.getAuthor(author, function(err, value) {
|
||||
if (ERR(err, callback)) return;
|
||||
let author = statusObject.authorID;
|
||||
|
||||
authorColorId = value.colorId;
|
||||
authorName = value.name;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// get all authordata of this new user, and load the pad-object from the database
|
||||
let value = await authorManager.getAuthor(author);
|
||||
let authorColorId = value.colorId;
|
||||
let authorName = value.name;
|
||||
|
||||
// get pad
|
||||
function(callback) {
|
||||
padManager.getPad(padIds.padId, function(err, value) {
|
||||
if (ERR(err, callback)) return;
|
||||
// get pad
|
||||
let pad = await padManager.getPad(padIds.padId);
|
||||
|
||||
pad = value;
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
// these db requests all need the pad object (timestamp of latest revision, author data)
|
||||
let authors = pad.getAllAuthors();
|
||||
|
||||
// these db requests all need the pad object (timestamp of latest revission, author data)
|
||||
function(callback) {
|
||||
var authors = pad.getAllAuthors();
|
||||
// get timestamp of latest revision needed for timeslider
|
||||
let currentTime = await pad.getRevisionDate(pad.getHeadRevisionNumber());
|
||||
|
||||
async.parallel([
|
||||
// get timestamp of latest revission needed for timeslider
|
||||
function(callback) {
|
||||
pad.getRevisionDate(pad.getHeadRevisionNumber(), function(err, date) {
|
||||
if (ERR(err, callback)) return;
|
||||
// get all author data out of the database
|
||||
for (let authorId of authors) {
|
||||
try {
|
||||
let author = await authorManager.getAuthor(authorId);
|
||||
historicalAuthorData[authorId] = { name: author.name, colorId: author.colorId }; // Filter author attribs (e.g. don't send author's pads to all clients)
|
||||
} catch (err) {
|
||||
messageLogger.error("There is no author for authorId:", authorId);
|
||||
}
|
||||
}
|
||||
|
||||
currentTime = date;
|
||||
callback();
|
||||
});
|
||||
},
|
||||
// glue the clientVars together, send them and tell the other clients that a new one is there
|
||||
|
||||
// get all author data out of the database
|
||||
function(callback) {
|
||||
async.forEach(authors, function(authorId, callback) {
|
||||
authorManager.getAuthor(authorId, function(err, author) {
|
||||
if (!author && !err) {
|
||||
messageLogger.error("There is no author for authorId:", authorId);
|
||||
// Check that the client is still here. It might have disconnected between callbacks.
|
||||
if (sessioninfos[client.id] === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
return callback();
|
||||
}
|
||||
// Check if this author is already on the pad, if yes, kick the other sessions!
|
||||
let roomClients = _getRoomClients(pad.id);
|
||||
|
||||
if (ERR(err, callback)) return;
|
||||
for (let client of roomClients) {
|
||||
let sinfo = sessioninfos[client.id];
|
||||
if (sinfo && sinfo.author == author) {
|
||||
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
|
||||
sessioninfos[client.id] = {};
|
||||
client.leave(padIds.padId);
|
||||
client.json.send({disconnect:"userdup"});
|
||||
}
|
||||
}
|
||||
|
||||
historicalAuthorData[authorId] = { name: author.name, colorId: author.colorId }; // Filter author attribs (e.g. don't send author's pads to all clients)
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
], callback);
|
||||
// Save in sessioninfos that this session belonges to this pad
|
||||
sessioninfos[client.id].padId = padIds.padId;
|
||||
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
|
||||
sessioninfos[client.id].readonly = padIds.readonly;
|
||||
|
||||
},
|
||||
// Log creation/(re-)entering of a pad
|
||||
let ip = remoteAddress[client.id];
|
||||
|
||||
// glue the clientVars together, send them and tell the other clients that a new one is there
|
||||
function(callback) {
|
||||
// Check that the client is still here. It might have disconnected between callbacks.
|
||||
if(sessioninfos[client.id] === undefined) {
|
||||
return callback();
|
||||
}
|
||||
// Anonymize the IP address if IP logging is disabled
|
||||
if (settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
|
||||
// Check if this author is already on the pad, if yes, kick the other sessions!
|
||||
var roomClients = _getRoomClients(pad.id);
|
||||
if (pad.head > 0) {
|
||||
accessLogger.info('[ENTER] Pad "' + padIds.padId + '": Client ' + client.id + ' with IP "' + ip + '" entered the pad');
|
||||
} else if (pad.head == 0) {
|
||||
accessLogger.info('[CREATE] Pad "' + padIds.padId + '": Client ' + client.id + ' with IP "' + ip + '" created the pad');
|
||||
}
|
||||
|
||||
async.forEach(roomClients, function(client, callback) {
|
||||
var sinfo = sessioninfos[client.id];
|
||||
if (message.reconnect) {
|
||||
// If this is a reconnect, we don't have to send the client the ClientVars again
|
||||
// Join the pad and start receiving updates
|
||||
client.join(padIds.padId);
|
||||
|
||||
if (sinfo && sinfo.author == author) {
|
||||
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
|
||||
sessioninfos[client.id] = {};
|
||||
client.leave(padIds.padId);
|
||||
client.json.send({ disconnect:"userdup" });
|
||||
}
|
||||
});
|
||||
// Save the revision in sessioninfos, we take the revision from the info the client send to us
|
||||
sessioninfos[client.id].rev = message.client_rev;
|
||||
|
||||
// Save in sessioninfos that this session belonges to this pad
|
||||
sessioninfos[client.id].padId = padIds.padId;
|
||||
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
|
||||
sessioninfos[client.id].readonly = padIds.readonly;
|
||||
// During the client reconnect, client might miss some revisions from other clients. By using client revision,
|
||||
// this below code sends all the revisions missed during the client reconnect
|
||||
var revisionsNeeded = [];
|
||||
var changesets = {};
|
||||
|
||||
// Log creation/(re-)entering of a pad
|
||||
var ip = remoteAddress[client.id];
|
||||
var startNum = message.client_rev + 1;
|
||||
var endNum = pad.getHeadRevisionNumber() + 1;
|
||||
|
||||
// Anonymize the IP address if IP logging is disabled
|
||||
if (settings.disableIPlogging) {
|
||||
ip = 'ANONYMOUS';
|
||||
}
|
||||
var headNum = pad.getHeadRevisionNumber();
|
||||
|
||||
if (pad.head > 0) {
|
||||
accessLogger.info('[ENTER] Pad "' + padIds.padId + '": Client ' + client.id + ' with IP "' + ip + '" entered the pad');
|
||||
} else if (pad.head == 0) {
|
||||
accessLogger.info('[CREATE] Pad "' + padIds.padId + '": Client ' + client.id + ' with IP "' + ip + '" created the pad');
|
||||
}
|
||||
if (endNum > headNum + 1) {
|
||||
endNum = headNum + 1;
|
||||
}
|
||||
|
||||
if (message.reconnect == true) {
|
||||
// If this is a reconnect, we don't have to send the client the ClientVars again
|
||||
// Join the pad and start receiving updates
|
||||
client.join(padIds.padId);
|
||||
if (startNum < 0) {
|
||||
startNum = 0;
|
||||
}
|
||||
|
||||
// Save the revision in sessioninfos, we take the revision from the info the client send to us
|
||||
sessioninfos[client.id].rev = message.client_rev;
|
||||
for (let r = startNum; r < endNum; r++) {
|
||||
revisionsNeeded.push(r);
|
||||
changesets[r] = {};
|
||||
}
|
||||
|
||||
// During the client reconnect, client might miss some revisions from other clients. By using client revision,
|
||||
// this below code sends all the revisions missed during the client reconnect
|
||||
var revisionsNeeded = [];
|
||||
var changesets = {};
|
||||
// get changesets, author and timestamp needed for pending revisions
|
||||
for (let revNum of revisionsNeeded) {
|
||||
changesets[revNum]['changeset'] = await pad.getRevisionChangeset(revNum);
|
||||
changesets[revNum]['author'] = await pad.getRevisionAuthor(revNum);
|
||||
changesets[revNum]['timestamp'] = await pad.getRevisionDate(revNum);
|
||||
}
|
||||
|
||||
var startNum = message.client_rev + 1;
|
||||
var endNum = pad.getHeadRevisionNumber() + 1;
|
||||
// return pending changesets
|
||||
for (let r of revisionsNeeded) {
|
||||
|
||||
async.series([
|
||||
// push all the revision numbers needed into revisionsNeeded array
|
||||
function(callback) {
|
||||
var headNum = pad.getHeadRevisionNumber();
|
||||
|
||||
if (endNum > headNum+1) {
|
||||
endNum = headNum+1;
|
||||
}
|
||||
|
||||
if (startNum < 0) {
|
||||
startNum = 0;
|
||||
}
|
||||
|
||||
for (var r = startNum; r < endNum; r++) {
|
||||
revisionsNeeded.push(r);
|
||||
changesets[r] = {};
|
||||
}
|
||||
|
||||
callback();
|
||||
},
|
||||
|
||||
// get changesets needed for pending revisions
|
||||
function(callback) {
|
||||
async.eachSeries(revisionsNeeded, function(revNum, callback) {
|
||||
pad.getRevisionChangeset(revNum, function(err, value) {
|
||||
if (ERR(err)) return;
|
||||
|
||||
changesets[revNum]['changeset'] = value;
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
|
||||
// get author for each changeset
|
||||
function(callback) {
|
||||
async.eachSeries(revisionsNeeded, function(revNum, callback) {
|
||||
pad.getRevisionAuthor(revNum, function(err, value) {
|
||||
if (ERR(err)) return;
|
||||
|
||||
changesets[revNum]['author'] = value;
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
},
|
||||
|
||||
// get timestamp for each changeset
|
||||
function(callback) {
|
||||
async.eachSeries(revisionsNeeded, function(revNum, callback) {
|
||||
pad.getRevisionDate(revNum, function(err, value) {
|
||||
if (ERR(err)) return;
|
||||
|
||||
changesets[revNum]['timestamp'] = value;
|
||||
callback();
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
],
|
||||
|
||||
// return error and pending changesets
|
||||
function(err) {
|
||||
if (ERR(err, callback)) return;
|
||||
|
||||
async.eachSeries(revisionsNeeded, function(r, callback) {
|
||||
var forWire = Changeset.prepareForWire(changesets[r]['changeset'], pad.pool);
|
||||
var wireMsg = {"type":"COLLABROOM",
|
||||
"data":{type:"CLIENT_RECONNECT",
|
||||
headRev:pad.getHeadRevisionNumber(),
|
||||
newRev:r,
|
||||
changeset:forWire.translated,
|
||||
apool: forWire.pool,
|
||||
author: changesets[r]['author'],
|
||||
currentTime: changesets[r]['timestamp']
|
||||
let forWire = Changeset.prepareForWire(changesets[r]['changeset'], pad.pool);
|
||||
let wireMsg = {"type":"COLLABROOM",
|
||||
"data":{type:"CLIENT_RECONNECT",
|
||||
headRev:pad.getHeadRevisionNumber(),
|
||||
newRev:r,
|
||||
changeset:forWire.translated,
|
||||
apool: forWire.pool,
|
||||
author: changesets[r]['author'],
|
||||
currentTime: changesets[r]['timestamp']
|
||||
}};
|
||||
client.json.send(wireMsg);
|
||||
callback();
|
||||
});
|
||||
client.json.send(wireMsg);
|
||||
}
|
||||
|
||||
if (startNum == endNum) {
|
||||
var Msg = {"type":"COLLABROOM",
|
||||
"data":{type:"CLIENT_RECONNECT",
|
||||
noChanges: true,
|
||||
newRev: pad.getHeadRevisionNumber()
|
||||
}};
|
||||
client.json.send(Msg);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// This is a normal first connect
|
||||
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
|
||||
try {
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
} catch(e) {
|
||||
console.error(e.stack || e)
|
||||
client.json.send({ disconnect:"corruptPad" });// pull the brakes
|
||||
return callback();
|
||||
}
|
||||
if (startNum == endNum) {
|
||||
var Msg = {"type":"COLLABROOM",
|
||||
"data":{type:"CLIENT_RECONNECT",
|
||||
noChanges: true,
|
||||
newRev: pad.getHeadRevisionNumber()
|
||||
}};
|
||||
client.json.send(Msg);
|
||||
}
|
||||
|
||||
// Warning: never ever send padIds.padId to the client. If the
|
||||
// client is read only you would open a security hole 1 swedish
|
||||
// mile wide...
|
||||
var clientVars = {
|
||||
"skinName": settings.skinName,
|
||||
"accountPrivs": {
|
||||
"maxRevisions": 100
|
||||
},
|
||||
"automaticReconnectionTimeout": settings.automaticReconnectionTimeout,
|
||||
"initialRevisionList": [],
|
||||
"initialOptions": {
|
||||
"guestPolicy": "deny"
|
||||
},
|
||||
"savedRevisions": pad.getSavedRevisions(),
|
||||
"collab_client_vars": {
|
||||
"initialAttributedText": atext,
|
||||
"clientIp": "127.0.0.1",
|
||||
"padId": message.padId,
|
||||
"historicalAuthorData": historicalAuthorData,
|
||||
"apool": apool,
|
||||
"rev": pad.getHeadRevisionNumber(),
|
||||
"time": currentTime,
|
||||
},
|
||||
"colorPalette": authorManager.getColorPalette(),
|
||||
} else {
|
||||
// This is a normal first connect
|
||||
|
||||
// prepare all values for the wire, there's a chance that this throws, if the pad is corrupted
|
||||
try {
|
||||
var atext = Changeset.cloneAText(pad.atext);
|
||||
var attribsForWire = Changeset.prepareForWire(atext.attribs, pad.pool);
|
||||
var apool = attribsForWire.pool.toJsonable();
|
||||
atext.attribs = attribsForWire.translated;
|
||||
} catch(e) {
|
||||
console.error(e.stack || e)
|
||||
client.json.send({ disconnect:"corruptPad" }); // pull the brakes
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Warning: never ever send padIds.padId to the client. If the
|
||||
// client is read only you would open a security hole 1 swedish
|
||||
// mile wide...
|
||||
var clientVars = {
|
||||
"skinName": settings.skinName,
|
||||
"accountPrivs": {
|
||||
"maxRevisions": 100
|
||||
},
|
||||
"automaticReconnectionTimeout": settings.automaticReconnectionTimeout,
|
||||
"initialRevisionList": [],
|
||||
"initialOptions": {
|
||||
"guestPolicy": "deny"
|
||||
},
|
||||
"savedRevisions": pad.getSavedRevisions(),
|
||||
"collab_client_vars": {
|
||||
"initialAttributedText": atext,
|
||||
"clientIp": "127.0.0.1",
|
||||
"userIsGuest": true,
|
||||
"userColor": authorColorId,
|
||||
"padId": message.padId,
|
||||
"padOptions": settings.padOptions,
|
||||
"padShortcutEnabled": settings.padShortcutEnabled,
|
||||
"initialTitle": "Pad: " + message.padId,
|
||||
"opts": {},
|
||||
// tell the client the number of the latest chat-message, which will be
|
||||
// used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES)
|
||||
"chatHead": pad.chatHead,
|
||||
"numConnectedUsers": roomClients.length,
|
||||
"readOnlyId": padIds.readOnlyPadId,
|
||||
"readonly": padIds.readonly,
|
||||
"serverTimestamp": Date.now(),
|
||||
"userId": author,
|
||||
"abiwordAvailable": settings.abiwordAvailable(),
|
||||
"sofficeAvailable": settings.sofficeAvailable(),
|
||||
"exportAvailable": settings.exportAvailable(),
|
||||
"plugins": {
|
||||
"plugins": plugins.plugins,
|
||||
"parts": plugins.parts,
|
||||
},
|
||||
"indentationOnNewLine": settings.indentationOnNewLine,
|
||||
"scrollWhenFocusLineIsOutOfViewport": {
|
||||
"percentage" : {
|
||||
"editionAboveViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
|
||||
"editionBelowViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
|
||||
},
|
||||
"duration": settings.scrollWhenFocusLineIsOutOfViewport.duration,
|
||||
"scrollWhenCaretIsInTheLastLineOfViewport": settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
|
||||
"percentageToScrollWhenUserPressesArrowUp": settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
|
||||
},
|
||||
"initialChangesets": [] // FIXME: REMOVE THIS SHIT
|
||||
"historicalAuthorData": historicalAuthorData,
|
||||
"apool": apool,
|
||||
"rev": pad.getHeadRevisionNumber(),
|
||||
"time": currentTime,
|
||||
},
|
||||
"colorPalette": authorManager.getColorPalette(),
|
||||
"clientIp": "127.0.0.1",
|
||||
"userIsGuest": true,
|
||||
"userColor": authorColorId,
|
||||
"padId": message.padId,
|
||||
"padOptions": settings.padOptions,
|
||||
"padShortcutEnabled": settings.padShortcutEnabled,
|
||||
"initialTitle": "Pad: " + message.padId,
|
||||
"opts": {},
|
||||
// tell the client the number of the latest chat-message, which will be
|
||||
// used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES)
|
||||
"chatHead": pad.chatHead,
|
||||
"numConnectedUsers": roomClients.length,
|
||||
"readOnlyId": padIds.readOnlyPadId,
|
||||
"readonly": padIds.readonly,
|
||||
"serverTimestamp": Date.now(),
|
||||
"userId": author,
|
||||
"abiwordAvailable": settings.abiwordAvailable(),
|
||||
"sofficeAvailable": settings.sofficeAvailable(),
|
||||
"exportAvailable": settings.exportAvailable(),
|
||||
"plugins": {
|
||||
"plugins": plugins.plugins,
|
||||
"parts": plugins.parts,
|
||||
},
|
||||
"indentationOnNewLine": settings.indentationOnNewLine,
|
||||
"scrollWhenFocusLineIsOutOfViewport": {
|
||||
"percentage" : {
|
||||
"editionAboveViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionAboveViewport,
|
||||
"editionBelowViewport": settings.scrollWhenFocusLineIsOutOfViewport.percentage.editionBelowViewport,
|
||||
},
|
||||
"duration": settings.scrollWhenFocusLineIsOutOfViewport.duration,
|
||||
"scrollWhenCaretIsInTheLastLineOfViewport": settings.scrollWhenFocusLineIsOutOfViewport.scrollWhenCaretIsInTheLastLineOfViewport,
|
||||
"percentageToScrollWhenUserPressesArrowUp": settings.scrollWhenFocusLineIsOutOfViewport.percentageToScrollWhenUserPressesArrowUp,
|
||||
},
|
||||
"initialChangesets": [] // FIXME: REMOVE THIS SHIT
|
||||
}
|
||||
|
||||
// Add a username to the clientVars if one avaiable
|
||||
if (authorName != null) {
|
||||
clientVars.userName = authorName;
|
||||
}
|
||||
|
||||
// call the clientVars-hook so plugins can modify them before they get sent to the client
|
||||
let messages = await hooks.aCallAll("clientVars", { clientVars: clientVars, pad: pad });
|
||||
|
||||
// combine our old object with the new attributes from the hook
|
||||
for (let msg of messages) {
|
||||
Object.assign(clientVars, msg);
|
||||
}
|
||||
|
||||
// Join the pad and start receiving updates
|
||||
client.join(padIds.padId);
|
||||
|
||||
// Send the clientVars to the Client
|
||||
client.json.send({type: "CLIENT_VARS", data: clientVars});
|
||||
|
||||
// Save the current revision in sessioninfos, should be the same as in clientVars
|
||||
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
|
||||
|
||||
sessioninfos[client.id].author = author;
|
||||
|
||||
// prepare the notification for the other users on the pad, that this user joined
|
||||
let messageToTheOtherUsers = {
|
||||
"type": "COLLABROOM",
|
||||
"data": {
|
||||
type: "USER_NEWINFO",
|
||||
userInfo: {
|
||||
"ip": "127.0.0.1",
|
||||
"colorId": authorColorId,
|
||||
"userAgent": "Anonymous",
|
||||
"userId": author
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add a username to the clientVars if one avaiable
|
||||
if (authorName != null) {
|
||||
clientVars.userName = authorName;
|
||||
}
|
||||
// Add the authorname of this new User, if avaiable
|
||||
if (authorName != null) {
|
||||
messageToTheOtherUsers.data.userInfo.name = authorName;
|
||||
}
|
||||
|
||||
// call the clientVars-hook so plugins can modify them before they get sent to the client
|
||||
hooks.aCallAll("clientVars", { clientVars: clientVars, pad: pad }, function( err, messages ) {
|
||||
if (ERR(err, callback)) return;
|
||||
// notify all existing users about new user
|
||||
client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers);
|
||||
|
||||
_.each(messages, function(newVars) {
|
||||
// combine our old object with the new attributes from the hook
|
||||
for(var attr in newVars) {
|
||||
clientVars[attr] = newVars[attr];
|
||||
}
|
||||
});
|
||||
// Get sessions for this pad
|
||||
roomClients = _getRoomClients(pad.id);
|
||||
for (let roomClient of roomClients) {
|
||||
|
||||
// Join the pad and start receiving updates
|
||||
client.join(padIds.padId);
|
||||
|
||||
// Send the clientVars to the Client
|
||||
client.json.send({ type: "CLIENT_VARS", data: clientVars });
|
||||
|
||||
// Save the current revision in sessioninfos, should be the same as in clientVars
|
||||
sessioninfos[client.id].rev = pad.getHeadRevisionNumber();
|
||||
});
|
||||
// Jump over, if this session is the connection session
|
||||
if (roomClient.id == client.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sessioninfos[client.id].author = author;
|
||||
// Since sessioninfos might change while being enumerated, check if the
|
||||
// sessionID is still assigned to a valid session
|
||||
if (sessioninfos[roomClient.id] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// prepare the notification for the other users on the pad, that this user joined
|
||||
var messageToTheOtherUsers = {
|
||||
let author = sessioninfos[roomClient.id].author;
|
||||
|
||||
// get the authorname & colorId
|
||||
|
||||
// reuse previously created cache of author's data
|
||||
let authorInfo = historicalAuthorData[author] || await authorManager.getAuthor(author);
|
||||
|
||||
// Send the new User a Notification about this other user
|
||||
let msg = {
|
||||
"type": "COLLABROOM",
|
||||
"data": {
|
||||
type: "USER_NEWINFO",
|
||||
userInfo: {
|
||||
"ip": "127.0.0.1",
|
||||
"colorId": authorColorId,
|
||||
"colorId": authorInfo.colorId,
|
||||
"name": authorInfo.name,
|
||||
"userAgent": "Anonymous",
|
||||
"userId": author
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add the authorname of this new User, if avaiable
|
||||
if (authorName != null) {
|
||||
messageToTheOtherUsers.data.userInfo.name = authorName;
|
||||
}
|
||||
|
||||
// notify all existing users about new user
|
||||
client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers);
|
||||
|
||||
// Get sessions for this pad
|
||||
var roomClients = _getRoomClients(pad.id);
|
||||
|
||||
async.forEach(roomClients, function(roomClient, callback) {
|
||||
var author;
|
||||
|
||||
// Jump over, if this session is the connection session
|
||||
if (roomClient.id == client.id) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
// Since sessioninfos might change while being enumerated, check if the
|
||||
// sessionID is still assigned to a valid session
|
||||
if (sessioninfos[roomClient.id] !== undefined) {
|
||||
author = sessioninfos[roomClient.id].author;
|
||||
} else {
|
||||
// If the client id is not valid, callback();
|
||||
return callback();
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
// get the authorname & colorId
|
||||
function(callback) {
|
||||
// reuse previously created cache of author's data
|
||||
if (historicalAuthorData[author]) {
|
||||
callback(null, historicalAuthorData[author]);
|
||||
} else {
|
||||
authorManager.getAuthor(author, callback);
|
||||
}
|
||||
},
|
||||
|
||||
function(authorInfo, callback) {
|
||||
// Send the new User a Notification about this other user
|
||||
var msg = {
|
||||
"type": "COLLABROOM",
|
||||
"data": {
|
||||
type: "USER_NEWINFO",
|
||||
userInfo: {
|
||||
"ip": "127.0.0.1",
|
||||
"colorId": authorInfo.colorId,
|
||||
"name": authorInfo.name,
|
||||
"userAgent": "Anonymous",
|
||||
"userId": author
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.json.send(msg);
|
||||
}
|
||||
], callback);
|
||||
}, callback);
|
||||
client.json.send(msg);
|
||||
}
|
||||
],
|
||||
function(err) {
|
||||
ERR(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1496,7 +1374,6 @@ function handleChangesetRequest(client, message)
|
|||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to rebuild the getChangestInfo function of the original Etherpad
|
||||
* https://github.com/ether/pad/blob/master/etherpad/src/etherpad/control/pad/pad_changeset_control.js#L144
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
var ERR = require("async-stacktrace");
|
||||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
|
@ -80,7 +79,7 @@ exports.setSocketIO = function(_socket) {
|
|||
components[i].handleConnect(client);
|
||||
}
|
||||
|
||||
client.on('message', function(message) {
|
||||
client.on('message', async function(message) {
|
||||
if (message.protocolVersion && message.protocolVersion != 2) {
|
||||
messageLogger.warn("Protocolversion header is not correct:" + stringifyWithoutPassword(message));
|
||||
return;
|
||||
|
@ -92,27 +91,22 @@ exports.setSocketIO = function(_socket) {
|
|||
} else {
|
||||
// try to authorize the client
|
||||
if (message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined) {
|
||||
var checkAccessCallback = function(err, statusObject) {
|
||||
ERR(err);
|
||||
// check for read-only pads
|
||||
let padId = message.padId;
|
||||
if (padId.indexOf("r.") === 0) {
|
||||
padId = await readOnlyManager.getPadId(message.padId);
|
||||
}
|
||||
|
||||
if (statusObject.accessStatus === "grant") {
|
||||
// access was granted, mark the client as authorized and handle the message
|
||||
clientAuthorized = true;
|
||||
handleMessage(client, message);
|
||||
} else {
|
||||
// no access, send the client a message that tells him why
|
||||
messageLogger.warn("Authentication try failed:" + stringifyWithoutPassword(message));
|
||||
client.json.send({accessStatus: statusObject.accessStatus});
|
||||
}
|
||||
};
|
||||
if (message.padId.indexOf("r.") === 0) {
|
||||
readOnlyManager.getPadId(message.padId, function(err, value) {
|
||||
ERR(err);
|
||||
securityManager.checkAccess(value, message.sessionID, message.token, message.password, checkAccessCallback);
|
||||
});
|
||||
let { accessStatus } = await securityManager.checkAccess(padId, message.sessionID, message.token, message.password);
|
||||
|
||||
if (accessStatus === "grant") {
|
||||
// access was granted, mark the client as authorized and handle the message
|
||||
clientAuthorized = true;
|
||||
handleMessage(client, message);
|
||||
} else {
|
||||
// this message has everything to try an authorization
|
||||
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, checkAccessCallback);
|
||||
// no access, send the client a message that tells him why
|
||||
messageLogger.warn("Authentication try failed:" + stringifyWithoutPassword(message));
|
||||
client.json.send({ accessStatus });
|
||||
}
|
||||
} else {
|
||||
// drop message
|
||||
|
|
Loading…
Reference in New Issue