cleansing, js-coffee moved to js where it is supposed to be. legacy js files removed. README updated

This commit is contained in:
Dominic Bosch 2014-04-05 02:05:51 +02:00
parent 21ff603f36
commit 745dddf04f
33 changed files with 1907 additions and 3676 deletions

View file

@ -1,11 +1,9 @@
README: webapi-eca
==================
> A Modular ECA Engine Server which acts as a middleware between WebAPI's.
> This folder continues examples of an ECA engine and how certain use cases
> could be implemented together with a rules language.
>
> The server is started through the [server.js](server.html) module by calling
> `node rule_server.js`.
> The server is started through the [webapi-eca.js](webapi-eca.html) module by calling
> `node js/webapi-eca.js`.
Getting started
@ -29,26 +27,29 @@ Download and install dependencies:
cd webapi-eca
npm install
Get your [redis](http://redis.io/) instance up and running (and find the port for the config file below) or create your own `js/db_interface.js`.
Get your [redis](http://redis.io/) instance up and running (and find the port for the config file below) or create your own `js/persistence.js`.
Edit the configuration file:
vi config/config.json
vi config/system.json
Apply your settings, for example:
# TODO Remake
{
"http_port": 8125,
"db_port": 6379,
"crypto_key": "[your key]",
"session_secret": "[your secret]"
"http-port": 8125, # The port on which the system listens for requests
"db-port": 6379, # The db-port where your redis instance is listening
"log": { ### logging configurations
"mode": "development", # if set to productive no expensive origin lookup is performed and logged
"io-level": "info", # the log-level for the std I/O stream
"file-level": "info", # the log-level for the log file
"file-path": "server.log" # log file path, relative to cwd
( add "nolog": "true" if no log shall be generated at all. Mainly used for unit tests )
}
}
Start the server:
run_server.sh
run_engine.sh
*Congratulations, your own WebAPI based ECA engine server is now up and running!*
@ -69,32 +70,3 @@ Create the doc *(to be accessed via the webserver, e.g.: localhost:8125/doc/)*:
Run test suite:
run_tests.sh
_
TODO
----
* Redis queue
* user handling (personal credentials)
* security in terms of users (private key, login)
* vm for modules, only give few libraries (no fs!)
* rules generator (provide webpage that is used to create rules dependent on the existing modues)
* geo location module, test on smartphone.
_
TODO per module
---------------
Testing | clean documentation | Clean error handling (Especially in loading of modules and their credentials):
* DB Interface
* Engine
* Event Poller
* HTTP Listener
* Logging
* Module Loader
* Module Manager
* Server

View file

@ -1,3 +1,3 @@
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo "Started listening on file changes to compile them!..."
coffee -wc -o $DIR/js-coffee $DIR/coffee/
coffee -wc -o $DIR/js $DIR/coffee/

View file

@ -4,48 +4,15 @@
* Create the documentation to be displayed through the webserver.
*/
//
require('groc').CLI(
require( 'groc' ).CLI(
[
"README.md",
"LICENSE.md",
"coffee/*.coffee",
"mod_actions/**/*.js",
"mod_events/**/*.js",
"-o./webpages/public/doc"
],
function(err) {
if (err) console.error(err);
else console.log('Done!');
function( err ) {
if ( err ) console.error( err );
else console.log( 'Done!' );
}
);
/*
* # docco Documentation
* Create the documentation to be displayed through the webserver.
*/
// var glob = require("glob"),
// docco = require('docco'),
// opt = ["", "", "--output", "webpages/doc"],
// files = [
// "README.md",
// "LICENSE.md",
// "create_doc.js",
// "coffee/*.coffee",
// "mod_actions/**/*.js",
// "mod_events/**/*.js"
// ];
//
// var semaphore = files.length;
// for(var i = 0; i < files.length; i++) {
// glob(files[i], null, function (er, files) {
// if(!er) {
// opt = opt.concat(files);
// } else {
// console.error(er);
// }
// if(--semaphore === 0) {
// docco.run(opt);
// }
// });
// }

View file

@ -1,152 +0,0 @@
// Generated by CoffeeScript 1.7.1
/*
Configuration
=============
> Loads the configuration file and acts as an interface to it.
*/
(function() {
var exports, fetchProp, fs, loadConfigFile, path;
fs = require('fs');
path = require('path');
/*
Module call
-----------
Calling the module as a function will act as a constructor and load the config file.
It is possible to hand an args object with the properties nolog (true if no outputs shall
be generated) and configPath for a custom configuration file path.
@param {Object} args
*/
exports = module.exports = (function(_this) {
return function(args) {
args = args != null ? args : {};
if (args.nolog) {
_this.nolog = true;
}
if (args.configPath) {
loadConfigFile(args.configPath);
} else {
loadConfigFile(path.join('config', 'system.json'));
}
return module.exports;
};
})(this);
/*
Tries to load a configuration file from the path relative to this module's parent folder.
Reads the config file synchronously from the file system and try to parse it.
@private loadConfigFile
@param {String} configPath
*/
loadConfigFile = (function(_this) {
return function(configPath) {
var confProperties, e, prop, _i, _len;
_this.config = null;
confProperties = ['log', 'http-port', 'db-port'];
try {
_this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', configPath)));
_this.isReady = true;
for (_i = 0, _len = confProperties.length; _i < _len; _i++) {
prop = confProperties[_i];
if (!_this.config[prop]) {
_this.isReady = false;
}
}
if (!_this.isReady && !_this.nolog) {
return console.error("Missing property in config file, requires:\n" + (" - " + (confProperties.join("\n - "))));
}
} catch (_error) {
e = _error;
_this.isReady = false;
if (!_this.nolog) {
return console.error("Failed loading config file: " + e.message);
}
}
};
})(this);
/*
Fetch a property from the configuration
@private fetchProp( *prop* )
@param {String} prop
*/
fetchProp = (function(_this) {
return function(prop) {
var _ref;
return (_ref = _this.config) != null ? _ref[prop] : void 0;
};
})(this);
/*
***Returns*** true if the config file is ready, else false
@public isReady()
*/
exports.isReady = (function(_this) {
return function() {
return _this.isReady;
};
})(this);
/*
***Returns*** the HTTP port
@public getHttpPort()
*/
exports.getHttpPort = function() {
return fetchProp('http-port');
};
/*
***Returns*** the DB port*
@public getDBPort()
*/
exports.getDbPort = function() {
return fetchProp('db-port');
};
/*
***Returns*** the log conf object
@public getLogConf()
*/
exports.getLogConf = function() {
return fetchProp('log');
};
/*
***Returns*** the crypto key
@public getCryptoKey()
*/
exports.getKeygenPassphrase = function() {
return fetchProp('keygen-passphrase');
};
}).call(this);

View file

@ -1,157 +0,0 @@
// Generated by CoffeeScript 1.7.1
/*
Dynamic Modules
===============
> Compiles CoffeeScript modules and loads JS modules in a VM, together
> with only a few allowed node.js modules.
*/
(function() {
var cryptico, cs, db, exports, issueApiCall, needle, vm;
db = require('./persistence');
vm = require('vm');
needle = require('needle');
cs = require('coffee-script');
cryptico = require('my-cryptico');
/*
Module call
-----------
Initializes the dynamic module handler.
@param {Object} args
*/
exports = module.exports = (function(_this) {
return function(args) {
var numBits, passPhrase;
_this.log = args.logger;
if (!_this.strPublicKey && args['keygen']) {
db(args);
passPhrase = args['keygen'];
numBits = 1024;
_this.oPrivateRSAkey = cryptico.generateRSAKey(passPhrase, numBits);
_this.strPublicKey = cryptico.publicKeyString(_this.oPrivateRSAkey);
_this.log.info("DM | Public Key generated: " + _this.strPublicKey);
}
return module.exports;
};
})(this);
exports.getPublicKey = (function(_this) {
return function() {
return _this.strPublicKey;
};
})(this);
issueApiCall = (function(_this) {
return function(method, url, credentials, cb) {
var err, func;
try {
if (method === 'get') {
func = needle.get;
} else {
func = needle.post;
}
return func(url, credentials, function(err, resp, body) {
if (!err) {
return cb(body);
} else {
return cb();
}
});
} catch (_error) {
err = _error;
return _this.log.info('DM | Error even before calling!');
}
};
})(this);
/*
Try to run a JS module from a string, together with the
given parameters. If it is written in CoffeeScript we
compile it first into JS.
@public compileString ( *src, id, params, lang* )
@param {String} src
@param {String} id
@param {Object} params
@param {String} lang
*/
exports.compileString = (function(_this) {
return function(src, userId, ruleId, modId, lang, dbMod, cb) {
var answ, err, fTryToLoad, logFunction;
answ = {
code: 200,
message: 'Successfully compiled'
};
if (lang === 'CoffeeScript') {
try {
src = cs.compile(src);
} catch (_error) {
err = _error;
answ.code = 400;
answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line;
}
}
logFunction = function(uId, rId, mId) {
return function(msg) {
return db.appendLog(uId, rId, mId, msg);
};
};
db.resetLog(userId, ruleId);
fTryToLoad = function(params) {
var oDecrypted, sandbox;
if (params) {
try {
oDecrypted = cryptico.decrypt(params, _this.oPrivateRSAkey);
params = JSON.parse(oDecrypted.plaintext);
} catch (_error) {
err = _error;
_this.log.warn("DM | Error during parsing of user defined params for " + userId + ", " + ruleId + ", " + modId);
params = {};
}
} else {
params = {};
}
sandbox = {
id: userId + '.' + modId + '.vm',
params: params,
apicall: issueApiCall,
log: logFunction(userId, ruleId, modId),
exports: {}
};
try {
vm.runInNewContext(src, sandbox, sandbox.id);
} catch (_error) {
err = _error;
console.log(err);
answ.code = 400;
answ.message = 'Loading Module failed: ' + err.message;
}
return cb({
answ: answ,
module: sandbox.exports
});
};
if (dbMod) {
return dbMod.getUserParams(modId, userId, function(err, obj) {
return fTryToLoad(obj);
});
} else {
return fTryToLoad();
}
};
})(this);
}).call(this);

View file

@ -1,293 +0,0 @@
// Generated by CoffeeScript 1.7.1
/*
Engine
==================
> The heart of the WebAPI ECA System. The engine loads action invoker modules
> corresponding to active rules actions and invokes them if an appropriate event
> is retrieved.
*/
(function() {
var db, dynmod, exports, isRunning, jsonQuery, listUserRules, pollQueue, processEvent, updateActionModules, validConditions;
db = require('./persistence');
dynmod = require('./dynamic-modules');
jsonQuery = require('js-select');
/*
This is ging to have a structure like:
An object of users with their active rules and the required action modules
"user-1":
"rule-1":
"rule": oRule-1
"actions":
"action-1": oAction-1
"action-2": oAction-2
"rule-2":
"rule": oRule-2
"actions":
"action-1": oAction-1
"user-2":
"rule-3":
"rule": oRule-3
"actions":
"action-3": oAction-3
*/
listUserRules = {};
isRunning = false;
/*
Module call
-----------
Initializes the Engine and starts polling the event queue for new events.
@param {Object} args
*/
exports = module.exports = (function(_this) {
return function(args) {
if (!isRunning) {
isRunning = true;
_this.log = args.logger;
db(args);
dynmod(args);
setTimeout(pollQueue, 10);
return module.exports;
}
};
})(this);
/*
This is a helper function for the unit tests so we can verify that action
modules are loaded correctly
*TODO we should change this to functions returning true or false rather than returning
*the whole list
@public getListUserRules ()
*/
exports.getListUserRules = function() {
return listUserRules;
};
/*
An event associated to rules happened and is captured here. Such events
are basically CRUD on rules.
@public internalEvent ( *evt* )
@param {Object} evt
*/
exports.internalEvent = (function(_this) {
return function(evt) {
var oRule, oUser;
if (!listUserRules[evt.user] && evt.event !== 'del') {
listUserRules[evt.user] = {};
}
oUser = listUserRules[evt.user];
oRule = evt.rule;
if (evt.event === 'new' || (evt.event === 'init' && !oUser[oRule.id])) {
oUser[oRule.id] = {
rule: oRule,
actions: {}
};
updateActionModules(oRule.id);
}
if (evt.event === 'del' && oUser) {
delete oUser[evt.ruleId];
}
if (JSON.stringify(oUser) === "{}") {
return delete listUserRules[evt.user];
}
};
})(this);
/*
As soon as changes were made to the rule set we need to ensure that the aprropriate action
invoker modules are loaded, updated or deleted.
@private updateActionModules ( *updatedRuleId* )
@param {Object} updatedRuleId
*/
updateActionModules = function(updatedRuleId) {
var fAddRequired, fRemoveNotRequired, name, oUser, userName, _results;
fRemoveNotRequired = function(oUser) {
var action, fRequired, _results;
fRequired = function(actionName) {
var action, _i, _len, _ref;
_ref = oUser[updatedRuleId].rule.actions;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
if ((action.split(' -> '))[0] === actionName) {
return true;
}
}
return false;
};
_results = [];
for (action in oUser[updatedRuleId].rule.actions) {
if (!fRequired(action)) {
_results.push(delete oUser[updatedRuleId].actions[action]);
} else {
_results.push(void 0);
}
}
return _results;
};
for (name in listUserRules) {
oUser = listUserRules[name];
fRemoveNotRequired(oUser);
}
fAddRequired = function(userName, oUser) {
var fCheckRules, nmRl, oRl, _results;
fCheckRules = function(oMyRule) {
var action, fAddIfNewOrNotExisting, _i, _len, _ref, _results;
fAddIfNewOrNotExisting = function(actionName) {
var moduleName;
moduleName = (actionName.split(' -> '))[0];
if (!oMyRule.actions[moduleName] || oMyRule.rule.id === updatedRuleId) {
return db.actionInvokers.getModule(moduleName, function(err, obj) {
return dynmod.compileString(obj.data, userName, oMyRule.rule.id, moduleName, obj.lang, db.actionInvokers, function(result) {
if (!result.answ === 200) {
this.log.error("EN | Compilation of code failed! " + userName + ", " + oMyRule.rule.id + ", " + moduleName);
}
return oMyRule.actions[moduleName] = result.module;
});
});
}
};
_ref = oMyRule.rule.actions;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
_results.push(fAddIfNewOrNotExisting(action));
}
return _results;
};
_results = [];
for (nmRl in oUser) {
oRl = oUser[nmRl];
_results.push(fCheckRules(oRl));
}
return _results;
};
_results = [];
for (userName in listUserRules) {
oUser = listUserRules[userName];
_results.push(fAddRequired(userName, oUser));
}
return _results;
};
pollQueue = function() {
if (isRunning) {
return db.popEvent(function(err, obj) {
if (!err && obj) {
processEvent(obj);
}
return setTimeout(pollQueue, 50);
});
}
};
/*
Checks whether all conditions of the rule are met by the event.
@private validConditions ( *evt, rule* )
@param {Object} evt
@param {Object} rule
*/
validConditions = function(evt, rule) {
var prop, _i, _len, _ref;
if (rule.conditions.length === 0) {
return true;
}
_ref = rule.conditions;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
prop = _ref[_i];
if (jsonQuery(evt, prop).nodes().length === 0) {
return false;
}
}
return true;
};
/*
Handles retrieved events.
@private processEvent ( *evt* )
@param {Object} evt
*/
processEvent = (function(_this) {
return function(evt) {
var action, arr, fSearchAndInvokeAction, oMyRule, oUser, ruleName, userName, _results;
fSearchAndInvokeAction = function(node, arrPath, evt, depth) {
var err;
if (!node) {
this.log.error("EN | Didn't find property in user rule list: " + arrPath.join(', ' + " at depth " + depth));
return;
}
if (depth === arrPath.length) {
try {
return node(evt.payload);
} catch (_error) {
err = _error;
return this.log.info("EN | ERROR IN ACTION INVOKER: " + err.message);
}
} else {
return fSearchAndInvokeAction(node[arrPath[depth]], arrPath, evt, depth + 1);
}
};
_this.log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')');
_results = [];
for (userName in listUserRules) {
oUser = listUserRules[userName];
_results.push((function() {
var _results1;
_results1 = [];
for (ruleName in oUser) {
oMyRule = oUser[ruleName];
if (evt.event === oMyRule.rule.event && validConditions(evt, oMyRule.rule)) {
this.log.info('EN | EVENT FIRED: ' + evt.event + '(' + evt.eventid + ') for rule ' + ruleName);
_results1.push((function() {
var _i, _len, _ref, _results2;
_ref = oMyRule.rule.actions;
_results2 = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
arr = action.split(' -> ');
_results2.push(fSearchAndInvokeAction(listUserRules, [userName, ruleName, 'actions', arr[0], arr[1]], evt, 0));
}
return _results2;
})());
} else {
_results1.push(void 0);
}
}
return _results1;
}).call(_this));
}
return _results;
};
})(this);
exports.shutDown = function() {
return isRunning = false;
};
}).call(this);

View file

@ -1,98 +0,0 @@
// Generated by CoffeeScript 1.7.1
/*
HTTP Listener
=============
> Receives the HTTP requests to the server at the given port. The requests
> (bound to a method) are then redirected to the appropriate handler which
> takes care of the request.
*/
(function() {
var app, exports, express, initRouting, path, qs, requestHandler;
requestHandler = require('./request-handler');
path = require('path');
qs = require('querystring');
express = require('express');
app = express();
/*
Module call
-----------
Initializes the HTTP listener and its request handler.
@param {Object} args
*/
exports = module.exports = (function(_this) {
return function(args) {
_this.log = args.logger;
_this.shutDownSystem = args['shutdown-function'];
requestHandler(args);
initRouting(args['http-port']);
return module.exports;
};
})(this);
/*
Initializes the request routing and starts listening on the given port.
@param {int} port
@private initRouting( *fShutDown* )
*/
initRouting = (function(_this) {
return function(port) {
var server, sess_sec;
app.use(express.cookieParser());
sess_sec = "149u*y8C:@kmN/520Gt\\v'+KFBnQ!\\r<>5X/xRI`sT<Iw";
app.use(express.session({
secret: sess_sec
}));
_this.log.info('HL | no session backbone');
app.use('/', express["static"](path.resolve(__dirname, '..', 'webpages', 'public')));
app.get('/admin', requestHandler.handleAdmin);
app.get('/forge', requestHandler.handleForge);
app.post('/event', requestHandler.handleEvent);
app.post('/login', requestHandler.handleLogin);
app.post('/logout', requestHandler.handleLogout);
app.post('/usercommand', requestHandler.handleUserCommand);
app.post('/admincommand', requestHandler.handleAdminCommand);
server = app.listen(parseInt(port) || 8111);
server.on('listening', function() {
var addr;
addr = server.address();
if (addr.port !== port) {
return _this.shutDownSystem();
}
});
return server.on('error', function(err) {
/*
Error handling of the express port listener requires special attention,
thus we have to catch the error, which is issued if the port is already in use.
*/
switch (err.errno) {
case 'EADDRINUSE':
_this.log.error(err, 'HL | http-port already in use, shutting down!');
break;
case 'EACCES':
_this.log.error(err, 'HL | http-port not accessible, shutting down!');
break;
default:
_this.log.error(err, 'HL | Error in server, shutting down!');
}
return _this.shutDownSystem();
});
};
})(this);
}).call(this);

