diff --git a/README.md b/README.md index 8f1311e..41e6d7a 100644 --- a/README.md +++ b/README.md @@ -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 - diff --git a/compile_coffee.sh b/compile_coffee.sh index 5fd055b..df12a94 100755 --- a/compile_coffee.sh +++ b/compile_coffee.sh @@ -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/ diff --git a/gen_doc.sh b/gen_doc.sh index 08a505e..b38575a 100755 --- a/gen_doc.sh +++ b/gen_doc.sh @@ -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); - // } - // }); -// } diff --git a/js-coffee/config.js b/js-coffee/config.js deleted file mode 100644 index e984a2e..0000000 --- a/js-coffee/config.js +++ /dev/null @@ -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); diff --git a/js-coffee/dynamic-modules.js b/js-coffee/dynamic-modules.js deleted file mode 100644 index 543f47f..0000000 --- a/js-coffee/dynamic-modules.js +++ /dev/null @@ -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); diff --git a/js-coffee/engine.js b/js-coffee/engine.js deleted file mode 100644 index 9b29b5e..0000000 --- a/js-coffee/engine.js +++ /dev/null @@ -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); diff --git a/js-coffee/http-listener.js b/js-coffee/http-listener.js deleted file mode 100644 index aa686b2..0000000 --- a/js-coffee/http-listener.js +++ /dev/null @@ -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 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); diff --git a/js-coffee/components-manager.js b/js/components-manager.js similarity index 70% rename from js-coffee/components-manager.js rename to js/components-manager.js index c6e5322..1bf7015 100644 --- a/js-coffee/components-manager.js +++ b/js/components-manager.js @@ -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) { diff --git a/js/config.js b/js/config.js index 8ba3402..4c48e7a 100644 --- a/js/config.js +++ b/js/config.js @@ -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; + }; - \ No newline at end of file + /* + ***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); diff --git a/js/db_interface.js b/js/db_interface.js deleted file mode 100644 index b31cfaf..0000000 --- a/js/db_interface.js +++ /dev/null @@ -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!')); -}; diff --git a/js/dynamic-modules.js b/js/dynamic-modules.js new file mode 100644 index 0000000..3291020 --- /dev/null +++ b/js/dynamic-modules.js @@ -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); diff --git a/js/engine.js b/js/engine.js index c9b88ad..94e82f9 100644 --- a/js/engine.js +++ b/js/engine.js @@ -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); diff --git a/js-coffee/event-poller.js b/js/event-poller.js similarity index 95% rename from js-coffee/event-poller.js rename to js/event-poller.js index 9b4121e..3922e1b 100644 --- a/js-coffee/event-poller.js +++ b/js/event-poller.js @@ -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; diff --git a/js/eventpoller.js b/js/eventpoller.js deleted file mode 100644 index e69de29..0000000 diff --git a/js/http-listener.js b/js/http-listener.js new file mode 100644 index 0000000..ba133cc --- /dev/null +++ b/js/http-listener.js @@ -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 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); diff --git a/js/module_loader.js b/js/module_loader.js deleted file mode 100644 index 321a0bf..0000000 --- a/js/module_loader.js +++ /dev/null @@ -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); - } - }); - }); - }); -}; - diff --git a/js/module_manager.js b/js/module_manager.js deleted file mode 100644 index 3db5e36..0000000 --- a/js/module_manager.js +++ /dev/null @@ -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); -}; - diff --git a/js/persistence.js b/js/persistence.js new file mode 100644 index 0000000..d4ce09c --- /dev/null +++ b/js/persistence.js @@ -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); diff --git a/js/queue.js b/js/queue.js deleted file mode 100644 index 906692e..0000000 --- a/js/queue.js +++ /dev/null @@ -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); - }; -}; diff --git a/js-coffee/request-handler.js b/js/request-handler.js similarity index 71% rename from js-coffee/request-handler.js rename to js/request-handler.js index 212cdc0..88a95de 100644 --- a/js-coffee/request-handler.js +++ b/js/request-handler.js @@ -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); diff --git a/js/server.js b/js/server.js deleted file mode 100644 index 0bf4d15..0000000 --- a/js/server.js +++ /dev/null @@ -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(); diff --git a/js/user_handler.js b/js/user_handler.js deleted file mode 100644 index 3e45386..0000000 --- a/js/user_handler.js +++ /dev/null @@ -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 -*/ diff --git a/js/users.js b/js/users.js deleted file mode 100644 index 813b71f..0000000 --- a/js/users.js +++ /dev/null @@ -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) { - -} diff --git a/js-coffee/webapi-eca.js b/js/webapi-eca.js similarity index 63% rename from js-coffee/webapi-eca.js rename to js/webapi-eca.js index a0eaa22..5a89e07 100644 --- a/js-coffee/webapi-eca.js +++ b/js/webapi-eca.js @@ -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; diff --git a/node_modules/my-cryptico/index.js b/node_modules/my-cryptico/index.js index b0a93a7..e27b5e6 100644 --- a/node_modules/my-cryptico/index.js +++ b/node_modules/my-cryptico/index.js @@ -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; \ No newline at end of file diff --git a/package.json b/package.json index 7812640..6f333fb 100644 --- a/package.json +++ b/package.json @@ -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" - } - } } } \ No newline at end of file diff --git a/run_engine.sh b/run_engine.sh index cce1f24..af0258c 100755 --- a/run_engine.sh +++ b/run_engine.sh @@ -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 diff --git a/sandbox.js b/sandbox.js deleted file mode 100644 index 95e8a6d..0000000 --- a/sandbox.js +++ /dev/null @@ -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)); \ No newline at end of file diff --git a/unit_tests.sh b/unit_tests.sh index 64ac42c..285a4b7 100755 --- a/unit_tests.sh +++ b/unit_tests.sh @@ -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(); };