UNPKG

access-controls

Version:

rule based access-controls engine for node.js (browser compatible)

248 lines (210 loc) 7 kB
var _ = require('lodash') var ACL = require('../lib/AccessControlList.js') var patrun = require('patrun') /** An access control procedure runs a set of ACLs against a given pair of <entity> and <action> */ function AccessControlProcedure(acls) { this._accessControls = [] if(acls) { this.addAccessControls(acls) } } AccessControlProcedure.ACL = ACL AccessControlProcedure.generateActionsMapping = function(accessControls) { var mapping = patrun() for(var i = 0 ; i < accessControls.length ; i++) { var aclDefinition = accessControls[i] for(var j = 0 ; j < aclDefinition.entities.length ; j++) { var actions = aclDefinition.actions for(var k = 0 ; k < actions.length ; k++) { var argsMatching = _.clone(aclDefinition.entities[j]) argsMatching.role = 'entity' // TODO: differentiate create from update switch(actions[k]) { case 'save': case 'save_new': case 'save_existing': argsMatching.cmd = 'save' break case 'load': argsMatching.cmd = 'load' break case 'list': argsMatching.cmd = 'list' break case 'remove': argsMatching.cmd = 'remove' break default: throw new Error('unsupported action ['+actions[k]+'] in ' + JSON.stringify(aclDefinition)) } var aclProcedure = mapping.find(argsMatching) if(!aclProcedure) { aclProcedure = new AccessControlProcedure() mapping.add(argsMatching, aclProcedure) } aclProcedure.addAccessControls(aclDefinition) } } } return mapping } /** * mapping: patrun mapping returned by AccessControlProcedure.generateActionsMapping() * entityDef: { zone: ..., base: ..., name: ... } * action: 'load' | 'list' | 'save' | 'remove' */ AccessControlProcedure.getProcedureForEntity = function(mapping, entityDef, action) { return mapping.find({role: 'entity', zone: entityDef.zone, base: entityDef.base, name: entityDef.name, cmd: action}) } AccessControlProcedure.prototype.addAccessControls = function(acl) { if(_.isArray(acl)) { for(var i = 0 ; i < acl.length ; i++) { this.addAccessControls(acl[i]) } } else if(_.isObject(acl)) { this._accessControls.push(new ACL(acl)) } else { throw new Error('unsuported ACL object type: ' + typeof acl) } } AccessControlProcedure.prototype.authorize = function(obj, action, roles, context, callback) { this._nextACL(obj, action, roles, this._accessControls.slice(0), context, undefined, function(err, details) { callback(err, details) }) } AccessControlProcedure.prototype._nextACL = function(obj, action, roles, accessControls, context, details, callback) { if(!details) { details = {authorize: true} } if(!details.history) { details.history = [] } if(!details.inherit) { details.inherit = [] } if(!details.summary) { details.summary = [] } details.context = context details.roles = roles details.action = action details.hard = details.hard || false var self = this if(accessControls && accessControls.length > 0) { var accessControl = accessControls.shift() var shouldApply = accessControl.shouldApply(obj, action) // inherited acls can be bypassed via inherit.allow on the acl definition if (shouldApply.ok && context.inherit$) { shouldApply = accessControl.shouldApplyInherited(obj, action, roles, context) } if(shouldApply.ok) { //console.log('running authorization service', accessControl.name()) accessControl.authorize(obj, action, roles, context, function(err, result) { details.history.push({ service: accessControl.name(), authorize: result ? result.authorize : null, control: accessControl.control(), err: err || null, reason: result ? result.reason : null, hard: result ? result.hard : null }) //console.log(obj, action, roles, JSON.stringify(result)) if(err || !result) { details.authorize = false callback(err, details) } if(result.inherit) { details.inherit = details.inherit.concat(result.inherit) } var stop = false switch(accessControl.control()) { case 'filter': if(!details.filters) { details.filters = [] } if(result.filters) { details.filters = details.filters.concat(result.filters) } break case 'requisite': if(!result.authorize) { details.hard = details.hard || result.hard details.authorize = false stop = true details.summary.push({ service: accessControl.name(), reason: result.reason, missingRoles: result.missingRoles }) } break case 'required': if(!result.authorize) { details.hard = details.hard || result.hard details.authorize = false details.summary.push({ service: accessControl.name(), reason: result.reason, missingRoles: result.missingRoles }) } break case 'sufficient': if(result.authorize) { details.authorize = true stop = true details.summary = [{ service: accessControl.name(), reason: result.reason }] } break } if(stop) { callback(undefined, details) } else { self._nextACL(obj, action, roles, accessControls, context, details, callback) } }) } else { //console.log('ignoring authorization service', accessControl.name(), '. reason:', shouldApply.reason) self._nextACL(obj, action, roles, accessControls, context, details, callback) } } else { callback(undefined, details) } } AccessControlProcedure.prototype.applyFilters = function(filters, obj, action) { var filterType = 'read' switch(action) { case 'save': case 'save_new': case 'save_existing': filterType = 'write' break //case 'load': //case 'list': default: filterType = 'read' break } if(filters && filters.length > 0) { for(var i = 0 ; i < filters.length ; i++) { var filter = filters[i] switch(filter.access) { case 'denied': delete obj[filter.attribute] break case 'partial': if(filterType === 'read') { obj[filter.attribute] = filter.filteredValue } else { delete obj[filter.attribute] } break } } } } module.exports = AccessControlProcedure