View file

@ -1,73 +0,0 @@
// Generated by CoffeeScript 1.7.1
(function() {
var bunyan, fs, path;
fs = require('fs');
path = require('path');
bunyan = require('bunyan');
/*
Returns a bunyan logger according to the given arguments.
@public getLogger( *args* )
@param {Object} args
*/
exports.getLogger = (function(_this) {
return function(args) {
var e, emptylog, opt;
emptylog = {
trace: function() {},
debug: function() {},
info: function() {},
warn: function() {},
error: function() {},
fatal: function() {}
};
args = args != null ? args : {};
if (args.nolog) {
return emptylog;
} else {
try {
opt = {
name: "webapi-eca"
};
if (args['mode'] === 'development') {
opt.src = true;
}
if (args['file-path']) {
_this.logPath = path.resolve(args['file-path']);
} else {
_this.logPath = path.resolve(__dirname, '..', 'logs', 'server.log');
}
try {
fs.writeFileSync(_this.logPath + '.temp', 'temp');
fs.unlinkSync(_this.logPath + '.temp');
} catch (_error) {
e = _error;
console.error("Log folder '" + _this.logPath + "' is not writable");
return emptylog;
}
opt.streams = [
{
level: args['io-level'],
stream: process.stdout
}, {
level: args['file-level'],
path: _this.logPath
}
];
return bunyan.createLogger(opt);
} catch (_error) {
e = _error;
console.error(e);
return emptylog;
}
}
};
})(this);
}).call(this);

View file

@ -1,966 +0,0 @@
// Generated by CoffeeScript 1.7.1
/*
Persistence
============
> Handles the connection to the database and provides functionalities for event pollers,
> action invokers, rules and the (hopefully encrypted) storing of user-specific parameters
> per module.
> General functionality as a wrapper for the module holds initialization,
> the retrieval of modules and shut down.
>
> The general structure for linked data is that the key is stored in a set.
> By fetching all set entries we can then fetch all elements, which is
> automated in this function.
> For example, modules of the same group, e.g. action invokers are registered in an
> unordered set in the database, from where they can be retrieved again. For example
> a new action invoker has its ID (e.g 'probinder') first registered in the set
> 'action-invokers' and then stored in the db with the key 'action-invoker:' + ID
> (e.g. action-invoker:probinder).
>
*/
(function() {
var IndexedModules, exports, getSetRecords, redis, replyHandler,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
redis = require('redis');
/*
Module call
-----------
Initializes the DB connection with the given `db-port` property in the `args` object.
@param {Object} args
*/
exports = module.exports = (function(_this) {
return function(args) {
if (!_this.db) {
if (!args['db-port']) {
args['db-port'] = 6379;
}
_this.log = args.logger;
exports.eventPollers = new IndexedModules('event-poller', _this.log);
exports.actionInvokers = new IndexedModules('action-invoker', _this.log);
return exports.initPort(args['db-port']);
}
};
})(this);
exports.getLogger = (function(_this) {
return function() {
return _this.log;
};
})(this);
exports.initPort = (function(_this) {
return function(port) {
var _ref;
_this.connRefused = false;
if ((_ref = _this.db) != null) {
_ref.quit();
}
_this.db = redis.createClient(port, 'localhost', {
connect_timeout: 2000
});
_this.db.on('error', function(err) {
if (err.message.indexOf('ECONNREFUSED') > -1) {
_this.connRefused = true;
return _this.log.error(err, 'DB | Wrong port?');
}
});
exports.eventPollers.setDB(_this.db);
return exports.actionInvokers.setDB(_this.db);
};
})(this);
/*
Checks whether the db is connected and passes either an error on failure after
ten attempts within five seconds, or nothing on success to the callback(err).
@public isConnected( *cb* )
@param {function} cb
*/
exports.isConnected = (function(_this) {
return function(cb) {
var fCheckConnection, numAttempts;
if (!_this.db) {
return cb(new Error('DB | DB initialization did not occur or failed miserably!'));
} else {
if (_this.db.connected) {
return cb();
} else {
numAttempts = 0;
fCheckConnection = function() {
var _ref;
if (_this.connRefused) {
if ((_ref = _this.db) != null) {
_ref.quit();
}
return cb(new Error('DB | Connection refused! Wrong port?'));
} else {
if (_this.db.connected) {
_this.log.info('DB | Successfully connected to DB!');
return cb();
} else if (numAttempts++ < 10) {
return setTimeout(fCheckConnection, 100);
} else {
return cb(new Error('DB | Connection to DB failed!'));
}
}
};
return setTimeout(fCheckConnection, 100);
}
}
};
})(this);
/*
Abstracts logging for simple action replies from the DB.
@private replyHandler( *action* )
@param {String} action
*/
replyHandler = (function(_this) {
return function(action) {
return function(err, reply) {
if (err) {
return _this.log.warn(err, "during '" + action + "'");
} else {
return _this.log.info("DB | " + action + ": " + reply);
}
};
};
})(this);
/*
Push an event into the event queue.
@public pushEvent( *oEvent* )
@param {Object} oEvent
*/
exports.pushEvent = (function(_this) {
return function(oEvent) {
if (oEvent) {
_this.log.info("DB | Event pushed into the queue: '" + oEvent.eventid + "'");
return _this.db.rpush('event_queue', JSON.stringify(oEvent));
} else {
return _this.log.warn('DB | Why would you give me an empty event...');
}
};
})(this);
/*
Pop an event from the event queue and pass it to cb(err, obj).
@public popEvent( *cb* )
@param {function} cb
*/
exports.popEvent = (function(_this) {
return function(cb) {
var makeObj;
makeObj = function(pcb) {
return function(err, obj) {
return pcb(err, JSON.parse(obj));
};
};
return _this.db.lpop('event_queue', makeObj(cb));
};
})(this);
/*
Purge the event queue.
@public purgeEventQueue()
*/
exports.purgeEventQueue = (function(_this) {
return function() {
return _this.db.del('event_queue', replyHandler('purging event queue'));
};
})(this);
/*
Fetches all linked data set keys from a linking set, fetches the single
data objects via the provided function and returns the results to cb(err, obj).
@private getSetRecords( *set, fSingle, cb* )
@param {String} set the set name how it is stored in the DB
@param {function} fSingle a function to retrieve a single data element
per set entry
@param {function} cb the callback(err, obj) function that receives all
the retrieved data or an error
*/
getSetRecords = (function(_this) {
return function(set, fSingle, cb) {
_this.log.info("DB | Fetching set records: '" + set + "'");
return _this.db.smembers(set, function(err, arrReply) {
var fCallback, objReplies, reply, semaphore, _i, _len, _results;
if (err) {
_this.log.warn(err, "DB | fetching '" + set + "'");
return cb(err);
} else if (arrReply.length === 0) {
return cb();
} else {
semaphore = arrReply.length;
objReplies = {};
setTimeout(function() {
if (semaphore > 0) {
return cb(new Error("Timeout fetching '" + set + "'"));
}
}, 2000);
fCallback = function(prop) {
return function(err, data) {
--semaphore;
if (err) {
_this.log.warn(err, "DB | fetching single element: '" + prop + "'");
} else if (!data) {
_this.log.warn(new Error("Empty key in DB: '" + prop + "'"));
} else {
objReplies[prop] = data;
}
if (semaphore === 0) {
return cb(null, objReplies);
}
};
};
_results = [];
for (_i = 0, _len = arrReply.length; _i < _len; _i++) {
reply = arrReply[_i];
_results.push(fSingle(reply, fCallback(reply)));
}
return _results;
}
});
};
})(this);
IndexedModules = (function() {
function IndexedModules(setname, log) {
this.setname = setname;
this.log = log;
this.deleteUserParams = __bind(this.deleteUserParams, this);
this.getUserParamsIds = __bind(this.getUserParamsIds, this);
this.getUserParams = __bind(this.getUserParams, this);
this.storeUserParams = __bind(this.storeUserParams, this);
this.deleteModule = __bind(this.deleteModule, this);
this.getModules = __bind(this.getModules, this);
this.getModuleIds = __bind(this.getModuleIds, this);
this.getAvailableModuleIds = __bind(this.getAvailableModuleIds, this);
this.getModuleParams = __bind(this.getModuleParams, this);
this.getModule = __bind(this.getModule, this);
this.unpublish = __bind(this.unpublish, this);
this.publish = __bind(this.publish, this);
this.unlinkModule = __bind(this.unlinkModule, this);
this.linkModule = __bind(this.linkModule, this);
this.storeModule = __bind(this.storeModule, this);
this.log.info("DB | (IdxedMods) Instantiated indexed modules for '" + this.setname + "'");
}
IndexedModules.prototype.setDB = function(db) {
this.db = db;
return this.log.info("DB | (IdxedMods) Registered new DB connection for '" + this.setname + "'");
};
/*
Stores a module and links it to the user.
@private storeModule( *userId, oModule* )
@param {String} userId
@param {object} oModule
*/
IndexedModules.prototype.storeModule = function(userId, oModule) {
this.log.info("DB | (IdxedMods) " + this.setname + ".storeModule( " + userId + ", oModule )");
this.db.sadd("" + this.setname + "s", oModule.id, replyHandler("sadd '" + oModule.id + "' to '" + this.setname + "'"));
this.db.hmset("" + this.setname + ":" + oModule.id, oModule, replyHandler("hmset properties in hash '" + this.setname + ":" + oModule.id + "'"));
return this.linkModule(oModule.id, userId);
};
IndexedModules.prototype.linkModule = function(mId, userId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".linkModule( " + mId + ", " + userId + " )");
this.db.sadd("" + this.setname + ":" + mId + ":users", userId, replyHandler("sadd " + userId + " to '" + this.setname + ":" + mId + ":users'"));
return this.db.sadd("user:" + userId + ":" + this.setname + "s", mId, replyHandler("sadd " + mId + " to 'user:" + userId + ":" + this.setname + "s'"));
};
IndexedModules.prototype.unlinkModule = function(mId, userId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".unlinkModule( " + mId + ", " + userId + " )");
this.db.srem("" + this.setname + ":" + mId + ":users", userId, replyHandler("srem " + userId + " from '" + this.setname + ":" + mId + ":users'"));
return this.db.srem("user:" + userId + ":" + this.setname + "s", mId, replyHandler("srem " + mId + " from 'user:" + userId + ":" + this.setname + "s'"));
};
IndexedModules.prototype.publish = function(mId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".publish( " + mId + " )");
return this.db.sadd("public-" + this.setname + "s", mId, replyHandler("sadd '" + mId + "' to 'public-" + this.setname + "s'"));
};
IndexedModules.prototype.unpublish = function(mId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".unpublish( " + mId + " )");
return this.db.srem("public-" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from 'public-" + this.setname + "s'"));
};
IndexedModules.prototype.getModule = function(mId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModule( " + mId + " )");
return this.db.hgetall("" + this.setname + ":" + mId, cb);
};
IndexedModules.prototype.getModuleParams = function(mId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModuleParams( " + mId + " )");
return this.db.hget("" + this.setname + ":" + mId, "params", cb);
};
IndexedModules.prototype.getAvailableModuleIds = function(userId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getPublicModuleIds( " + userId + " )");
return this.db.sunion("public-" + this.setname + "s", "user:" + userId + ":" + this.setname + "s", cb);
};
IndexedModules.prototype.getModuleIds = function(cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModuleIds()");
return this.db.smembers("" + this.setname + "s", cb);
};
IndexedModules.prototype.getModules = function(cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModules()");
return getSetRecords("" + this.setname + "s", this.getModule, cb);
};
IndexedModules.prototype.deleteModule = function(mId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".deleteModule( " + mId + " )");
this.db.srem("" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from " + this.setname + "s"));
this.db.del("" + this.setname + ":" + mId, replyHandler("del of '" + this.setname + ":" + mId + "'"));
this.unpublish(mId);
return this.db.smembers("" + this.setname + ":" + mId + ":users", (function(_this) {
return function(err, obj) {
var userId, _i, _j, _len, _len1, _results;
for (_i = 0, _len = obj.length; _i < _len; _i++) {
userId = obj[_i];
_this.unlinkModule(mId, userId);
}
_results = [];
for (_j = 0, _len1 = obj.length; _j < _len1; _j++) {
userId = obj[_j];
_results.push(_this.deleteUserParams(mId, userId));
}
return _results;
};
})(this));
};
/*
Stores user params for a module. They are expected to be RSA encrypted with helps of
the provided cryptico JS library and will only be decrypted right before the module is loaded!
@private storeUserParams( *mId, userId, encData* )
@param {String} mId
@param {String} userId
@param {object} encData
*/
IndexedModules.prototype.storeUserParams = function(mId, userId, encData) {
this.log.info("DB | (IdxedMods) " + this.setname + ".storeUserParams( " + mId + ", " + userId + ", encData )");
this.db.sadd("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("sadd '" + mId + ":" + userId + "' to '" + this.setname + "-params'"));
return this.db.set("" + this.setname + "-params:" + mId + ":" + userId, encData, replyHandler("set user params in '" + this.setname + "-params:" + mId + ":" + userId + "'"));
};
IndexedModules.prototype.getUserParams = function(mId, userId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getUserParams( " + mId + ", " + userId + " )");
return this.db.get("" + this.setname + "-params:" + mId + ":" + userId, cb);
};
IndexedModules.prototype.getUserParamsIds = function(cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getUserParamsIds()");
return this.db.smembers("" + this.setname + "-params", cb);
};
IndexedModules.prototype.deleteUserParams = function(mId, userId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".deleteUserParams(" + mId + ", " + userId + " )");
this.db.srem("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("srem '" + mId + ":" + userId + "' from '" + this.setname + "-params'"));
return this.db.del("" + this.setname + "-params:" + mId + ":" + userId, replyHandler("del '" + this.setname + "-params:" + mId + ":" + userId + "'"));
};
return IndexedModules;
})();
/*
*# Rules
*/
/*
Appends a log entry.
@public log( *userId, ruleId, message* )
@param {String} userId
@param {String} ruleId
@param {String} message
*/
exports.appendLog = (function(_this) {
return function(userId, ruleId, moduleId, message) {
return _this.db.append("" + userId + ":" + ruleId, "[" + ((new Date).toISOString()) + "] {" + moduleId + "} " + message + "\n");
};
})(this);
/*
Retrieves a log entry.
@public getLog( *userId, ruleId* )
@param {String} userId
@param {String} ruleId
@param {function} cb
*/
exports.getLog = (function(_this) {
return function(userId, ruleId, cb) {
return _this.db.get("" + userId + ":" + ruleId, cb);
};
})(this);
/*
Resets a log entry.
@public resetLog( *userId, ruleId* )
@param {String} userId
@param {String} ruleId
*/
exports.resetLog = (function(_this) {
return function(userId, ruleId) {
return _this.db.del("" + userId + ":" + ruleId, replyHandler("RESET LOG '" + userId + ":" + ruleId + "'"));
};
})(this);
/*
Query the DB for a rule and pass it to cb(err, obj).
@public getRule( *ruleId, cb* )
@param {String} ruleId
@param {function} cb
*/
exports.getRule = (function(_this) {
return function(ruleId, cb) {
_this.log.info("DB | getRule: '" + ruleId + "'");
return _this.db.get("rule:" + ruleId, cb);
};
})(this);
/*
Fetch all rules and pass them to cb(err, obj).
@public getRules( *cb* )
@param {function} cb
*/
exports.getRules = (function(_this) {
return function(cb) {
_this.log.info('DB | Fetching all Rules');
return getSetRecords('rules', exports.getRule, cb);
};
})(this);
/*
Fetch all rule IDs and hand it to cb(err, obj).
@public getRuleIds( *cb* )
@param {function} cb
*/
exports.getRuleIds = (function(_this) {
return function(cb) {
_this.log.info('DB | Fetching all Rule IDs');
return _this.db.smembers('rules', cb);
};
})(this);
/*
Store a string representation of a rule in the DB.
@public storeRule( *ruleId, data* )
@param {String} ruleId
@param {String} data
*/
exports.storeRule = (function(_this) {
return function(ruleId, data) {
_this.log.info("DB | storeRule: '" + ruleId + "'");
_this.db.sadd('rules', "" + ruleId, replyHandler("storing rule key '" + ruleId + "'"));
return _this.db.set("rule:" + ruleId, data, replyHandler("storing rule '" + ruleId + "'"));
};
})(this);
/*
Delete a string representation of a rule.
@public deleteRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.deleteRule = (function(_this) {
return function(ruleId) {
_this.log.info("DB | deleteRule: '" + ruleId + "'");
_this.db.srem("rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "'"));
_this.db.del("rule:" + ruleId, replyHandler("Deleting rule '" + ruleId + "'"));
_this.db.smembers("rule:" + ruleId + ":users", function(err, obj) {
var delLinkedUserRule, id, _i, _len, _results;
delLinkedUserRule = function(userId) {
return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in linked user '" + userId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delLinkedUserRule(id));
}
return _results;
});
_this.db.del("rule:" + ruleId + ":users", replyHandler("Deleting rule '" + ruleId + "' users"));
_this.db.smembers("rule:" + ruleId + ":active-users", function(err, obj) {
var delActiveUserRule, id, _i, _len, _results;
delActiveUserRule = function(userId) {
return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in active user '" + userId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delActiveUserRule(id));
}
return _results;
});
return _this.db.del("rule:" + ruleId + ":active-users", replyHandler("Deleting rule '" + ruleId + "' active users"));
};
})(this);
/*
Associate a rule to a user.
@public linkRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.linkRule = (function(_this) {
return function(ruleId, userId) {
_this.log.info("DB | linkRule: '" + ruleId + "' for user '" + userId + "'");
_this.db.sadd("rule:" + ruleId + ":users", userId, replyHandler("storing user '" + userId + "' for rule key '" + ruleId + "'"));
return _this.db.sadd("user:" + userId + ":rules", ruleId, replyHandler("storing rule key '" + ruleId + "' for user '" + userId + "'"));
};
})(this);
/*
Get rules linked to a user and hand it to cb(err, obj).
@public getUserLinkRule( *userId, cb* )
@param {String} userId
@param {function} cb
*/
exports.getUserLinkedRules = (function(_this) {
return function(userId, cb) {
_this.log.info("DB | getUserLinkedRules: for user '" + userId + "'");
return _this.db.smembers("user:" + userId + ":rules", cb);
};
})(this);
/*
Get users linked to a rule and hand it to cb(err, obj).
@public getRuleLinkedUsers( *ruleId, cb* )
@param {String} ruleId
@param {function} cb
*/
exports.getRuleLinkedUsers = (function(_this) {
return function(ruleId, cb) {
_this.log.info("DB | getRuleLinkedUsers: for rule '" + ruleId + "'");
return _this.db.smembers("rule:" + ruleId + ":users", cb);
};
})(this);
/*
Delete an association of a rule to a user.
@public unlinkRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.unlinkRule = (function(_this) {
return function(ruleId, userId) {
_this.log.info("DB | unlinkRule: '" + ruleId + ":" + userId + "'");
_this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("removing user '" + userId + "' for rule key '" + ruleId + "'"));
return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("removing rule key '" + ruleId + "' for user '" + userId + "'"));
};
})(this);
/*
Activate a rule.
@public activateRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.activateRule = (function(_this) {
return function(ruleId, userId) {
_this.log.info("DB | activateRule: '" + ruleId + "' for '" + userId + "'");
_this.db.sadd("rule:" + ruleId + ":active-users", userId, replyHandler("storing activated user '" + userId + "' in rule '" + ruleId + "'"));
return _this.db.sadd("user:" + userId + ":active-rules", ruleId, replyHandler("storing activated rule '" + ruleId + "' in user '" + userId + "'"));
};
})(this);
/*
Get rules activated for a user and hand it to cb(err, obj).
@public getUserLinkRule( *userId, cb* )
@param {String} userId
@param {function} cb
*/
exports.getUserActivatedRules = (function(_this) {
return function(userId, cb) {
_this.log.info("DB | getUserActivatedRules: for user '" + userId + "'");
return _this.db.smembers("user:" + userId + ":active-rules", cb);
};
})(this);
/*
Get users activated for a rule and hand it to cb(err, obj).
@public getRuleActivatedUsers ( *ruleId, cb* )
@param {String} ruleId
@param {function} cb
*/
exports.getRuleActivatedUsers = (function(_this) {
return function(ruleId, cb) {
_this.log.info("DB | getRuleActivatedUsers: for rule '" + ruleId + "'");
return _this.db.smembers("rule:" + ruleId + ":active-users", cb);
};
})(this);
/*
Deactivate a rule.
@public deactivateRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.deactivateRule = (function(_this) {
return function(ruleId, userId) {
_this.log.info("DB | deactivateRule: '" + ruleId + "' for '" + userId + "'");
_this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("removing activated user '" + userId + "' in rule '" + ruleId + "'"));
return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("removing activated rule '" + ruleId + "' in user '" + userId + "'"));
};
})(this);
/*
Fetch all active ruleIds and pass them to cb(err, obj).
@public getAllActivatedRuleIds( *cb* )
@param {function} cb
*/
exports.getAllActivatedRuleIdsPerUser = (function(_this) {
return function(cb) {
_this.log.info("DB | Fetching all active rules");
return _this.db.smembers('users', function(err, obj) {
var fFetchActiveUserRules, result, semaphore, user, _i, _len, _results;
result = {};
if (obj.length === 0) {
return cb(null, result);
} else {
semaphore = obj.length;
fFetchActiveUserRules = function(userId) {
return _this.db.smembers("user:" + user + ":active-rules", function(err, obj) {
if (obj.length > 0) {
result[userId] = obj;
}
if (--semaphore === 0) {
return cb(null, result);
}
});
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
user = obj[_i];
_results.push(fFetchActiveUserRules(user));
}
return _results;
}
});
};
})(this);
/*
*# Users
*/
/*
Store a user object (needs to be a flat structure).
The password should be hashed before it is passed to this function.
@public storeUser( *objUser* )
@param {Object} objUser
*/
exports.storeUser = (function(_this) {
return function(objUser) {
_this.log.info("DB | storeUser: '" + objUser.username + "'");
if (objUser && objUser.username && objUser.password) {
_this.db.sadd('users', objUser.username, replyHandler("storing user key '" + objUser.username + "'"));
objUser.password = objUser.password;
return _this.db.hmset("user:" + objUser.username, objUser, replyHandler("storing user properties '" + objUser.username + "'"));
} else {
return _this.log.warn(new Error('DB | username or password was missing'));
}
};
})(this);
/*
Fetch all user IDs and pass them to cb(err, obj).
@public getUserIds( *cb* )
@param {function} cb
*/
exports.getUserIds = (function(_this) {
return function(cb) {
_this.log.info("DB | getUserIds");
return _this.db.smembers("users", cb);
};
})(this);
/*
Fetch a user by id and pass it to cb(err, obj).
@public getUser( *userId, cb* )
@param {String} userId
@param {function} cb
*/
exports.getUser = (function(_this) {
return function(userId, cb) {
_this.log.info("DB | getUser: '" + userId + "'");
return _this.db.hgetall("user:" + userId, cb);
};
})(this);
/*
Deletes a user and all his associated linked and active rules.
@public deleteUser( *userId* )
@param {String} userId
*/
exports.deleteUser = (function(_this) {
return function(userId) {
_this.log.info("DB | deleteUser: '" + userId + "'");
_this.db.srem("users", userId, replyHandler("Deleting user key '" + userId + "'"));
_this.db.del("user:" + userId, replyHandler("Deleting user '" + userId + "'"));
_this.db.smembers("user:" + userId + ":rules", function(err, obj) {
var delLinkedRuleUser, id, _i, _len, _results;
delLinkedRuleUser = function(ruleId) {
return _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in linked rule '" + ruleId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delLinkedRuleUser(id));
}
return _results;
});
_this.db.del("user:" + userId + ":rules", replyHandler("Deleting user '" + userId + "' rules"));
_this.db.smembers("user:" + userId + ":active-rules", function(err, obj) {
var delActivatedRuleUser, id, _i, _len, _results;
delActivatedRuleUser = function(ruleId) {
return _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("Deleting user key '" + userId + "' in active rule '" + ruleId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delActivatedRuleUser(id));
}
return _results;
});
_this.db.del("user:" + userId + ":active-rules", replyHandler("Deleting user '" + userId + "' rules"));
_this.db.smembers("user:" + userId + ":roles", function(err, obj) {
var delRoleUser, id, _i, _len, _results;
delRoleUser = function(roleId) {
return _this.db.srem("role:" + roleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in role '" + roleId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delRoleUser(id));
}
return _results;
});
return _this.db.del("user:" + userId + ":roles", replyHandler("Deleting user '" + userId + "' roles"));
};
})(this);
/*
Checks the credentials and on success returns the user object to the
callback(err, obj) function. The password has to be hashed (SHA-3-512)
beforehand by the instance closest to the user that enters the password,
because we only store hashes of passwords for security6 reasons.
@public loginUser( *userId, password, cb* )
@param {String} userId
@param {String} password
@param {function} cb
*/
exports.loginUser = (function(_this) {
return function(userId, password, cb) {
var fCheck;
_this.log.info("DB | User '" + userId + "' tries to log in");
fCheck = function(pw) {
return function(err, obj) {
if (err) {
return cb(err, null);
} else if (obj && obj.password) {
if (pw === obj.password) {
_this.log.info("DB | User '" + obj.username + "' logged in!");
return cb(null, obj);
} else {
return cb(new Error('Wrong credentials!'), null);
}
} else {
return cb(new Error('User not found!'), null);
}
};
};
return _this.db.hgetall("user:" + userId, fCheck(password));
};
})(this);
/*
*# User Roles
*/
/*
Associate a role with a user.
@public storeUserRole( *userId, role* )
@param {String} userId
@param {String} role
*/
exports.storeUserRole = (function(_this) {
return function(userId, role) {
_this.log.info("DB | storeUserRole: '" + userId + ":" + role + "'");
_this.db.sadd('roles', role, replyHandler("adding role '" + role + "' to role index set"));
_this.db.sadd("user:" + userId + ":roles", role, replyHandler("adding role '" + role + "' to user '" + userId + "'"));
return _this.db.sadd("role:" + role + ":users", userId, replyHandler("adding user '" + userId + "' to role '" + role + "'"));
};
})(this);
/*
Fetch all roles of a user and pass them to cb(err, obj).
@public getUserRoles( *userId* )
@param {String} userId
@param {function} cb
*/
exports.getUserRoles = (function(_this) {
return function(userId, cb) {
_this.log.info("DB | getUserRoles: '" + userId + "'");
return _this.db.smembers("user:" + userId + ":roles", cb);
};
})(this);
/*
Fetch all users of a role and pass them to cb(err, obj).
@public getUserRoles( *role* )
@param {String} role
@param {function} cb
*/
exports.getRoleUsers = (function(_this) {
return function(role, cb) {
_this.log.info("DB | getRoleUsers: '" + role + "'");
return _this.db.smembers("role:" + role + ":users", cb);
};
})(this);
/*
Remove a role from a user.
@public removeRoleFromUser( *role, userId* )
@param {String} role
@param {String} userId
*/
exports.removeUserRole = (function(_this) {
return function(userId, role) {
_this.log.info("DB | removeRoleFromUser: role '" + role + "', user '" + userId + "'");
_this.db.srem("user:" + userId + ":roles", role, replyHandler("Removing role '" + role + "' from user '" + userId + "'"));
return _this.db.srem("role:" + role + ":users", userId, replyHandler("Removing user '" + userId + "' from role '" + role + "'"));
};
})(this);
/*
Shuts down the db link.
@public shutDown()
*/
exports.shutDown = (function(_this) {
return function() {
var _ref;
return (_ref = _this.db) != null ? _ref.quit() : void 0;
};
})(this);
}).call(this);

