Merge branch 'api' of ../etherpad-lite.dev/
This commit is contained in:
commit
1151ec3278
28 changed files with 2172 additions and 190 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
node_modules
|
||||
settings.json
|
||||
static/js/jquery.min.js
|
||||
APIKEY.txt
|
||||
bin/abiword.exe
|
||||
bin/node.exe
|
||||
etherpad-lite-win.zip
|
||||
|
|
69
doc/database.md
Normal file
69
doc/database.md
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Database structure
|
||||
|
||||
## Used so far
|
||||
|
||||
### pad:$PADID
|
||||
Saves all informations about pads
|
||||
|
||||
* **atext** - the latest attributed text
|
||||
* **pool** - the attribute pool
|
||||
* **head** - the number of the latest revision
|
||||
* **chatHead** - the number of the latest chat entry
|
||||
|
||||
*planed:*
|
||||
|
||||
* **public** - flag that disables security for this pad
|
||||
* **passwordHash** - string that contains a bcrypt hashed password for this pad
|
||||
|
||||
### pad:$PADID:revs:$REVNUM
|
||||
Saves a revision $REVNUM of pad $PADID
|
||||
|
||||
* **meta**
|
||||
* **author** - the autorID of this revision
|
||||
* **timestamp** - the timestamp of when this revision was created
|
||||
* **changeset** - the changeset of this revision
|
||||
|
||||
### pad:$PADID:chat:$CHATNUM
|
||||
Saves a chatentry with num $CHATNUM of pad $PADID
|
||||
|
||||
* **text** - the text of this chat entry
|
||||
* **userId** - the autorID of this chat entry
|
||||
* **time** - the timestamp of this chat entry
|
||||
|
||||
### pad2readonly:$PADID
|
||||
Translates a padID to a readonlyID
|
||||
### readonly2pad:$READONLYID
|
||||
Translates a readonlyID to a padID
|
||||
### token2author:$TOKENID
|
||||
Translates a token to an authorID
|
||||
### globalAuthor:$AUTHORID
|
||||
Information about an author
|
||||
|
||||
* **name** - the name of this author as shown in the pad
|
||||
* **colorID** - the colorID of this author as shown in the pad
|
||||
|
||||
## Planed
|
||||
|
||||
### mapper2group:$MAPPER
|
||||
Maps an external application identifier to an internal group
|
||||
### mapper2author:$MAPPER
|
||||
Maps an external application identifier to an internal author
|
||||
### group:$GROUPID
|
||||
a group of pads
|
||||
|
||||
* **pads** - object with pad names in it, values are 1
|
||||
### session:$SESSIONID
|
||||
a session between an author and a group
|
||||
|
||||
* **groupID** - the groupID the session belongs too
|
||||
* **authorID** - the authorID the session belongs too
|
||||
* **validUntil** - the timestamp until this session is valid
|
||||
|
||||
### author2sessions:$AUTHORID
|
||||
saves the sessions of an author
|
||||
|
||||
* **sessionsIDs** - object with sessionIDs in it, values are 1
|
||||
|
||||
### group2sessions:$GROUPID
|
||||
|
||||
* **sessionsIDs** - object with sessionIDs in it, values are 1
|
432
node/db/API.js
Normal file
432
node/db/API.js
Normal file
|
@ -0,0 +1,432 @@
|
|||
/**
|
||||
* This module provides all API functions
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 padManager = require("./PadManager");
|
||||
var padMessageHandler = require("../handler/PadMessageHandler");
|
||||
var readOnlyManager = require("./ReadOnlyManager");
|
||||
var groupManager = require("./GroupManager");
|
||||
var authorManager = require("./AuthorManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
var async = require("async");
|
||||
|
||||
/**********************/
|
||||
/**GROUP FUNCTIONS*****/
|
||||
/**********************/
|
||||
|
||||
exports.createGroup = groupManager.createGroup;
|
||||
exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor;
|
||||
exports.deleteGroup = groupManager.deleteGroup;
|
||||
exports.listPads = groupManager.listPads;
|
||||
exports.createGroupPad = groupManager.createGroupPad;
|
||||
|
||||
/**********************/
|
||||
/**AUTHOR FUNCTIONS****/
|
||||
/**********************/
|
||||
|
||||
exports.createAuthor = authorManager.createAuthor;
|
||||
exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor;
|
||||
|
||||
/**********************/
|
||||
/**SESSION FUNCTIONS***/
|
||||
/**********************/
|
||||
|
||||
exports.createSession = sessionManager.createSession;
|
||||
exports.deleteSession = sessionManager.deleteSession;
|
||||
exports.getSessionInfo = sessionManager.getSessionInfo;
|
||||
exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup;
|
||||
exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
|
||||
|
||||
/************************/
|
||||
/**PAD CONTENT FUNCTIONS*/
|
||||
/************************/
|
||||
|
||||
/**
|
||||
getText(padID, [rev]) returns the text of a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {text:"Welcome Text"}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getText = function(padID, rev, callback)
|
||||
{
|
||||
//check if rev is set
|
||||
if(typeof rev == "function")
|
||||
{
|
||||
callback = rev;
|
||||
rev = undefined;
|
||||
}
|
||||
|
||||
//check if rev is a number
|
||||
if(rev !== undefined && typeof rev != "number")
|
||||
{
|
||||
//try to parse the number
|
||||
if(!isNaN(parseInt(rev)))
|
||||
{
|
||||
rev = parseInt(rev);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback({stop: "rev is not a number"});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//ensure this is not a negativ number
|
||||
if(rev !== undefined && rev < 0)
|
||||
{
|
||||
callback({stop: "rev is a negativ number"});
|
||||
return;
|
||||
}
|
||||
|
||||
//ensure this is not a float value
|
||||
if(rev !== undefined && !is_int(rev))
|
||||
{
|
||||
callback({stop: "rev is a float value"});
|
||||
return;
|
||||
}
|
||||
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//the client asked for a special revision
|
||||
if(rev !== undefined)
|
||||
{
|
||||
//check if this is a valid revision
|
||||
if(rev > pad.getHeadRevisionNumber())
|
||||
{
|
||||
callback({stop: "rev is higher than the head revision of the pad"});
|
||||
return;
|
||||
}
|
||||
|
||||
//get the text of this revision
|
||||
pad.getInternalRevisionAText(rev, function(err, atext)
|
||||
{
|
||||
if(!err)
|
||||
{
|
||||
data = {text: atext.text};
|
||||
}
|
||||
|
||||
callback(err, data);
|
||||
})
|
||||
}
|
||||
//the client wants the latest text, lets return it to him
|
||||
else
|
||||
{
|
||||
callback(null, {"text": pad.text()});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
setText(padID, text) sets the text of a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.setText = function(padID, text, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//set the text
|
||||
pad.setText(text);
|
||||
|
||||
//update the clients on the pad
|
||||
padMessageHandler.updatePadClients(pad, callback);
|
||||
});
|
||||
}
|
||||
|
||||
/*****************/
|
||||
/**PAD FUNCTIONS */
|
||||
/*****************/
|
||||
|
||||
/**
|
||||
getRevisionsCount(padID) returns the number of revisions of this pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {revisions: 56}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getRevisionsCount = function(padID, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, {revisions: pad.getHeadRevisionNumber()});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
createPad(padName [, text]) creates a new pad in this group
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"pad does already exist", data: null}
|
||||
*/
|
||||
exports.createPad = function(padID, text, callback)
|
||||
{
|
||||
//ensure there is no $ in the padID
|
||||
if(padID.indexOf("$") != -1)
|
||||
{
|
||||
callback({stop: "createPad can't create group pads"});
|
||||
return;
|
||||
}
|
||||
|
||||
//create pad
|
||||
getPadSafe(padID, false, text, function(err)
|
||||
{
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
deletePad(padID) deletes a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.deletePad = function(padID, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
getReadOnlyLink(padID) returns the read only link of a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getReadOnlyID = function(padID, callback)
|
||||
{
|
||||
//we don't need the pad object, but this function does all the security stuff for us
|
||||
getPadSafe(padID, true, function(err)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//get the readonlyId
|
||||
readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId)
|
||||
{
|
||||
callback(err, {readOnlyID: readOnlyId});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.setPublicStatus = function(padID, publicStatus, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//convert string to boolean
|
||||
if(typeof publicStatus == "string")
|
||||
publicStatus = publicStatus == "true" ? true : false;
|
||||
|
||||
//set the password
|
||||
pad.setPublicStatus(publicStatus);
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
getPublicStatus(padID) return true of false
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {publicStatus: true}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.getPublicStatus = function(padID, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, {publicStatus: pad.getPublicStatus()});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
setPassword(padID, password) returns ok or a error message
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.setPassword = function(padID, password, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//set the password
|
||||
pad.setPassword(password);
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
isPasswordProtected(padID) returns true or false
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: {passwordProtection: true}}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.isPasswordProtected = function(padID, callback)
|
||||
{
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, {isPasswordProtected: pad.isPasswordProtected()});
|
||||
});
|
||||
}
|
||||
|
||||
/******************************/
|
||||
/** INTERNAL HELPER FUNCTIONS */
|
||||
/******************************/
|
||||
|
||||
//checks if a number is an int
|
||||
function is_int(value)
|
||||
{
|
||||
return (parseFloat(value) == parseInt(value)) && !isNaN(value)
|
||||
}
|
||||
|
||||
//gets a pad safe
|
||||
function getPadSafe(padID, shouldExist, text, callback)
|
||||
{
|
||||
if(typeof text == "function")
|
||||
{
|
||||
callback = text;
|
||||
text = null;
|
||||
}
|
||||
|
||||
//check if padID is a string
|
||||
if(typeof padID != "string")
|
||||
{
|
||||
callback({stop: "padID is not a string"});
|
||||
return;
|
||||
}
|
||||
|
||||
//check if the padID maches the requirements
|
||||
if(!padManager.isValidPadId(padID))
|
||||
{
|
||||
callback({stop: "padID did not match requirements"});
|
||||
return;
|
||||
}
|
||||
|
||||
//check if the pad exists
|
||||
padManager.doesPadExists(padID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//does not exist, but should
|
||||
else if(exists == false && shouldExist == true)
|
||||
{
|
||||
callback({stop: "padID does not exist"});
|
||||
}
|
||||
//does exists, but shouldn't
|
||||
else if(exists == true && shouldExist == false)
|
||||
{
|
||||
callback({stop: "padID does already exist"});
|
||||
}
|
||||
//pad exists, let's get it
|
||||
else
|
||||
{
|
||||
padManager.getPad(padID, text, callback);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
@ -22,94 +22,122 @@ var db = require("./DB").db;
|
|||
var async = require("async");
|
||||
|
||||
/**
|
||||
* Returns the Author Id for a token. If the token is unkown,
|
||||
* it creates a author for the token
|
||||
* Checks if the author exists
|
||||
*/
|
||||
exports.doesAuthorExists = function (authorID, callback)
|
||||
{
|
||||
//check if the database entry of this author exists
|
||||
db.get("globalAuthor:" + authorID, function (err, author)
|
||||
{
|
||||
callback(err, author != null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthorID for a token.
|
||||
* @param {String} token The token
|
||||
* @param {Function} callback callback (err, author)
|
||||
* The callback function that is called when the result is here
|
||||
*/
|
||||
exports.getAuthor4Token = function (token, callback)
|
||||
{
|
||||
var author;
|
||||
|
||||
async.series([
|
||||
//try to get the author for this token
|
||||
function(callback)
|
||||
mapAuthorWithDBKey("token2author", token, function(err, author)
|
||||
{
|
||||
db.get("token2author:" + token, function (err, _author)
|
||||
{
|
||||
author = _author;
|
||||
callback(err);
|
||||
//return only the sub value authorID
|
||||
callback(err, author ? author.authorID : author);
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthorID for a mapper.
|
||||
* @param {String} token The mapper
|
||||
* @param {Function} callback callback (err, author)
|
||||
*/
|
||||
exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback)
|
||||
{
|
||||
mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author)
|
||||
{
|
||||
//there is no author with this token, so create one
|
||||
//error?
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//set the name of this author
|
||||
if(name)
|
||||
exports.setAuthorName(author.authorID, name);
|
||||
|
||||
//return the authorID
|
||||
callback(null, author);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AuthorID for a mapper. We can map using a mapperkey,
|
||||
* so far this is token2author and mapper2author
|
||||
* @param {String} mapperkey The database key name for this mapper
|
||||
* @param {String} mapper The mapper
|
||||
* @param {Function} callback callback (err, author)
|
||||
*/
|
||||
function mapAuthorWithDBKey (mapperkey, mapper, callback)
|
||||
{
|
||||
//try to map to an author
|
||||
db.get(mapperkey + ":" + mapper, function (err, author)
|
||||
{
|
||||
//error?
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//there is no author with this mapper, so create one
|
||||
if(author == null)
|
||||
{
|
||||
createAuthor(token, function(err, _author)
|
||||
exports.createAuthor(null, function(err, author)
|
||||
{
|
||||
//error?
|
||||
if(err)
|
||||
{
|
||||
author = _author;
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//create the token2author relation
|
||||
db.set(mapperkey + ":" + mapper, author.authorID);
|
||||
|
||||
//return the author
|
||||
callback(null, author);
|
||||
});
|
||||
}
|
||||
//there is a author with this token
|
||||
else
|
||||
{
|
||||
//check if there is also an author object for this token, if not, create one
|
||||
db.get("globalAuthor:" + author, function(err, authorObject)
|
||||
{
|
||||
if(authorObject == null)
|
||||
{
|
||||
createAuthor(token, function(err, _author)
|
||||
{
|
||||
author = _author;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
//the author exists, update the timestamp of this author
|
||||
//there is a author with this mapper
|
||||
else
|
||||
{
|
||||
//update the timestamp of this author
|
||||
db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime());
|
||||
callback();
|
||||
|
||||
//return the author
|
||||
callback(null, {authorID: author});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, author);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function that creates the database entry for an author
|
||||
* @param {String} token The token
|
||||
* @param {String} name The name of the author
|
||||
*/
|
||||
function createAuthor (token, callback)
|
||||
exports.createAuthor = function(name, callback)
|
||||
{
|
||||
//create the new author name
|
||||
var author = "g." + _randomString(16);
|
||||
var author = "a." + randomString(16);
|
||||
|
||||
//create the globalAuthors db entry
|
||||
var authorObj = {colorId : Math.floor(Math.random()*32), name: null, timestamp: new Date().getTime()};
|
||||
var authorObj = {"colorId" : Math.floor(Math.random()*32), "name": name, "timestamp": new Date().getTime()};
|
||||
|
||||
//we do this in series to ensure this db entries are written in the correct order
|
||||
async.series([
|
||||
//set the global author db entry
|
||||
function(callback)
|
||||
{
|
||||
db.set("globalAuthor:" + author, authorObj, callback);
|
||||
},
|
||||
//set the token2author db entry
|
||||
function(callback)
|
||||
{
|
||||
db.set("token2author:" + token, author, callback);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, author);
|
||||
});
|
||||
db.set("globalAuthor:" + author, authorObj);
|
||||
|
||||
callback(null, {authorID: author});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -165,11 +193,14 @@ exports.setAuthorName = function (author, name, callback)
|
|||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the Author Ids
|
||||
*/
|
||||
function _randomString(len) {
|
||||
// use only numbers and lowercase letters
|
||||
var pieces = [];
|
||||
for(var i=0;i<len;i++) {
|
||||
pieces.push(Math.floor(Math.random()*36).toString(36).slice(-1));
|
||||
function randomString(len)
|
||||
{
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return pieces.join('');
|
||||
return randomstring;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
|
196
node/db/GroupManager.js
Normal file
196
node/db/GroupManager.js
Normal file
|
@ -0,0 +1,196 @@
|
|||
/**
|
||||
* The Group Manager provides functions to manage groups in the database
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 db = require("./DB").db;
|
||||
var async = require("async");
|
||||
var padManager = require("./PadManager");
|
||||
|
||||
exports.doesGroupExist = function(groupID, callback)
|
||||
{
|
||||
//try to get the group entry
|
||||
db.get("group:" + groupID, function (err, group)
|
||||
{
|
||||
callback(err, group != null);
|
||||
});
|
||||
}
|
||||
|
||||
exports.createGroup = function(callback)
|
||||
{
|
||||
//search for non existing groupID
|
||||
var groupID = "g." + randomString(16);
|
||||
|
||||
//create the group
|
||||
db.set("group:" + groupID, {pads: {}});
|
||||
callback(null, {groupID: groupID});
|
||||
}
|
||||
|
||||
exports.createGroupIfNotExistsFor = function(groupMapper, callback)
|
||||
{
|
||||
//ensure mapper is optional
|
||||
if(typeof groupMapper != "string")
|
||||
{
|
||||
callback({stop: "groupMapper is no string"});
|
||||
return;
|
||||
}
|
||||
|
||||
//try to get a group for this mapper
|
||||
db.get("mapper2group:"+groupMapper, function(err, groupID)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//there is no group for this mapper, let's create a group
|
||||
if(groupID == null)
|
||||
{
|
||||
exports.createGroup(function(err, responseObj)
|
||||
{
|
||||
//check for errors
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//create the mapper entry for this group
|
||||
db.set("mapper2group:"+groupMapper, responseObj.groupID);
|
||||
|
||||
callback(null, responseObj);
|
||||
});
|
||||
}
|
||||
//there is a group for this mapper, let's return it
|
||||
else
|
||||
{
|
||||
callback(err, {groupID: groupID});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.createGroupPad = function(groupID, padName, text, callback)
|
||||
{
|
||||
//create the padID
|
||||
var padID = groupID + "$" + padName;
|
||||
|
||||
async.series([
|
||||
//ensure group exists
|
||||
function (callback)
|
||||
{
|
||||
exports.doesGroupExist(groupID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//group does not exist
|
||||
else if(exists == false)
|
||||
{
|
||||
callback({stop: "groupID does not exist"});
|
||||
}
|
||||
//group exists, everything is fine
|
||||
else
|
||||
{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
//ensure pad does not exists
|
||||
function (callback)
|
||||
{
|
||||
padManager.doesPadExists(padID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//pad exists already
|
||||
else if(exists == true)
|
||||
{
|
||||
callback({stop: "padName does already exist"});
|
||||
}
|
||||
//pad does not exist, everything is fine
|
||||
else
|
||||
{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
//create the pad
|
||||
function (callback)
|
||||
{
|
||||
padManager.getPad(padID, text, function(err)
|
||||
{
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//create an entry in the group for this pad
|
||||
function (callback)
|
||||
{
|
||||
db.setSub("group:" + groupID, ["pads", padID], 1);
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, {padID: padID});
|
||||
});
|
||||
}
|
||||
|
||||
exports.listPads = function(groupID, callback)
|
||||
{
|
||||
exports.doesGroupExist(groupID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//group does not exist
|
||||
else if(exists == false)
|
||||
{
|
||||
callback({stop: "groupID does not exist"});
|
||||
}
|
||||
//group exists, let's get the pads
|
||||
else
|
||||
{
|
||||
db.getSub("group:" + groupID, ["pads"], function(err, pads)
|
||||
{
|
||||
callback(err, {padIDs: pads});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the Author Ids
|
||||
*/
|
||||
function randomString(len)
|
||||
{
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
|
@ -8,6 +8,7 @@ var db = require("./DB").db;
|
|||
var async = require("async");
|
||||
var settings = require('../utils/Settings');
|
||||
var authorManager = require("./AuthorManager");
|
||||
var crypto = require("crypto");
|
||||
|
||||
/**
|
||||
* Copied from the Etherpad source code. It converts Windows line breaks to Unix line breaks and convert Tabs to spaces
|
||||
|
@ -44,6 +45,17 @@ Class('Pad', {
|
|||
init: -1
|
||||
}, // chatHead
|
||||
|
||||
publicStatus : {
|
||||
is: 'rw',
|
||||
init: false,
|
||||
getterName : 'getPublicStatus'
|
||||
}, //publicStatus
|
||||
|
||||
passwordHash : {
|
||||
is: 'rw',
|
||||
init: null
|
||||
}, // passwordHash
|
||||
|
||||
id : { is : 'r' }
|
||||
},
|
||||
|
||||
|
@ -82,7 +94,12 @@ Class('Pad', {
|
|||
}
|
||||
|
||||
db.set("pad:"+this.id+":revs:"+newRev, newRevData);
|
||||
db.set("pad:"+this.id, {atext: this.atext, pool: this.pool.toJsonable(), head: this.head, chatHead: this.chatHead});
|
||||
db.set("pad:"+this.id, {atext: this.atext,
|
||||
pool: this.pool.toJsonable(),
|
||||
head: this.head,
|
||||
chatHead: this.chatHead,
|
||||
publicStatus: this.publicStatus,
|
||||
passwordHash: this.passwordHash});
|
||||
}, //appendRevision
|
||||
|
||||
getRevisionChangeset : function(revNum, callback)
|
||||
|
@ -310,10 +327,16 @@ Class('Pad', {
|
|||
});
|
||||
},
|
||||
|
||||
init : function (callback)
|
||||
init : function (text, callback)
|
||||
{
|
||||
var _this = this;
|
||||
|
||||
//replace text with default text if text isn't set
|
||||
if(text == null)
|
||||
{
|
||||
text = settings.defaultPadText;
|
||||
}
|
||||
|
||||
//try to load the pad
|
||||
db.get("pad:"+this.id, function(err, value)
|
||||
{
|
||||
|
@ -330,22 +353,80 @@ Class('Pad', {
|
|||
_this.atext = value.atext;
|
||||
_this.pool = _this.pool.fromJsonable(value.pool);
|
||||
|
||||
//ensure we have a local chatHead variable
|
||||
if(value.chatHead != null)
|
||||
_this.chatHead = value.chatHead;
|
||||
else
|
||||
_this.chatHead = -1;
|
||||
|
||||
//ensure we have a local publicStatus variable
|
||||
if(value.publicStatus != null)
|
||||
_this.publicStatus = value.publicStatus;
|
||||
else
|
||||
_this.publicStatus = false;
|
||||
|
||||
//ensure we have a local passwordHash variable
|
||||
if(value.passwordHash != null)
|
||||
_this.passwordHash = value.passwordHash;
|
||||
else
|
||||
_this.passwordHash = null;
|
||||
}
|
||||
//this pad doesn't exist, so create it
|
||||
else
|
||||
{
|
||||
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(settings.defaultPadText));
|
||||
var firstChangeset = Changeset.makeSplice("\n", 0, 0, exports.cleanText(text));
|
||||
|
||||
_this.appendRevision(firstChangeset, '');
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
},
|
||||
//set in db
|
||||
setPublicStatus: function(publicStatus)
|
||||
{
|
||||
this.publicStatus = publicStatus;
|
||||
db.setSub("pad:"+this.id, ["publicStatus"], this.publicStatus);
|
||||
},
|
||||
setPassword: function(password)
|
||||
{
|
||||
this.passwordHash = password == null ? null : hash(password, generateSalt());
|
||||
db.setSub("pad:"+this.id, ["passwordHash"], this.passwordHash);
|
||||
},
|
||||
isCorrectPassword: function(password)
|
||||
{
|
||||
return compare(this.passwordHash, password)
|
||||
},
|
||||
isPasswordProtected: function()
|
||||
{
|
||||
return this.passwordHash != null;
|
||||
}
|
||||
|
||||
}, // methods
|
||||
});
|
||||
|
||||
/* Crypto helper methods */
|
||||
|
||||
function hash(password, salt)
|
||||
{
|
||||
var shasum = crypto.createHash('sha512');
|
||||
shasum.update(password + salt);
|
||||
return shasum.digest("hex") + "$" + salt;
|
||||
}
|
||||
|
||||
function generateSalt()
|
||||
{
|
||||
var len = 86;
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz./";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
||||
|
||||
function compare(hashStr, password)
|
||||
{
|
||||
return hash(password, hashStr.split("$")[1]) === hashStr;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
require("../db/Pad");
|
||||
var db = require("./DB").db;
|
||||
|
||||
/**
|
||||
* A Array with all known Pads
|
||||
|
@ -30,8 +31,40 @@ globalPads = [];
|
|||
* @param id A String with the id of the pad
|
||||
* @param {Function} callback
|
||||
*/
|
||||
exports.getPad = function(id, callback)
|
||||
exports.getPad = function(id, text, callback)
|
||||
{
|
||||
//check if this is a valid padId
|
||||
if(!exports.isValidPadId(id))
|
||||
{
|
||||
callback({stop: id + " is not a valid padId"});
|
||||
return;
|
||||
}
|
||||
|
||||
//make text an optional parameter
|
||||
if(typeof text == "function")
|
||||
{
|
||||
callback = text;
|
||||
text = null;
|
||||
}
|
||||
|
||||
//check if this is a valid text
|
||||
if(text != null)
|
||||
{
|
||||
//check if text is a string
|
||||
if(typeof text != "string")
|
||||
{
|
||||
callback({stop: "text is not a string"});
|
||||
return;
|
||||
}
|
||||
|
||||
//check if text is less than 100k chars
|
||||
if(text.length > 100000)
|
||||
{
|
||||
callback({stop: "text must be less than 100k chars"});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var pad = globalPads[id];
|
||||
|
||||
//return pad if its already loaded
|
||||
|
@ -45,7 +78,7 @@ exports.getPad = function(id, callback)
|
|||
pad = new Pad(id);
|
||||
|
||||
//initalize the pad
|
||||
pad.init(function(err)
|
||||
pad.init(text, function(err)
|
||||
{
|
||||
if(err)
|
||||
{
|
||||
|
@ -58,6 +91,19 @@ exports.getPad = function(id, callback)
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
//globalPads[id].timestamp = new Date().getTime();
|
||||
}
|
||||
|
||||
//checks if a pad exists
|
||||
exports.doesPadExists = function(padId, callback)
|
||||
{
|
||||
db.get("pad:"+padId, function(err, value)
|
||||
{
|
||||
callback(err, value != null);
|
||||
});
|
||||
}
|
||||
|
||||
exports.isValidPadId = function(padId)
|
||||
{
|
||||
return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
@ -40,7 +40,7 @@ exports.getReadOnlyId = function (padId, callback)
|
|||
//there is no readOnly Entry in the database, let's create one
|
||||
if(dbReadOnlyId == null)
|
||||
{
|
||||
readOnlyId = randomString(10);
|
||||
readOnlyId = "r." + randomString(16);
|
||||
|
||||
db.set("pad2readonly:" + padId, readOnlyId);
|
||||
db.set("readonly2pad:" + readOnlyId, padId);
|
||||
|
@ -74,10 +74,12 @@ exports.getPadId = function(readOnlyId, callback)
|
|||
*/
|
||||
function randomString(len)
|
||||
{
|
||||
// use only numbers and lowercase letters
|
||||
var pieces = [];
|
||||
for(var i=0;i<len;i++) {
|
||||
pieces.push(Math.floor(Math.random()*36).toString(36).slice(-1));
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return pieces.join('');
|
||||
return randomstring;
|
||||
}
|
||||
|
|
235
node/db/SecurityManager.js
Normal file
235
node/db/SecurityManager.js
Normal file
|
@ -0,0 +1,235 @@
|
|||
/**
|
||||
* 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 db = require("./DB").db;
|
||||
var async = require("async");
|
||||
var authorManager = require("./AuthorManager");
|
||||
var padManager = require("./PadManager");
|
||||
var sessionManager = require("./SessionManager");
|
||||
|
||||
/**
|
||||
* 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 sesssionID 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, sessionID, token, password, callback)
|
||||
{
|
||||
// it's not a group pad, means we can grant access
|
||||
if(padID.indexOf("$") == -1)
|
||||
{
|
||||
//get author for this token
|
||||
authorManager.getAuthor4Token(token, function(err, author)
|
||||
{
|
||||
// grant access, with author of token
|
||||
callback(err, {accessStatus: "grant", authorID: author});
|
||||
})
|
||||
|
||||
//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
|
||||
|
||||
var statusObject;
|
||||
|
||||
async.series([
|
||||
//get basic informations from the database
|
||||
function(callback)
|
||||
{
|
||||
async.parallel([
|
||||
//does pad exists
|
||||
function(callback)
|
||||
{
|
||||
padManager.doesPadExists(padID, function(err, exists)
|
||||
{
|
||||
padExists = exists;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//get informations about this session
|
||||
function(callback)
|
||||
{
|
||||
sessionManager.getSessionInfo(sessionID, function(err, sessionInfo)
|
||||
{
|
||||
//skip session validation if the session doesn't exists
|
||||
if(err && err.stop == "sessionID does not exist")
|
||||
{
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if(err) {callback(err); return}
|
||||
|
||||
var now = Math.floor(new Date().getTime()/1000);
|
||||
|
||||
//is it for this group? and is validUntil still ok? --> validSession
|
||||
if(sessionInfo.groupID == groupID && sessionInfo.validUntil > now)
|
||||
{
|
||||
validSession = true;
|
||||
}
|
||||
|
||||
sessionAuthor = sessionInfo.authorID;
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//get author for token
|
||||
function(callback)
|
||||
{
|
||||
//get author for this token
|
||||
authorManager.getAuthor4Token(token, function(err, author)
|
||||
{
|
||||
tokenAuthor = author;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
], callback);
|
||||
},
|
||||
//get more informations of this pad, if avaiable
|
||||
function(callback)
|
||||
{
|
||||
//skip this if the pad doesn't exists
|
||||
if(padExists == false)
|
||||
{
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
padManager.getPad(padID, function(err, pad)
|
||||
{
|
||||
if(err) {callback(err); 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)
|
||||
{
|
||||
//- a valid session for this group is avaible AND pad exists
|
||||
if(validSession && padExists)
|
||||
{
|
||||
//- the pad is not password protected
|
||||
if(!isPasswordProtected)
|
||||
{
|
||||
//--> grant access
|
||||
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
|
||||
}
|
||||
//- the pad is password protected and password is correct
|
||||
else if(isPasswordProtected && passwordStatus == "correct")
|
||||
{
|
||||
//--> grant access
|
||||
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
|
||||
}
|
||||
//- the pad is password protected but wrong password given
|
||||
else if(isPasswordProtected && passwordStatus == "wrong")
|
||||
{
|
||||
//--> deny access, ask for new password and tell them that the password is wrong
|
||||
statusObject = {accessStatus: "wrongPassword"};
|
||||
}
|
||||
//- the pad is password protected but no password given
|
||||
else if(isPasswordProtected && passwordStatus == "notGiven")
|
||||
{
|
||||
//--> ask for password
|
||||
statusObject = {accessStatus: "needPassword"};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error("Ops, something wrong happend");
|
||||
}
|
||||
}
|
||||
//- a valid session for this group avaible but pad doesn't exists
|
||||
else if(validSession && !padExists)
|
||||
{
|
||||
//--> grant access
|
||||
statusObject = {accessStatus: "grant", authorID: sessionAuthor};
|
||||
}
|
||||
// there is no valid session avaiable AND pad exists
|
||||
else if(!validSession && padExists)
|
||||
{
|
||||
//-- its public and not password protected
|
||||
if(isPublic && !isPasswordProtected)
|
||||
{
|
||||
//--> grant access, with author of token
|
||||
statusObject = {accessStatus: "grant", authorID: tokenAuthor};
|
||||
}
|
||||
//- its public and password protected and password is correct
|
||||
else if(isPublic && isPasswordProtected && passwordStatus == "correct")
|
||||
{
|
||||
//--> grant access, with author of token
|
||||
statusObject = {accessStatus: "grant", authorID: tokenAuthor};
|
||||
}
|
||||
//- its public and the pad is password protected but wrong password given
|
||||
else if(isPublic && isPasswordProtected && passwordStatus == "wrong")
|
||||
{
|
||||
//--> deny access, ask for new password and tell them that the password is wrong
|
||||
statusObject = {accessStatus: "wrongPassword"};
|
||||
}
|
||||
//- its public and the pad is password protected but no password given
|
||||
else if(isPublic && isPasswordProtected && passwordStatus == "notGiven")
|
||||
{
|
||||
//--> ask for password
|
||||
statusObject = {accessStatus: "needPassword"};
|
||||
}
|
||||
//- its not public
|
||||
else if(!isPublic)
|
||||
{
|
||||
//--> deny access
|
||||
statusObject = {accessStatus: "deny"};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error("Ops, something wrong happend");
|
||||
}
|
||||
}
|
||||
// there is no valid session avaiable AND pad doesn't exists
|
||||
else
|
||||
{
|
||||
//--> deny access
|
||||
statusObject = {accessStatus: "deny"};
|
||||
}
|
||||
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, statusObject);
|
||||
});
|
||||
}
|
397
node/db/SessionManager.js
Normal file
397
node/db/SessionManager.js
Normal file
|
@ -0,0 +1,397 @@
|
|||
/**
|
||||
* The Session Manager provides functions to manage session in the database
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 db = require("./DB").db;
|
||||
var async = require("async");
|
||||
var groupMangager = require("./GroupManager");
|
||||
var authorMangager = require("./AuthorManager");
|
||||
|
||||
exports.doesSessionExist = function(sessionID, callback)
|
||||
{
|
||||
//check if the database entry of this session exists
|
||||
db.get("session:" + sessionID, function (err, session)
|
||||
{
|
||||
callback(err, session != null);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new session between an author and a group
|
||||
*/
|
||||
exports.createSession = function(groupID, authorID, validUntil, callback)
|
||||
{
|
||||
var sessionID;
|
||||
|
||||
async.series([
|
||||
//check if group exists
|
||||
function(callback)
|
||||
{
|
||||
groupMangager.doesGroupExist(groupID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//group does not exist
|
||||
else if(exists == false)
|
||||
{
|
||||
callback({stop: "groupID does not exist"});
|
||||
}
|
||||
//everything is fine, continue
|
||||
else
|
||||
{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
//check if author exists
|
||||
function(callback)
|
||||
{
|
||||
authorMangager.doesAuthorExists(authorID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//author does not exist
|
||||
else if(exists == false)
|
||||
{
|
||||
callback({stop: "authorID does not exist"});
|
||||
}
|
||||
//everything is fine, continue
|
||||
else
|
||||
{
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
//check validUntil and create the session db entry
|
||||
function(callback)
|
||||
{
|
||||
//check if rev is a number
|
||||
if(typeof validUntil != "number")
|
||||
{
|
||||
//try to parse the number
|
||||
if(!isNaN(parseInt(validUntil)))
|
||||
{
|
||||
validUntil = parseInt(validUntil);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback({stop: "validUntil is not a number"});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//ensure this is not a negativ number
|
||||
if(validUntil < 0)
|
||||
{
|
||||
callback({stop: "validUntil is a negativ number"});
|
||||
return;
|
||||
}
|
||||
|
||||
//ensure this is not a float value
|
||||
if(!is_int(validUntil))
|
||||
{
|
||||
callback({stop: "validUntil is a float value"});
|
||||
return;
|
||||
}
|
||||
|
||||
//check if validUntil is in the future
|
||||
if(Math.floor(new Date().getTime()/1000) > validUntil)
|
||||
{
|
||||
callback({stop: "validUntil is in the past"});
|
||||
return;
|
||||
}
|
||||
|
||||
//generate sessionID
|
||||
sessionID = "s." + randomString(16);
|
||||
|
||||
//set the session into the database
|
||||
db.set("session:" + sessionID, {"groupID": groupID, "authorID": authorID, "validUntil": validUntil});
|
||||
|
||||
callback();
|
||||
},
|
||||
//set the group2sessions entry
|
||||
function(callback)
|
||||
{
|
||||
//get the entry
|
||||
db.get("group2sessions:" + groupID, function(err, group2sessions)
|
||||
{
|
||||
//did a error happen?
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//the entry doesn't exist so far, let's create it
|
||||
if(group2sessions == null)
|
||||
{
|
||||
group2sessions = {sessionIDs : {}};
|
||||
}
|
||||
|
||||
//add the entry for this session
|
||||
group2sessions.sessionIDs[sessionID] = 1;
|
||||
|
||||
//save the new element back
|
||||
db.set("group2sessions:" + groupID, group2sessions);
|
||||
|
||||
callback();
|
||||
});
|
||||
},
|
||||
//set the author2sessions entry
|
||||
function(callback)
|
||||
{
|
||||
//get the entry
|
||||
db.get("author2sessions:" + authorID, function(err, author2sessions)
|
||||
{
|
||||
//did a error happen?
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
//the entry doesn't exist so far, let's create it
|
||||
if(author2sessions == null)
|
||||
{
|
||||
author2sessions = {sessionIDs : {}};
|
||||
}
|
||||
|
||||
//add the entry for this session
|
||||
author2sessions.sessionIDs[sessionID] = 1;
|
||||
|
||||
//save the new element back
|
||||
db.set("author2sessions:" + authorID, author2sessions);
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
//return error and sessionID
|
||||
callback(err, {sessionID: sessionID});
|
||||
})
|
||||
}
|
||||
|
||||
exports.getSessionInfo = function(sessionID, callback)
|
||||
{
|
||||
//check if the database entry of this session exists
|
||||
db.get("session:" + sessionID, function (err, session)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//session does not exists
|
||||
else if(session == null)
|
||||
{
|
||||
callback({stop: "sessionID does not exist"})
|
||||
}
|
||||
//everything is fine, return the sessioninfos
|
||||
else
|
||||
{
|
||||
callback(null, session);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a session
|
||||
*/
|
||||
exports.deleteSession = function(sessionID, callback)
|
||||
{
|
||||
var authorID, groupID;
|
||||
var group2sessions, author2sessions;
|
||||
|
||||
async.series([
|
||||
function(callback)
|
||||
{
|
||||
//get the session entry
|
||||
db.get("session:" + sessionID, function (err, session)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//session does not exists
|
||||
else if(session == null)
|
||||
{
|
||||
callback({stop: "sessionID does not exist"})
|
||||
}
|
||||
//everything is fine, return the sessioninfos
|
||||
else
|
||||
{
|
||||
authorID = session.authorID;
|
||||
groupID = session.groupID;
|
||||
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
//get the group2sessions entry
|
||||
function(callback)
|
||||
{
|
||||
db.get("group2sessions:" + groupID, function (err, _group2sessions)
|
||||
{
|
||||
group2sessions = _group2sessions;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//get the author2sessions entry
|
||||
function(callback)
|
||||
{
|
||||
db.get("author2sessions:" + authorID, function (err, _author2sessions)
|
||||
{
|
||||
author2sessions = _author2sessions;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//remove the values from the database
|
||||
function(callback)
|
||||
{
|
||||
//remove the session
|
||||
db.remove("session:" + sessionID);
|
||||
|
||||
//remove session from group2sessions
|
||||
delete group2sessions.sessionIDs[sessionID];
|
||||
db.set("group2sessions:" + groupID, group2sessions);
|
||||
|
||||
//remove session from author2sessions
|
||||
delete author2sessions.sessionIDs[sessionID];
|
||||
db.set("author2sessions:" + authorID, author2sessions);
|
||||
|
||||
callback();
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err);
|
||||
})
|
||||
}
|
||||
|
||||
exports.listSessionsOfGroup = function(groupID, callback)
|
||||
{
|
||||
groupMangager.doesGroupExist(groupID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//group does not exist
|
||||
else if(exists == false)
|
||||
{
|
||||
callback({stop: "groupID does not exist"});
|
||||
}
|
||||
//everything is fine, continue
|
||||
else
|
||||
{
|
||||
listSessionsWithDBKey("group2sessions:" + groupID, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.listSessionsOfAuthor = function(authorID, callback)
|
||||
{
|
||||
authorMangager.doesAuthorExists(authorID, function(err, exists)
|
||||
{
|
||||
//error
|
||||
if(err)
|
||||
{
|
||||
callback(err);
|
||||
}
|
||||
//group does not exist
|
||||
else if(exists == false)
|
||||
{
|
||||
callback({stop: "authorID does not exist"});
|
||||
}
|
||||
//everything is fine, continue
|
||||
else
|
||||
{
|
||||
listSessionsWithDBKey("author2sessions:" + authorID, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common
|
||||
function listSessionsWithDBKey (dbkey, callback)
|
||||
{
|
||||
var sessions;
|
||||
|
||||
async.series([
|
||||
function(callback)
|
||||
{
|
||||
//get the group2sessions entry
|
||||
db.get(dbkey, function(err, sessionObject)
|
||||
{
|
||||
sessions = sessionObject ? sessionObject.sessionIDs : null;
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function(callback)
|
||||
{
|
||||
//collect all sessionIDs in an arrary
|
||||
var sessionIDs = [];
|
||||
for (var i in sessions)
|
||||
{
|
||||
sessionIDs.push(i);
|
||||
}
|
||||
|
||||
//foreach trough the sessions and get the sessioninfos
|
||||
async.forEach(sessionIDs, function(sessionID, callback)
|
||||
{
|
||||
exports.getSessionInfo(sessionID, function(err, sessionInfo)
|
||||
{
|
||||
sessions[sessionID] = sessionInfo;
|
||||
callback(err);
|
||||
});
|
||||
}, callback);
|
||||
}
|
||||
], function(err)
|
||||
{
|
||||
callback(err, sessions);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the SessionIDs
|
||||
*/
|
||||
function randomString(len)
|
||||
{
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
||||
|
||||
//checks if a number is an int
|
||||
function is_int(value)
|
||||
{
|
||||
return (parseFloat(value) == parseInt(value)) && !isNaN(value)
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
* Copyright 2009 Google Inc., 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.
|
||||
|
|
144
node/handler/APIHandler.js
Normal file
144
node/handler/APIHandler.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* The API Handler handles all API http requests
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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 fs = require("fs");
|
||||
var api = require("../db/API");
|
||||
|
||||
//ensure we have an apikey
|
||||
var apikey = null;
|
||||
try
|
||||
{
|
||||
apikey = fs.readFileSync("../APIKEY.txt","utf8");
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
apikey = randomString(32);
|
||||
fs.writeFileSync("../APIKEY.txt",apikey,"utf8");
|
||||
}
|
||||
|
||||
//a list of all functions
|
||||
var functions = {
|
||||
"createGroup" : [],
|
||||
"createGroupIfNotExistsFor" : ["groupMapper"],
|
||||
// "deleteGroup" : ["groupID"],
|
||||
"listPads" : ["groupID"],
|
||||
"createPad" : ["padID", "text"],
|
||||
"createGroupPad" : ["groupID", "padName", "text"],
|
||||
"createAuthor" : ["name"],
|
||||
"createAuthorIfNotExistsFor" : ["authorMapper" , "name"],
|
||||
"createSession" : ["groupID", "authorID", "validUntil"],
|
||||
"deleteSession" : ["sessionID"],
|
||||
"getSessionInfo" : ["sessionID"],
|
||||
"listSessionsOfGroup" : ["groupID"],
|
||||
"listSessionsOfAuthor" : ["authorID"],
|
||||
"getText" : ["padID", "rev"],
|
||||
"setText" : ["padID", "text"],
|
||||
"getRevisionsCount" : ["padID"],
|
||||
// "deletePad" : ["padID"],
|
||||
"getReadOnlyID" : ["padID"],
|
||||
"setPublicStatus" : ["padID", "publicStatus"],
|
||||
"getPublicStatus" : ["padID"],
|
||||
"setPassword" : ["padID", "password"],
|
||||
"isPasswordProtected" : ["padID"]
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a HTTP API call
|
||||
* @param functionName the name of the called function
|
||||
* @param fields the params of the called function
|
||||
* @req express request object
|
||||
* @res express response object
|
||||
*/
|
||||
exports.handle = function(functionName, fields, req, res)
|
||||
{
|
||||
//check the api key!
|
||||
if(fields["apikey"] != apikey)
|
||||
{
|
||||
res.send({code: 4, message: "no or wrong API Key", data: null});
|
||||
return;
|
||||
}
|
||||
|
||||
//check if this is a valid function name
|
||||
var isKnownFunctionname = false;
|
||||
for(var knownFunctionname in functions)
|
||||
{
|
||||
if(knownFunctionname == functionName)
|
||||
{
|
||||
isKnownFunctionname = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//say goodbye if this is a unkown function
|
||||
if(!isKnownFunctionname)
|
||||
{
|
||||
res.send({code: 3, message: "no such function", data: null});
|
||||
return;
|
||||
}
|
||||
|
||||
//put the function parameters in an array
|
||||
var functionParams = [];
|
||||
for(var i=0;i<functions[functionName].length;i++)
|
||||
{
|
||||
functionParams.push(fields[functions[functionName][i]]);
|
||||
}
|
||||
|
||||
//add a callback function to handle the response
|
||||
functionParams.push(function(err, data)
|
||||
{
|
||||
// no error happend, everything is fine
|
||||
if(err == null)
|
||||
{
|
||||
if(!data)
|
||||
data = null;
|
||||
|
||||
res.send({code: 0, message: "ok", data: data});
|
||||
}
|
||||
// parameters were wrong and the api stopped execution, pass the error
|
||||
else if(err.stop)
|
||||
{
|
||||
res.send({code: 1, message: err.stop, data: null});
|
||||
}
|
||||
//an unkown error happend
|
||||
else
|
||||
{
|
||||
res.send({code: 2, message: "internal error", data: null});
|
||||
throw (err);
|
||||
}
|
||||
});
|
||||
|
||||
//call the api function
|
||||
api[functionName](functionParams[0],functionParams[1],functionParams[2],functionParams[3],functionParams[4]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random String with the given length. Is needed to generate the Author Ids
|
||||
*/
|
||||
function randomString(len)
|
||||
{
|
||||
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
var randomstring = '';
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
var rnum = Math.floor(Math.random() * chars.length);
|
||||
randomstring += chars.substring(rnum, rnum + 1);
|
||||
}
|
||||
return randomstring;
|
||||
}
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
* Copyright 2009 Google Inc., 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.
|
||||
|
@ -25,6 +25,7 @@ var AttributePoolFactory = require("../utils/AttributePoolFactory");
|
|||
var authorManager = require("../db/AuthorManager");
|
||||
var readOnlyManager = require("../db/ReadOnlyManager");
|
||||
var settings = require('../utils/Settings');
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
|
||||
/**
|
||||
* A associative array that translates a session to a pad
|
||||
|
@ -585,14 +586,29 @@ function handleClientReady(client, message)
|
|||
var chatMessages;
|
||||
|
||||
async.series([
|
||||
//check permissions
|
||||
function(callback)
|
||||
{
|
||||
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
|
||||
{
|
||||
if(err) {callback(err); return}
|
||||
|
||||
//access was granted
|
||||
if(statusObject.accessStatus == "grant")
|
||||
{
|
||||
author = statusObject.authorID;
|
||||
callback();
|
||||
}
|
||||
//no access, send the client a message that tell him why
|
||||
else
|
||||
{
|
||||
client.json.send({accessStatus: statusObject.accessStatus})
|
||||
}
|
||||
});
|
||||
},
|
||||
//get all authordata of this new user
|
||||
function(callback)
|
||||
{
|
||||
//Ask the author Manager for a author of this token.
|
||||
authorManager.getAuthor4Token(message.token, function(err,value)
|
||||
{
|
||||
author = value;
|
||||
|
||||
async.parallel([
|
||||
//get colorId
|
||||
function(callback)
|
||||
|
@ -629,7 +645,6 @@ function handleClientReady(client, message)
|
|||
});
|
||||
}
|
||||
], callback);
|
||||
});
|
||||
},
|
||||
//these db requests all need the pad object
|
||||
function(callback)
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
@ -21,6 +21,7 @@
|
|||
|
||||
var log4js = require('log4js');
|
||||
var messageLogger = log4js.getLogger("message");
|
||||
var securityManager = require("../db/SecurityManager");
|
||||
|
||||
/**
|
||||
* Saves all components
|
||||
|
@ -53,11 +54,13 @@ exports.setSocketIO = function(_socket)
|
|||
|
||||
socket.sockets.on('connection', function(client)
|
||||
{
|
||||
var clientAuthorized = false;
|
||||
|
||||
//wrap the original send function to log the messages
|
||||
client._send = client.send;
|
||||
client.send = function(message)
|
||||
{
|
||||
messageLogger.info("to " + client.id + ": " + JSON.stringify(message));
|
||||
messageLogger.info("to " + client.id + ": " + stringifyWithoutPassword(message));
|
||||
client._send(message);
|
||||
}
|
||||
|
||||
|
@ -67,28 +70,66 @@ exports.setSocketIO = function(_socket)
|
|||
components[i].handleConnect(client);
|
||||
}
|
||||
|
||||
client.on('message', function(message)
|
||||
//try to handle the message of this client
|
||||
function handleMessage(message)
|
||||
{
|
||||
if(message.protocolVersion && message.protocolVersion != 2)
|
||||
{
|
||||
messageLogger.warn("Protocolversion header is not correct:" + JSON.stringify(message));
|
||||
return;
|
||||
}
|
||||
|
||||
//route this message to the correct component, if possible
|
||||
if(message.component && components[message.component])
|
||||
{
|
||||
messageLogger.info("from " + client.id + ": " + JSON.stringify(message));
|
||||
|
||||
//check if component is registered in the components array
|
||||
if(components[message.component])
|
||||
{
|
||||
messageLogger.info("from " + client.id + ": " + stringifyWithoutPassword(message));
|
||||
components[message.component].handleMessage(client, message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
messageLogger.error("Can't route the message:" + JSON.stringify(message));
|
||||
messageLogger.error("Can't route the message:" + stringifyWithoutPassword(message));
|
||||
}
|
||||
}
|
||||
|
||||
client.on('message', function(message)
|
||||
{
|
||||
if(message.protocolVersion && message.protocolVersion != 2)
|
||||
{
|
||||
messageLogger.warn("Protocolversion header is not correct:" + stringifyWithoutPassword(message));
|
||||
return;
|
||||
}
|
||||
|
||||
//client is authorized, everything ok
|
||||
if(clientAuthorized)
|
||||
{
|
||||
handleMessage(message);
|
||||
}
|
||||
//try to authorize the client
|
||||
else
|
||||
{
|
||||
//this message has everything to try an authorization
|
||||
if(message.padId !== undefined && message.sessionID !== undefined && message.token !== undefined && message.password !== undefined)
|
||||
{
|
||||
securityManager.checkAccess (message.padId, message.sessionID, message.token, message.password, function(err, statusObject)
|
||||
{
|
||||
if(err) throw err;
|
||||
|
||||
//access was granted, mark the client as authorized and handle the message
|
||||
if(statusObject.accessStatus == "grant")
|
||||
{
|
||||
clientAuthorized = true;
|
||||
handleMessage(message);
|
||||
}
|
||||
//no access, send the client a message that tell him why
|
||||
else
|
||||
{
|
||||
messageLogger.warn("Authentication try failed:" + stringifyWithoutPassword(message));
|
||||
client.json.send({accessStatus: statusObject.accessStatus});
|
||||
}
|
||||
});
|
||||
}
|
||||
//drop message
|
||||
else
|
||||
{
|
||||
messageLogger.warn("Droped message cause of bad permissions:" + stringifyWithoutPassword(message));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -102,3 +143,20 @@ exports.setSocketIO = function(_socket)
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
//returns a stringified representation of a message, removes the password
|
||||
//this ensures there are no passwords in the log
|
||||
function stringifyWithoutPassword(message)
|
||||
{
|
||||
var newMessage = {};
|
||||
|
||||
for(var i in message)
|
||||
{
|
||||
if(i == "password" && message[i] != null)
|
||||
newMessage["password"] = "xxx";
|
||||
else
|
||||
newMessage[i]=message[i];
|
||||
}
|
||||
|
||||
return JSON.stringify(newMessage);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
* Copyright 2009 Google Inc., 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.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
@ -22,21 +22,24 @@
|
|||
|
||||
require('joose');
|
||||
|
||||
var log4js = require('log4js');
|
||||
var socketio = require('socket.io');
|
||||
var fs = require('fs');
|
||||
var settings = require('./utils/Settings');
|
||||
var socketIORouter = require("./handler/SocketIORouter");
|
||||
var db = require('./db/DB');
|
||||
var async = require('async');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var minify = require('./utils/Minify');
|
||||
var formidable = require('formidable');
|
||||
var log4js = require('log4js');
|
||||
var apiHandler;
|
||||
var exportHandler;
|
||||
var importHandler;
|
||||
var exporthtml;
|
||||
var readOnlyManager;
|
||||
var padManager;
|
||||
var securityManager;
|
||||
var socketIORouter;
|
||||
|
||||
//try to get the git version
|
||||
var version = "";
|
||||
|
@ -74,12 +77,17 @@ async.waterfall([
|
|||
exporthtml = require("./utils/ExportHtml");
|
||||
exportHandler = require('./handler/ExportHandler');
|
||||
importHandler = require('./handler/ImportHandler');
|
||||
apiHandler = require('./handler/APIHandler');
|
||||
padManager = require('./db/PadManager');
|
||||
securityManager = require('./db/SecurityManager');
|
||||
socketIORouter = require("./handler/SocketIORouter");
|
||||
|
||||
//install logging
|
||||
var httpLogger = log4js.getLogger("http");
|
||||
app.configure(function()
|
||||
{
|
||||
app.use(log4js.connectLogger(httpLogger, { level: log4js.levels.INFO, format: ':status, :method :url'}));
|
||||
app.use(express.cookieParser());
|
||||
});
|
||||
|
||||
//serve static files
|
||||
|
@ -156,11 +164,31 @@ async.waterfall([
|
|||
});
|
||||
});
|
||||
|
||||
//checks for padAccess
|
||||
function hasPadAccess(req, res, callback)
|
||||
{
|
||||
securityManager.checkAccess(req.params.pad, req.cookies.sessionid, req.cookies.token, req.cookies.password, function(err, accessObj)
|
||||
{
|
||||
if(err) throw err;
|
||||
|
||||
//there is access, continue
|
||||
if(accessObj.accessStatus == "grant")
|
||||
{
|
||||
callback();
|
||||
}
|
||||
//no access
|
||||
else
|
||||
{
|
||||
res.send("403 - Can't touch this", 403);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//serve pad.html under /p
|
||||
app.get('/p/:pad', function(req, res, next)
|
||||
{
|
||||
//ensure the padname is valid and the url doesn't end with a /
|
||||
if(!isValidPadname(req.params.pad) || /\/$/.test(req.url))
|
||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
||||
{
|
||||
res.send('Such a padname is forbidden', 404);
|
||||
return;
|
||||
|
@ -175,7 +203,7 @@ async.waterfall([
|
|||
app.get('/p/:pad/timeslider', function(req, res, next)
|
||||
{
|
||||
//ensure the padname is valid and the url doesn't end with a /
|
||||
if(!isValidPadname(req.params.pad) || /\/$/.test(req.url))
|
||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
||||
{
|
||||
res.send('Such a padname is forbidden', 404);
|
||||
return;
|
||||
|
@ -190,7 +218,7 @@ async.waterfall([
|
|||
app.get('/p/:pad/export/:type', function(req, res, next)
|
||||
{
|
||||
//ensure the padname is valid and the url doesn't end with a /
|
||||
if(!isValidPadname(req.params.pad) || /\/$/.test(req.url))
|
||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
||||
{
|
||||
res.send('Such a padname is forbidden', 404);
|
||||
return;
|
||||
|
@ -213,14 +241,18 @@ async.waterfall([
|
|||
|
||||
res.header("Access-Control-Allow-Origin", "*");
|
||||
res.header("Server", serverName);
|
||||
|
||||
hasPadAccess(req, res, function()
|
||||
{
|
||||
exportHandler.doExport(req, res, req.params.pad, req.params.type);
|
||||
});
|
||||
});
|
||||
|
||||
//handle import requests
|
||||
app.post('/p/:pad/import', function(req, res, next)
|
||||
{
|
||||
//ensure the padname is valid and the url doesn't end with a /
|
||||
if(!isValidPadname(req.params.pad) || /\/$/.test(req.url))
|
||||
if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url))
|
||||
{
|
||||
res.send('Such a padname is forbidden', 404);
|
||||
return;
|
||||
|
@ -234,11 +266,36 @@ async.waterfall([
|
|||
}
|
||||
|
||||
res.header("Server", serverName);
|
||||
|
||||
hasPadAccess(req, res, function()
|
||||
{
|
||||
importHandler.doImport(req, res, req.params.pad);
|
||||
});
|
||||
});
|
||||
|
||||
var apiLogger = log4js.getLogger("API");
|
||||
|
||||
//This is a api call, collect all post informations and pass it to the apiHandler
|
||||
app.get('/api/1/:func', function(req, res)
|
||||
{
|
||||
res.header("Server", serverName);
|
||||
|
||||
apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(req.query));
|
||||
|
||||
//wrap the send function so we can log the response
|
||||
res._send = res.send;
|
||||
res.send = function(response)
|
||||
{
|
||||
response = JSON.stringify(response);
|
||||
apiLogger.info("RESPONSE, " + req.params.func + ", " + response);
|
||||
res._send(response);
|
||||
}
|
||||
|
||||
//call the api handler
|
||||
apiHandler.handle(req.params.func, req.query, req, res);
|
||||
});
|
||||
|
||||
//The Etherpad client side sends information about how a disconnect happen
|
||||
//I don't know how to use them, but maybe there usefull, so we should print them out to the log
|
||||
app.post('/ep/pad/connection-diagnostic-info', function(req, res)
|
||||
{
|
||||
new formidable.IncomingForm().parse(req, function(err, fields, files)
|
||||
|
@ -319,12 +376,3 @@ async.waterfall([
|
|||
callback(null);
|
||||
}
|
||||
]);
|
||||
|
||||
function isValidPadname(padname)
|
||||
{
|
||||
//ensure there is no dollar sign in the pad name
|
||||
if(padname.indexOf("$")!=-1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
* Copyright 2009 Google Inc., 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.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
* Copyright 2009 Google Inc., 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.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
/*
|
||||
* 2011 Peter 'Pita' Martischka
|
||||
* 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.
|
||||
|
@ -77,7 +77,7 @@ for(var i in settings)
|
|||
//test if the setting start with a low character
|
||||
if(i.charAt(0).search("[a-z]") !== 0)
|
||||
{
|
||||
console.error("WARNING: Settings should start with a low character: '" + i + "'");
|
||||
console.warn("Settings should start with a low character: '" + i + "'");
|
||||
}
|
||||
|
||||
//we know this setting, so we overwrite it
|
||||
|
@ -88,7 +88,7 @@ for(var i in settings)
|
|||
//this setting is unkown, output a warning and throw it away
|
||||
else
|
||||
{
|
||||
console.error("WARNING: Unkown Setting: '" + i + "'");
|
||||
console.error("This setting doesn't exist or it was removed");
|
||||
console.warn("Unkown Setting: '" + i + "'");
|
||||
console.warn("This setting doesn't exist or it was removed");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
/**
|
||||
* Copyright 2009 Google Inc., 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 chat = (function()
|
||||
{
|
||||
var self = {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Copyright 2009 Google Inc., 2011 Peter 'Pita' Martischka
|
||||
* Copyright 2009 Google Inc., 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.
|
||||
|
@ -34,7 +34,7 @@ $(window).unload(function()
|
|||
pad.dispose();
|
||||
});
|
||||
|
||||
function createCookie(name, value, days)
|
||||
function createCookie(name, value, days, path)
|
||||
{
|
||||
if (days)
|
||||
{
|
||||
|
@ -43,7 +43,11 @@ function createCookie(name, value, days)
|
|||
var expires = "; expires=" + date.toGMTString();
|
||||
}
|
||||
else var expires = "";
|
||||
document.cookie = name + "=" + value + expires + "; path=/";
|
||||
|
||||
if(!path)
|
||||
path = "/";
|
||||
|
||||
document.cookie = name + "=" + value + expires + "; path=" + path;
|
||||
}
|
||||
|
||||
function readCookie(name)
|
||||
|
@ -133,6 +137,14 @@ function getUrlVars()
|
|||
return vars;
|
||||
}
|
||||
|
||||
function savePassword()
|
||||
{
|
||||
//set the password cookie
|
||||
createCookie("password",$("#passwordinput").val(),null,document.location.pathname);
|
||||
//reload
|
||||
document.location=document.location;
|
||||
}
|
||||
|
||||
function handshake()
|
||||
{
|
||||
var loc = document.location;
|
||||
|
@ -161,10 +173,15 @@ function handshake()
|
|||
createCookie("token", token, 60);
|
||||
}
|
||||
|
||||
var sessionID = readCookie("sessionID");
|
||||
var password = readCookie("password");
|
||||
|
||||
var msg = {
|
||||
"component": "pad",
|
||||
"type": "CLIENT_READY",
|
||||
"padId": padId,
|
||||
"sessionID": sessionID,
|
||||
"password": password,
|
||||
"token": token,
|
||||
"protocolVersion": 2
|
||||
};
|
||||
|
@ -176,17 +193,41 @@ function handshake()
|
|||
|
||||
socket.on('message', function(obj)
|
||||
{
|
||||
//if we haven't recieved the clientVars yet, then this message should it be
|
||||
if (!receivedClientVars)
|
||||
//the access was not granted, give the user a message
|
||||
if(!receivedClientVars && obj.accessStatus)
|
||||
{
|
||||
if(obj.accessStatus == "deny")
|
||||
{
|
||||
$("#editorloadingbox").html("<b>You have no permission to access this pad</b>");
|
||||
}
|
||||
else if(obj.accessStatus == "needPassword")
|
||||
{
|
||||
$("#editorloadingbox").html("<b>You need a password to access this pad</b><br>" +
|
||||
"<input id='passwordinput' type='password' name='password'>"+
|
||||
"<button type='button' onclick='savePassword()'>ok</button>");
|
||||
}
|
||||
else if(obj.accessStatus == "wrongPassword")
|
||||
{
|
||||
$("#editorloadingbox").html("<b>You're password was wrong</b><br>" +
|
||||
"<input id='passwordinput' type='password' name='password'>"+
|
||||
"<button type='button' onclick='savePassword()'>ok</button>");
|
||||
}
|
||||
}
|
||||
|
||||
//if we haven't recieved the clientVars yet, then this message should it be
|
||||
else if (!receivedClientVars)
|
||||
{
|
||||
//log the message
|
||||
if (window.console) console.log(obj);
|
||||
|
||||
receivedClientVars = true;
|
||||
|
||||
//set some client vars
|
||||
clientVars = obj;
|
||||
clientVars.userAgent = "Anonymous";
|
||||
clientVars.collab_client_vars.clientAgent = "Anonymous";
|
||||
|
||||
//initalize the pad
|
||||
pad.init();
|
||||
initalized = true;
|
||||
|
||||
|
@ -195,20 +236,17 @@ function handshake()
|
|||
{
|
||||
pad.changeViewOption('showLineNumbers', false);
|
||||
}
|
||||
|
||||
// If the Monospacefont value is set to true then change it to monospace.
|
||||
if (useMonospaceFontGlobal == true)
|
||||
{
|
||||
pad.changeViewOption('useMonospaceFont', true);
|
||||
}
|
||||
|
||||
// if the globalUserName value is set we need to tell the server and the client about the new authorname
|
||||
if (globalUserName !== false)
|
||||
{
|
||||
pad.notifyChangeName(globalUserName); // Notifies the server
|
||||
$('#myusernameedit').attr({"value":globalUserName}); // Updates the current users UI
|
||||
}
|
||||
|
||||
}
|
||||
//This handles every Message after the clientVars
|
||||
else
|
||||
|
|
164
static/tests.html
Normal file
164
static/tests.html
Normal file
|
@ -0,0 +1,164 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>API Test and Examples Page</title>
|
||||
<script type="text/javascript" src="js/jquery.min.js"></script>
|
||||
<style type="text/css">
|
||||
body {
|
||||
font-size:9pt;
|
||||
background: rgba(0, 0, 0, .05);
|
||||
color: #333;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
font: 14px helvetica,sans-serif;
|
||||
background: #ccc;
|
||||
background: -moz-radial-gradient(circle, #aaa, #eee) no-repeat center center fixed;
|
||||
background: -webkit-radial-gradient(circle, #aaa, #eee) no-repeat center center fixed;
|
||||
background: -ms-radial-gradient(circle, #aaa, #eee) no-repeat center center fixed;
|
||||
background: -o-radial-gradient(circle, #aaa, #eee) no-repeat center center fixed;
|
||||
width: 1000px;
|
||||
}
|
||||
.define, #template {
|
||||
display: none;
|
||||
}
|
||||
.test_group {
|
||||
overflow: auto;
|
||||
width: 300px;
|
||||
float:left;
|
||||
color: #555;
|
||||
|
||||
border-top: 1px solid #999;
|
||||
margin: 4px;
|
||||
padding: 4px 10px 4px 10px;
|
||||
background: #eee;
|
||||
background: -webkit-linear-gradient(#fff, #ccc);
|
||||
background: -moz-linear-gradient(#fff, #ccc);
|
||||
background: -ms-linear-gradient(#fff, #ccc);
|
||||
background: -o-linear-gradient(#fff, #ccc);
|
||||
opacity: .9;
|
||||
box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.test_group h2 {
|
||||
font-size: 10pt;
|
||||
}
|
||||
.test_group table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#apikeyDIV {
|
||||
width: 100%
|
||||
}
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('input[type=button]').live('click', function() {
|
||||
var $test_group = $(this).closest('.test_group');
|
||||
var name = parseName($test_group.find('h2').text());
|
||||
|
||||
var results_node = $test_group.find('.results');
|
||||
|
||||
var params = {};
|
||||
$test_group.find('input[type=text]').each(function() {
|
||||
params[$(this).attr('name')] = $(this).val();
|
||||
});
|
||||
|
||||
callFunction(name, results_node, params);
|
||||
});
|
||||
|
||||
$('.define').each(function() {
|
||||
var functionName = parseName($(this).text());
|
||||
var parameters = parseParameters($(this).text());
|
||||
|
||||
var $template = $('#template').clone();
|
||||
|
||||
$template.find('h2').text(functionName + "()");
|
||||
|
||||
var $table = $template.find('table');
|
||||
|
||||
$(parameters).each(function(index, el) {
|
||||
$table.prepend('<tr><td>' + el + ':</td>' +
|
||||
'<td style="width:200px"><input type="text" size="10" name="' + el + '" /></td></tr>');
|
||||
});
|
||||
|
||||
$template.css({display: "block"});
|
||||
$template.appendTo('body');
|
||||
});
|
||||
});
|
||||
|
||||
function parseName(str)
|
||||
{
|
||||
return str.substring(0, str.indexOf('('));
|
||||
}
|
||||
|
||||
function parseParameters(str)
|
||||
{
|
||||
// parse out the parameters by looking for parens
|
||||
var parens = str.substring(str.indexOf("("));
|
||||
|
||||
// return empty array if there are no paremeters
|
||||
if(parens.length < 3)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// remove parens from string
|
||||
parens = parens.substring(1);
|
||||
parens = parens.substring(0, parens.length-1);
|
||||
|
||||
return parens.split(',');
|
||||
}
|
||||
|
||||
function callFunction(memberName, results_node, params)
|
||||
{
|
||||
$('#result').text('Calling ' + memberName + "()...");
|
||||
|
||||
params["apikey"]=$("#apikey").val();
|
||||
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: "/api/1/" + memberName,
|
||||
data: params,
|
||||
success: function(json) {
|
||||
results_node.text(json);
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
results_node.html("textStatus: " + textStatus + "<br />errorThrown: " + errorThrown);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="apikeyDIV" class="test_group"><b>APIKEY: </b><input type="text" id="apikey"></div>
|
||||
<div class="test_group" id="template">
|
||||
<h2>createGroup()</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="buttonBox" colspan="2" style="text-align:right;"><input type="button" value="Run" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="results"/>
|
||||
</div>
|
||||
<div class="define">createGroup()</div>
|
||||
<div class="define">createGroupIfNotExistsFor(groupMapper)</div>
|
||||
<div class="define">listPads(groupID)</div>
|
||||
<div class="define">createPad(padID,text)</div>
|
||||
<div class="define">createGroupPad(groupID,padName,text)</div>
|
||||
<div class="define">createAuthor(name)</div>
|
||||
<div class="define">createAuthorIfNotExistsFor(authorMapper,name)</div>
|
||||
<div class="define">createSession(groupID,authorID,validUntil)</div>
|
||||
<div class="define">deleteSession(sessionID)</div>
|
||||
<div class="define">getSessionInfo(sessionID)</div>
|
||||
<div class="define">listSessionsOfGroup(groupID)</div>
|
||||
<div class="define">listSessionsOfAuthor(authorID)</div>
|
||||
<div class="define">getText(padID,rev)</div>
|
||||
<div class="define">setText(padID,text)</div>
|
||||
<div class="define">getRevisionsCount(padID)</div>
|
||||
<div class="define">deletePad(padID)</div>
|
||||
<div class="define">getReadOnlyID(padID)</div>
|
||||
<div class="define">setPublicStatus(padID,publicStatus)</div>
|
||||
<div class="define">getPublicStatus(padID)</div>
|
||||
<div class="define">setPassword(padID,password)</div>
|
||||
<div class="define">isPasswordProtected(padID)</div>
|
||||
</body>
|
||||
</html>
|
|
@ -105,17 +105,26 @@
|
|||
{
|
||||
changesetLoader.handleSocketResponse(message);
|
||||
}
|
||||
else if(message.accessStatus)
|
||||
{
|
||||
$("body").html("<h2>You have no permission to access this pad</h2>")
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//sends a message over the socket
|
||||
function sendSocketMsg(type, data)
|
||||
{
|
||||
var sessionID = readCookie("sessionID");
|
||||
var password = readCookie("password");
|
||||
|
||||
var msg = { "component" : "timeslider",
|
||||
"type": type,
|
||||
"data": data,
|
||||
"padId": padId,
|
||||
"token": token,
|
||||
"sessionID": sessionID,
|
||||
"password": password,
|
||||
"protocolVersion": 2};
|
||||
|
||||
socket.json.send(msg);
|
||||
|
|
Loading…
Reference in a new issue