From 31067f163f175ab7073df73179315a99a7814762 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 3 Aug 2011 12:09:19 +0100 Subject: [PATCH 01/34] added a documentation for the database structure --- doc/database.md | 69 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 doc/database.md diff --git a/doc/database.md b/doc/database.md new file mode 100644 index 000000000..95718ffd4 --- /dev/null +++ b/doc/database.md @@ -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 null +### 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 + +* **sessions** - object with sessionIDs in it, values are null + +### group2sessions:$GROUPID + +* **sessions** - object with sessionIDs in it, values are null From 39b5f57d4ebc64902e10876f668928c08192be48 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 3 Aug 2011 19:30:45 +0100 Subject: [PATCH 02/34] Let settings warnings output as warnings, not as an error --- node/utils/Settings.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/node/utils/Settings.js b/node/utils/Settings.js index 521a6b325..d78793b57 100644 --- a/node/utils/Settings.js +++ b/node/utils/Settings.js @@ -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"); } } From f45b7ce9ead53215eb8f63ab744361ae3f6057f2 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 3 Aug 2011 19:31:25 +0100 Subject: [PATCH 03/34] Installed API infrastructure, getText works already --- .gitignore | 3 +- node/db/API.js | 457 +++++++++++++++++++++++++++++++++++++ node/db/GroupManager.js | 21 ++ node/db/PadManager.js | 12 +- node/db/SessionManager.js | 19 ++ node/handler/APIHandler.js | 139 +++++++++++ node/server.js | 26 ++- 7 files changed, 672 insertions(+), 5 deletions(-) create mode 100644 node/db/API.js create mode 100644 node/db/GroupManager.js create mode 100644 node/db/SessionManager.js create mode 100644 node/handler/APIHandler.js diff --git a/.gitignore b/.gitignore index df6d75bec..25c577ecc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules settings.json -static/js/jquery.min.js \ No newline at end of file +static/js/jquery.min.js +APIKEY.txt diff --git a/node/db/API.js b/node/db/API.js new file mode 100644 index 000000000..d53e50121 --- /dev/null +++ b/node/db/API.js @@ -0,0 +1,457 @@ +/** + * This module provides all API functions + */ + +/* + * 2011 Peter 'Pita' Martischka + * + * 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 async = require("async"); + +/**********************/ +/**GROUP FUNCTIONS*****/ +/**********************/ + +/** +createGroup() creates a new group + +Example returns: + +{code: 0, message:"ok", data: {groupID: 5}} +*/ +exports.createGroup = function (callback) +{ + +} + +/** +getMappedGroup4(groupMapper) this functions helps you to map your application group ids to etherpad lite group ids + +Example returns: + +{code: 0, message:"ok", data: {groupID: 7}} +*/ +exports.getMappedGroup4 = function (groupMapper, callback) +{ + +} + +/** +deleteGroup(groupID) deletes a group + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"There is no group for this groupID", data: null} +*/ +exports.deleteGroup = function(groupID, callback) +{ + +} + +/** +listPads(groupID) returns all pads of this group + +Example returns: + +{code: 0, message:"ok", data: {padIDs : ["3$test", "3$test2"]} +{code: 1, message:"There is no group for this groupID", data: null} +*/ +exports.listPads = function(groupID, callback) +{ + +} + +/** +createPad(groupID, 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} +{code: 1, message:"There is no group for this groupID", data: null} +*/ +exports.createPad = function(groupID, padName, text, callback) +{ + +} + +/**********************/ +/**AUTHOR FUNCTIONS****/ +/**********************/ + + +/** +createAuthor([name]) creates a new author + +Example returns: + +{code: 0, message:"ok", data: {authorID: 5}} +*/ +exports.createAuthor = function(name, callback) +{ + +} + +/** +getMappedAuthor4(authorMapper [, name]) this functions helps you to map your application author ids to etherpad lite author ids + +Example returns: + +{code: 0, message:"ok", data: {authorID: 5}} +*/ +exports.getMappedAuthor4 = function(authorMapper ,name, callback) +{ + +} + +/**********************/ +/**SESSION FUNCTIONS***/ +/**********************/ + +/** +createSession(groupID, authorID, validUntil) creates a new session + +Example returns: + +{code: 0, message:"ok", data: {sessionID: 5}} +{code: 1, message:"groupID doesn't exist", data: null} +{code: 1, message:"authorID doesn't exist", data: null} +{code: 1, message:"validUntil is in the past", data: null} +*/ +exports.createSession = function(groupID, authorID, validUntil, callback) +{ + +} + +/** +deleteSession(sessionID) deletes a session + +Example returns: + +{code: 1, message:"ok", data: null} +{code: 1, message:"sessionID does not exist", data: null} +*/ +exports.deleteSession = function(sessionID, callback) +{ + +} + +/** +getSessionInfo(sessionID) returns informations about a session + +Example returns: + +{code: 0, message:"ok", data: {authorID: 5, groupID: 7, validUntil: 1312201246}} +{code: 1, message:"sessionID does not exist", data: null} +*/ +exports.getSessionInfo = function(sessionID, callback) +{ + +} + +/** +listSessionsOfGroup(groupID) returns all sessions of a group + +Example returns: + +{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} +{code: 1, message:"groupID does not exist", data: null} +*/ +exports.listSessionsOfGroup = function(groupID, callback) +{ + +} + +/** +listSessionsOfAuthor(authorID) returns all sessions of an author + +Example returns: + +{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} +{code: 1, message:"authorID does not exist", data: null} +*/ +exports.listSessionsOfAuthor = function(authorID, callback) +{ + +} + +/** +deleteAllSessionsOfGroup(groupID) deletes all sessions of a group + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"groupID does not exist", data: null} +*/ +exports.deleteAllSessionsOfGroup = function(groupID, callback) +{ + +} + +/** +deleteAllSessionsOfAuthor(authorID) deletes all sessions of an author + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"authorID does not exist", data: null} +*/ +exports.deleteAllSessionsOfAuthor = function(authorID, callback) +{ + +} + +/************************/ +/**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 padID is a string + if(typeof padID != "string") + { + callback({stop: "padID is not a string"}); + return; + } + + //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; + } + + var pad; + var data; + + async.series([ + //check if pad exists + function(callback) + { + padManager.doesPadExists(padID, function(err, exists) + { + if(err) + { + callback(err); + } + else + { + callback(exists == false ? {stop: "padID does not exist"} : null) + } + }); + }, + //get the pad object + function(callback) + { + padManager.getPad(padID, function(err, _pad) + { + pad=_pad; + callback(err); + }); + }, + //return the text + function(callback) + { + //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); + }) + } + //the client wants the latest text, lets return it to him + else + { + data = {"text": pad.text()}; + callback(); + } + } + ], function(err) + { + callback(err, data) + }); +} + +/** +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) +{ + +} + +/*****************/ +/**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) +{ + +} + +/** +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) +{ + +} + +/** +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.getReadOnlyLink = function(padID, callback) +{ + +} + +/** +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) +{ + +} + +/** +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) +{ + +} + +/** +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) +{ + +} + +/** +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) +{ + +} + +/******************************/ +/** INTERNAL HELPER FUNCTIONS */ +/******************************/ + +//checks if a number is an int +function is_int(value) +{ + return (parseFloat(value) == parseInt(value)) && !isNaN(value) +} diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js new file mode 100644 index 000000000..2ceb1b656 --- /dev/null +++ b/node/db/GroupManager.js @@ -0,0 +1,21 @@ +/** + * The Group Manager provides functions to manage groups in the database + */ + +/* + * 2011 Peter 'Pita' Martischka + * + * 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. + */ + + diff --git a/node/db/PadManager.js b/node/db/PadManager.js index 04ab7eb7f..4fada2c50 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -19,6 +19,7 @@ */ require("../db/Pad"); +var db = require("./DB").db; /** * A Array with all known Pads @@ -58,6 +59,13 @@ 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); + }); } diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js new file mode 100644 index 000000000..f25389111 --- /dev/null +++ b/node/db/SessionManager.js @@ -0,0 +1,19 @@ +/** + * The Session Manager provides functions to manage session in the database + */ + +/* + * 2011 Peter 'Pita' Martischka + * + * 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. + */ diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js new file mode 100644 index 000000000..2520bda3e --- /dev/null +++ b/node/handler/APIHandler.js @@ -0,0 +1,139 @@ +/** + * The API Handler handles all API http requests + */ + +/* + * 2011 Peter 'Pita' Martischka + * + * 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" : [], +// "getMappedGroup4" : ["groupMapper"], +// "deleteGroup" : ["groupID"], +// "listPads" : ["groupID"], +// "createPad" : ["groupID", "padName", "text"], +// "createAuthor" : ["name"], +// "getMappedAuthor4" : ["authorMapper" , "name"], +// "createSession" : ["groupID", "authorID", "validUntil"], +// "deleteSession" : ["sessionID"], +// "getSessionInfo" : ["sessionID"], +// "listSessionsOfGroup" : ["groupID"], +// "listSessionsOfAuthor" : ["authorID"], +// "deleteAllSessionsOfGroup" : ["groupID"], +// "deleteAllSessionsOfAuthor" : ["authorID"], + "getText" : ["padID", "rev"] +// "setText" : ["padID", "text"] +// "getRevisionsCount" : ["padID"], +// "deletePad" : ["padID"], +// "getReadOnlyLink" : ["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 Date: Thu, 4 Aug 2011 16:07:58 +0100 Subject: [PATCH 04/34] check the padID with a regular expression --- node/db/PadManager.js | 9 +++++++++ node/server.js | 29 ++++++++++++++++++----------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/node/db/PadManager.js b/node/db/PadManager.js index 4fada2c50..ff39849fb 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -33,6 +33,9 @@ globalPads = []; */ exports.getPad = function(id, callback) { + if(!exports.isValidPadId(id)) + throw new Error(id + " is not a valid padId"); + var pad = globalPads[id]; //return pad if its already loaded @@ -69,3 +72,9 @@ exports.doesPadExists = function(padId, callback) callback(err, value != null); }); } + +exports.isValidPadId = function(padId) +{ + return /^([0-9]+\$)?[^$]{1,50}$/.test(padId); +} + diff --git a/node/server.js b/node/server.js index 7566477db..cea64690e 100644 --- a/node/server.js +++ b/node/server.js @@ -38,6 +38,7 @@ var exportHandler; var importHandler; var exporthtml; var readOnlyManager; +var padManager; //try to get the git version var version = ""; @@ -76,6 +77,7 @@ async.waterfall([ exportHandler = require('./handler/ExportHandler'); importHandler = require('./handler/ImportHandler'); apiHandler = require('./handler/APIHandler'); + padManager = require('./db/PadManager'); //install logging var httpLogger = log4js.getLogger("http"); @@ -162,7 +164,7 @@ async.waterfall([ 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)) { next(); return; @@ -177,7 +179,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)) { next(); return; @@ -191,6 +193,13 @@ async.waterfall([ //serve timeslider.html under /p/$padname/timeslider app.get('/p/:pad/export/:type', function(req, res, next) { + //ensure the padname is valid and the url doesn't end with a / + if(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url)) + { + next(); + return; + } + var types = ["pdf", "doc", "txt", "html", "odt"]; //send a 404 if we don't support this filetype if(types.indexOf(req.params.type) == -1) @@ -213,6 +222,13 @@ async.waterfall([ //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(!padManager.isValidPadId(req.params.pad) || /\/$/.test(req.url)) + { + next(); + return; + } + //if abiword is disabled, skip handling this request if(settings.abiword == null) { @@ -326,12 +342,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; -} From d5d9830dd3485aa239cb9371059d3ab8f9f73325 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 4 Aug 2011 17:18:59 +0100 Subject: [PATCH 05/34] added setText and simplified getText --- node/db/API.js | 213 +++++++++++++++++++++++++------------ node/handler/APIHandler.js | 4 +- 2 files changed, 146 insertions(+), 71 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index d53e50121..28dabb133 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -19,6 +19,7 @@ */ var padManager = require("./PadManager"); +var padMessageHandler = require("../handler/PadMessageHandler"); var async = require("async"); /**********************/ @@ -236,13 +237,6 @@ exports.getText = function(padID, rev, callback) rev = undefined; } - //check if padID is a string - if(typeof padID != "string") - { - callback({stop: "padID is not a string"}); - return; - } - //check if rev is a number if(rev !== undefined && typeof rev != "number") { @@ -272,68 +266,41 @@ exports.getText = function(padID, rev, callback) return; } - var pad; - var data; - - async.series([ - //check if pad exists - function(callback) + //get the pad + getPadSafe(padID, function(err, pad) + { + if(err) { - padManager.doesPadExists(padID, function(err, exists) - { - if(err) - { - callback(err); - } - else - { - callback(exists == false ? {stop: "padID does not exist"} : null) - } - }); - }, - //get the pad object - function(callback) + callback(err); + return; + } + + //the client asked for a special revision + if(rev !== undefined) { - padManager.getPad(padID, function(err, _pad) + //check if this is a valid revision + if(rev > pad.getHeadRevisionNumber()) { - pad=_pad; - callback(err); - }); - }, - //return the text - function(callback) - { - //the client asked for a special revision - if(rev !== undefined) + 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) { - //check if this is a valid revision - if(rev > pad.getHeadRevisionNumber()) + if(!err) { - callback({stop: "rev is higher than the head revision of the pad"}); - return; + data = {text: atext.text}; } - //get the text of this revision - pad.getInternalRevisionAText(rev, function(err, atext) - { - if(!err) - { - data = {text: atext.text}; - } - - callback(err); - }) - } - //the client wants the latest text, lets return it to him - else - { - data = {"text": pad.text()}; - callback(); - } + callback(err, data); + }) + } + //the client wants the latest text, lets return it to him + else + { + callback(null, {"text": pad.text()}); } - ], function(err) - { - callback(err, data) }); } @@ -347,8 +314,36 @@ Example returns: {code: 1, message:"text too long", data: null} */ exports.setText = function(padID, text, callback) -{ +{ + //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; + } + + //get the pad + getPadSafe(padID, function(err, pad) + { + if(err) + { + callback(err); + return; + } + + //set the text + pad.setText(text); + + //update the clients on the pad + padMessageHandler.updatePadClients(pad, callback); + }); } /*****************/ @@ -365,7 +360,13 @@ Example returns: */ exports.getRevisionsCount = function(padID, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /** @@ -378,7 +379,13 @@ Example returns: */ exports.deletePad = function(padID, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /** @@ -391,7 +398,13 @@ Example returns: */ exports.getReadOnlyLink = function(padID, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /** @@ -404,7 +417,13 @@ Example returns: */ exports.setPublicStatus = function(padID, publicStatus, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /** @@ -417,7 +436,13 @@ Example returns: */ exports.getPublicStatus = function(padID, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /** @@ -430,7 +455,13 @@ Example returns: */ exports.setPassword = function(padID, password, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /** @@ -443,7 +474,13 @@ Example returns: */ exports.isPasswordProtected = function(padID, callback) { - + //check if this is a valid padID + var notValidReason = isValidPadID(padID); + if(notValidReason != null) + { + callback(notValidReason); + return; + } } /******************************/ @@ -455,3 +492,41 @@ function is_int(value) { return (parseFloat(value) == parseInt(value)) && !isNaN(value) } + +//gets a pad safe +function getPadSafe(padID, callback) +{ + //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 exists + else if(exists == false) + { + callback({stop: "padID does not exist"}); + } + //pad exists, let's get it + else + { + padManager.getPad(padID, callback); + } + }); +} diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 2520bda3e..20d4d30f1 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -49,8 +49,8 @@ var functions = { // "listSessionsOfAuthor" : ["authorID"], // "deleteAllSessionsOfGroup" : ["groupID"], // "deleteAllSessionsOfAuthor" : ["authorID"], - "getText" : ["padID", "rev"] -// "setText" : ["padID", "text"] + "getText" : ["padID", "rev"], + "setText" : ["padID", "text"] // "getRevisionsCount" : ["padID"], // "deletePad" : ["padID"], // "getReadOnlyLink" : ["padID"], From 7b030710d22415a749cacc64574aa08227befbf6 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 4 Aug 2011 17:24:36 +0100 Subject: [PATCH 06/34] added getRevisionsCount --- node/db/API.js | 104 ++++++++++++++++++++++--------------- node/handler/APIHandler.js | 4 +- 2 files changed, 63 insertions(+), 45 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index 28dabb133..6bfc4f420 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -360,13 +360,17 @@ Example returns: */ exports.getRevisionsCount = function(padID, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) + //get the pad + getPadSafe(padID, function(err, pad) { - callback(notValidReason); - return; - } + if(err) + { + callback(err); + return; + } + + callback(null, {revisions: pad.getHeadRevisionNumber()}); + }); } /** @@ -379,13 +383,17 @@ Example returns: */ exports.deletePad = function(padID, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) + //get the pad + getPadSafe(padID, function(err, pad) { - callback(notValidReason); - return; - } + if(err) + { + callback(err); + return; + } + + + }); } /** @@ -398,13 +406,7 @@ Example returns: */ exports.getReadOnlyLink = function(padID, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) - { - callback(notValidReason); - return; - } + } /** @@ -417,13 +419,17 @@ Example returns: */ exports.setPublicStatus = function(padID, publicStatus, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) + //get the pad + getPadSafe(padID, function(err, pad) { - callback(notValidReason); - return; - } + if(err) + { + callback(err); + return; + } + + + }); } /** @@ -436,13 +442,17 @@ Example returns: */ exports.getPublicStatus = function(padID, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) + //get the pad + getPadSafe(padID, function(err, pad) { - callback(notValidReason); - return; - } + if(err) + { + callback(err); + return; + } + + + }); } /** @@ -455,13 +465,17 @@ Example returns: */ exports.setPassword = function(padID, password, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) + //get the pad + getPadSafe(padID, function(err, pad) { - callback(notValidReason); - return; - } + if(err) + { + callback(err); + return; + } + + + }); } /** @@ -474,13 +488,17 @@ Example returns: */ exports.isPasswordProtected = function(padID, callback) { - //check if this is a valid padID - var notValidReason = isValidPadID(padID); - if(notValidReason != null) + //get the pad + getPadSafe(padID, function(err, pad) { - callback(notValidReason); - return; - } + if(err) + { + callback(err); + return; + } + + + }); } /******************************/ diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 20d4d30f1..8cb9e9c89 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -50,8 +50,8 @@ var functions = { // "deleteAllSessionsOfGroup" : ["groupID"], // "deleteAllSessionsOfAuthor" : ["authorID"], "getText" : ["padID", "rev"], - "setText" : ["padID", "text"] -// "getRevisionsCount" : ["padID"], + "setText" : ["padID", "text"], + "getRevisionsCount" : ["padID"], // "deletePad" : ["padID"], // "getReadOnlyLink" : ["padID"], // "setPublicStatus" : ["padID", "publicStatus"], From 3e0f30bddef185db6617cd67283c45eccd071e8d Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 4 Aug 2011 17:40:51 +0100 Subject: [PATCH 07/34] added getReadOnlyID --- node/db/API.js | 19 +++++++++++++++++-- node/handler/APIHandler.js | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index 6bfc4f420..8d19dab35 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -20,6 +20,7 @@ var padManager = require("./PadManager"); var padMessageHandler = require("../handler/PadMessageHandler"); +var readOnlyManager = require("./ReadOnlyManager"); var async = require("async"); /**********************/ @@ -404,9 +405,23 @@ Example returns: {code: 0, message:"ok", data: null} {code: 1, message:"padID does not exist", data: null} */ -exports.getReadOnlyLink = function(padID, callback) +exports.getReadOnlyID = function(padID, callback) { - + //we don't need the pad object, but this function does all the security stuff for us + getPadSafe(padID, function(err) + { + if(err) + { + callback(err); + return; + } + + //get the readonlyId + readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId) + { + callback(err, {readOnlyID: readOnlyId}); + }); + }); } /** diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 8cb9e9c89..46733bfcd 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -53,7 +53,7 @@ var functions = { "setText" : ["padID", "text"], "getRevisionsCount" : ["padID"], // "deletePad" : ["padID"], -// "getReadOnlyLink" : ["padID"], + "getReadOnlyID" : ["padID"], // "setPublicStatus" : ["padID", "publicStatus"], // "getPublicStatus" : ["padID"], // "setPassword" : ["padID", "password"], From 8dabdc8e95db0c0969073984a935708ae6725b2d Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 4 Aug 2011 18:22:03 +0100 Subject: [PATCH 08/34] split createPad to createPad and createGroupPad --- node/handler/APIHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 46733bfcd..15744374c 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -39,7 +39,8 @@ var functions = { // "getMappedGroup4" : ["groupMapper"], // "deleteGroup" : ["groupID"], // "listPads" : ["groupID"], -// "createPad" : ["groupID", "padName", "text"], +// "createPad" : ["padName", "text"], +// "createGroupPad" : ["groupID", "padName", "text"], // "createAuthor" : ["name"], // "getMappedAuthor4" : ["authorMapper" , "name"], // "createSession" : ["groupID", "authorID", "validUntil"], From 9fa52c0e66819fdf4651f231abe1c14cff59a5d1 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 4 Aug 2011 19:20:14 +0100 Subject: [PATCH 09/34] added createPad --- node/db/API.js | 109 ++++++++++++++++++++++++++++--------- node/db/Pad.js | 10 +++- node/db/PadManager.js | 11 +++- node/handler/APIHandler.js | 4 +- 4 files changed, 102 insertions(+), 32 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index 8d19dab35..611826d3f 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -78,7 +78,7 @@ exports.listPads = function(groupID, callback) } /** -createPad(groupID, padName [, text]) creates a new pad in this group +createGroupPad(groupID, padName [, text]) creates a new pad in this group Example returns: @@ -86,7 +86,7 @@ Example returns: {code: 1, message:"pad does already exist", data: null} {code: 1, message:"There is no group for this groupID", data: null} */ -exports.createPad = function(groupID, padName, text, callback) +exports.createGroupPad = function(groupID, padName, text, callback) { } @@ -268,7 +268,7 @@ exports.getText = function(padID, rev, callback) } //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -316,22 +316,16 @@ Example returns: */ exports.setText = function(padID, text, callback) { - //check if text is a string - if(typeof text != "string") + //check the text + var textCheck = checkPadText(text); + if(textCheck != null) { - 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"}) + callback(textCheck); return; } //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -362,7 +356,7 @@ Example returns: exports.getRevisionsCount = function(padID, callback) { //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -374,6 +368,41 @@ exports.getRevisionsCount = function(padID, callback) }); } +/** +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) +{ + if(text) + { + //check the text + var textCheck = checkPadText(text); + if(textCheck != null) + { + callback(textCheck); + return; + } + } + + //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 @@ -385,7 +414,7 @@ Example returns: exports.deletePad = function(padID, callback) { //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -408,7 +437,7 @@ Example returns: exports.getReadOnlyID = function(padID, callback) { //we don't need the pad object, but this function does all the security stuff for us - getPadSafe(padID, function(err) + getPadSafe(padID, true, function(err) { if(err) { @@ -435,7 +464,7 @@ Example returns: exports.setPublicStatus = function(padID, publicStatus, callback) { //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -458,7 +487,7 @@ Example returns: exports.getPublicStatus = function(padID, callback) { //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -481,7 +510,7 @@ Example returns: exports.setPassword = function(padID, password, callback) { //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -504,7 +533,7 @@ Example returns: exports.isPasswordProtected = function(padID, callback) { //get the pad - getPadSafe(padID, function(err, pad) + getPadSafe(padID, true, function(err, pad) { if(err) { @@ -526,9 +555,32 @@ function is_int(value) return (parseFloat(value) == parseInt(value)) && !isNaN(value) } -//gets a pad safe -function getPadSafe(padID, callback) +function checkPadText(text) { + //check if text is a string + if(typeof text != "string") + { + return {stop: "text is not a string"}; + } + + //check if text is less than 100k chars + if(text.length > 100000) + { + return {stop: "text must be less than 100k chars"}; + } + + return null; +} + +//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") { @@ -551,15 +603,20 @@ function getPadSafe(padID, callback) { callback(err); } - //does not exists - else if(exists == false) + //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, callback); + padManager.getPad(padID, text, callback); } }); } diff --git a/node/db/Pad.js b/node/db/Pad.js index d80098701..982ba1c35 100644 --- a/node/db/Pad.js +++ b/node/db/Pad.js @@ -310,9 +310,15 @@ 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) @@ -338,7 +344,7 @@ Class('Pad', { //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, ''); } diff --git a/node/db/PadManager.js b/node/db/PadManager.js index ff39849fb..c1b69bd57 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -31,11 +31,18 @@ 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) { if(!exports.isValidPadId(id)) throw new Error(id + " is not a valid padId"); + //make text an optional parameter + if(typeof text == "function") + { + callback = text; + text = null; + } + var pad = globalPads[id]; //return pad if its already loaded @@ -49,7 +56,7 @@ exports.getPad = function(id, callback) pad = new Pad(id); //initalize the pad - pad.init(function(err) + pad.init(text, function(err) { if(err) { diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 15744374c..6afe11733 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -39,7 +39,7 @@ var functions = { // "getMappedGroup4" : ["groupMapper"], // "deleteGroup" : ["groupID"], // "listPads" : ["groupID"], -// "createPad" : ["padName", "text"], + "createPad" : ["padID", "text"], // "createGroupPad" : ["groupID", "padName", "text"], // "createAuthor" : ["name"], // "getMappedAuthor4" : ["authorMapper" , "name"], @@ -54,7 +54,7 @@ var functions = { "setText" : ["padID", "text"], "getRevisionsCount" : ["padID"], // "deletePad" : ["padID"], - "getReadOnlyID" : ["padID"], + "getReadOnlyID" : ["padID"] // "setPublicStatus" : ["padID", "publicStatus"], // "getPublicStatus" : ["padID"], // "setPassword" : ["padID", "password"], From de0e341b4bc7739036c870a101ef5fea1a7673f6 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 4 Aug 2011 19:22:46 +0100 Subject: [PATCH 10/34] fixed ok response --- node/handler/APIHandler.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 6afe11733..383930c7c 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -108,7 +108,10 @@ exports.handle = function(functionName, fields, req, res) // no error happend, everything is fine if(err == null) { - res.send({code: 0, message: null, data: data}); + 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) From 178b4a95ecf0a51d411b5a81507640c577e7a87f Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 8 Aug 2011 16:21:31 +0100 Subject: [PATCH 11/34] added getMappedGroup4 and createGroup --- node/db/API.js | 11 ++--- node/db/GroupManager.js | 96 +++++++++++++++++++++++++++++++++++++ node/handler/APIHandler.js | 4 +- static/favicon.ico | Bin 0 -> 1150 bytes 4 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 static/favicon.ico diff --git a/node/db/API.js b/node/db/API.js index 611826d3f..c8f1b9ca9 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -21,6 +21,7 @@ var padManager = require("./PadManager"); var padMessageHandler = require("../handler/PadMessageHandler"); var readOnlyManager = require("./ReadOnlyManager"); +var groupManager = require("./GroupManager"); var async = require("async"); /**********************/ @@ -34,10 +35,7 @@ Example returns: {code: 0, message:"ok", data: {groupID: 5}} */ -exports.createGroup = function (callback) -{ - -} +exports.createGroup = groupManager.createGroup; /** getMappedGroup4(groupMapper) this functions helps you to map your application group ids to etherpad lite group ids @@ -46,10 +44,7 @@ Example returns: {code: 0, message:"ok", data: {groupID: 7}} */ -exports.getMappedGroup4 = function (groupMapper, callback) -{ - -} +exports.getMappedGroup4 = groupManager.getMappedGroup4; /** deleteGroup(groupID) deletes a group diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js index 2ceb1b656..2fcd31c80 100644 --- a/node/db/GroupManager.js +++ b/node/db/GroupManager.js @@ -18,4 +18,100 @@ * limitations under the License. */ +var db = require("./DB").db; +var async = require("async"); + +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; + var foundNonExistingGroupID = false; + async.whilst( + function () { return !foundNonExistingGroupID; }, + function (callback) + { + //generate a random 10 digit groupID + groupID = ""; + for(var i=0;i<10;i++) + { + groupID+=Math.floor(Math.random()*10); + } + + //check if this groupID already exisits + exports.doesGroupExist(groupID, function(err, exists) + { + foundNonExistingGroupID = !exists; + callback(err); + }) + }, + //we found a non existing groupID or an error happend + function (err) + { + //check for errors + if(err) + { + callback(err); + return; + } + + //create the group + db.set("group:" + groupID, {pads: {}}); + callback(null, {groupID: groupID}); + } + ); +} + +exports.getMappedGroup4 = 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}); + } + }); +} + + diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 383930c7c..f91e92ab7 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -35,8 +35,8 @@ catch(e) //a list of all functions var functions = { -// "createGroup" : [], -// "getMappedGroup4" : ["groupMapper"], + "createGroup" : [], + "getMappedGroup4" : ["groupMapper"], // "deleteGroup" : ["groupID"], // "listPads" : ["groupID"], "createPad" : ["padID", "text"], diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..2529c923f30e7ad806630c1ec40c4ca9db439d64 GIT binary patch literal 1150 zcmc)J&q@MO6vy$S1hx=!5ki9KvS?#%+GrSr&;ukPRNI#A16OVW-ABDZi(Vj6(kE!u zDk!3;Aczo+Bx?E|?p(O&6ye4rAJ6^cZ^ju%kr1zVTzJpO-jql}MCQQ~D`uVcTWzuN zjV9Wt;0ztux?*tZPr`H$m$-oDyKw72`Me((H?QA8jhio#P5+T^v$um=4A8?JmZ0Z- zgzo9>&cUrkCRrp=7!qT?=BzQCXsvK|y&mg;@q$064V4%5#AWXPC!Z=~7aLf|14hfu z>e%Gp>C0F}1VwzHfn%g#Dmbslxxo|Wp!rv9LyvI}Q^9%tZD*0e3^f0YB&v9Uso=c5 yciQ#;qu0^L7p}1hQ=@siGm(CHByxTN+oyRDxC*QOCelK4U!=|x-9nAA-Twi9>5l{e literal 0 HcmV?d00001 From 212cf83a257dfab2956be11635fc98da583f41e5 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 8 Aug 2011 17:33:25 +0100 Subject: [PATCH 12/34] updated the database doc --- doc/database.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/database.md b/doc/database.md index 95718ffd4..baddf437f 100644 --- a/doc/database.md +++ b/doc/database.md @@ -51,7 +51,7 @@ Maps an external application identifier to an internal author ### group:$GROUPID a group of pads -* **pads** - object with pad names in it, values are null +* **pads** - object with pad names in it, values are 1 ### session:$SESSIONID a session between an author and a group @@ -62,8 +62,8 @@ a session between an author and a group ### author2sessions:$AUTHORID saves the sessions of an author -* **sessions** - object with sessionIDs in it, values are null +* **sessions** - object with sessionIDs in it, values are 1 ### group2sessions:$GROUPID -* **sessions** - object with sessionIDs in it, values are null +* **sessions** - object with sessionIDs in it, values are 1 From 8d28fcbf23ef7635adb7bfc77348527613c13a8b Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 8 Aug 2011 17:34:51 +0100 Subject: [PATCH 13/34] ensure server header is also send with the api functions --- node/server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/node/server.js b/node/server.js index cea64690e..aab3cda5b 100644 --- a/node/server.js +++ b/node/server.js @@ -243,6 +243,8 @@ async.waterfall([ //This is a api call, collect all post informations and pass it to the apiHandler app.all('/api/1/:func', function(req, res) { + res.header("Server", serverName); + //check if this is a post request if(req.method == "POST") { From 4670cbc60adb192feb6050ca7cfafbb79a67f5a0 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 8 Aug 2011 17:35:40 +0100 Subject: [PATCH 14/34] added the listPads and createGroupPad --- node/db/API.js | 50 ++----------------- node/db/GroupManager.js | 99 ++++++++++++++++++++++++++++++++++++++ node/db/PadManager.js | 24 ++++++++- node/handler/APIHandler.js | 4 +- 4 files changed, 128 insertions(+), 49 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index c8f1b9ca9..37ed330b4 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -67,10 +67,7 @@ Example returns: {code: 0, message:"ok", data: {padIDs : ["3$test", "3$test2"]} {code: 1, message:"There is no group for this groupID", data: null} */ -exports.listPads = function(groupID, callback) -{ - -} +exports.listPads = groupManager.listPads; /** createGroupPad(groupID, padName [, text]) creates a new pad in this group @@ -81,10 +78,7 @@ Example returns: {code: 1, message:"pad does already exist", data: null} {code: 1, message:"There is no group for this groupID", data: null} */ -exports.createGroupPad = function(groupID, padName, text, callback) -{ - -} +exports.createGroupPad = groupManager.createGroupPad; /**********************/ /**AUTHOR FUNCTIONS****/ @@ -310,15 +304,7 @@ Example returns: {code: 1, message:"text too long", data: null} */ exports.setText = function(padID, text, callback) -{ - //check the text - var textCheck = checkPadText(text); - if(textCheck != null) - { - callback(textCheck); - return; - } - +{ //get the pad getPadSafe(padID, true, function(err, pad) { @@ -372,18 +358,7 @@ Example returns: {code: 1, message:"pad does already exist", data: null} */ exports.createPad = function(padID, text, callback) -{ - if(text) - { - //check the text - var textCheck = checkPadText(text); - if(textCheck != null) - { - callback(textCheck); - return; - } - } - +{ //ensure there is no $ in the padID if(padID.indexOf("$") != -1) { @@ -550,23 +525,6 @@ function is_int(value) return (parseFloat(value) == parseInt(value)) && !isNaN(value) } -function checkPadText(text) -{ - //check if text is a string - if(typeof text != "string") - { - return {stop: "text is not a string"}; - } - - //check if text is less than 100k chars - if(text.length > 100000) - { - return {stop: "text must be less than 100k chars"}; - } - - return null; -} - //gets a pad safe function getPadSafe(padID, shouldExist, text, callback) { diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js index 2fcd31c80..10e73ed11 100644 --- a/node/db/GroupManager.js +++ b/node/db/GroupManager.js @@ -20,6 +20,7 @@ var db = require("./DB").db; var async = require("async"); +var padManager = require("./PadManager"); exports.doesGroupExist = function(groupID, callback) { @@ -102,6 +103,7 @@ exports.getMappedGroup4 = function(groupMapper, callback) //create the mapper entry for this group db.set("mapper2group:"+groupMapper, responseObj.groupID); + callback(null, responseObj); }); } @@ -113,5 +115,102 @@ exports.getMappedGroup4 = function(groupMapper, callback) }); } +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}); + }); + //check if groupID exists + //check if pad already exists + //create the pad + //create the subentry in the padobject +} + +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}); + }); + } + }); +} diff --git a/node/db/PadManager.js b/node/db/PadManager.js index c1b69bd57..f0ce88183 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -33,8 +33,12 @@ globalPads = []; */ exports.getPad = function(id, text, callback) { + //check if this is a valid padId if(!exports.isValidPadId(id)) - throw new Error(id + " is not a valid padId"); + { + callback({stop: id + " is not a valid padId"}); + return; + } //make text an optional parameter if(typeof text == "function") @@ -43,6 +47,24 @@ exports.getPad = function(id, text, callback) 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 diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index f91e92ab7..2d7d9ae6f 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -38,9 +38,9 @@ var functions = { "createGroup" : [], "getMappedGroup4" : ["groupMapper"], // "deleteGroup" : ["groupID"], -// "listPads" : ["groupID"], + "listPads" : ["groupID"], "createPad" : ["padID", "text"], -// "createGroupPad" : ["groupID", "padName", "text"], + "createGroupPad" : ["groupID", "padName", "text"], // "createAuthor" : ["name"], // "getMappedAuthor4" : ["authorMapper" , "name"], // "createSession" : ["groupID", "authorID", "validUntil"], From 9d91a3873c7a6d409a4f6deb04fb4aaefd698ee7 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 8 Aug 2011 17:45:44 +0100 Subject: [PATCH 15/34] added logging for http api calls --- node/server.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/node/server.js b/node/server.js index aab3cda5b..7b8cec6c0 100644 --- a/node/server.js +++ b/node/server.js @@ -240,6 +240,8 @@ async.waterfall([ 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.all('/api/1/:func', function(req, res) { @@ -252,6 +254,17 @@ async.waterfall([ { if(err) throw err; + apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields)); + + //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, fields, req, res); }); From a6df6ab0a7f13879ee57e8a0ce5654dda62dee2d Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 8 Aug 2011 20:14:01 +0100 Subject: [PATCH 16/34] change api calls from POST to GET --- node/server.js | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/node/server.js b/node/server.js index 7b8cec6c0..8764be8a5 100644 --- a/node/server.js +++ b/node/server.js @@ -243,37 +243,23 @@ async.waterfall([ var apiLogger = log4js.getLogger("API"); //This is a api call, collect all post informations and pass it to the apiHandler - app.all('/api/1/:func', function(req, res) + app.get('/api/1/:func', function(req, res) { res.header("Server", serverName); - //check if this is a post request - if(req.method == "POST") + 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) { - new formidable.IncomingForm().parse(req, function(err, fields, files) - { - if(err) throw err; - - apiLogger.info("REQUEST, " + req.params.func + ", " + JSON.stringify(fields)); - - //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, fields, req, res); - }); - } - //say goodbye if this is not a post request - else - { - res.send({code: 5, message: "no POST request", data: null}); + 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 From e6ba60bddcd3d4e7201c949ad5a41ac9e82f999a Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 9 Aug 2011 11:55:12 +0100 Subject: [PATCH 17/34] cleaned up authormanager --- node/db/AuthorManager.js | 60 +++++++++++++++------------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index 6a9064ea9..a0187ed79 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -47,33 +47,29 @@ exports.getAuthor4Token = function (token, callback) //there is no author with this token, so create one if(author == null) { - createAuthor(token, function(err, _author) + createAuthor(null, function(err, _author) { + //error? + if(err) + { + callback(err); + return; + } + author = _author; - callback(err); + + //create the token2author relation + db.set("token2author:" + token, author); + + callback(); }); } //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 - else - { - db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); - callback(); - } - }); + //update the timestamp of this author + db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); + callback(); } } ], function(err) @@ -86,30 +82,18 @@ exports.getAuthor4Token = function (token, callback) * Internal function that creates the database entry for an author * @param {String} token The token */ -function createAuthor (token, callback) +function createAuthor (name, callback) { //create the new author name var author = "g." + _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); - }); + //set the global author db entry + db.set("globalAuthor:" + author, authorObj); + + callback(null, author); } /** From e336b4f940d129e7b50e965ccb7f42a2fae2e0f6 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 9 Aug 2011 12:09:04 +0100 Subject: [PATCH 18/34] added create Author --- node/db/API.js | 6 ++---- node/db/AuthorManager.js | 10 +++++----- node/handler/APIHandler.js | 2 +- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index 37ed330b4..e3690671e 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -22,6 +22,7 @@ var padManager = require("./PadManager"); var padMessageHandler = require("../handler/PadMessageHandler"); var readOnlyManager = require("./ReadOnlyManager"); var groupManager = require("./GroupManager"); +var authorManager = require("./AuthorManager"); var async = require("async"); /**********************/ @@ -92,10 +93,7 @@ Example returns: {code: 0, message:"ok", data: {authorID: 5}} */ -exports.createAuthor = function(name, callback) -{ - -} +exports.createAuthor = authorManager.createAuthor; /** getMappedAuthor4(authorMapper [, name]) this functions helps you to map your application author ids to etherpad lite author ids diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index a0187ed79..a6fc37287 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -47,7 +47,7 @@ exports.getAuthor4Token = function (token, callback) //there is no author with this token, so create one if(author == null) { - createAuthor(null, function(err, _author) + exports.createAuthor(null, function(err, _author) { //error? if(err) @@ -56,7 +56,7 @@ exports.getAuthor4Token = function (token, callback) return; } - author = _author; + author = _author.authorID; //create the token2author relation db.set("token2author:" + token, author); @@ -80,9 +80,9 @@ exports.getAuthor4Token = function (token, callback) /** * 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 (name, callback) +exports.createAuthor = function(name, callback) { //create the new author name var author = "g." + _randomString(16); @@ -93,7 +93,7 @@ function createAuthor (name, callback) //set the global author db entry db.set("globalAuthor:" + author, authorObj); - callback(null, author); + callback(null, {authorID: author}); } /** diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 2d7d9ae6f..0c12a271d 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -41,7 +41,7 @@ var functions = { "listPads" : ["groupID"], "createPad" : ["padID", "text"], "createGroupPad" : ["groupID", "padName", "text"], -// "createAuthor" : ["name"], + "createAuthor" : ["name"], // "getMappedAuthor4" : ["authorMapper" , "name"], // "createSession" : ["groupID", "authorID", "validUntil"], // "deleteSession" : ["sessionID"], From f01906d815d9fd8083113e52787aba2b2d8f8054 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 9 Aug 2011 14:42:12 +0100 Subject: [PATCH 19/34] added getMappedAuthor4 --- node/db/API.js | 5 +- node/db/AuthorManager.js | 128 +++++++++++++++++++++++-------------- node/handler/APIHandler.js | 2 +- 3 files changed, 82 insertions(+), 53 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index e3690671e..f9d1ec17d 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -102,10 +102,7 @@ Example returns: {code: 0, message:"ok", data: {authorID: 5}} */ -exports.getMappedAuthor4 = function(authorMapper ,name, callback) -{ - -} +exports.getMappedAuthor4 = authorManager.getMappedAuthor4; /**********************/ /**SESSION FUNCTIONS***/ diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index a6fc37287..abb44151c 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -22,59 +22,91 @@ 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 + * 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) - { - db.get("token2author:" + token, function (err, _author) - { - author = _author; - callback(err); - }); - }, - function(callback) - { - //there is no author with this token, so create one - if(author == null) - { - exports.createAuthor(null, function(err, _author) - { - //error? - if(err) - { - callback(err); - return; - } - - author = _author.authorID; - - //create the token2author relation - db.set("token2author:" + token, author); - - callback(); - }); - } - //there is a author with this token - else - { - //update the timestamp of this author - db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); - callback(); - } - } - ], function(err) +{ + mapAuthorWithDBKey("token2author", token, function(err, author) { - callback(err, author); + //return only the sub value authorID + callback(err, author ? author.authorID : author); + }); +} + +/** + * Returns the AuthorID for a mapper. + * @param {String} token The mapper + * @param {Function} callback callback (err, author) + */ +exports.getMappedAuthor4 = function (authorMapper, name, callback) +{ + mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) + { + //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) + { + exports.createAuthor(null, function(err, author) + { + //error? + if(err) + { + 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 mapper + else + { + //update the timestamp of this author + db.setSub("globalAuthor:" + author, ["timestamp"], new Date().getTime()); + + //return the author + callback(null, {authorID: author}); + } }); } diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 0c12a271d..2368a47ca 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -42,7 +42,7 @@ var functions = { "createPad" : ["padID", "text"], "createGroupPad" : ["groupID", "padName", "text"], "createAuthor" : ["name"], -// "getMappedAuthor4" : ["authorMapper" , "name"], + "getMappedAuthor4" : ["authorMapper" , "name"], // "createSession" : ["groupID", "authorID", "validUntil"], // "deleteSession" : ["sessionID"], // "getSessionInfo" : ["sessionID"], From 4b48380a2f7e5cc16f40b503d6698724d2c4883a Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 9 Aug 2011 16:45:49 +0100 Subject: [PATCH 20/34] added createSession and getSessionInfo --- node/db/API.js | 11 +- node/db/AuthorManager.js | 12 ++ node/db/SessionManager.js | 279 +++++++++++++++++++++++++++++++++++++ node/handler/APIHandler.js | 4 +- 4 files changed, 296 insertions(+), 10 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index f9d1ec17d..a8c0ec03b 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -23,6 +23,7 @@ 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"); /**********************/ @@ -118,10 +119,7 @@ Example returns: {code: 1, message:"authorID doesn't exist", data: null} {code: 1, message:"validUntil is in the past", data: null} */ -exports.createSession = function(groupID, authorID, validUntil, callback) -{ - -} +exports.createSession = sessionManager.createSession; /** deleteSession(sessionID) deletes a session @@ -144,10 +142,7 @@ Example returns: {code: 0, message:"ok", data: {authorID: 5, groupID: 7, validUntil: 1312201246}} {code: 1, message:"sessionID does not exist", data: null} */ -exports.getSessionInfo = function(sessionID, callback) -{ - -} +exports.getSessionInfo = sessionManager.getSessionInfo; /** listSessionsOfGroup(groupID) returns all sessions of a group diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index abb44151c..5354d48e6 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -21,6 +21,18 @@ var db = require("./DB").db; var async = require("async"); +/** + * 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 diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js index f25389111..6663d7d2c 100644 --- a/node/db/SessionManager.js +++ b/node/db/SessionManager.js @@ -17,3 +17,282 @@ * 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 validUntil 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(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 = {sessions : {}}; + } + + //add the entry for this session + group2sessions.sessions[sessionID] = 1; + + 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 = {sessions : {}}; + } + + //add the entry for this session + author2sessions.sessions[sessionID] = 1; + + 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) +{ + //check if session exits + //delete session + //delete group2sessions + //delete author2sessions +} + +/** +returns all sessions of a group + +Example returns: + +{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} +{code: 1, message:"groupID does not exist", data: null} +*/ +exports.listSessionsOfGroup = function(groupID, callback) +{ + //check if group exists + //get the group2sessions entry +} + +/** +listSessionsOfAuthor(authorID) returns all sessions of an author + +Example returns: + +{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} +{code: 1, message:"authorID does not exist", data: null} +*/ +exports.listSessionsOfAuthor = function(authorID, callback) +{ + //check if author exists + //get the author2sessions entry +} + +/** +deleteAllSessionsOfGroup(groupID) deletes all sessions of a group + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"groupID does not exist", data: null} +*/ +exports.deleteAllSessionsOfGroup = function(groupID, callback) +{ + //call listsessionsofgroup + //foreach the group and delete the sessions +} + +/** +deleteAllSessionsOfAuthor(authorID) deletes all sessions of an author + +Example returns: + +{code: 0, message:"ok", data: null} +{code: 1, message:"authorID does not exist", data: null} +*/ +exports.deleteAllSessionsOfAuthor = function(authorID, callback) +{ + //call listsessionsofauthor + //foreach the group and delete the sessions +} + +/** + * Generates a random String with the given length. Is needed to generate the SessionIDs + */ +function randomString(len) +{ + // use only numbers and lowercase letters + var pieces = []; + for(var i=0;i Date: Tue, 9 Aug 2011 20:14:32 +0100 Subject: [PATCH 21/34] added listSessionsOfGroup and listSessionsOfAuthor --- doc/database.md | 4 +- node/db/API.js | 36 +--------- node/db/SessionManager.js | 133 +++++++++++++++++++++++-------------- node/handler/APIHandler.js | 6 +- 4 files changed, 89 insertions(+), 90 deletions(-) diff --git a/doc/database.md b/doc/database.md index baddf437f..2d43d6f1c 100644 --- a/doc/database.md +++ b/doc/database.md @@ -62,8 +62,8 @@ a session between an author and a group ### author2sessions:$AUTHORID saves the sessions of an author -* **sessions** - object with sessionIDs in it, values are 1 +* **sessionsIDs** - object with sessionIDs in it, values are 1 ### group2sessions:$GROUPID -* **sessions** - object with sessionIDs in it, values are 1 +* **sessionsIDs** - object with sessionIDs in it, values are 1 diff --git a/node/db/API.js b/node/db/API.js index a8c0ec03b..e67a3113e 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -152,10 +152,7 @@ Example returns: {code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} {code: 1, message:"groupID does not exist", data: null} */ -exports.listSessionsOfGroup = function(groupID, callback) -{ - -} +exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup; /** listSessionsOfAuthor(authorID) returns all sessions of an author @@ -165,36 +162,7 @@ Example returns: {code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} {code: 1, message:"authorID does not exist", data: null} */ -exports.listSessionsOfAuthor = function(authorID, callback) -{ - -} - -/** -deleteAllSessionsOfGroup(groupID) deletes all sessions of a group - -Example returns: - -{code: 0, message:"ok", data: null} -{code: 1, message:"groupID does not exist", data: null} -*/ -exports.deleteAllSessionsOfGroup = function(groupID, callback) -{ - -} - -/** -deleteAllSessionsOfAuthor(authorID) deletes all sessions of an author - -Example returns: - -{code: 0, message:"ok", data: null} -{code: 1, message:"authorID does not exist", data: null} -*/ -exports.deleteAllSessionsOfAuthor = function(authorID, callback) -{ - -} +exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor; /************************/ /**PAD CONTENT FUNCTIONS*/ diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js index 6663d7d2c..d9ceb5bd5 100644 --- a/node/db/SessionManager.js +++ b/node/db/SessionManager.js @@ -87,7 +87,7 @@ exports.createSession = function(groupID, authorID, validUntil, callback) //check validUntil and create the session db entry function(callback) { - //check if validUntil is a number + //check if rev is a number if(typeof validUntil != "number") { //try to parse the number @@ -117,7 +117,7 @@ exports.createSession = function(groupID, authorID, validUntil, callback) } //check if validUntil is in the future - if(new Date().getTime()/1000 > validUntil) + if(Math.floor(new Date().getTime()/1000) > validUntil) { callback({stop: "validUntil is in the past"}); return; @@ -147,11 +147,14 @@ exports.createSession = function(groupID, authorID, validUntil, callback) //the entry doesn't exist so far, let's create it if(group2sessions == null) { - group2sessions = {sessions : {}}; + group2sessions = {sessionIDs : {}}; } //add the entry for this session - group2sessions.sessions[sessionID] = 1; + group2sessions.sessionIDs[sessionID] = 1; + + //save the new element back + db.set("group2sessions:" + groupID, group2sessions); callback(); }); @@ -172,11 +175,14 @@ exports.createSession = function(groupID, authorID, validUntil, callback) //the entry doesn't exist so far, let's create it if(author2sessions == null) { - author2sessions = {sessions : {}}; + author2sessions = {sessionIDs : {}}; } //add the entry for this session - author2sessions.sessions[sessionID] = 1; + author2sessions.sessionIDs[sessionID] = 1; + + //save the new element back + db.set("author2sessions:" + authorID, author2sessions); callback(); }); @@ -222,60 +228,87 @@ exports.deleteSession = function(sessionID, callback) //delete author2sessions } -/** -returns all sessions of a group - -Example returns: - -{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} -{code: 1, message:"groupID does not exist", data: null} -*/ exports.listSessionsOfGroup = function(groupID, callback) { - //check if group exists - //get the group2sessions entry + 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); + } + }); } -/** -listSessionsOfAuthor(authorID) returns all sessions of an author - -Example returns: - -{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} -{code: 1, message:"authorID does not exist", data: null} -*/ exports.listSessionsOfAuthor = function(authorID, callback) { - //check if author exists - //get the author2sessions entry + 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); + } + }); } -/** -deleteAllSessionsOfGroup(groupID) deletes all sessions of a group - -Example returns: - -{code: 0, message:"ok", data: null} -{code: 1, message:"groupID does not exist", data: null} -*/ -exports.deleteAllSessionsOfGroup = function(groupID, callback) +function listSessionsWithDBKey (dbkey, callback) { - //call listsessionsofgroup - //foreach the group and delete the sessions -} + var sessions; -/** -deleteAllSessionsOfAuthor(authorID) deletes all sessions of an author - -Example returns: - -{code: 0, message:"ok", data: null} -{code: 1, message:"authorID does not exist", data: null} -*/ -exports.deleteAllSessionsOfAuthor = function(authorID, callback) -{ - //call listsessionsofauthor - //foreach the group and delete the 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); + }); } /** diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 81821fd97..ba378a9ac 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -46,10 +46,8 @@ var functions = { "createSession" : ["groupID", "authorID", "validUntil"], // "deleteSession" : ["sessionID"], "getSessionInfo" : ["sessionID"], -// "listSessionsOfGroup" : ["groupID"], -// "listSessionsOfAuthor" : ["authorID"], -// "deleteAllSessionsOfGroup" : ["groupID"], -// "deleteAllSessionsOfAuthor" : ["authorID"], + "listSessionsOfGroup" : ["groupID"], + "listSessionsOfAuthor" : ["authorID"], "getText" : ["padID", "rev"], "setText" : ["padID", "text"], "getRevisionsCount" : ["padID"], From 183d1597c6b00e9453b7887b4252b675666399ce Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 9 Aug 2011 20:22:39 +0100 Subject: [PATCH 22/34] clean up --- node/db/API.js | 115 +------------------------------------------------ 1 file changed, 2 insertions(+), 113 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index e67a3113e..546520353 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -30,138 +30,27 @@ var async = require("async"); /**GROUP FUNCTIONS*****/ /**********************/ -/** -createGroup() creates a new group - -Example returns: - -{code: 0, message:"ok", data: {groupID: 5}} -*/ exports.createGroup = groupManager.createGroup; - -/** -getMappedGroup4(groupMapper) this functions helps you to map your application group ids to etherpad lite group ids - -Example returns: - -{code: 0, message:"ok", data: {groupID: 7}} -*/ exports.getMappedGroup4 = groupManager.getMappedGroup4; - -/** -deleteGroup(groupID) deletes a group - -Example returns: - -{code: 0, message:"ok", data: null} -{code: 1, message:"There is no group for this groupID", data: null} -*/ -exports.deleteGroup = function(groupID, callback) -{ - -} - -/** -listPads(groupID) returns all pads of this group - -Example returns: - -{code: 0, message:"ok", data: {padIDs : ["3$test", "3$test2"]} -{code: 1, message:"There is no group for this groupID", data: null} -*/ +exports.deleteGroup = groupManager.deleteGroup; exports.listPads = groupManager.listPads; - -/** -createGroupPad(groupID, 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} -{code: 1, message:"There is no group for this groupID", data: null} -*/ exports.createGroupPad = groupManager.createGroupPad; /**********************/ /**AUTHOR FUNCTIONS****/ /**********************/ - -/** -createAuthor([name]) creates a new author - -Example returns: - -{code: 0, message:"ok", data: {authorID: 5}} -*/ exports.createAuthor = authorManager.createAuthor; - -/** -getMappedAuthor4(authorMapper [, name]) this functions helps you to map your application author ids to etherpad lite author ids - -Example returns: - -{code: 0, message:"ok", data: {authorID: 5}} -*/ exports.getMappedAuthor4 = authorManager.getMappedAuthor4; /**********************/ /**SESSION FUNCTIONS***/ /**********************/ -/** -createSession(groupID, authorID, validUntil) creates a new session - -Example returns: - -{code: 0, message:"ok", data: {sessionID: 5}} -{code: 1, message:"groupID doesn't exist", data: null} -{code: 1, message:"authorID doesn't exist", data: null} -{code: 1, message:"validUntil is in the past", data: null} -*/ exports.createSession = sessionManager.createSession; - -/** -deleteSession(sessionID) deletes a session - -Example returns: - -{code: 1, message:"ok", data: null} -{code: 1, message:"sessionID does not exist", data: null} -*/ -exports.deleteSession = function(sessionID, callback) -{ - -} - -/** -getSessionInfo(sessionID) returns informations about a session - -Example returns: - -{code: 0, message:"ok", data: {authorID: 5, groupID: 7, validUntil: 1312201246}} -{code: 1, message:"sessionID does not exist", data: null} -*/ +exports.deleteSession = sessionManager.deleteSession; exports.getSessionInfo = sessionManager.getSessionInfo; - -/** -listSessionsOfGroup(groupID) returns all sessions of a group - -Example returns: - -{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} -{code: 1, message:"groupID does not exist", data: null} -*/ exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup; - -/** -listSessionsOfAuthor(authorID) returns all sessions of an author - -Example returns: - -{code: 0, message:"ok", data: {32: {authorID: 5, groupID: 7, validUntil: 1312201246}, 53: {authorID: 3, groupID: 2, validUntil: 1312201216}}} -{code: 1, message:"authorID does not exist", data: null} -*/ exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor; /************************/ From 9f530962ee5a3ef54695fb288b059bad198fcd53 Mon Sep 17 00:00:00 2001 From: Randy Date: Tue, 9 Aug 2011 16:51:33 -0700 Subject: [PATCH 23/34] Add js tests to assist with api development --- static/tests.html | 155 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 static/tests.html diff --git a/static/tests.html b/static/tests.html new file mode 100644 index 000000000..d5cb453c7 --- /dev/null +++ b/static/tests.html @@ -0,0 +1,155 @@ + + + + + API Test and Examples Page + + + + + +
+