View file

@ -1,5 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
Components Manager
@ -7,10 +6,12 @@ Components Manager
> The components manager takes care of the dynamic JS modules and the rules.
> Event Poller and Action Invoker modules are loaded as strings and stored in the database,
> then compiled into node modules and rules and used in the engine and event poller.
*/
*/
(function() {
var commandFunctions, db, dynmod, eventEmitter, events, exports, forgeModule, fs, getModuleParams, getModules, hasRequiredParams, path;
var commandFunctions, db, dynmod, eventEmitter, events, exports, forgeModule, fs, getModuleParams, getModules, hasRequiredParams, path,
_this = this;
db = require('./persistence');
@ -24,67 +25,61 @@ Components Manager
eventEmitter = new events.EventEmitter();
/*
Module call
-----------
Initializes the Components Manager and constructs a new Event Emitter.
@param {Object} args
*/
*/
exports = module.exports = (function(_this) {
return function(args) {
_this.log = args.logger;
db(args);
dynmod(args);
return module.exports;
};
})(this);
exports = module.exports = function(args) {
_this.log = args.logger;
db(args);
dynmod(args);
return module.exports;
};
/*
Add an event handler (eh) that listens for rules.
@public addRuleListener ( *eh* )
@param {function} eh
*/
*/
exports.addRuleListener = (function(_this) {
return function(eh) {
eventEmitter.addListener('rule', eh);
return db.getAllActivatedRuleIdsPerUser(function(err, objUsers) {
var fGoThroughUsers, rules, user, _results;
fGoThroughUsers = function(user, rules) {
var fFetchRule, rule, _i, _len, _results;
fFetchRule = function(rule) {
return db.getRule(rule, (function(_this) {
return function(err, oRule) {
return eventEmitter.emit('rule', {
event: 'init',
user: user,
rule: JSON.parse(oRule)
});
};
})(this));
};
_results = [];
for (_i = 0, _len = rules.length; _i < _len; _i++) {
rule = rules[_i];
_results.push(fFetchRule(rule));
}
return _results;
exports.addRuleListener = function(eh) {
eventEmitter.addListener('rule', eh);
return db.getAllActivatedRuleIdsPerUser(function(err, objUsers) {
var fGoThroughUsers, rules, user, _results;
fGoThroughUsers = function(user, rules) {
var fFetchRule, rule, _i, _len, _results;
fFetchRule = function(rule) {
var _this = this;
return db.getRule(rule, function(err, oRule) {
return eventEmitter.emit('rule', {
event: 'init',
user: user,
rule: JSON.parse(oRule)
});
});
};
_results = [];
for (user in objUsers) {
rules = objUsers[user];
_results.push(fGoThroughUsers(user, rules));
for (_i = 0, _len = rules.length; _i < _len; _i++) {
rule = rules[_i];
_results.push(fFetchRule(rule));
}
return _results;
});
};
})(this);
};
_results = [];
for (user in objUsers) {
rules = objUsers[user];
_results.push(fGoThroughUsers(user, rules));
}
return _results;
});
};
/*
Processes a user request coming through the request-handler.
@ -99,7 +94,8 @@ Components Manager
@param {Object} user
@param {Object} oReq
@param {function} callback
*/
*/
exports.processRequest = function(user, oReq, callback) {
var dat, err;
@ -144,7 +140,8 @@ Components Manager
getModules = function(user, oPayload, dbMod, callback) {
return dbMod.getAvailableModuleIds(user.username, function(err, arrNames) {
var answReq, fGetFunctions, id, oRes, sem, _i, _len, _results;
var answReq, fGetFunctions, id, oRes, sem, _i, _len, _results,
_this = this;
oRes = {};
answReq = function() {
return callback({
@ -156,18 +153,16 @@ Components Manager
if (sem === 0) {
return answReq();
} else {
fGetFunctions = (function(_this) {
return function(id) {
return dbMod.getModule(id, function(err, oModule) {
if (oModule) {
oRes[id] = JSON.parse(oModule.functions);
}
if (--sem === 0) {
return answReq();
}
});
};
})(this);
fGetFunctions = function(id) {
return dbMod.getModule(id, function(err, oModule) {
if (oModule) {
oRes[id] = JSON.parse(oModule.functions);
}
if (--sem === 0) {
return answReq();
}
});
};
_results = [];
for (_i = 0, _len = arrNames.length; _i < _len; _i++) {
id = arrNames[_i];
@ -191,46 +186,44 @@ Components Manager
}
};
forgeModule = (function(_this) {
return function(user, oPayload, dbMod, callback) {
var answ;
answ = hasRequiredParams(['id', 'params', 'lang', 'data'], oPayload);
if (answ.code !== 200) {
return callback(answ);
} else {
return dbMod.getModule(oPayload.id, function(err, mod) {
var src;
if (mod) {
answ.code = 409;
answ.message = 'Module name already existing: ' + oPayload.id;
return callback(answ);
} else {
src = oPayload.data;
return dynmod.compileString(src, user.username, 'dummyRule', oPayload.id, oPayload.lang, null, function(cm) {
var funcs, id, name, _ref;
answ = cm.answ;
if (answ.code === 200) {
funcs = [];
_ref = cm.module;
for (name in _ref) {
id = _ref[name];
funcs.push(name);
}
_this.log.info("CM | Storing new module with functions " + (funcs.join()));
answ.message = "Event Poller module successfully stored! Found following function(s): " + funcs;
oPayload.functions = JSON.stringify(funcs);
dbMod.storeModule(user.username, oPayload);
if (oPayload["public"] === 'true') {
dbMod.publish(oPayload.id);
}
forgeModule = function(user, oPayload, dbMod, callback) {
var answ;
answ = hasRequiredParams(['id', 'params', 'lang', 'data'], oPayload);
if (answ.code !== 200) {
return callback(answ);
} else {
return dbMod.getModule(oPayload.id, function(err, mod) {
var src;
if (mod) {
answ.code = 409;
answ.message = 'Module name already existing: ' + oPayload.id;
return callback(answ);
} else {
src = oPayload.data;
return dynmod.compileString(src, user.username, 'dummyRule', oPayload.id, oPayload.lang, null, function(cm) {
var funcs, id, name, _ref;
answ = cm.answ;
if (answ.code === 200) {
funcs = [];
_ref = cm.module;
for (name in _ref) {
id = _ref[name];
funcs.push(name);
}
return callback(answ);
});
}
});
}
};
})(this);
_this.log.info("CM | Storing new module with functions " + (funcs.join()));
answ.message = "Event Poller module successfully stored! Found following function(s): " + funcs;
oPayload.functions = JSON.stringify(funcs);
dbMod.storeModule(user.username, oPayload);
if (oPayload["public"] === 'true') {
dbMod.publish(oPayload.id);
}
}
return callback(answ);
});
}
});
}
};
commandFunctions = {
get_public_key: function(user, oPayload, callback) {

View file

@ -1,79 +1,145 @@
'use strict';
// Generated by CoffeeScript 1.6.3
/*
var path = require('path'),
log = require('./logging'),
config;
Configuration
=============
> Loads the configuration file and acts as an interface to it.
*/
exports = module.exports = function(args) {
args = args || {};
log(args);
if(typeof args.relPath === 'string') loadConfigFile(args.relPath);
return module.exports;
};
loadConfigFile(path.join('config', 'config.json'));
(function() {
var exports, fetchProp, fs, loadConfigFile, path,
_this = this;
function loadConfigFile(relPath) {
try {
config = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', relPath)));
if(config && config.http_port && config.db_port
&& config.crypto_key && config.session_secret) {
log.print('CF', 'config file loaded successfully!');
} else {
log.error('CF', new Error('Missing property in config file, requires:\n'
+ ' - http_port\n'
+ ' - db_port\n'
+ ' - crypto_key\n'
+ ' - session_secret'));
fs = require('fs');
path = require('path');
/*
Module call
-----------
Calling the module as a function will act as a constructor and load the config file.
It is possible to hand an args object with the properties nolog (true if no outputs shall
be generated) and configPath for a custom configuration file path.
@param {Object} args
*/
exports = module.exports = function(args) {
args = args != null ? args : {};
if (args.nolog) {
_this.nolog = true;
}
} catch (e) {
e.addInfo = 'no config ready';
log.error('CF', e);
}
}
if (args.configPath) {
loadConfigFile(args.configPath);
} else {
loadConfigFile(path.join('config', 'system.json'));
}
return module.exports;
};
/**
* Answer true if the config file is ready, else false
*/
exports.isReady = function() {
if(config) return true;
else return false;
};
/*
Tries to load a configuration file from the path relative to this module's parent folder.
Reads the config file synchronously from the file system and try to parse it.
@private loadConfigFile
@param {String} configPath
*/
/**
* Fetch a property from the configuration
* @param {String} prop
*/
function fetchProp(prop) {
if(config) return config[prop];
}
/**
* Get the HTTP port
*/
exports.getHttpPort = function() {
return fetchProp('http_port');
};
loadConfigFile = function(configPath) {
var confProperties, e, prop, _i, _len;
_this.config = null;
confProperties = ['log', 'http-port', 'db-port'];
try {
_this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', configPath)));
_this.isReady = true;
for (_i = 0, _len = confProperties.length; _i < _len; _i++) {
prop = confProperties[_i];
if (!_this.config[prop]) {
_this.isReady = false;
}
}
if (!_this.isReady && !_this.nolog) {
return console.error("Missing property in config file, requires:\n" + (" - " + (confProperties.join("\n - "))));
}
} catch (_error) {
e = _error;
_this.isReady = false;
if (!_this.nolog) {
return console.error("Failed loading config file: " + e.message);
}
}
};
/**
* Get the DB port
*/
exports.getDBPort = function() {
return fetchProp('db_port');
};
/*
Fetch a property from the configuration
@private fetchProp( *prop* )
@param {String} prop
*/
/**
* Get the crypto key
*/
exports.getCryptoKey = function() {
return fetchProp('crypto_key');
};
/**
* Get the session secret
*/
exports.getSessionSecret = function() {
return fetchProp('session_secret');
};
fetchProp = function(prop) {
var _ref;
return (_ref = _this.config) != null ? _ref[prop] : void 0;
};
/*
***Returns*** true if the config file is ready, else false
@public isReady()
*/
exports.isReady = function() {
return _this.isReady;
};
/*
***Returns*** the HTTP port
@public getHttpPort()
*/
exports.getHttpPort = function() {
return fetchProp('http-port');
};
/*
***Returns*** the DB port*
@public getDBPort()
*/
exports.getDbPort = function() {
return fetchProp('db-port');
};
/*
***Returns*** the log conf object
@public getLogConf()
*/
exports.getLogConf = function() {
return fetchProp('log');
};
/*
***Returns*** the crypto key
@public getCryptoKey()
*/
exports.getKeygenPassphrase = function() {
return fetchProp('keygen-passphrase');
};
}).call(this);

View file

@ -1,322 +0,0 @@
/**
* # DB Interface
* Handles the connection to the database and provides functionalities for
* event/action modules, rules and the encrypted storing of authentication tokens.
*
* ## General
* General functionality as a wrapper for the module holds initialization,
* encryption/decryption, the retrieval of modules and shut down.
* Modules of the same group, e.g. action modules are registered in an unordered
* set in the database, from where they can be retrieved again. For example a new
* action module has its ID (e.g 'probinder') first registered in the set
* 'action_modules' and then stored in the db with the key 'action\_module\_' + ID
* (e.g. action\_module\_probinder).
*/
'use strict';
var redis = require('redis'),
crypto = require('crypto'),
log = require('./logging'),
crypto_key, db;
/**
* Initializes the DB connection. Requires a valid configuration file which contains
* a db port and a crypto key.
*
*/
exports = module.exports = function(args) {
args = args || {};
log(args);
var config = require('./config')(args);
crypto_key = config.getCryptoKey();
db = redis.createClient(config.getDBPort(), 'localhost', { connect_timeout: 2000 });
db.on("error", function (err) {
err.addInfo = 'message from DB';
log.error('DB', err);
});
return module.exports;
};
exports.isConnected = function(cb) {
if(db.connected) cb(null);
else setTimeout(function() {
if(db.connected) {
log.print('DB', 'Successfully connected to DB!');
cb(null);
} else {
var e = new Error('Connection to DB failed!');
log.error('DB', e);
cb(e);
}
}, 3000);
};
/**
* ### encrypt
* this is used to decrypt
* @param {String} plainText
*/
function encrypt(plainText) {
if(!plainText) return null;
try {
var enciph = crypto.createCipher('aes-256-cbc', crypto_key);
var et = enciph.update(plainText, 'utf8', 'base64') + enciph.final('base64');
log.print('DB', 'Encrypted credentials into: ' + et);
return et;
} catch (err) {
log.error('DB', 'in encrypting: ' + err);
return null;
}
}
/**
* ### decrypt
*/
function decrypt(crypticText, id) {
if(!crypticText) return null;
try {
var deciph = crypto.createDecipher('aes-256-cbc', crypto_key);
return deciph.update(crypticText, 'base64', 'utf8') + deciph.final('utf8');
} catch (err) {
log.error('DB', 'in decrypting "' + id + '": ' + err);
return null;
}
}
/**
* ### replyHandler
* Abstraction answer handling for simple information replies from the DB.
* @param {String} action the action to be displayed in the output string.
*/
function replyHandler(action) {
return function(err, reply) {
if(err) log.error('DB', ' during "' + action + '": ' + err);
else log.print('DB', action + ': ' + reply);
};
}
/**
* ### getSetRecords
* The general structure for modules is that the key is stored in a set.
* By fetching all set entries we can then fetch all modules, which is
* automated in this function.
*
* @param {String} set the set name how it is stored in the DB
* @param {function} funcSingle the function that fetches single entries from the DB
* @param {function} cb the function to be called on success or error, receives
* arguments (err, obj)
*/
function getSetRecords(set, funcSingle, cb) {
if(db) db.smembers(set, function(err, reply) {
if(err) log.error('DB', 'fetching ' + set + ': ' + err);
else {
if(reply.length === 0) {
cb(null, null);
} else {
var semaphore = reply.length, objReplies = {};
setTimeout(function() {
if(semaphore > 0) {
cb('Timeout fetching ' + set, null);
}
}, 1000);
for(var i = 0; i < reply.length; i++){
funcSingle(reply[i], function(prop) {
return function(err, reply) {
if(err) log.error('DB', ' fetching single element: ' + prop);
else {
objReplies[prop] = reply;
if(--semaphore === 0) cb(null, objReplies);
}
};
}(reply[i]));
}
}
}
});
}
// @method shutDown()
// Shuts down the db link.
exports.shutDown = function() { if(db) db.quit(); };
// ## Action Modules
/**
* ### storeActionModule
* Store a string representation of an action module in the DB.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeActionModule = function(id, data) {
if(db) {
db.sadd('action_modules', id, replyHandler('storing action module key ' + id));
db.set('action_module_' + id, data, replyHandler('storing action module ' + id));
}
};
/**
* ### getActionModule(id, cb)
* Query the DB for an action module.
* @param {String} id the module id
* @param {function} cb the cb to receive the answer (err, obj)
*/
exports.getActionModule = function(id, cb) {
if(cb && db) db.get('action_module_' + id, cb);
};
/**
* ### getActionModules(cb)
* Fetch all action modules.
* @param {function} cb the cb to receive the answer (err, obj)
*/
exports.getActionModules = function(cb) {
getSetRecords('action_modules', exports.getActionModule, cb);
};
/**
* storeActionModuleAuth(id, data)
* Store a string representation of the authentication parameters for an action module.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeActionModuleAuth = function(id, data) {
if(data && db) {
db.sadd('action_modules_auth', id, replyHandler('storing action module auth key ' + id));
db.set('action_module_' + id +'_auth', encrypt(data), replyHandler('storing action module auth ' + id));
}
};
/**
* ### getActionModuleAuth(id, cb)
* Query the DB for an action module authentication token.
* @param {String} id the module id
* @param {function} cb the cb to receive the answer (err, obj)
*/
exports.getActionModuleAuth = function(id, cb) {
if(cb && db) db.get('action_module_' + id + '_auth', function(id) {
return function(err, txt) { cb(err, decrypt(txt, 'action_module_' + id + '_auth')); };
}(id));
};
// ## Event Modules
/**
* ### storeEventModule(id, data)
* Store a string representation of an event module in the DB.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeEventModule = function(id, data) {
if(db) {
db.sadd('event_modules', id, replyHandler('storing event module key ' + id));
db.set('event_module_' + id, data, replyHandler('storing event module ' + id));
}
};
/**
* ### getEventModule(id, cb)
* Query the DB for an event module.
* @param {String} id the module id
* @param {function} cb the cb to receive the answer (err, obj)
*/
exports.getEventModule = function(id, cb) {
if(cb && db) db.get('event_module_' + id, cb);
};
/**
* ### getEventModules(cb)
* Fetch all event modules.
* @param {function} cb the cb that receives the arguments (err, obj)
*/
exports.getEventModules = function(cb) {
getSetRecords('event_modules', exports.getEventModule, cb);
};
/**
* ### storeEventModuleAuth(id, data)
* Store a string representation of he authentication parameters for an event module.
* @param {String} id the unique identifier of the module
* @param {String} data the string representation
*/
exports.storeEventModuleAuth = function(id, data) {
if(data && db) {
db.sadd('event_modules_auth', id, replyHandler('storing event module auth key ' + id));
db.set('event_module_' + id +'_auth', encrypt(data), replyHandler('storing event module auth ' + id));
}
};
// @method getEventModuleAuth(id, cb)
// Query the DB for an event module authentication token.
// @param {String} id the module id
// @param {function} cb the cb to receive the answer (err, obj)
exports.getEventModuleAuth = function(id, cb) {
if(cb) db.get('event_module_' + id +'_auth', function(id) {
return function(err, txt) { cb(err, decrypt(txt, 'event_module_' + id + '_auth')); };
}(id));
};
// ## Rules
// @method storeRule(id, data)
// Store a string representation of a rule in the DB.
// @param {String} id the unique identifier of the rule
// @param {String} data the string representation
exports.storeRule = function(id, data) {
if(db) {
db.sadd('rules', id, replyHandler('storing rule key ' + id));
db.set('rule_' + id, data, replyHandler('storing rule ' + id));
}
};
// @method getRule(id, cb)
// Query the DB for a rule.
// @param {String} id the rule id
// @param {function} cb the cb to receive the answer (err, obj)
exports.getRule = function(id, cb) {
if(db) db.get('rule_' + id, cb);
};
// @method getRules(cb)
// Fetch all rules from the database.
// @param {function} cb
exports.getRules = function(cb) {
getSetRecords('rules', exports.getRule, cb);
};
/**
*
* @param {Object} objUser
* @param {function} cb
*/
exports.storeUser = function(objUser, cb) {
if(db && objUser && objUser.username && objUser.password) {
db.sadd('users', objUser.username, replyHandler('storing user key ' + objUser.username));
objUser.password = encrypt(objUser.password);
db.set('user:' + objUser.username, objUser, replyHandler('storing user properties ' + objUser.username));
}
};
/**
* Checks the credentials and on success returns the user object.
* @param {Object} objUser
* @param {function} cb
*/
exports.loginUser = function(username, password, cb) {
if(typeof cb !== 'function') return;
if(db) db.get('user:' + username, function(p) {
return function(err, obj) {
if(err) cb(err);
else if(encrypt(obj.password) === p) cb(null, obj);
else cb(new Error('Wrong credentials!'));
};
}(password));
else cb(new Error('No database link available!'));
};

150
js/dynamic-modules.js Normal file
View file

@ -0,0 +1,150 @@
// Generated by CoffeeScript 1.6.3
/*
Dynamic Modules
===============
> Compiles CoffeeScript modules and loads JS modules in a VM, together
> with only a few allowed node.js modules.
*/
(function() {
var cryptico, cs, db, exports, issueApiCall, needle, vm,
_this = this;
db = require('./persistence');
vm = require('vm');
needle = require('needle');
cs = require('coffee-script');
cryptico = require('my-cryptico');
/*
Module call
-----------
Initializes the dynamic module handler.
@param {Object} args
*/
exports = module.exports = function(args) {
var numBits, passPhrase;
_this.log = args.logger;
if (!_this.strPublicKey && args['keygen']) {
db(args);
passPhrase = args['keygen'];
numBits = 1024;
_this.oPrivateRSAkey = cryptico.generateRSAKey(passPhrase, numBits);
_this.strPublicKey = cryptico.publicKeyString(_this.oPrivateRSAkey);
_this.log.info("DM | Public Key generated: " + _this.strPublicKey);
}
return module.exports;
};
exports.getPublicKey = function() {
return _this.strPublicKey;
};
issueApiCall = function(method, url, credentials, cb) {
var err, func;
try {
if (method === 'get') {
func = needle.get;
} else {
func = needle.post;
}
return func(url, credentials, function(err, resp, body) {
if (!err) {
return cb(body);
} else {
return cb();
}
});
} catch (_error) {
err = _error;
return _this.log.info('DM | Error even before calling!');
}
};
/*
Try to run a JS module from a string, together with the
given parameters. If it is written in CoffeeScript we
compile it first into JS.
@public compileString ( *src, id, params, lang* )
@param {String} src
@param {String} id
@param {Object} params
@param {String} lang
*/
exports.compileString = function(src, userId, ruleId, modId, lang, dbMod, cb) {
var answ, err, fTryToLoad, logFunction;
answ = {
code: 200,
message: 'Successfully compiled'
};
if (lang === 'CoffeeScript') {
try {
src = cs.compile(src);
} catch (_error) {
err = _error;
answ.code = 400;
answ.message = 'Compilation of CoffeeScript failed at line ' + err.location.first_line;
}
}
logFunction = function(uId, rId, mId) {
return function(msg) {
return db.appendLog(uId, rId, mId, msg);
};
};
db.resetLog(userId, ruleId);
fTryToLoad = function(params) {
var oDecrypted, sandbox;
if (params) {
try {
oDecrypted = cryptico.decrypt(params, _this.oPrivateRSAkey);
params = JSON.parse(oDecrypted.plaintext);
} catch (_error) {
err = _error;
_this.log.warn("DM | Error during parsing of user defined params for " + userId + ", " + ruleId + ", " + modId);
params = {};
}
} else {
params = {};
}
sandbox = {
id: userId + '.' + modId + '.vm',
params: params,
apicall: issueApiCall,
log: logFunction(userId, ruleId, modId),
exports: {}
};
try {
vm.runInNewContext(src, sandbox, sandbox.id);
} catch (_error) {
err = _error;
console.log(err);
answ.code = 400;
answ.message = 'Loading Module failed: ' + err.message;
}
return cb({
answ: answ,
module: sandbox.exports
});
};
if (dbMod) {
return dbMod.getUserParams(modId, userId, function(err, obj) {
return fTryToLoad(obj);
});
} else {
return fTryToLoad();
}
};
}).call(this);

View file

@ -1,245 +1,288 @@
'use strict';
// Generated by CoffeeScript 1.6.3
/*
var path = require('path'),
regex = /\$X\.[\w\.\[\]]*/g, // find properties of $X
listRules = {},
listActionModules = {},
isRunning = true,
dynmod = require('./dynamic-modules'),
db = require('./persistence'), log;
exports = module.exports = function( args ) {
log = args.logger;
db( args);
dynmod(args);
pollQueue();
return module.exports;
};
var updateActionModules = function() {
for ( var user in listRules ) {
if(!listActionModules[user]) listActionModules[user] = {};
for ( var rule in listRules[user] ) {
var actions = listRules[user][rule].actions;
console.log(actions);
for ( var module in actions ){
for ( var i = 0; i < actions[module]['functions'].length; i++ ){
db.actionInvokers.getModule(module, function( err, objAM ){
db.actionInvokers.getUserParams(module, user, function( err, objParams ) {
console.log (objAM);
//FIXME am name is called 'actions'???
// if(objParams) { //TODO we don't need them for all modules
var answ = dynmod.compileString(objAM.code, objAM.actions + "_" + user, objParams, objAM.lang);
console.log('answ');
console.log(answ);
listActionModules[user][module] = answ.module;
console.log('loaded ' + user + ': ' + module);
console.log(listActionModules);
// }
});
});
}
}
}
}
};
exports.internalEvent = function( evt ) {
try {
// TODO do we need to determine init from newRule?
console.log (evt);
// console.log (data);
// var obj = JSON.parse( data );
// db.getRuleActivatedUsers(obj.id, function ( err, arrUsers ) {
// console.log (arrUsers);
// for(var i = 0; i < arrUsers.length; i++) {
// if( !listRules[arrUsers[i]]) listRules[arrUsers[i]] = {};
// listRules[arrUsers[i]][obj.id] = obj;
// updateActionModules();
// }
// });
} catch( err ) {
console.log( err );
}
console.log('internal event handled');
};
Engine
==================
> The heart of the WebAPI ECA System. The engine loads action invoker modules
> corresponding to active rules actions and invokes them if an appropriate event
> is retrieved.
*/
function pollQueue() {
if(isRunning) {
db.popEvent(function (err, obj) {
if(!err && obj) {
processEvent(obj);
}
setTimeout(pollQueue, 50); //TODO adapt to load
});
}
}
(function() {
var db, dynmod, exports, isRunning, jsonQuery, listUserRules, pollQueue, processEvent, updateActionModules, validConditions,
_this = this;
/**
* Handles correctly posted events
* @param {Object} evt The event object
*/
function processEvent(evt) {
log.info('EN', 'processing event: ' + evt.event + '(' + evt.eventid + ')');
var actions = checkEvent(evt);
console.log('found actions to invoke:');
console.log(actions);
for(var user in actions) {
for(var module in actions[user]) {
for(var i = 0; i < actions[user][module]['functions'].length; i++) {
var act = {
module: module,
function: actions[user][module]['functions'][i]
}
invokeAction(evt, user, act);
}
}
}
}
db = require('./persistence');
/**
FIXME merge with processEvent
dynmod = require('./dynamic-modules');
* Check an event against the rules repository and return the actions
* if the conditons are met.
* @param {Object} evt the event to check
*/
function checkEvent(evt) {
var actions = {}, tEvt;
for(var user in listRules) {
actions[user] = {};
for(var rule in listRules[user]) {
//TODO this needs to get depth safe, not only data but eventually also
// on one level above (eventid and other meta)
tEvt = listRules[user][rule].event;
if(tEvt.module + ' -> ' + tEvt.function === evt.event && validConditions(evt.payload, listRules[user][rule])) {
log.info('EN', 'Rule "' + rule + '" fired');
var oAct = listRules[user][rule].actions;
console.log (oAct);
for(var module in oAct) {
if(!actions[user][module]) {
actions[user][module] = {
functions: []
};
}
for(var i = 0; i < oAct[module]['functions'].length; i++ ){
console.log ('processing action ' + i + ', ' + oAct[module]['functions'][i]);
actions[user][module]['functions'].push(oAct[module]['functions'][i]);
// if(actions[user].indexOf(arrAct[i]) === -1) actions[user].push(arrAct[i]);
}
}
}
}
}
return actions;
}
jsonQuery = require('js-select');
// {
// "event": "emailyak -> newMail",
// "payload": {
// "TextBody": "hello"
// }
// }
// exports.sendMail = ( args ) ->
// url = 'https://api.emailyak.com/v1/ps1g59ndfcwg10w/json/send/email/'
/*
This is ging to have a structure like:
An object of users with their active rules and the required action modules
"user-1":
"rule-1":
"rule": oRule-1
"actions":
"action-1": oAction-1
"action-2": oAction-2
"rule-2":
"rule": oRule-2
"actions":
"action-1": oAction-1
"user-2":
"rule-3":
"rule": oRule-3
"actions":
"action-3": oAction-3
*/
// data =
// FromAddress: 'tester@mscliveweb.simpleyak.com'
// ToAddress: 'dominic.bosch.db@gmail.com'
// TextBody: 'test'
// needle.post url, JSON.stringify( data ), {json: true}, ( err, resp, body ) ->
// log err
// log body
/**
* Checks whether all conditions of the rule are met by the event.
* @param {Object} evt the event to check
* @param {Object} rule the rule with its conditions
*/
function validConditions(evt, rule) {
for(var property in rule.conditions){
if(!evt[property] || evt[property] != rule.condition[property]) return false;
}
return true;
}
listUserRules = {};
/**
* Invoke an action according to its type.
* @param {Object} evt The event that invoked the action
* @param {Object} action The action to be invoked
*/
function invokeAction( evt, user, action ) {
console.log('invoking action');
var actionargs = {};
//FIXME internal events, such as loopback ha sno arrow
//TODO this requires change. the module property will be the identifier
// in the actions object (or shall we allow several times the same action?)
console.log(action.module);
console.log(listActionModules);
var srvc = listActionModules[user][action.module];
console.log(srvc);
if(srvc && srvc[action.function]) {
//FIXME preprocessing not only on data
//FIXME no preprocessing at all, why don't we just pass the whole event to the action?'
// preprocessActionArguments(evt.payload, action.arguments, actionargs);
try {
if(srvc[action.function]) srvc[action.function](evt.payload);
} catch(err) {
log.error('EN', 'during action execution: ' + err);
}
}
else log.info('EN', 'No api interface found for: ' + action.module);
}
// /**
// * Action properties may contain event properties which need to be resolved beforehand.
// * @param {Object} evt The event whose property values can be used in the rules action
// * @param {Object} act The rules action arguments
// * @param {Object} res The object to be used to enter the new properties
// */
// function preprocessActionArguments(evt, act, res) {
// for(var prop in act) {
// /*
// * If the property is an object itself we go into recursion
// */
// if(typeof act[prop] === 'object') {
// res[prop] = {};
// preprocessActionArguments(evt, act[prop], res[prop]);
// }
// else {
// var txt = act[prop];
// var arr = txt.match(regex);
// * If rules action property holds event properties we resolve them and
// * replace the original action property
// // console.log(evt);
// if(arr) {
// for(var i = 0; i < arr.length; i++) {
// /*
// * The first three characters are '$X.', followed by the property
// */
// var actionProp = arr[i].substring(3).toLowerCase();
// // console.log(actionProp);
// for(var eprop in evt) {
// // our rules language doesn't care about upper or lower case
// if(eprop.toLowerCase() === actionProp) {
// txt = txt.replace(arr[i], evt[eprop]);
// }
// }
// txt = txt.replace(arr[i], '[property not available]');
// }
// }
// res[prop] = txt;
// }
// }
// }
exports.shutDown = function() {
if(log) log.info('EN', 'Shutting down Poller and DB Link');
isRunning = false;
if(db) db.shutDown();
};
/*
Module call
-----------
Initializes the Engine and starts polling the event queue for new events.
@param {Object} args
*/
exports = module.exports = function(args) {
if (!isRunning) {
isRunning = true;
_this.log = args.logger;
db(args);
dynmod(args);
setTimeout(pollQueue, 10);
return module.exports;
}
};
/*
This is a helper function for the unit tests so we can verify that action
modules are loaded correctly
#TODO we should change this to functions returning true or false rather than returning
#the whole list
@public getListUserRules ()
*/
exports.getListUserRules = function() {
return listUserRules;
};
/*
An event associated to rules happened and is captured here. Such events
are basically CRUD on rules.
@public internalEvent ( *evt* )
@param {Object} evt
*/
exports.internalEvent = function(evt) {
var oRule, oUser;
if (!listUserRules[evt.user] && evt.event !== 'del') {
listUserRules[evt.user] = {};
}
oUser = listUserRules[evt.user];
oRule = evt.rule;
if (evt.event === 'new' || (evt.event === 'init' && !oUser[oRule.id])) {
oUser[oRule.id] = {
rule: oRule,
actions: {}
};
updateActionModules(oRule.id);
}
if (evt.event === 'del' && oUser) {
delete oUser[evt.ruleId];
}
if (JSON.stringify(oUser) === "{}") {
return delete listUserRules[evt.user];
}
};
/*
As soon as changes were made to the rule set we need to ensure that the aprropriate action
invoker modules are loaded, updated or deleted.
@private updateActionModules ( *updatedRuleId* )
@param {Object} updatedRuleId
*/
updateActionModules = function(updatedRuleId) {
var fAddRequired, fRemoveNotRequired, name, oUser, userName, _results;
fRemoveNotRequired = function(oUser) {
var action, fRequired, _results;
fRequired = function(actionName) {
var action, _i, _len, _ref;
_ref = oUser[updatedRuleId].rule.actions;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
if ((action.split(' -> '))[0] === actionName) {
return true;
}
}
return false;
};
_results = [];
for (action in oUser[updatedRuleId].rule.actions) {
if (!fRequired(action)) {
_results.push(delete oUser[updatedRuleId].actions[action]);
} else {
_results.push(void 0);
}
}
return _results;
};
for (name in listUserRules) {
oUser = listUserRules[name];
fRemoveNotRequired(oUser);
}
fAddRequired = function(userName, oUser) {
var fCheckRules, nmRl, oRl, _results;
fCheckRules = function(oMyRule) {
var action, fAddIfNewOrNotExisting, _i, _len, _ref, _results;
fAddIfNewOrNotExisting = function(actionName) {
var moduleName;
moduleName = (actionName.split(' -> '))[0];
if (!oMyRule.actions[moduleName] || oMyRule.rule.id === updatedRuleId) {
return db.actionInvokers.getModule(moduleName, function(err, obj) {
return dynmod.compileString(obj.data, userName, oMyRule.rule.id, moduleName, obj.lang, db.actionInvokers, function(result) {
if (!result.answ === 200) {
this.log.error("EN | Compilation of code failed! " + userName + ", " + oMyRule.rule.id + ", " + moduleName);
}
return oMyRule.actions[moduleName] = result.module;
});
});
}
};
_ref = oMyRule.rule.actions;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
_results.push(fAddIfNewOrNotExisting(action));
}
return _results;
};
_results = [];
for (nmRl in oUser) {
oRl = oUser[nmRl];
_results.push(fCheckRules(oRl));
}
return _results;
};
_results = [];
for (userName in listUserRules) {
oUser = listUserRules[userName];
_results.push(fAddRequired(userName, oUser));
}
return _results;
};
pollQueue = function() {
if (isRunning) {
return db.popEvent(function(err, obj) {
if (!err && obj) {
processEvent(obj);
}
return setTimeout(pollQueue, 50);
});
}
};
/*
Checks whether all conditions of the rule are met by the event.
@private validConditions ( *evt, rule* )
@param {Object} evt
@param {Object} rule
*/
validConditions = function(evt, rule) {
var prop, _i, _len, _ref;
if (rule.conditions.length === 0) {
return true;
}
_ref = rule.conditions;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
prop = _ref[_i];
if (jsonQuery(evt, prop).nodes().length === 0) {
return false;
}
}
return true;
};
/*
Handles retrieved events.
@private processEvent ( *evt* )
@param {Object} evt
*/
processEvent = function(evt) {
var action, arr, fSearchAndInvokeAction, oMyRule, oUser, ruleName, userName, _results;
fSearchAndInvokeAction = function(node, arrPath, evt, depth) {
var err;
if (!node) {
this.log.error("EN | Didn't find property in user rule list: " + arrPath.join(', ' + " at depth " + depth));
return;
}
if (depth === arrPath.length) {
try {
return node(evt.payload);
} catch (_error) {
err = _error;
return this.log.info("EN | ERROR IN ACTION INVOKER: " + err.message);
}
} else {
return fSearchAndInvokeAction(node[arrPath[depth]], arrPath, evt, depth + 1);
}
};
_this.log.info('EN | processing event: ' + evt.event + '(' + evt.eventid + ')');
_results = [];
for (userName in listUserRules) {
oUser = listUserRules[userName];
_results.push((function() {
var _results1;
_results1 = [];
for (ruleName in oUser) {
oMyRule = oUser[ruleName];
if (evt.event === oMyRule.rule.event && validConditions(evt, oMyRule.rule)) {
this.log.info('EN | EVENT FIRED: ' + evt.event + '(' + evt.eventid + ') for rule ' + ruleName);
_results1.push((function() {
var _i, _len, _ref, _results2;
_ref = oMyRule.rule.actions;
_results2 = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
action = _ref[_i];
arr = action.split(' -> ');
_results2.push(fSearchAndInvokeAction(listUserRules, [userName, ruleName, 'actions', arr[0], arr[1]], evt, 0));
}
return _results2;
})());
} else {
_results1.push(void 0);
}
}
return _results1;
}).call(_this));
}
return _results;
};
exports.shutDown = function() {
return isRunning = false;
};
}).call(this);

View file

@ -1,12 +1,12 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
Dynamic Modules
===============
> Compiles CoffeeScript modules and loads JS modules in a VM, together
> with only a few allowed node.js modules.
*/
*/
(function() {
var db, dynmod, fLoadModule, isRunning, listUserModules, log, logconf, logger, pollLoop;
@ -77,7 +77,7 @@ Dynamic Modules
} else {
return dynmod.compileString(obj.data, msg.user, msg.rule.id, arrName[0], obj.lang, db.eventPollers, function(result) {
if (!result.answ === 200) {
log.error("EP | Compilation of code failed! " + msg.user + ", " + msg.rule.id + ", " + arrName[0]);
log.error("EP | Compilation of code failed! " + msg.user + ", " + msg.rule.id + ", " + arrName[0]);
}
if (!listUserModules[msg.user]) {
listUserModules[msg.user] = {};
@ -87,7 +87,7 @@ Dynamic Modules
pollfunc: arrName[1],
module: result.module
};
return log.info("EP | New event module loaded! " + msg.user + ", " + msg.rule.id + ", " + arrName[0]);
return log.info("EP | New event module loaded! " + msg.user + ", " + msg.rule.id + ", " + arrName[0]);
});
}
});
@ -97,12 +97,12 @@ Dynamic Modules
}
};
/*
This function will loop infinitely every 10 seconds until isRunning is set to false
@private pollLoop()
*/
*/
pollLoop = function() {
var err, fPoll, fRegisterModuleReference, myRule, oRules, ruleName, userName;

View file

95
js/http-listener.js Normal file
View file

@ -0,0 +1,95 @@
// Generated by CoffeeScript 1.6.3
/*
HTTP Listener
=============
> Receives the HTTP requests to the server at the given port. The requests
> (bound to a method) are then redirected to the appropriate handler which
> takes care of the request.
*/
(function() {
var app, exports, express, initRouting, path, qs, requestHandler,
_this = this;
requestHandler = require('./request-handler');
path = require('path');
qs = require('querystring');
express = require('express');
app = express();
/*
Module call
-----------
Initializes the HTTP listener and its request handler.
@param {Object} args
*/
exports = module.exports = function(args) {
_this.log = args.logger;
_this.shutDownSystem = args['shutdown-function'];
requestHandler(args);
initRouting(args['http-port']);
return module.exports;
};
/*
Initializes the request routing and starts listening on the given port.
@param {int} port
@private initRouting( *fShutDown* )
*/
initRouting = function(port) {
var server, sess_sec;
app.use(express.cookieParser());
sess_sec = "149u*y8C:@kmN/520Gt\\v'+KFBnQ!\\r<>5X/xRI`sT<Iw";
app.use(express.session({
secret: sess_sec
}));
_this.log.info('HL | no session backbone');
app.use('/', express["static"](path.resolve(__dirname, '..', 'webpages', 'public')));
app.get('/admin', requestHandler.handleAdmin);
app.get('/forge', requestHandler.handleForge);
app.post('/event', requestHandler.handleEvent);
app.post('/login', requestHandler.handleLogin);
app.post('/logout', requestHandler.handleLogout);
app.post('/usercommand', requestHandler.handleUserCommand);
app.post('/admincommand', requestHandler.handleAdminCommand);
server = app.listen(parseInt(port) || 8111);
server.on('listening', function() {
var addr;
addr = server.address();
if (addr.port !== port) {
return _this.shutDownSystem();
}
});
return server.on('error', function(err) {
/*
Error handling of the express port listener requires special attention,
thus we have to catch the error, which is issued if the port is already in use.
*/
switch (err.errno) {
case 'EADDRINUSE':
_this.log.error(err, 'HL | http-port already in use, shutting down!');
break;
case 'EACCES':
_this.log.error(err, 'HL | http-port not accessible, shutting down!');
break;
default:
_this.log.error(err, 'HL | Error in server, shutting down!');
}
return _this.shutDownSystem();
});
};
}).call(this);

View file

@ -1,116 +0,0 @@
// HTTP Listener
// =============
//
// Handles the HTTP requests to the server at the port specified by the [config](config.html) file.
'use strict';
var path = require('path'),
express = require('express'),
app = express(),
RedisStore = require('connect-redis')(express),
qs = require('querystring'),
log = require('./logging'),
sess_sec = '#C[>;j`@".TXm2TA;A2Tg)',
db_port, http_port, server,
eventHandler, userHandler;
/*
* The module needs to be called as a function to initialize it.
* After that it fetches the http\_port, db\_port & sess\_sec properties
* from the configuration file.
*/
exports = module.exports = function(args) {
args = args || {};
log(args);
var config = require('./config')(args);
userHandler = require('./user_handler')(args);
db_port = config.getDBPort(),
sess_sec = config.getSessionSecret(),
http_port = config.getHttpPort();
return module.exports;
};
exports.addHandlers = function(funcAdminHandler, funcEvtHandler) {
if(!funcAdminHandler || !funcEvtHandler) {
log.error('HL', 'ERROR: either adminHandler or eventHandler function not defined!');
return;
}
userHandler.addHandler(funcAdminHandler);
eventHandler = funcEvtHandler;
// Add cookie support for session handling.
app.use(express.cookieParser());
app.use(express.session({secret: sess_sec}));
log.print('HL', 'no session backbone');
// ^ TODO figure out why redis backbone doesn't work. eventually the db pass has to be set in the DB?
// } session information seems to be stored in DB but not retrieved correctly
// } if(db_port) {
// } app.use(express.session({
// } store: new RedisStore({
// } host: 'localhost',
// } port: db_port,
// } db: 2
// } ,
// } pass: null
// } }),
// } secret: sess_sec
// } }));
// } log.print('HL', 'Added redis DB as session backbone');
// } } else {
// } app.use(express.session({secret: sess_sec}));
// } log.print('HL', 'no session backbone');
// } }
// Redirect the requests to the appropriate handler.
app.use('/', express.static(path.resolve(__dirname, '..', 'webpages')));
// app.use('/doc/', express.static(path.resolve(__dirname, '..', 'webpages', 'doc')));
// app.get('/mobile', userHandler.handleRequest);
app.get('/rulesforge', userHandler.handleRequest);
// app.use('/mobile', express.static(path.resolve(__dirname, '..', 'webpages', 'mobile')));
// } app.use('/rulesforge/', express.static(path.resolve(__dirname, '..', 'webpages', 'rulesforge')));
app.get('/admin', userHandler.handleRequest);
app.post('/login', userHandler.handleLogin);
app.post('/push_event', onPushEvent);
try {
if(http_port) server = app.listen(http_port); // inbound event channel
else log.error('HL', new Error('No HTTP port found!? Nothing to listen on!...'));
} catch(e) {
e.addInfo = 'port unavailable';
log.error(e);
funcAdminHandler({cmd: 'shutdown'});
}
};
/**
* If a post request reaches the server, this function handles it and treats the request as a possible event.
*/
function onPushEvent(req, resp) {
var body = '';
req.on('data', function (data) { body += data; });
req.on('end', function () {
var obj = qs.parse(body);
/* If required event properties are present we process the event */
if(obj && obj.event && obj.eventid){
resp.writeHead(200, { "Content-Type": "text/plain" });
resp.write('Thank you for the event (' + obj.event + '[' + obj.eventid + '])!');
eventHandler(obj);
} else {
resp.writeHead(400, { "Content-Type": "text/plain" });
resp.write('Your event was missing important parameters!');
}
resp.end();
});
}
exports.loadUsers = function() {
var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', relPath)));
for(var name in users) {
}
};
exports.shutDown = function() {
log.print('HL', 'Shutting down HTTP listener');
process.exit(); // This is a bit brute force...
};

View file

@ -1,107 +1,72 @@
/*
* Logging
* =======
* Functions to handle logging and errors.
*
* Valid log types are:
*
* - 0 standard I/O
* - 1 file
* - 2 silent
*/
//TODO dynamic log file names (especially to track unit test logs)
var fs = require('fs'),
wst = require('winston'),
logTypes = [ flushToConsole, flushToFile, null],
logFile = require('path').resolve(__dirname, '..', 'server.log'),
logType = 0;
// Generated by CoffeeScript 1.6.3
(function() {
var bunyan, fs, path,
_this = this;
exports = module.exports = function(args) {
args = args || {};
if(args.logType) logType = parseInt(args.logType) || 0;
if(logType == 1) fs.truncateSync(logFile, 0);
if(logType > logTypes.length - 1) logType = 0;
// winston.add(winston.transports.File, { filename: 'somefile.log' });
// winston.remove(winston.transports.Console);
return module.exports;
};
fs = require('fs');
exports.getLogType = function() { return logType; };
path = require('path');
function flush(err, msg) {
if(typeof logTypes[logType] === 'function') logTypes[logType](err, msg);
}
bunyan = require('bunyan');
function flushToConsole(err, msg) {
if(err) console.error("\033[31m" + msg + "\033[0m");
else console.log(msg);
// if(err) console.error(msg);
// else console.log(msg);
}
/*
Returns a bunyan logger according to the given arguments.
@public getLogger( *args* )
@param {Object} args
*/
function flushToFile(err, msg) {
fs.appendFile(logFile, msg + '\n', function (err) {});
}
// @function print(module, msg)
exports.getLogger = function(args) {
var e, emptylog, opt;
emptylog = {
trace: function() {},
debug: function() {},
info: function() {},
warn: function() {},
error: function() {},
fatal: function() {}
};
args = args != null ? args : {};
if (args.nolog) {
return emptylog;
} else {
try {
opt = {
name: "webapi-eca"
};
if (args['mode'] === 'development') {
opt.src = true;
}
if (args['file-path']) {
_this.logPath = path.resolve(args['file-path']);
} else {
_this.logPath = path.resolve(__dirname, '..', 'logs', 'server.log');
}
try {
fs.writeFileSync(_this.logPath + '.temp', 'temp');
fs.unlinkSync(_this.logPath + '.temp');
} catch (_error) {
e = _error;
console.error("Log folder '" + _this.logPath + "' is not writable");
return emptylog;
}
opt.streams = [
{
level: args['io-level'],
stream: process.stdout
}, {
level: args['file-level'],
path: _this.logPath
}
];
return bunyan.createLogger(opt);
} catch (_error) {
e = _error;
console.error(e);
return emptylog;
}
}
};
/*
* Prints a log to stdout.
* @param {String} module
* @param {String} msg
*/
exports.print = function(module, msg) {
flush(false, (new Date()).toISOString() + ' | ' + module + ' | ' + msg);
};
/**
* Prints a log to stderr.
* @param {String} module
* @param {Error} err
*/
function printError(module, err, isSevere) {
var ts = (new Date()).toISOString() + ' | ', ai = '';
if(!err) {
err = new Error('Unexpected error');
isSevere = true;
}
if(typeof err === 'string') err = new Error(err);
// if(module) flush(true, ts + module + ' | ERROR AND BAD HANDLING: ' + err + '\n' + e.stack);
// else flush(true, ts + '!N/A! | ERROR, BAD HANDLING AND NO MODULE NAME: ' + err + '\n' + e.stack);
// } else if(err) {
if(err.addInfo) ai = ' (' + err.addInfo + ')';
if(!err.message) err.message = 'UNKNOWN REASON!\n' + err.stack;
if(module) {
var msg = ts + module + ' | ERROR'+ai+': ' + err.message;
if(isSevere) msg += '\n' + err.stack;
flush(true, msg);
} else flush(true, ts + '!N/A! | ERROR AND NO MODULE NAME'+ai+': ' + err.message + '\n' + err.stack);
// } else {
// var e = new Error('Unexpected error');
// flush(true, e.message + ': \n' + e.stack);
// }
};
/**
* Prints a message to stderr.
* @param {String} module
* @param {Error} err
*/
exports.error = function(module, err) {
printError(module, err, false);
};
/**
* Prints a message with error stack to stderr
* @param {String} module
* @param {Error} err
*/
exports.severe = function(module, err) {
printError(module, err, true);
};
exports.obj = function (varname, obj) {
var arrS = (new Error).stack.split('\n');
console.log('Dumping object "' + varname + '"' + arrS[2]);
console.log(obj);
};
}).call(this);

View file

@ -1,86 +0,0 @@
'use strict';
var cs = require('coffee-script');
var fs = require('fs'),
path = require('path'),
log = require('./logging');
exports = module.exports = function(args) {
args = args || {};
log(args);
return module.exports;
};
exports.requireFromString = function(src, name, dir) {
if(!dir) dir = __dirname;
// Allow coffee scripts!
console.log(cs.compile(src));
var id = path.resolve(dir, name, name + '.vm');
var vm = require('vm'),
sandbox = {
log: log,
needle: require('needle')
};
var m = vm.runInNewContext(src, sandbox, id + '.vm');
console.log('module loader');
console.log(m);
// var m = new module.constructor(id, module);
// m.paths = module.paths;
// try {
// m._compile(src);
// } catch(err) {
// err.addInfo = 'during compilation of module ' + name;
// log.error('LM', err);
// // log.error('LM', ' during compilation of ' + name + ': ' + err);
// }
return m.exports;
};
exports.loadModule = function(directory, name, callback) {
try {
fs.readFile(path.resolve(__dirname, '..', directory, name, name + '.js'), 'utf8', function (err, data) {
if (err) {
log.error('LM', 'Loading module file!');
return;
}
var mod = exports.requireFromString(data, name, directory);
if(mod && fs.existsSync(path.resolve(__dirname, '..', directory, name, 'credentials.json'))) {
fs.readFile(path.resolve(__dirname, '..', directory, name, 'credentials.json'), 'utf8', function (err, auth) {
if (err) {
log.error('LM', 'Loading credentials file for "' + name + '"!');
callback(name, data, mod, null);
return;
}
if(mod.loadCredentials) mod.loadCredentials(JSON.parse(auth));
callback(name, data, mod, auth);
});
} else {
// Hand back the name, the string contents and the compiled module
callback(name, data, mod, null);
}
});
} catch(err) {
log.error('LM', 'Failed loading module "' + name + '"');
}
};
exports.loadModules = function(directory, callback) {
fs.readdir(path.resolve(__dirname, '..', directory), function (err, list) {
if (err) {
log.error('LM', 'loading modules directory: ' + err);
return;
}
log.print('LM', 'Loading ' + list.length + ' modules from "' + directory + '"');
list.forEach(function (file) {
fs.stat(path.resolve(__dirname, '..', directory, file), function (err, stat) {
if (stat && stat.isDirectory()) {
exports.loadModule(directory, file, callback);
}
});
});
});
};

View file

@ -1,138 +0,0 @@
/*
# Module Manager
> The module manager takes care of the module and rules loading in the initialization
> phase and on user request.
> Event and Action modules are loaded as strings and stored in the database,
> then compiled into node modules and rules
*/
'use strict';
var fs = require('fs'),
path = require('path'),
log = require('./logging'),
ml, db, funcLoadAction, funcLoadRule;
exports = module.exports = function(args) {
args = args || {};
log(args);
ml = require('./module_loader')(args);
return module.exports;
};
exports.addDBLink = function(db_link) {
db = db_link;
};
exports.storeEventModule = function (user, obj, answHandler) {
log.print('MM', 'implement storeEventModule');
answHandler.answerSuccess('Thank you for the event!');
};
exports.storeActionModule = function (user, obj, answHandler) {
log.print('MM', 'implement storeActionModule');
answHandler.answerSuccess('Thank you for the action!');
};
exports.storeRule = function (user, obj, answHandler) {
log.print('MM', 'implement storeRule');
answHandler.answerSuccess('Thank you for the rule!');
};
/*
* Legacy file system loaders
*/
/*
* Load Rules from fs
* ------------------
*/
exports.loadRulesFromFS = function(args, answHandler) {
if(!args) args = {};
if(!args.name) args.name = 'rules';
if(!funcLoadRule) log.error('ML', 'no rule loader function available');
else {
fs.readFile(path.resolve(__dirname, '..', 'rules', args.name + '.json'), 'utf8', function (err, data) {
if (err) {
log.error('ML', 'Loading rules file: ' + args.name + '.json');
return;
}
try {
var arr = JSON.parse(data), txt = '';
log.print('ML', 'Loading ' + arr.length + ' rules:');
for(var i = 0; i < arr.length; i++) {
txt += arr[i].id + ', ';
db.storeRule(arr[i].id, JSON.stringify(arr[i]));
// funcLoadRule(arr[i]);
}
answHandler.answerSuccess('Yep, loaded rules: ' + txt);
} catch (e) {
log.error('ML', 'rules file was corrupt! (' + args.name + '.json)');
}
});
}
};
/*
* Load Action Modules from fs
* ---------------------------
*/
/**
*
* @param {Object} name
* @param {Object} data
* @param {Object} mod
* @param {String} [auth] The string representation of the auth json
*/
function loadActionCallback(name, data, mod, auth) {
db.storeActionModule(name, data); // store module in db
// funcLoadAction(name, mod); // hand back compiled module
if(auth) db.storeActionModuleAuth(name, auth);
}
exports.loadActionModuleFromFS = function (args, answHandler) {
if(ml) {
if(args && args.name) {
answHandler.answerSuccess('Loading action module ' + args.name + '...');
ml.loadModule('mod_actions', args.name, loadActionCallback);
} else log.error('MM', 'Action Module name not provided!');
}
};
exports.loadActionModulesFromFS = function(args, answHandler) {
if(ml) {
answHandler.answerSuccess('Loading action modules...');
ml.loadModules('mod_actions', loadActionCallback);
}
};
/*
* Load Event Modules from fs
* --------------------------
*/
function loadEventCallback(name, data, mod, auth) {
if(db) {
db.storeEventModule(name, data); // store module in db
if(auth) db.storeEventModuleAuth(name, auth);
}
}
exports.loadEventModuleFromFS = function(args, answHandler) {
if(ml) {
if(args && args.name) {
answHandler.answerSuccess('Loading event module ' + args.name + '...');
ml.loadModule('mod_events', args.name, loadEventCallback);
} else log.error('MM', 'Event Module name not provided!');
}
};
exports.loadEventModulesFromFS = function(args, answHandler) {
answHandler.answerSuccess('Loading event moules...');
ml.loadModules('mod_actions', loadEventCallback);
};

894
js/persistence.js Normal file
View file

@ -0,0 +1,894 @@
// Generated by CoffeeScript 1.6.3
/*
Persistence
============
> Handles the connection to the database and provides functionalities for event pollers,
> action invokers, rules and the (hopefully encrypted) storing of user-specific parameters
> per module.
> General functionality as a wrapper for the module holds initialization,
> the retrieval of modules and shut down.
>
> The general structure for linked data is that the key is stored in a set.
> By fetching all set entries we can then fetch all elements, which is
> automated in this function.
> For example, modules of the same group, e.g. action invokers are registered in an
> unordered set in the database, from where they can be retrieved again. For example
> a new action invoker has its ID (e.g 'probinder') first registered in the set
> 'action-invokers' and then stored in the db with the key 'action-invoker:' + ID
> (e.g. action-invoker:probinder).
>
*/
(function() {
var IndexedModules, exports, getSetRecords, redis, replyHandler,
_this = this,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
redis = require('redis');
/*
Module call
-----------
Initializes the DB connection with the given `db-port` property in the `args` object.
@param {Object} args
*/
exports = module.exports = function(args) {
if (!_this.db) {
if (!args['db-port']) {
args['db-port'] = 6379;
}
_this.log = args.logger;
exports.eventPollers = new IndexedModules('event-poller', _this.log);
exports.actionInvokers = new IndexedModules('action-invoker', _this.log);
return exports.initPort(args['db-port']);
}
};
exports.getLogger = function() {
return _this.log;
};
exports.initPort = function(port) {
var _ref;
_this.connRefused = false;
if ((_ref = _this.db) != null) {
_ref.quit();
}
_this.db = redis.createClient(port, 'localhost', {
connect_timeout: 2000
});
_this.db.on('error', function(err) {
if (err.message.indexOf('ECONNREFUSED') > -1) {
_this.connRefused = true;
return _this.log.error(err, 'DB | Wrong port?');
}
});
exports.eventPollers.setDB(_this.db);
return exports.actionInvokers.setDB(_this.db);
};
/*
Checks whether the db is connected and passes either an error on failure after
ten attempts within five seconds, or nothing on success to the callback(err).
@public isConnected( *cb* )
@param {function} cb
*/
exports.isConnected = function(cb) {
var fCheckConnection, numAttempts;
if (!_this.db) {
return cb(new Error('DB | DB initialization did not occur or failed miserably!'));
} else {
if (_this.db.connected) {
return cb();
} else {
numAttempts = 0;
fCheckConnection = function() {
var _ref;
if (_this.connRefused) {
if ((_ref = _this.db) != null) {
_ref.quit();
}
return cb(new Error('DB | Connection refused! Wrong port?'));
} else {
if (_this.db.connected) {
_this.log.info('DB | Successfully connected to DB!');
return cb();
} else if (numAttempts++ < 10) {
return setTimeout(fCheckConnection, 100);
} else {
return cb(new Error('DB | Connection to DB failed!'));
}
}
};
return setTimeout(fCheckConnection, 100);
}
}
};
/*
Abstracts logging for simple action replies from the DB.
@private replyHandler( *action* )
@param {String} action
*/
replyHandler = function(action) {
return function(err, reply) {
if (err) {
return _this.log.warn(err, "during '" + action + "'");
} else {
return _this.log.info("DB | " + action + ": " + reply);
}
};
};
/*
Push an event into the event queue.
@public pushEvent( *oEvent* )
@param {Object} oEvent
*/
exports.pushEvent = function(oEvent) {
if (oEvent) {
_this.log.info("DB | Event pushed into the queue: '" + oEvent.eventid + "'");
return _this.db.rpush('event_queue', JSON.stringify(oEvent));
} else {
return _this.log.warn('DB | Why would you give me an empty event...');
}
};
/*
Pop an event from the event queue and pass it to cb(err, obj).
@public popEvent( *cb* )
@param {function} cb
*/
exports.popEvent = function(cb) {
var makeObj;
makeObj = function(pcb) {
return function(err, obj) {
return pcb(err, JSON.parse(obj));
};
};
return _this.db.lpop('event_queue', makeObj(cb));
};
/*
Purge the event queue.
@public purgeEventQueue()
*/
exports.purgeEventQueue = function() {
return _this.db.del('event_queue', replyHandler('purging event queue'));
};
/*
Fetches all linked data set keys from a linking set, fetches the single
data objects via the provided function and returns the results to cb(err, obj).
@private getSetRecords( *set, fSingle, cb* )
@param {String} set the set name how it is stored in the DB
@param {function} fSingle a function to retrieve a single data element
per set entry
@param {function} cb the callback(err, obj) function that receives all
the retrieved data or an error
*/
getSetRecords = function(set, fSingle, cb) {
_this.log.info("DB | Fetching set records: '" + set + "'");
return _this.db.smembers(set, function(err, arrReply) {
var fCallback, objReplies, reply, semaphore, _i, _len, _results;
if (err) {
_this.log.warn(err, "DB | fetching '" + set + "'");
return cb(err);
} else if (arrReply.length === 0) {
return cb();
} else {
semaphore = arrReply.length;
objReplies = {};
setTimeout(function() {
if (semaphore > 0) {
return cb(new Error("Timeout fetching '" + set + "'"));
}
}, 2000);
fCallback = function(prop) {
return function(err, data) {
--semaphore;
if (err) {
_this.log.warn(err, "DB | fetching single element: '" + prop + "'");
} else if (!data) {
_this.log.warn(new Error("Empty key in DB: '" + prop + "'"));
} else {
objReplies[prop] = data;
}
if (semaphore === 0) {
return cb(null, objReplies);
}
};
};
_results = [];
for (_i = 0, _len = arrReply.length; _i < _len; _i++) {
reply = arrReply[_i];
_results.push(fSingle(reply, fCallback(reply)));
}
return _results;
}
});
};
IndexedModules = (function() {
function IndexedModules(setname, log) {
this.setname = setname;
this.log = log;
this.deleteUserParams = __bind(this.deleteUserParams, this);
this.getUserParamsIds = __bind(this.getUserParamsIds, this);
this.getUserParams = __bind(this.getUserParams, this);
this.storeUserParams = __bind(this.storeUserParams, this);
this.deleteModule = __bind(this.deleteModule, this);
this.getModules = __bind(this.getModules, this);
this.getModuleIds = __bind(this.getModuleIds, this);
this.getAvailableModuleIds = __bind(this.getAvailableModuleIds, this);
this.getModuleParams = __bind(this.getModuleParams, this);
this.getModule = __bind(this.getModule, this);
this.unpublish = __bind(this.unpublish, this);
this.publish = __bind(this.publish, this);
this.unlinkModule = __bind(this.unlinkModule, this);
this.linkModule = __bind(this.linkModule, this);
this.storeModule = __bind(this.storeModule, this);
this.log.info("DB | (IdxedMods) Instantiated indexed modules for '" + this.setname + "'");
}
IndexedModules.prototype.setDB = function(db) {
this.db = db;
return this.log.info("DB | (IdxedMods) Registered new DB connection for '" + this.setname + "'");
};
/*
Stores a module and links it to the user.
@private storeModule( *userId, oModule* )
@param {String} userId
@param {object} oModule
*/
IndexedModules.prototype.storeModule = function(userId, oModule) {
this.log.info("DB | (IdxedMods) " + this.setname + ".storeModule( " + userId + ", oModule )");
this.db.sadd("" + this.setname + "s", oModule.id, replyHandler("sadd '" + oModule.id + "' to '" + this.setname + "'"));
this.db.hmset("" + this.setname + ":" + oModule.id, oModule, replyHandler("hmset properties in hash '" + this.setname + ":" + oModule.id + "'"));
return this.linkModule(oModule.id, userId);
};
IndexedModules.prototype.linkModule = function(mId, userId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".linkModule( " + mId + ", " + userId + " )");
this.db.sadd("" + this.setname + ":" + mId + ":users", userId, replyHandler("sadd " + userId + " to '" + this.setname + ":" + mId + ":users'"));
return this.db.sadd("user:" + userId + ":" + this.setname + "s", mId, replyHandler("sadd " + mId + " to 'user:" + userId + ":" + this.setname + "s'"));
};
IndexedModules.prototype.unlinkModule = function(mId, userId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".unlinkModule( " + mId + ", " + userId + " )");
this.db.srem("" + this.setname + ":" + mId + ":users", userId, replyHandler("srem " + userId + " from '" + this.setname + ":" + mId + ":users'"));
return this.db.srem("user:" + userId + ":" + this.setname + "s", mId, replyHandler("srem " + mId + " from 'user:" + userId + ":" + this.setname + "s'"));
};
IndexedModules.prototype.publish = function(mId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".publish( " + mId + " )");
return this.db.sadd("public-" + this.setname + "s", mId, replyHandler("sadd '" + mId + "' to 'public-" + this.setname + "s'"));
};
IndexedModules.prototype.unpublish = function(mId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".unpublish( " + mId + " )");
return this.db.srem("public-" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from 'public-" + this.setname + "s'"));
};
IndexedModules.prototype.getModule = function(mId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModule( " + mId + " )");
return this.db.hgetall("" + this.setname + ":" + mId, cb);
};
IndexedModules.prototype.getModuleParams = function(mId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModuleParams( " + mId + " )");
return this.db.hget("" + this.setname + ":" + mId, "params", cb);
};
IndexedModules.prototype.getAvailableModuleIds = function(userId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getPublicModuleIds( " + userId + " )");
return this.db.sunion("public-" + this.setname + "s", "user:" + userId + ":" + this.setname + "s", cb);
};
IndexedModules.prototype.getModuleIds = function(cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModuleIds()");
return this.db.smembers("" + this.setname + "s", cb);
};
IndexedModules.prototype.getModules = function(cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getModules()");
return getSetRecords("" + this.setname + "s", this.getModule, cb);
};
IndexedModules.prototype.deleteModule = function(mId) {
var _this = this;
this.log.info("DB | (IdxedMods) " + this.setname + ".deleteModule( " + mId + " )");
this.db.srem("" + this.setname + "s", mId, replyHandler("srem '" + mId + "' from " + this.setname + "s"));
this.db.del("" + this.setname + ":" + mId, replyHandler("del of '" + this.setname + ":" + mId + "'"));
this.unpublish(mId);
return this.db.smembers("" + this.setname + ":" + mId + ":users", function(err, obj) {
var userId, _i, _j, _len, _len1, _results;
for (_i = 0, _len = obj.length; _i < _len; _i++) {
userId = obj[_i];
_this.unlinkModule(mId, userId);
}
_results = [];
for (_j = 0, _len1 = obj.length; _j < _len1; _j++) {
userId = obj[_j];
_results.push(_this.deleteUserParams(mId, userId));
}
return _results;
});
};
/*
Stores user params for a module. They are expected to be RSA encrypted with helps of
the provided cryptico JS library and will only be decrypted right before the module is loaded!
@private storeUserParams( *mId, userId, encData* )
@param {String} mId
@param {String} userId
@param {object} encData
*/
IndexedModules.prototype.storeUserParams = function(mId, userId, encData) {
this.log.info("DB | (IdxedMods) " + this.setname + ".storeUserParams( " + mId + ", " + userId + ", encData )");
this.db.sadd("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("sadd '" + mId + ":" + userId + "' to '" + this.setname + "-params'"));
return this.db.set("" + this.setname + "-params:" + mId + ":" + userId, encData, replyHandler("set user params in '" + this.setname + "-params:" + mId + ":" + userId + "'"));
};
IndexedModules.prototype.getUserParams = function(mId, userId, cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getUserParams( " + mId + ", " + userId + " )");
return this.db.get("" + this.setname + "-params:" + mId + ":" + userId, cb);
};
IndexedModules.prototype.getUserParamsIds = function(cb) {
this.log.info("DB | (IdxedMods) " + this.setname + ".getUserParamsIds()");
return this.db.smembers("" + this.setname + "-params", cb);
};
IndexedModules.prototype.deleteUserParams = function(mId, userId) {
this.log.info("DB | (IdxedMods) " + this.setname + ".deleteUserParams(" + mId + ", " + userId + " )");
this.db.srem("" + this.setname + "-params", "" + mId + ":" + userId, replyHandler("srem '" + mId + ":" + userId + "' from '" + this.setname + "-params'"));
return this.db.del("" + this.setname + "-params:" + mId + ":" + userId, replyHandler("del '" + this.setname + "-params:" + mId + ":" + userId + "'"));
};
return IndexedModules;
})();
/*
## Rules
*/
/*
Appends a log entry.
@public log( *userId, ruleId, message* )
@param {String} userId
@param {String} ruleId
@param {String} message
*/
exports.appendLog = function(userId, ruleId, moduleId, message) {
return _this.db.append("" + userId + ":" + ruleId, "[" + ((new Date).toISOString()) + "] {" + moduleId + "} " + message + "\n");
};
/*
Retrieves a log entry.
@public getLog( *userId, ruleId* )
@param {String} userId
@param {String} ruleId
@param {function} cb
*/
exports.getLog = function(userId, ruleId, cb) {
return _this.db.get("" + userId + ":" + ruleId, cb);
};
/*
Resets a log entry.
@public resetLog( *userId, ruleId* )
@param {String} userId
@param {String} ruleId
*/
exports.resetLog = function(userId, ruleId) {
return _this.db.del("" + userId + ":" + ruleId, replyHandler("RESET LOG '" + userId + ":" + ruleId + "'"));
};
/*
Query the DB for a rule and pass it to cb(err, obj).
@public getRule( *ruleId, cb* )
@param {String} ruleId
@param {function} cb
*/
exports.getRule = function(ruleId, cb) {
_this.log.info("DB | getRule: '" + ruleId + "'");
return _this.db.get("rule:" + ruleId, cb);
};
/*
Fetch all rules and pass them to cb(err, obj).
@public getRules( *cb* )
@param {function} cb
*/
exports.getRules = function(cb) {
_this.log.info('DB | Fetching all Rules');
return getSetRecords('rules', exports.getRule, cb);
};
/*
Fetch all rule IDs and hand it to cb(err, obj).
@public getRuleIds( *cb* )
@param {function} cb
*/
exports.getRuleIds = function(cb) {
_this.log.info('DB | Fetching all Rule IDs');
return _this.db.smembers('rules', cb);
};
/*
Store a string representation of a rule in the DB.
@public storeRule( *ruleId, data* )
@param {String} ruleId
@param {String} data
*/
exports.storeRule = function(ruleId, data) {
_this.log.info("DB | storeRule: '" + ruleId + "'");
_this.db.sadd('rules', "" + ruleId, replyHandler("storing rule key '" + ruleId + "'"));
return _this.db.set("rule:" + ruleId, data, replyHandler("storing rule '" + ruleId + "'"));
};
/*
Delete a string representation of a rule.
@public deleteRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.deleteRule = function(ruleId) {
_this.log.info("DB | deleteRule: '" + ruleId + "'");
_this.db.srem("rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "'"));
_this.db.del("rule:" + ruleId, replyHandler("Deleting rule '" + ruleId + "'"));
_this.db.smembers("rule:" + ruleId + ":users", function(err, obj) {
var delLinkedUserRule, id, _i, _len, _results;
delLinkedUserRule = function(userId) {
return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in linked user '" + userId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delLinkedUserRule(id));
}
return _results;
});
_this.db.del("rule:" + ruleId + ":users", replyHandler("Deleting rule '" + ruleId + "' users"));
_this.db.smembers("rule:" + ruleId + ":active-users", function(err, obj) {
var delActiveUserRule, id, _i, _len, _results;
delActiveUserRule = function(userId) {
return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("Deleting rule key '" + ruleId + "' in active user '" + userId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delActiveUserRule(id));
}
return _results;
});
return _this.db.del("rule:" + ruleId + ":active-users", replyHandler("Deleting rule '" + ruleId + "' active users"));
};
/*
Associate a rule to a user.
@public linkRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.linkRule = function(ruleId, userId) {
_this.log.info("DB | linkRule: '" + ruleId + "' for user '" + userId + "'");
_this.db.sadd("rule:" + ruleId + ":users", userId, replyHandler("storing user '" + userId + "' for rule key '" + ruleId + "'"));
return _this.db.sadd("user:" + userId + ":rules", ruleId, replyHandler("storing rule key '" + ruleId + "' for user '" + userId + "'"));
};
/*
Get rules linked to a user and hand it to cb(err, obj).
@public getUserLinkRule( *userId, cb* )
@param {String} userId
@param {function} cb
*/
exports.getUserLinkedRules = function(userId, cb) {
_this.log.info("DB | getUserLinkedRules: for user '" + userId + "'");
return _this.db.smembers("user:" + userId + ":rules", cb);
};
/*
Get users linked to a rule and hand it to cb(err, obj).
@public getRuleLinkedUsers( *ruleId, cb* )
@param {String} ruleId
@param {function} cb
*/
exports.getRuleLinkedUsers = function(ruleId, cb) {
_this.log.info("DB | getRuleLinkedUsers: for rule '" + ruleId + "'");
return _this.db.smembers("rule:" + ruleId + ":users", cb);
};
/*
Delete an association of a rule to a user.
@public unlinkRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.unlinkRule = function(ruleId, userId) {
_this.log.info("DB | unlinkRule: '" + ruleId + ":" + userId + "'");
_this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("removing user '" + userId + "' for rule key '" + ruleId + "'"));
return _this.db.srem("user:" + userId + ":rules", ruleId, replyHandler("removing rule key '" + ruleId + "' for user '" + userId + "'"));
};
/*
Activate a rule.
@public activateRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.activateRule = function(ruleId, userId) {
_this.log.info("DB | activateRule: '" + ruleId + "' for '" + userId + "'");
_this.db.sadd("rule:" + ruleId + ":active-users", userId, replyHandler("storing activated user '" + userId + "' in rule '" + ruleId + "'"));
return _this.db.sadd("user:" + userId + ":active-rules", ruleId, replyHandler("storing activated rule '" + ruleId + "' in user '" + userId + "'"));
};
/*
Get rules activated for a user and hand it to cb(err, obj).
@public getUserLinkRule( *userId, cb* )
@param {String} userId
@param {function} cb
*/
exports.getUserActivatedRules = function(userId, cb) {
_this.log.info("DB | getUserActivatedRules: for user '" + userId + "'");
return _this.db.smembers("user:" + userId + ":active-rules", cb);
};
/*
Get users activated for a rule and hand it to cb(err, obj).
@public getRuleActivatedUsers ( *ruleId, cb* )
@param {String} ruleId
@param {function} cb
*/
exports.getRuleActivatedUsers = function(ruleId, cb) {
_this.log.info("DB | getRuleActivatedUsers: for rule '" + ruleId + "'");
return _this.db.smembers("rule:" + ruleId + ":active-users", cb);
};
/*
Deactivate a rule.
@public deactivateRule( *ruleId, userId* )
@param {String} ruleId
@param {String} userId
*/
exports.deactivateRule = function(ruleId, userId) {
_this.log.info("DB | deactivateRule: '" + ruleId + "' for '" + userId + "'");
_this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("removing activated user '" + userId + "' in rule '" + ruleId + "'"));
return _this.db.srem("user:" + userId + ":active-rules", ruleId, replyHandler("removing activated rule '" + ruleId + "' in user '" + userId + "'"));
};
/*
Fetch all active ruleIds and pass them to cb(err, obj).
@public getAllActivatedRuleIds( *cb* )
@param {function} cb
*/
exports.getAllActivatedRuleIdsPerUser = function(cb) {
_this.log.info("DB | Fetching all active rules");
return _this.db.smembers('users', function(err, obj) {
var fFetchActiveUserRules, result, semaphore, user, _i, _len, _results;
result = {};
if (obj.length === 0) {
return cb(null, result);
} else {
semaphore = obj.length;
fFetchActiveUserRules = function(userId) {
return _this.db.smembers("user:" + user + ":active-rules", function(err, obj) {
if (obj.length > 0) {
result[userId] = obj;
}
if (--semaphore === 0) {
return cb(null, result);
}
});
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
user = obj[_i];
_results.push(fFetchActiveUserRules(user));
}
return _results;
}
});
};
/*
## Users
*/
/*
Store a user object (needs to be a flat structure).
The password should be hashed before it is passed to this function.
@public storeUser( *objUser* )
@param {Object} objUser
*/
exports.storeUser = function(objUser) {
_this.log.info("DB | storeUser: '" + objUser.username + "'");
if (objUser && objUser.username && objUser.password) {
_this.db.sadd('users', objUser.username, replyHandler("storing user key '" + objUser.username + "'"));
objUser.password = objUser.password;
return _this.db.hmset("user:" + objUser.username, objUser, replyHandler("storing user properties '" + objUser.username + "'"));
} else {
return _this.log.warn(new Error('DB | username or password was missing'));
}
};
/*
Fetch all user IDs and pass them to cb(err, obj).
@public getUserIds( *cb* )
@param {function} cb
*/
exports.getUserIds = function(cb) {
_this.log.info("DB | getUserIds");
return _this.db.smembers("users", cb);
};
/*
Fetch a user by id and pass it to cb(err, obj).
@public getUser( *userId, cb* )
@param {String} userId
@param {function} cb
*/
exports.getUser = function(userId, cb) {
_this.log.info("DB | getUser: '" + userId + "'");
return _this.db.hgetall("user:" + userId, cb);
};
/*
Deletes a user and all his associated linked and active rules.
@public deleteUser( *userId* )
@param {String} userId
*/
exports.deleteUser = function(userId) {
_this.log.info("DB | deleteUser: '" + userId + "'");
_this.db.srem("users", userId, replyHandler("Deleting user key '" + userId + "'"));
_this.db.del("user:" + userId, replyHandler("Deleting user '" + userId + "'"));
_this.db.smembers("user:" + userId + ":rules", function(err, obj) {
var delLinkedRuleUser, id, _i, _len, _results;
delLinkedRuleUser = function(ruleId) {
return _this.db.srem("rule:" + ruleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in linked rule '" + ruleId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delLinkedRuleUser(id));
}
return _results;
});
_this.db.del("user:" + userId + ":rules", replyHandler("Deleting user '" + userId + "' rules"));
_this.db.smembers("user:" + userId + ":active-rules", function(err, obj) {
var delActivatedRuleUser, id, _i, _len, _results;
delActivatedRuleUser = function(ruleId) {
return _this.db.srem("rule:" + ruleId + ":active-users", userId, replyHandler("Deleting user key '" + userId + "' in active rule '" + ruleId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delActivatedRuleUser(id));
}
return _results;
});
_this.db.del("user:" + userId + ":active-rules", replyHandler("Deleting user '" + userId + "' rules"));
_this.db.smembers("user:" + userId + ":roles", function(err, obj) {
var delRoleUser, id, _i, _len, _results;
delRoleUser = function(roleId) {
return _this.db.srem("role:" + roleId + ":users", userId, replyHandler("Deleting user key '" + userId + "' in role '" + roleId + "'"));
};
_results = [];
for (_i = 0, _len = obj.length; _i < _len; _i++) {
id = obj[_i];
_results.push(delRoleUser(id));
}
return _results;
});
return _this.db.del("user:" + userId + ":roles", replyHandler("Deleting user '" + userId + "' roles"));
};
/*
Checks the credentials and on success returns the user object to the
callback(err, obj) function. The password has to be hashed (SHA-3-512)
beforehand by the instance closest to the user that enters the password,
because we only store hashes of passwords for security6 reasons.
@public loginUser( *userId, password, cb* )
@param {String} userId
@param {String} password
@param {function} cb
*/
exports.loginUser = function(userId, password, cb) {
var fCheck;
_this.log.info("DB | User '" + userId + "' tries to log in");
fCheck = function(pw) {
return function(err, obj) {
if (err) {
return cb(err, null);
} else if (obj && obj.password) {
if (pw === obj.password) {
_this.log.info("DB | User '" + obj.username + "' logged in!");
return cb(null, obj);
} else {
return cb(new Error('Wrong credentials!'), null);
}
} else {
return cb(new Error('User not found!'), null);
}
};
};
return _this.db.hgetall("user:" + userId, fCheck(password));
};
/*
## User Roles
*/
/*
Associate a role with a user.
@public storeUserRole( *userId, role* )
@param {String} userId
@param {String} role
*/
exports.storeUserRole = function(userId, role) {
_this.log.info("DB | storeUserRole: '" + userId + ":" + role + "'");
_this.db.sadd('roles', role, replyHandler("adding role '" + role + "' to role index set"));
_this.db.sadd("user:" + userId + ":roles", role, replyHandler("adding role '" + role + "' to user '" + userId + "'"));
return _this.db.sadd("role:" + role + ":users", userId, replyHandler("adding user '" + userId + "' to role '" + role + "'"));
};
/*
Fetch all roles of a user and pass them to cb(err, obj).
@public getUserRoles( *userId* )
@param {String} userId
@param {function} cb
*/
exports.getUserRoles = function(userId, cb) {
_this.log.info("DB | getUserRoles: '" + userId + "'");
return _this.db.smembers("user:" + userId + ":roles", cb);
};
/*
Fetch all users of a role and pass them to cb(err, obj).
@public getUserRoles( *role* )
@param {String} role
@param {function} cb
*/
exports.getRoleUsers = function(role, cb) {
_this.log.info("DB | getRoleUsers: '" + role + "'");
return _this.db.smembers("role:" + role + ":users", cb);
};
/*
Remove a role from a user.
@public removeRoleFromUser( *role, userId* )
@param {String} role
@param {String} userId
*/
exports.removeUserRole = function(userId, role) {
_this.log.info("DB | removeRoleFromUser: role '" + role + "', user '" + userId + "'");
_this.db.srem("user:" + userId + ":roles", role, replyHandler("Removing role '" + role + "' from user '" + userId + "'"));
return _this.db.srem("role:" + role + ":users", userId, replyHandler("Removing user '" + userId + "' from role '" + role + "'"));
};
/*
Shuts down the db link.
@public shutDown()
*/
exports.shutDown = function() {
var _ref;
return (_ref = _this.db) != null ? _ref.quit() : void 0;
};
}).call(this);

View file

@ -1,57 +0,0 @@
// *(will be replaced by a Redis DB queue)*
// Queue.js
// ========
//
// *A function to represent a queue*
// *Created by Stephen Morley - http://code.stephenmorley.org/ - and released under
// the terms of the CC0 1.0 Universal legal code:*
// *http://creativecommons.org/publicdomain/zero/1.0/legalcode*
// *items are added to the end of the queue and removed from the front.*
exports.Queue = function(){
// initialise the queue and offset
var queue = [];
var offset = 0;
this.getLength = function(){
// return the length of the queue
return (queue.length - offset);
};
/* Returns true if the queue is empty, and false otherwise.
*/
this.isEmpty = function(){ return (queue.length == 0); };
/* Enqueues the specified item. The parameter is:
* item - the item to enqueue
*/
this.enqueue = function(item){ queue.push(item); };
/* Dequeues an item and returns it. If the queue is empty then undefined is
* returned.
*/
this.dequeue = function(){
// if the queue is empty, return undefined
if (queue.length == 0) return undefined;
// store the item at the front of the queue
var item = queue[offset];
// increment the offset and remove the free space if necessary
if (++ offset * 2 >= queue.length){
queue = queue.slice(offset);
offset = 0;
}
// return the dequeued item
return item;
};
/* Returns the item at the front of the queue (without dequeuing it). If the
* queue is empty then undefined is returned.
*/
this.peek = function(){
// return the item at the front of the queue
return (queue.length > 0 ? queue[offset] : undefined);
};
};

View file

@ -1,5 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
Request Handler
@ -8,10 +7,12 @@ Request Handler
> the [HTTP Listener](http-listener.html). It will handle user requests for
> pages as well as POST requests such as user login, module storing, event
> invocation and also admin commands.
*/
*/
(function() {
var crypto, db, dirHandlers, exports, fs, getHandlerPath, getRemoteScripts, getScript, getTemplate, mustache, path, qs, renderPage;
var crypto, db, dirHandlers, exports, fs, getHandlerPath, getRemoteScripts, getScript, getTemplate, mustache, path, qs, renderPage,
_this = this;
db = require('./persistence');
@ -27,35 +28,32 @@ Request Handler
dirHandlers = path.resolve(__dirname, '..', 'webpages', 'handlers');
exports = module.exports = (function(_this) {
return function(args) {
var fStoreUser, user, users;
_this.log = args.logger;
_this.userRequestHandler = args['request-service'];
_this.objAdminCmds = {
shutdown: function(obj, cb) {
var data;
data = {
code: 200,
message: 'Shutting down... BYE!'
};
setTimeout(args['shutdown-function'], 500);
return cb(null, data);
}
};
db(args);
users = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'config', 'users.json')));
fStoreUser = function(username, oUser) {
oUser.username = username;
return db.storeUser(oUser);
};
for (user in users) {
fStoreUser(user, users[user]);
exports = module.exports = function(args) {
var fStoreUser, user, users;
_this.log = args.logger;
_this.userRequestHandler = args['request-service'];
_this.objAdminCmds = {
shutdown: function(obj, cb) {
var data;
data = {
code: 200,
message: 'Shutting down... BYE!'
};
setTimeout(args['shutdown-function'], 500);
return cb(null, data);
}
return module.exports;
};
})(this);
db(args);
users = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'config', 'users.json')));
fStoreUser = function(username, oUser) {
oUser.username = username;
return db.storeUser(oUser);
};
for (user in users) {
fStoreUser(user, users[user]);
}
return module.exports;
};
/*
Handles possible events that were posted to this server and pushes them into the
@ -67,7 +65,8 @@ Request Handler
objects.*
@public handleEvent( *req, resp* )
*/
*/
exports.handleEvent = function(req, resp) {
var body;
@ -103,7 +102,6 @@ Request Handler
});
};
/*
Associates the user object with the session if login is successful.
@ -113,34 +111,32 @@ Request Handler
objects.*
@public handleLogin( *req, resp* )
*/
*/
exports.handleLogin = (function(_this) {
return function(req, resp) {
var body;
body = '';
req.on('data', function(data) {
return body += data;
});
return req.on('end', function() {
var obj;
obj = qs.parse(body);
return db.loginUser(obj.username, obj.password, function(err, usr) {
if (err) {
_this.log.warn("RH | AUTH-UH-OH ( " + obj.username + " ): " + err.message);
} else {
req.session.user = usr;
}
if (req.session.user) {
return resp.send('OK!');
} else {
return resp.send(401, 'NO!');
}
});
});
};
})(this);
exports.handleLogin = function(req, resp) {
var body;
body = '';
req.on('data', function(data) {
return body += data;
});
return req.on('end', function() {
var obj;
obj = qs.parse(body);
return db.loginUser(obj.username, obj.password, function(err, usr) {
if (err) {
_this.log.warn("RH | AUTH-UH-OH ( " + obj.username + " ): " + err.message);
} else {
req.session.user = usr;
}
if (req.session.user) {
return resp.send('OK!');
} else {
return resp.send(401, 'NO!');
}
});
});
};
/*
A post request retrieved on this handler causes the user object to be
@ -152,7 +148,8 @@ Request Handler
objects.*
@public handleLogout( *req, resp* )
*/
*/
exports.handleLogout = function(req, resp) {
if (req.session) {
@ -161,25 +158,25 @@ Request Handler
}
};
/*
Resolves the path to a handler webpage.
@private getHandlerPath( *name* )
@param {String} name
*/
*/
getHandlerPath = function(name) {
return path.join(dirHandlers, name + '.html');
};
/*
Fetches a template.
@private getTemplate( *name* )
@param {String} name
*/
*/
getTemplate = function(name) {
var pth;
@ -187,13 +184,13 @@ Request Handler
return fs.readFileSync(pth, 'utf8');
};
/*
Fetches a script.
@private getScript( *name* )
@param {String} name
*/
*/
getScript = function(name) {
var pth;
@ -201,13 +198,13 @@ Request Handler
return fs.readFileSync(pth, 'utf8');
};
/*
Fetches remote scripts snippets.
@private getRemoteScripts( *name* )
@param {String} name
*/
*/
getRemoteScripts = function(name) {
var pth;
@ -215,7 +212,6 @@ Request Handler
return fs.readFileSync(pth, 'utf8');
};
/*
Renders a page, with helps of mustache, depending on the user session and returns it.
@ -223,7 +219,8 @@ Request Handler
@param {String} name
@param {Object} sess
@param {Object} msg
*/
*/
renderPage = function(name, req, resp, msg) {
var code, content, data, err, menubar, page, pageElements, pathSkel, remote_scripts, script, skeleton;
@ -262,7 +259,6 @@ Request Handler
return resp.send(code, mustache.render(page, data));
};
/*
Present the desired forge page to the user.
@ -272,7 +268,8 @@ Request Handler
objects.*
@public handleForge( *req, resp* )
*/
*/
exports.handleForge = function(req, resp) {
var page;
@ -283,7 +280,6 @@ Request Handler
return renderPage(page, req, resp);
};
/*
Handles the user command requests.
@ -293,29 +289,27 @@ Request Handler
objects.*
@public handleUser( *req, resp* )
*/
*/
exports.handleUserCommand = (function(_this) {
return function(req, resp) {
var body;
if (req.session && req.session.user) {
body = '';
req.on('data', function(data) {
return body += data;
});
return req.on('end', function() {
var obj;
obj = qs.parse(body);
return _this.userRequestHandler(req.session.user, obj, function(obj) {
return resp.send(obj.code, obj);
});
});
} else {
return resp.send(401, 'Login first!');
}
};
})(this);
exports.handleUserCommand = function(req, resp) {
var body;
if (req.session && req.session.user) {
body = '';
req.on('data', function(data) {
return body += data;
});
return req.on('end', function() {
var obj;
obj = qs.parse(body);
return _this.userRequestHandler(req.session.user, obj, function(obj) {
return resp.send(obj.code, obj);
});
});
} else {
return resp.send(401, 'Login first!');
}
};
/*
Present the admin console to the user if he's allowed to see it.
@ -326,7 +320,8 @@ Request Handler
objects.*
@public handleForge( *req, resp* )
*/
*/
exports.handleAdmin = function(req, resp) {
var msg, page;
@ -341,7 +336,6 @@ Request Handler
return renderPage(page, req, resp, msg);
};
/*
Handles the admin command requests.
@ -351,32 +345,31 @@ Request Handler
objects.*
@public handleAdminCommand( *req, resp* )
*/
*/
exports.handleAdminCommand = (function(_this) {
return function(req, resp) {
var body;
if (req.session && req.session.user && req.session.user.isAdmin === "true") {
body = '';
req.on('data', function(data) {
return body += data;
});
return req.on('end', function() {
var obj;
obj = qs.parse(body);
_this.log.info('RH | Received admin request: ' + obj.command);
if (obj.command && _this.objAdminCmds[obj.command]) {
return _this.objAdminCmds[obj.command](obj, function(err, obj) {
return resp.send(obj.code, obj);
});
} else {
return resp.send(404, 'Command unknown!');
}
});
} else {
return resp.send(401, 'You need to be logged in as admin!');
}
};
})(this);
exports.handleAdminCommand = function(req, resp) {
var body;
if (req.session && req.session.user && req.session.user.isAdmin === "true") {
body = '';
req.on('data', function(data) {
return body += data;
});
return req.on('end', function() {
var obj;
obj = qs.parse(body);
_this.log.info('RH | Received admin request: ' + obj.command);
if (obj.command && _this.objAdminCmds[obj.command]) {
return _this.objAdminCmds[obj.command](obj, function(err, obj) {
return resp.send(obj.code, obj);
});
} else {
return resp.send(404, 'Command unknown!');
}
});
} else {
return resp.send(401, 'You need to be logged in as admin!');
}
};
}).call(this);

View file

@ -1,158 +0,0 @@
/*
* Rules Server
* ============
* >This is the main module that is used to run the whole server:
* >
* > node server [log_type http_port]
* >
* >Valid `log_type`'s are:
* >
* >- `0`: standard I/O output (default)
* >- `1`: log file (server.log)
* >- `2`: silent
* >
* >`http_port` can be set to use another port, than defined in the
* >[config](config.html) file, to listen to, e.g. used by the test suite.
* >
* >---
*/
'use strict';
// Grab all required modules
var path = require('path'),
log = require('./logging'),
conf = require('./config'),
http_listener = require('./http_listener'),
mm = require('./module_manager'),
db = require('./db_interface'),
engine = require('./engine'),
semaphore = 0,
args = {},
procCmds = {},
// Prepare the admin commands that are issued via HTTP requests.
adminCmds = {
'loadrules': mm.loadRulesFromFS,
'loadaction': mm.loadActionModuleFromFS,
'loadactions': mm.loadActionModulesFromFS,
'loadevent': mm.loadEventModuleFromFS,
'loadevents': mm.loadEventModulesFromFS,
'loadusers': http_listener.loadUsers,
'shutdown': shutDown
};
/*
* Error handling of the express port listener requires special attention,
* thus we have to catch the process error, which is issued if
* the port is already in use.
*/
process.on('uncaughtException', function(err) {
switch(err.errno) {
case 'EADDRINUSE':
err.addInfo = 'http_port already in use, shutting down!';
log.error('RS', err);
shutDown();
break;
default: log.error(err);
}
});
/**
* ## Initialize the Server
* This function is invoked right after the module is loaded and starts the server.
*/
function init() {
log.print('RS', 'STARTING SERVER');
// Check whether the config file is ready, which is required to start the server.
if(!conf.isReady()) {
log.error('RS', 'Config file not ready!');
process.exit();
}
// Fetch the `log_type` argument and post a log about which log type is used.
if(process.argv.length > 2) {
args.logType = parseInt(process.argv[2]) || 0;
if(args.logType === 0) log.print('RS', 'Log type set to standard I/O output');
else if(args.logType === 1) log.print('RS', 'Log type set to file output');
else if(args.logType === 2) log.print('RS', 'Log type set to silent');
else log.print('RS', 'Unknown log type, using standard I/O');
log(args);
} else log.print('RS', 'No log method passed, using standard I/O');
// Fetch the `http_port` argument
if(process.argv.length > 3) args.http_port = parseInt(process.argv[3]);
else log.print('RS', 'No HTTP port passed, using standard port from config file');
db(args);
// We only proceed with the initialization if the DB is ready
db.isConnected(function(err, result) {
if(!err) {
// Initialize all required modules with the args object.
log.print('RS', 'Initialzing engine');
engine(args);
log.print('RS', 'Initialzing http listener');
http_listener(args);
log.print('RS', 'Initialzing module manager');
mm(args);
log.print('RS', 'Initialzing DB');
// Distribute handlers between modules to link the application.
log.print('RS', 'Passing handlers to engine');
engine.addDBLinkAndLoadActionsAndRules(db);
log.print('RS', 'Passing handlers to http listener');
http_listener.addHandlers(db, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Passing handlers to module manager');
mm.addHandlers(db, engine.loadActionModule, engine.addRule);
}
});
}
/**
* admin commands handler receives all command arguments and an answerHandler
* object that eases response handling to the HTTP request issuer.
*/
function handleAdminCommands(args, answHandler) {
if(args && args.cmd) {
var func = adminCmds[args.cmd];
if(typeof func == 'function') func(args, answHandler);
} else log.print('RS', 'No command in request');
setTimeout(function(ah) {
return function() {
if(!ah.isAnswered()) ah.answerError('Not handeled...');
};
}(answHandler), 2000);
}
/**
* Shuts down the server.
* @param {Object} args
* @param {Object} answHandler
*/
function shutDown(args, answHandler) {
if(answHandler) answHandler.answerSuccess('Goodbye!');
log.print('RS', 'Received shut down command!');
if(engine) engine.shutDown();
if(http_listener) http_listener.shutDown();
}
/*
* ### Process Commands
*
* When the server is run as a child process, this function handles messages
* from the parent process (e.g. the testing suite)
*/
process.on('message', function(cmd) {
if(typeof procCmds[cmd] === 'function') procCmds[cmd]();
else console.error('err with command');
});
/**
* The die command redirects to the shutDown function.
*/
procCmds.die = shutDown;
/*
* *Start initialization*
*/
init();

View file

@ -1,182 +0,0 @@
var path = require('path'),
qs = require('querystring'),
log = require('./logging'),
db = require('./db_interface'),
mm = require('./module_manager'),
// ### Prepare the admin command handlers that are issued via HTTP requests. ###
objAdminCmds = {
'loadrules': mm.loadRulesFromFS,
'loadaction': mm.loadActionModuleFromFS,
'loadactions': mm.loadActionModulesFromFS,
'loadevent': mm.loadEventModuleFromFS,
'loadevents': mm.loadEventModulesFromFS
};
exports = module.exports = function(args) {
args = args || {};
log(args);
db(args);
mm(args);
mm.addDBLink(db);
var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', 'config', 'users.json')));
for(var name in users) {
db.storeUser(users[name]);
}
log.print('RS', 'Initialzing module manager');
return module.exports;
};
exports.addHandler = function(fShutdown) {
objAdminCmds.shutdown = fShutdown;
};
exports.handleRequest = function(req, resp) {
req.on('end', function () {
resp.end();
});
if(req.session && req.session.user) {
resp.send('You\'re logged in');
} else resp.sendfile(path.resolve(__dirname, '..', 'webpages', 'handlers', 'login.html'));
// resp.end();
log.print('UH', 'last: '+ req.session.lastPage);
req.session.lastPage = req.originalUrl;
log.print('UH', 'last: '+ req.session.lastPage);
log.print('UH', 'retrieved req: '+ req.originalUrl);
// console.log(req);
};
exports.handleLogin = function(req, resp) {
var body = '';
req.on('data', function (data) { body += data; });
req.on('end', function () {
if(!req.session || !req.session.user) {
var obj = qs.parse(body);
db.loginUser(obj.username, obj.password, function(err, obj) {
if(!err) req.session.user = obj;
if(req.session.user) {
resp.write('Welcome ' + req.session.user.name + '!');
} else {
resp.writeHead(401, { "Content-Type": "text/plain" });
resp.write('Login failed!');
}
resp.end();
});
}
});
};
function answerHandler(r) {
var response = r, hasBeenAnswered = false;
function postAnswer(msg) {
if(!hasBeenAnswered) {
response.write(msg);
response.end();
hasBeenAnswered = true;
}
}
return {
answerSuccess: function(msg) {
if(!hasBeenAnswered) response.writeHead(200, { "Content-Type": "text/plain" });
postAnswer(msg);
},
answerError: function(msg) {
if(!hasBeenAnswered) response.writeHead(400, { "Content-Type": "text/plain" });
postAnswer(msg);
},
isAnswered: function() { return hasBeenAnswered; }
};
};
//TODO add loadUsers as directive to admin commands
exports.loadUsers = function () {
var users = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'users.json')));
for(var name in users) {
db.storeUser(users[name]);
}
};
function onAdminCommand(request, response) {
var q = request.query;
log.print('HL', 'Received admin request: ' + request.originalUrl);
if(q.cmd) {
fAdminCommands(q, answerHandler(response));
// answerSuccess(response, 'Thank you, we try our best!');
} else answerError(response, 'I\'m not sure about what you want from me...');
}
/*
admin commands handler receives all command arguments and an answerHandler
object that eases response handling to the HTTP request issuer.
@private fAdminCommands( *args, answHandler* )
*/
function fAdminCommands(args, answHandler) {
var fAnsw, _name;
if (args && args.cmd) {
if (typeof objAdminCmds[_name = args.cmd] === "function") {
objAdminCmds[_name](args, answHandler);
}
} else {
log.print('RS', 'No command in request');
}
/*
The fAnsw function receives an answerHandler object as an argument when called
and returns an anonymous function
*/
fAnsw = function(ah) {
/*
The anonymous function checks whether the answerHandler was already used to
issue an answer, if no answer was provided we answer with an error message
*/
return function() {
if (!ah.isAnswered()) {
return ah.answerError('Not handled...');
}
};
};
/*
Delayed function call of the anonymous function that checks the answer handler
*/
return setTimeout(fAnsw(answHandler), 2000);
};
/*
###
admin commands handler receives all command arguments and an answerHandler
object that eases response handling to the HTTP request issuer.
@private fAdminCommands( *args, answHandler* )
###
fAdminCommands = (args, answHandler) ->
if args and args.cmd
adminCmds[args.cmd]? args, answHandler
else
log.print 'RS', 'No command in request'
###
The fAnsw function receives an answerHandler object as an argument when called
and returns an anonymous function
###
fAnsw = (ah) ->
###
The anonymous function checks whether the answerHandler was already used to
issue an answer, if no answer was provided we answer with an error message
###
() ->
if not ah.isAnswered()
ah.answerError 'Not handled...'
###
Delayed function call of the anonymous function that checks the answer handler
###
setTimeout fAnsw(answHandler), 2000
*/

