I have the requirement to use a rule engine to implement role permissions in the system ( could this be an overkill? ) however the permissions are kind of complicated and complex itself. I got confused in how to grant access or not using a rule-engine.
I also have doubts about the design that I should use in order to implement it in a scalable and maintainable way. So any help in the design or explain to me how to use the rule engine would be great.
Using nools, mongoDB, node.js as backend.
I was thinking in creating a rule engine instance encapsulating Nools ( would be an anti-pattern inner-platform maybe?) in the bootstrap of my node.js app and let it as a global variable.
something like:
'use strict';
var nools = require('nools');
var flows = require('./rule-engine.flows');
// A flow is a container of rules, so each flow could be a category of rules
// In the same flow could have some more specific subcategories would be actionGroups?
// I'm creating a ruleEngine instance that would contain nools but I'm not sure
// if that would be a good practice, I have that to maybe encapsulate boilerplate code
// or some straight forward operations in the future.. don't sure of this.
var ruleEngine = function(){
this.nools = nools;
};
ruleEngine.prototype.getFlowSession = function(flow){
if(this.nools.hasFlow(flow)){
return this.nools.getFlow(flow).getSession();
}else{
var fl = this.nools.flow(flow, flows[flow]);
return fl.getSession();
}
};
ruleEngine.prototype.createRule = function(flow, rule){
if(this.nools.hasFlow(flow)){
// implementation to add rules to a flow
}
};
ruleEngine.prototype.editRule = function(flow, rule, update){};
ruleEngine.prototype.retractRule = function(flow, rule){};
//could be global object, or cache object but certainly should be a single object.
if(!GLOBAL.ruleEngine){
var ruleEngineInstance = new ruleEngine();
GLOBAL.ruleEngine = ruleEngineInstance;
}
//module.exports = GLOBAL.ruleEngine;
rule-engine.flow:
'use strict';
var flowName = function(flow){
// query the rules to database or to cache.. then add the rules to the flow.
// query bla bla function(results){
for(Var i=0; i<results.length; i++)
flow.rule(results[i].name, results[i].constraints, results[i].action);
// alternately, I could just from the bootstrap create a flow,
// and create a function to add, modify or retract rules of a specific flow.
// What would be the better design approach ? or combine two approach ?
// bring from database the first time, and later use ruleModify,
// ruleCreate or rule retract functions.
};
module.exports = {
flowName: flowName,
// each would be a flow that would be a category of rules for the system
flowName2: flowName2
};
How to use it to implement permissions, is the only way to communicate the rule engine and external app / code through events?
these are some rules I created just to mess about ( at the same time are the ones used to create the flowName simulating the cache rules or MongoDB rules ).
var results = [
{
name: 'userAllow',
constraints: [Object, 'obj', 'obj.systemRole === \'user\''],
action: function(facts, session, next){
session.emit('event:userAllow', {data: 'user is allow'});
next();
}
},
{
name: 'userNotAllow',
constraints: [Object, 'obj', 'obj.systemRole !== \'user\''],
action: function(facts, session, next){
session.emit('event:userNotAllow', {data: 'user is not allow'});
next();
}
},
{
name: 'adminAllow',
constraints: [Object, 'obj', 'obj.systemRole === \'admin\''],
action: function(facts, session, next){
session.emit('event:adminAllow', {data: 'admin is allow!'});
next();
}
},
{
name: 'adminNotAllow',
constraints: [Object, 'obj', 'obj.systemRole !== \'admin\''],
action: function(facts, session, next){
session.emit('event:adminNotAllow', {data: 'admin not allow'});
next();
}
}
];
So with this few rules, I just want to grant access when user.systemRole is admin for example.. should I use events in the following way?
X-method in the system:
//validate delete with ruleEngine... supposed only admin would be able to delete
var self = this;
var session = ruleEngine.getFlowSession('flowName');
session.assert({systemRole: User.role}); //User.role = 'user' || 'admin'
session.on('event:adminAllow', function(d){
console.log('do the job because the user is admin');
// Delete implementation.
});
session.on('event:adminNotAllow', function(d){
console.log('User not allow because is not admin');
});
session.on('fire',function(name){
console.log(name);
});
session.match().then(function(){
session.dispose();
});
So far I have some problems with this implementation.. events can fire more than once and I can't allow it to fire twice on a delete operation or a create operation or things like that.
So besides that error that I need to fix ( don't sure how ) Edit:
I commented the last next() of my rules, and after that the events are fired once. I have other doubts:
- Have good practices broken or anti-patterns?
- this is scalable and easy to maintain?
- Is the normal way of working with rule-engines?
- Pros and Cons of this implementation?
- Is there a better way?
Thanks in advance for any help.
Are you committed to using nools? If not, there is a much simpler (IMHO) option to creating an access control system using node_acl.
The access control system is based on three things, roles; resources and permissions. You define certain roles and resources and then simply set the permissions of each role for each resource. For instance, you could have the role "admin" and set the permission "can modify" on the resource "system configuration". Then all you need to do is assign users to roles as needed.
I am happy to provide some sample code if you like but you can check out a tutorial I wrote on creating an access control system for nodejs.