createGroup()

+ + + + +
+
+
+
createGroup()
+
createMappedGroup4(groupMapper)
+
listPads(groupID)
+
createPad(padID,text)
+
createGroupPad(groupID,padName,text)
+
createAuthor(name)
+
getMappedAuthor4(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,publicStatud)
+
getPublicStatus(padID)
+
setPassword(padID,password)
+
isPasswordProtected(padID)
+ + From 578f8db3111d1008aa5318ab7b9cc9038581bb61 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 10 Aug 2011 12:12:29 +0100 Subject: [PATCH 24/34] improved test.html --- static/tests.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/static/tests.html b/static/tests.html index d5cb453c7..58dea0ffb 100644 --- a/static/tests.html +++ b/static/tests.html @@ -3,7 +3,7 @@ API Test and Examples Page - + +
APIKEY:

createGroup()

From dc15682a27a59df38425f3dd261d90ce6d9fdaef Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 10 Aug 2011 12:38:03 +0100 Subject: [PATCH 25/34] changed name of getMapped*4 functions to create*IfNotExistsFor --- node/db/API.js | 4 ++-- node/db/AuthorManager.js | 2 +- node/db/GroupManager.js | 2 +- node/handler/APIHandler.js | 4 ++-- static/tests.html | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index 546520353..3fcaafeb5 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -31,7 +31,7 @@ var async = require("async"); /**********************/ exports.createGroup = groupManager.createGroup; -exports.getMappedGroup4 = groupManager.getMappedGroup4; +exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor; exports.deleteGroup = groupManager.deleteGroup; exports.listPads = groupManager.listPads; exports.createGroupPad = groupManager.createGroupPad; @@ -41,7 +41,7 @@ exports.createGroupPad = groupManager.createGroupPad; /**********************/ exports.createAuthor = authorManager.createAuthor; -exports.getMappedAuthor4 = authorManager.getMappedAuthor4; +exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor; /**********************/ /**SESSION FUNCTIONS***/ diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index 5354d48e6..f93ebb682 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -52,7 +52,7 @@ exports.getAuthor4Token = function (token, callback) * @param {String} token The mapper * @param {Function} callback callback (err, author) */ -exports.getMappedAuthor4 = function (authorMapper, name, callback) +exports.createAuthorIfNotExistsFor = function (authorMapper, name, callback) { mapAuthorWithDBKey("mapper2author", authorMapper, function(err, author) { diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js index 10e73ed11..0d4ed7f17 100644 --- a/node/db/GroupManager.js +++ b/node/db/GroupManager.js @@ -71,7 +71,7 @@ exports.createGroup = function(callback) ); } -exports.getMappedGroup4 = function(groupMapper, callback) +exports.createGroupIfNotExistsFor = function(groupMapper, callback) { //ensure mapper is optional if(typeof groupMapper != "string") diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index ba378a9ac..d41d6b4ad 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -36,13 +36,13 @@ catch(e) //a list of all functions var functions = { "createGroup" : [], - "getMappedGroup4" : ["groupMapper"], + "createGroupIfNotExistsFor" : ["groupMapper"], // "deleteGroup" : ["groupID"], "listPads" : ["groupID"], "createPad" : ["padID", "text"], "createGroupPad" : ["groupID", "padName", "text"], "createAuthor" : ["name"], - "getMappedAuthor4" : ["authorMapper" , "name"], + "createAuthorIfNotExistsFor" : ["authorMapper" , "name"], "createSession" : ["groupID", "authorID", "validUntil"], // "deleteSession" : ["sessionID"], "getSessionInfo" : ["sessionID"], diff --git a/static/tests.html b/static/tests.html index 58dea0ffb..855d1ca45 100644 --- a/static/tests.html +++ b/static/tests.html @@ -140,12 +140,12 @@
createGroup()
-
createMappedGroup4(groupMapper)
+
createGroupIfNotExistsFor(groupMapper)
listPads(groupID)
createPad(padID,text)
createGroupPad(groupID,padName,text)
createAuthor(name)
-
getMappedAuthor4(authorMapper,name)
+
createAuthorIfNotExistsFor(authorMapper,name)
createSession(groupID,authorID,validUntil)
deleteSession(sessionID)
getSessionInfo(sessionID)
From f6b87daa270fc3e626fc495866cb1a1da20498e9 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 10 Aug 2011 14:04:28 +0100 Subject: [PATCH 26/34] added deleteSession --- node/db/SessionManager.js | 76 +++++++++++++++++++++++++++++++++++--- node/handler/APIHandler.js | 2 +- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js index d9ceb5bd5..05fe32088 100644 --- a/node/db/SessionManager.js +++ b/node/db/SessionManager.js @@ -222,10 +222,73 @@ exports.getSessionInfo = function(sessionID, callback) */ exports.deleteSession = function(sessionID, callback) { - //check if session exits - //delete session - //delete group2sessions - //delete author2sessions + 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) @@ -251,7 +314,7 @@ exports.listSessionsOfGroup = function(groupID, callback) } exports.listSessionsOfAuthor = function(authorID, callback) -{ +{ authorMangager.doesAuthorExists(authorID, function(err, exists) { //error @@ -272,6 +335,7 @@ exports.listSessionsOfAuthor = function(authorID, callback) }); } +//this function is basicly the code listSessionsOfAuthor and listSessionsOfGroup has in common function listSessionsWithDBKey (dbkey, callback) { var sessions; @@ -287,7 +351,7 @@ function listSessionsWithDBKey (dbkey, callback) }); }, function(callback) - { + { //collect all sessionIDs in an arrary var sessionIDs = []; for (var i in sessions) diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index d41d6b4ad..2aa78f2ad 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -44,7 +44,7 @@ var functions = { "createAuthor" : ["name"], "createAuthorIfNotExistsFor" : ["authorMapper" , "name"], "createSession" : ["groupID", "authorID", "validUntil"], -// "deleteSession" : ["sessionID"], + "deleteSession" : ["sessionID"], "getSessionInfo" : ["sessionID"], "listSessionsOfGroup" : ["groupID"], "listSessionsOfAuthor" : ["authorID"], From 292c68a0a5be997c59954fa689a62c58be7286d6 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Wed, 10 Aug 2011 14:24:21 +0100 Subject: [PATCH 27/34] harmonize different ID types, improved the prefixes --- node/db/AuthorManager.js | 17 ++++++----- node/db/GroupManager.js | 60 +++++++++++++------------------------- node/db/ReadOnlyManager.js | 14 +++++---- node/db/SessionManager.js | 12 ++++---- node/handler/APIHandler.js | 15 ++++++---- 5 files changed, 54 insertions(+), 64 deletions(-) diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index f93ebb682..8fa98189a 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -129,7 +129,7 @@ function mapAuthorWithDBKey (mapperkey, mapper, 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": name, "timestamp": new Date().getTime()}; @@ -193,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 Date: Wed, 10 Aug 2011 22:33:31 +0100 Subject: [PATCH 28/34] added setPublicStatus, getPublicStatus, setPassword and isPasswordProtected --- node/db/API.js | 14 ++++++- node/db/Pad.js | 81 ++++++++++++++++++++++++++++++++++++-- node/handler/APIHandler.js | 10 ++--- static/tests.html | 2 +- 4 files changed, 96 insertions(+), 11 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index 3fcaafeb5..c55385d85 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -289,7 +289,14 @@ exports.setPublicStatus = function(padID, publicStatus, callback) return; } + //convert string to boolean + if(typeof publicStatus == "string") + publicStatus = publicStatus == "true" ? true : false; + //set the password + pad.setPublicStatus(publicStatus); + + callback(); }); } @@ -312,7 +319,7 @@ exports.getPublicStatus = function(padID, callback) return; } - + callback(null, {publicStatus: pad.getPublicStatus()}); }); } @@ -335,7 +342,10 @@ exports.setPassword = function(padID, password, callback) return; } + //set the password + pad.setPassword(password); + callback(); }); } @@ -358,7 +368,7 @@ exports.isPasswordProtected = function(padID, callback) return; } - + callback(null, {isPasswordProtected: pad.isPasswordProtected()}); }); } diff --git a/node/db/Pad.js b/node/db/Pad.js index 982ba1c35..3511728fe 100644 --- a/node/db/Pad.js +++ b/node/db/Pad.js @@ -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) @@ -336,10 +353,23 @@ 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 @@ -351,7 +381,52 @@ Class('Pad', { 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; +} diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index 102178556..fc457f808 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -52,11 +52,11 @@ var functions = { "setText" : ["padID", "text"], "getRevisionsCount" : ["padID"], // "deletePad" : ["padID"], - "getReadOnlyID" : ["padID"] -// "setPublicStatus" : ["padID", "publicStatus"], -// "getPublicStatus" : ["padID"], -// "setPassword" : ["padID", "password"], -// "isPasswordProtected" : ["padID"] + "getReadOnlyID" : ["padID"], + "setPublicStatus" : ["padID", "publicStatus"], + "getPublicStatus" : ["padID"], + "setPassword" : ["padID", "password"], + "isPasswordProtected" : ["padID"] }; /** diff --git a/static/tests.html b/static/tests.html index 855d1ca45..f15f4567e 100644 --- a/static/tests.html +++ b/static/tests.html @@ -156,7 +156,7 @@
getRevisionsCount(padID)
deletePad(padID)
getReadOnlyID(padID)
-
setPublicStatus(padID,publicStatud)
+
setPublicStatus(padID,publicStatus)
getPublicStatus(padID)
setPassword(padID,password)
isPasswordProtected(padID)
From 0e05e58c768b62e7f050562d8f17234c35dbd791 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Thu, 11 Aug 2011 15:26:41 +0100 Subject: [PATCH 29/34] changed copyright from Peter 'Pita' Martischka to Peter 'Pita' Martischka (Primary Technology Ltd) --- node/db/API.js | 2 +- node/db/AuthorManager.js | 2 +- node/db/DB.js | 2 +- node/db/GroupManager.js | 2 +- node/db/PadManager.js | 2 +- node/db/ReadOnlyManager.js | 2 +- node/db/SessionManager.js | 2 +- node/easysync_tests.js | 2 +- node/handler/APIHandler.js | 2 +- node/handler/ExportHandler.js | 2 +- node/handler/ImportHandler.js | 2 +- node/handler/PadMessageHandler.js | 2 +- node/handler/SocketIORouter.js | 2 +- node/handler/TimesliderMessageHandler.js | 2 +- node/server.js | 2 +- node/utils/Abiword.js | 2 +- node/utils/AttributePoolFactory.js | 2 +- node/utils/Changeset.js | 2 +- node/utils/Minify.js | 2 +- node/utils/Settings.js | 2 +- static/js/chat.js | 16 ++++++++++++++++ static/js/pad2.js | 2 +- 22 files changed, 37 insertions(+), 21 deletions(-) diff --git a/node/db/API.js b/node/db/API.js index c55385d85..1fd1bb7b4 100644 --- a/node/db/API.js +++ b/node/db/API.js @@ -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. diff --git a/node/db/AuthorManager.js b/node/db/AuthorManager.js index 8fa98189a..958e2c16c 100644 --- a/node/db/AuthorManager.js +++ b/node/db/AuthorManager.js @@ -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. diff --git a/node/db/DB.js b/node/db/DB.js index aa1064ad0..6340a8fb8 100644 --- a/node/db/DB.js +++ b/node/db/DB.js @@ -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. diff --git a/node/db/GroupManager.js b/node/db/GroupManager.js index 424d32dea..995e6c93c 100644 --- a/node/db/GroupManager.js +++ b/node/db/GroupManager.js @@ -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. diff --git a/node/db/PadManager.js b/node/db/PadManager.js index f0ce88183..00128267b 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -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. diff --git a/node/db/ReadOnlyManager.js b/node/db/ReadOnlyManager.js index 741f9fd51..cd18a1888 100644 --- a/node/db/ReadOnlyManager.js +++ b/node/db/ReadOnlyManager.js @@ -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. diff --git a/node/db/SessionManager.js b/node/db/SessionManager.js index 797e6cde4..fc6fe306d 100644 --- a/node/db/SessionManager.js +++ b/node/db/SessionManager.js @@ -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. diff --git a/node/easysync_tests.js b/node/easysync_tests.js index 9eb68ec17..e0d82c33f 100644 --- a/node/easysync_tests.js +++ b/node/easysync_tests.js @@ -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. diff --git a/node/handler/APIHandler.js b/node/handler/APIHandler.js index fc457f808..5aa29a8a4 100644 --- a/node/handler/APIHandler.js +++ b/node/handler/APIHandler.js @@ -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. diff --git a/node/handler/ExportHandler.js b/node/handler/ExportHandler.js index 674950f40..8c8b775aa 100644 --- a/node/handler/ExportHandler.js +++ b/node/handler/ExportHandler.js @@ -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. diff --git a/node/handler/ImportHandler.js b/node/handler/ImportHandler.js index c02c9f508..07168e95a 100644 --- a/node/handler/ImportHandler.js +++ b/node/handler/ImportHandler.js @@ -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. diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js index f4a4a869b..550440884 100644 --- a/node/handler/PadMessageHandler.js +++ b/node/handler/PadMessageHandler.js @@ -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. diff --git a/node/handler/SocketIORouter.js b/node/handler/SocketIORouter.js index 9321cf8e7..08448f7cc 100644 --- a/node/handler/SocketIORouter.js +++ b/node/handler/SocketIORouter.js @@ -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. diff --git a/node/handler/TimesliderMessageHandler.js b/node/handler/TimesliderMessageHandler.js index ec11a5e52..82eb71369 100644 --- a/node/handler/TimesliderMessageHandler.js +++ b/node/handler/TimesliderMessageHandler.js @@ -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. diff --git a/node/server.js b/node/server.js index 8764be8a5..764d5c336 100644 --- a/node/server.js +++ b/node/server.js @@ -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. diff --git a/node/utils/Abiword.js b/node/utils/Abiword.js index 3a99f56be..2122fd80a 100644 --- a/node/utils/Abiword.js +++ b/node/utils/Abiword.js @@ -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. diff --git a/node/utils/AttributePoolFactory.js b/node/utils/AttributePoolFactory.js index 55aecf612..807c2b393 100644 --- a/node/utils/AttributePoolFactory.js +++ b/node/utils/AttributePoolFactory.js @@ -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. diff --git a/node/utils/Changeset.js b/node/utils/Changeset.js index ea2ac678b..2c9e06838 100644 --- a/node/utils/Changeset.js +++ b/node/utils/Changeset.js @@ -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. diff --git a/node/utils/Minify.js b/node/utils/Minify.js index a8146addd..8d6a72efd 100644 --- a/node/utils/Minify.js +++ b/node/utils/Minify.js @@ -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. diff --git a/node/utils/Settings.js b/node/utils/Settings.js index d78793b57..b6af57657 100644 --- a/node/utils/Settings.js +++ b/node/utils/Settings.js @@ -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. diff --git a/static/js/chat.js b/static/js/chat.js index f617bdb32..c3eddacb2 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -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 = { diff --git a/static/js/pad2.js b/static/js/pad2.js index f442607d7..2eb615c54 100644 --- a/static/js/pad2.js +++ b/static/js/pad2.js @@ -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. From 3c99e0703024a844c7e2f6b52e8d5146757c52f0 Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sat, 13 Aug 2011 17:48:36 +0100 Subject: [PATCH 30/34] fixed regular expression PadID check --- node/db/PadManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/db/PadManager.js b/node/db/PadManager.js index 00128267b..01ec5143f 100644 --- a/node/db/PadManager.js +++ b/node/db/PadManager.js @@ -104,6 +104,6 @@ exports.doesPadExists = function(padId, callback) exports.isValidPadId = function(padId) { - return /^([0-9]+\$)?[^$]{1,50}$/.test(padId); + return /^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$/.test(padId); } From d0e378f63aa92fe7257aef89ec747809a461a48f Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Sat, 13 Aug 2011 22:07:21 +0100 Subject: [PATCH 31/34] added a security manager to control pad access on group pads --- node/db/SecurityManager.js | 235 ++++++++++++++++++++++++++++++ node/handler/PadMessageHandler.js | 91 +++++++----- static/js/pad2.js | 3 + 3 files changed, 291 insertions(+), 38 deletions(-) create mode 100644 node/db/SecurityManager.js diff --git a/node/db/SecurityManager.js b/node/db/SecurityManager.js new file mode 100644 index 000000000..7ad8f8d25 --- /dev/null +++ b/node/db/SecurityManager.js @@ -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); + }); +} diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js index 550440884..846c19737 100644 --- a/node/handler/PadMessageHandler.js +++ b/node/handler/PadMessageHandler.js @@ -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,51 +586,65 @@ 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.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) + async.parallel([ + //get colorId + function(callback) + { + authorManager.getAuthorColorId(author, function(err, value) { - authorManager.getAuthorColorId(author, function(err, value) - { - authorColorId = value; - callback(err); - }); - }, - //get author name - function(callback) + authorColorId = value; + callback(err); + }); + }, + //get author name + function(callback) + { + authorManager.getAuthorName(author, function(err, value) { - authorManager.getAuthorName(author, function(err, value) - { - authorName = value; - callback(err); - }); - }, - function(callback) + authorName = value; + callback(err); + }); + }, + function(callback) + { + padManager.getPad(message.padId, function(err, value) { - padManager.getPad(message.padId, function(err, value) - { - pad = value; - callback(err); - }); - }, - function(callback) + pad = value; + callback(err); + }); + }, + function(callback) + { + readOnlyManager.getReadOnlyId(message.padId, function(err, value) { - readOnlyManager.getReadOnlyId(message.padId, function(err, value) - { - readOnlyId = value; - callback(err); - }); - } - ], callback); - }); + readOnlyId = value; + callback(err); + }); + } + ], callback); }, //these db requests all need the pad object function(callback) diff --git a/static/js/pad2.js b/static/js/pad2.js index 2eb615c54..84106aff4 100644 --- a/static/js/pad2.js +++ b/static/js/pad2.js @@ -104,11 +104,14 @@ function handshake() token = randomString(); createCookie("token", token, 60); } + + var sessionID = readCookie("sessionID"); var msg = { "component": "pad", "type": "CLIENT_READY", "padId": padId, + "sessionID": sessionID, "token": token, "protocolVersion": 2 }; From 48980f9e196d4b1bb2bf1ddba2918bda2f2e5b7a Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 15 Aug 2011 18:26:20 +0100 Subject: [PATCH 32/34] Show permission messages on the browser side --- node/handler/PadMessageHandler.js | 2 +- static/js/pad2.js | 49 ++++++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/node/handler/PadMessageHandler.js b/node/handler/PadMessageHandler.js index 846c19737..3b0323145 100644 --- a/node/handler/PadMessageHandler.js +++ b/node/handler/PadMessageHandler.js @@ -602,7 +602,7 @@ function handleClientReady(client, message) //no access, send the client a message that tell him why else { - client.send({accessStatus: statusObject.accessStatus}) + client.json.send({accessStatus: statusObject.accessStatus}) } }); }, diff --git a/static/js/pad2.js b/static/js/pad2.js index a72f6729c..5ebe6ae0e 100644 --- a/static/js/pad2.js +++ b/static/js/pad2.js @@ -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; @@ -162,12 +174,14 @@ function handshake() } 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 }; @@ -179,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("You have no permission to access this pad"); + } + else if(obj.accessStatus == "needPassword") + { + $("#editorloadingbox").html("You need a password to access this pad
" + + ""+ + ""); + } + else if(obj.accessStatus == "wrongPassword") + { + $("#editorloadingbox").html("You're password was wrong
" + + ""+ + ""); + } + } + + //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; @@ -198,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 From 317370da2c841bb15184e6f3da7ad99325c1645d Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Mon, 15 Aug 2011 22:20:37 +0100 Subject: [PATCH 33/34] restricted access to pad import and export --- node/server.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/node/server.js b/node/server.js index 1a49e2627..028d5c2fe 100644 --- a/node/server.js +++ b/node/server.js @@ -39,6 +39,7 @@ var importHandler; var exporthtml; var readOnlyManager; var padManager; +var securityManager; //try to get the git version var version = ""; @@ -78,12 +79,14 @@ async.waterfall([ importHandler = require('./handler/ImportHandler'); apiHandler = require('./handler/APIHandler'); padManager = require('./db/PadManager'); + securityManager = require('./db/SecurityManager'); //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 @@ -160,6 +163,26 @@ 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) { @@ -217,7 +240,11 @@ async.waterfall([ res.header("Access-Control-Allow-Origin", "*"); res.header("Server", serverName); - exportHandler.doExport(req, res, req.params.pad, req.params.type); + + hasPadAccess(req, res, function() + { + exportHandler.doExport(req, res, req.params.pad, req.params.type); + }); }); //handle import requests @@ -238,7 +265,11 @@ async.waterfall([ } res.header("Server", serverName); - importHandler.doImport(req, res, req.params.pad); + + hasPadAccess(req, res, function() + { + importHandler.doImport(req, res, req.params.pad); + }); }); var apiLogger = log4js.getLogger("API"); From 3ff34f50d1bebb01985bf9bab19e51e03a9ade0e Mon Sep 17 00:00:00 2001 From: Peter 'Pita' Martischka Date: Tue, 16 Aug 2011 15:53:09 +0100 Subject: [PATCH 34/34] added a security control at socketiorouter, pad security is now fully enforced --- node/handler/SocketIORouter.js | 84 ++++++++++++++++++++++++++++------ node/server.js | 3 +- static/timeslider.html | 9 ++++ 3 files changed, 82 insertions(+), 14 deletions(-) diff --git a/node/handler/SocketIORouter.js b/node/handler/SocketIORouter.js index 08448f7cc..2d8f4f44d 100644 --- a/node/handler/SocketIORouter.js +++ b/node/handler/SocketIORouter.js @@ -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,34 +70,72 @@ 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)); + } } }); client.on('disconnect', function() { - //tell all components about this disconnect + //tell all components about this disconnect for(var i in components) { components[i].handleDisconnect(client); @@ -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); +} diff --git a/node/server.js b/node/server.js index 028d5c2fe..fa7796d5b 100644 --- a/node/server.js +++ b/node/server.js @@ -26,7 +26,6 @@ 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'); @@ -40,6 +39,7 @@ var exporthtml; var readOnlyManager; var padManager; var securityManager; +var socketIORouter; //try to get the git version var version = ""; @@ -80,6 +80,7 @@ async.waterfall([ apiHandler = require('./handler/APIHandler'); padManager = require('./db/PadManager'); securityManager = require('./db/SecurityManager'); + socketIORouter = require("./handler/SocketIORouter"); //install logging var httpLogger = log4js.getLogger("http"); diff --git a/static/timeslider.html b/static/timeslider.html index 110b6d128..5282972d4 100644 --- a/static/timeslider.html +++ b/static/timeslider.html @@ -105,17 +105,26 @@ { changesetLoader.handleSocketResponse(message); } + else if(message.accessStatus) + { + $("body").html("

You have no permission to access this pad

") + } }); }); //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);