View file

@ -1,81 +0,0 @@
'use strict';
var log = require('./logging'),
objCmds = {
addUser: addUser,
getUser: getUser,
delUser: delUser,
addRule: addRule,
getRules: getRules,
delRule: delRule
};
exports = module.exports = function(args) {
args = args || {};
log(args);
return module.exports;
};
exports.handleCommand = function(args, cb) {
if(!args.cmd) {
var e = new Error('No command defined!');
if(typeof cb === 'function') cb(e);
else log.error('US', e);
} else {
objCmds[args.cmd](args, cb);
}
};
/**
*
* @param {Object} args
* @param {function} cb
*/
function addUser(args, cb) {
}
/**
*
* @param {Object} args
* @param {function} cb
*/
function getUser(args, cb) {
}
/**
*
* @param {Object} args
* @param {function} cb
*/
function delUser(args, cb) {
}
/**
*
* @param {Object} args
* @param {function} cb
*/
function addRule(args, cb) {
}
/**
*
* @param {Object} args
* @param {function} cb
*/
function getRule(args, cb) {
}
/**
*
* @param {Object} args
* @param {function} cb
*/
function delRule(args, cb) {
}

View file

@ -1,5 +1,4 @@
// Generated by CoffeeScript 1.7.1
// Generated by CoffeeScript 1.6.3
/*
WebAPI-ECA Engine
@ -10,10 +9,12 @@ WebAPI-ECA Engine
> node webapi-eca [opt]
>
> See below in the optimist CLI preparation for allowed optional parameters `[opt]`.
*/
*/
(function() {
var argv, cm, conf, cp, db, engine, fs, http, init, logconf, logger, nameEP, opt, optimist, path, procCmds, shutDown, usage;
var argv, cm, conf, cp, db, engine, fs, http, init, logconf, logger, nameEP, opt, optimist, path, procCmds, shutDown, usage,
_this = this;
logger = require('./logging');
@ -39,10 +40,10 @@ WebAPI-ECA Engine
procCmds = {};
/*
Let's prepare the optimist CLI optional arguments `[opt]`:
*/
*/
usage = 'This runs your webapi-based ECA engine';
@ -129,76 +130,72 @@ WebAPI-ECA Engine
this.log.info('RS | STARTING SERVER');
/*
This function is invoked right after the module is loaded and starts the server.
@private init()
*/
*/
init = (function(_this) {
return function() {
var args;
args = {
logger: _this.log,
logconf: logconf
};
args['http-port'] = parseInt(argv.w || conf.getHttpPort());
args['db-port'] = parseInt(argv.d || conf.getDbPort());
args['keygen'] = conf.getKeygenPassphrase();
_this.log.info('RS | Initialzing DB');
db(args);
return db.isConnected(function(err) {
var cliArgs, poller;
if (err) {
_this.log.error('RS | No DB connection, shutting down system!');
return shutDown();
} else {
_this.log.info('RS | Initialzing engine');
engine(args);
_this.log.info('RS | Forking a child process for the event poller');
cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog']];
poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs);
_this.log.info('RS | Initialzing module manager');
cm(args);
cm.addRuleListener(engine.internalEvent);
cm.addRuleListener(function(evt) {
return poller.send(evt);
});
_this.log.info('RS | Initialzing http listener');
args['request-service'] = cm.processRequest;
args['shutdown-function'] = shutDown;
return http(args);
}
});
init = function() {
var args;
args = {
logger: _this.log,
logconf: logconf
};
})(this);
args['http-port'] = parseInt(argv.w || conf.getHttpPort());
args['db-port'] = parseInt(argv.d || conf.getDbPort());
args['keygen'] = conf.getKeygenPassphrase();
_this.log.info('RS | Initialzing DB');
db(args);
return db.isConnected(function(err) {
var cliArgs, poller;
if (err) {
_this.log.error('RS | No DB connection, shutting down system!');
return shutDown();
} else {
_this.log.info('RS | Initialzing engine');
engine(args);
_this.log.info('RS | Forking a child process for the event poller');
cliArgs = [args.logconf['mode'], args.logconf['io-level'], args.logconf['file-level'], args.logconf['file-path'], args.logconf['nolog']];
poller = cp.fork(path.resolve(__dirname, nameEP), cliArgs);
_this.log.info('RS | Initialzing module manager');
cm(args);
cm.addRuleListener(engine.internalEvent);
cm.addRuleListener(function(evt) {
return poller.send(evt);
});
_this.log.info('RS | Initialzing http listener');
args['request-service'] = cm.processRequest;
args['shutdown-function'] = shutDown;
return http(args);
}
});
};
/*
Shuts down the server.
@private shutDown()
*/
*/
shutDown = (function(_this) {
return function() {
_this.log.warn('RS | Received shut down command!');
if (db != null) {
db.shutDown();
}
engine.shutDown();
return process.exit();
};
})(this);
shutDown = function() {
_this.log.warn('RS | Received shut down command!');
if (db != null) {
db.shutDown();
}
engine.shutDown();
return process.exit();
};
/*
*# Process Commands
## Process Commands
When the server is run as a child process, this function handles messages
from the parent process (e.g. the testing suite)
*/
*/
process.on('message', function(cmd) {
return typeof procCmds[cmd] === "function" ? procCmds[cmd]() : void 0;

4
node_modules/my-cryptico/index.js generated vendored
View file

@ -1,8 +1,8 @@
var fs = require('fs');
// We do this so we use the exact same librar in the browser and in node
// We do this so we use the exact same library in the browser and in node
// Read and eval library
// Eval is evil...
var filedata = fs.readFileSync('./webpages/public/js/cryptico.js','utf8');
eval(filedata);
exports = module.exports = cryptico;
exports = module.exports = cryptico;

View file

@ -2,7 +2,7 @@
"name": "webapi-eca",
"author": "Dominic Bosch",
"description": "An ECA inference engine as a middleware between Web API's",
"version": "0.1.0",
"version": "0.2.0",
"private": true,
"repository": {
"type" : "git",
@ -22,25 +22,5 @@
"redis": "0.10.0",
"request": "2.33.0",
"optimist": "0.6.1"
},
"__comment": {
"dependencies": {
"glob" :"3.2.7",
"docco": "0.6.2",
"socket.io": "0.9.16",
"contextify": "0.1.6",
"dependencies_safe": {
"connect-redis": "1.4.6",
"crypto-js": "3.1.2",
"express": "3.4.0",
"groc": "0.6.1",
"mustache": "0.7.3",
"needle": "0.6.1",
"nodeunit": "0.8.4",
"redis": "0.9.0",
"request": "2.27.0",
"coffee-script": "1.6.3"
}
}
}
}

View file

@ -1,3 +1,3 @@
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
node $DIR/js-coffee/webapi-eca | $DIR/node_modules/bunyan/bin/bunyan
node $DIR/js/webapi-eca | $DIR/node_modules/bunyan/bin/bunyan

View file

@ -1,5 +0,0 @@
var cs = require('coffee-script');
var fs = require('fs');
var csSrc = fs.readFileSync('coffee/config.coffee', { encoding: "utf-8" });
// csSrc = "log = require './logging'";
console.log(cs.compile(csSrc));

View file

@ -6,7 +6,7 @@ var fs = require( 'fs' ),
db = require( './js-coffee/persistence' ),
args = process.argv.slice( 2 ),
fEnd = function() {
console.log('Shutting down DB from unit_test.sh script...');
console.log( 'Shutting down DB from unit_test.sh script...' );
db.shutDown();
};