tdp-ah-acl
v0.3.5-alpha
Published
A simple yet powerful NodeJS Access Control List (ACL) module which is designed to work with TDPAHAuth and thus ActionHero
Downloads
5
Maintainers
Readme
##Overview TDPAHACL is designed to be a simple and fast access control list (ACL) module written in NodeJS, built specifically for use with the awesome ActionHeroJS API server. TDPAHACL controls access to actionHero actions based on user roles (complete with recursive inheritance) based on a simple JSON config file.
##Version Master: 0.3.5-alpha - Not recommended for production use
##Warning! This module is under development and should not be considered complere or secure...yet
##Operating system support Operating system support should not be a major issue for this module though I always mention in my projects for transparency that I do not design or test for correct function on non-*nix-like OS's (e.g. Windows). If you want to test/develop and submit pull requests for Windows support then I will certainly consider them but likely only if they have minimal to no impact on performance and reliability for *nix-like OSs. The reason for this is simply that I personally don't consider Windows to be a serious server operating system and thus a good direction in which to spend my time and i'm not trying to offend anyone. I realise that others may completely differ in opinion and you're always welcome (obviously) to fork the repo and create your own version with Windows support. If my time were unlimited then I would likely spend some time on Windows support but sadly it's not.
##Dependencies TDPAHACL itself has a few dependencies (which are all NPM modules):
- Normal usage:
- Installer:
- tdp-glob-file-copier - glob file copier, used in the installer script
- Testing:
##Issues/problems Please log any issues/problems in Github issues for this project.
##Installation Installation is simple, either:
- From your command line (under the relevant user account):
npm install tdp-ah-acl
or - Include
tdp-ah-acl
as a dependency in your NPM package.json file andnpm install
your module
or - clone the Github repo
git clone git://github.com/neilstuartcraig/TDPAHACL.git
(assuming you have git installed). You'll probably need to amend the require() path if you use this method (unless you clone into your node_modules directory)
##Usage
Once you have installed the NPM module, you can require()
the module as normal in your script, then use the new constructor and finially initialise e.g.
var TDPAHACL=require("tdp-ah-acl");
var tdpahacl=new TDPAHACL();
tdpahacl.init(api, "../config/TDPAHACL/TDPAHACLConfig.js", __dirname);
Then you'll need to run the ACL check, usually in an actionHero action preProcessor e.g:
var TDPAHACLPreProcessor=function(connection, actionTemplate, next)
{
tdpahacl.roleHasPermissionsOnAction(userRole, actionTemplate.name, actionTemplate.version, function TDPAHACLRoleHasPermissionsOnAction(roleAllowed)
{
if(roleAllowed)
{
next(connection, true);
}
else
{
next(connection, false);
}
});
};
// You could use unshift or push here
api.actions.preProcessors.unshift(TDPAHACLPreProcessor);
For a complete, working example, see the example initialiser file in this module.
##Configuration Configuration is via either a directly passed-in standard javascript object or a file (as shown below) which contains a valid NodeJS module which exports exactly/only a javascript object in the correct format.
###Configuration object format The format for the config file is:
var ACLTestConfig=
{
ruleProcessingOrder:"deny,allow", // "deny,allow" or "allow,deny"
allowReinitialisation:false, // true/false
exitOnRoleProcessingError:true, // true/false
rules: // object containing sub-objects, one per role
{
role1:
{
inheritsFromRoles:["<ROLE-2>","<ROLE-3>"], // Array of roles to inherit from
allow: // Array of actions (an optionally version ranges) to allow for this role
[
"<ACTION-NAME-1>[:<SEMVER-ACTION-1-VERSION-RANGE]",
"<ACTION-NAME-2>[:<SEMVER-ACTION-2-VERSION-RANGE]"
],
deny: // Array of actions (an optionally version ranges) to deny for this role
[
"<ACTION-NAME-3>[:<SEMVER-ACTION-3-VERSION-RANGE]",
"<ACTION-NAME-4>[:<SEMVER-ACTION-4-VERSION-RANGE]"
]
},
...
roleN:
{
...
},
...
}
}
###Configuration notes: Most of the config should be self-explanatory but some notes are:
- By default, access is denied i.e. if you do not explicitly allow access for a role to an action, it will be denied. This is not overrideable as it's a sensible behaviour which allows the least chance of incorrect configuration
- ruleProcessingOrder controls the order in which rules are processed and calculated e.g. allow,deny order with an allow {"*"} then a deny{"admin"} would allow that role access to everything except the "admin" action
- If a role inherits permissions from one or more other roles, permissions set on that user will override any inherited permissions ie.g. is user1 is denied access to actionA and user1 inherits from user2 who is allowed access to actionA, user1 will not be able to access actionA
- Inherited roles are applied right to left in the order specified in inheritsFromRoles
- Action version-specific rules are supported, you can (optionally) define semantic version compatible version ranges
- Action versions which are not semver compatible will be automatically converted during processing e.g. a version of 2 will be converted to 2.0
- allowReinitialisation is a simple control to prevent the init() function being re-run whilst actionHero is running - this is just a basic security measure to help prevent 3rd party modules overwriting the ACL
- exitOnRoleProcessingError will cause the init() function to exit if it encounters errors whilst processing ACL rules
- ACL rules for actions/versions which do not exist will be ignored and not rolled into the calculated ACL
###Configuration via config file
Configuration via config file is as simple as passing a path to the config file to the init()
function e.g.:
var TDPAHACL=require("tdp-ah-acl");
var tdpahacl=new TDPAHACL();
tdpahacl.init(api, "../config/TDPAHACL/TDPAHACLConfig.js", __dirname);
###Configuration via JavaScript object
Configuration via object is a simple matter of passing a JavaScript object to the run()
function e.g. (these rules are contrived so don't read too deeply into them specifically):
var ACLConfig=
{
ruleProcessingOrder:"deny,allow",
allowReinitialisation:false,
exitOnRoleProcessingError:true,
rules:
{
superUser:
{
allow:
[
"*"
]
},
admin:
{
inheritsFromRoles:["statsUser","authenticatedUser"],
allow:
[
"admin/*:>=2.0"
],
deny:
[
"admin/*:>=3.0"
]
},
statsUser:
{
allow:
[
"stats/appInfo:>=0.0",
"stats/serverInfo:>=0.0",
"stats/graphs/*"
],
deny:
[
"stats/graphs/sensitiveInfo"
]
},
authenticatedUser:
{
inheritsFromRoles:["public"],
allow:
[
"pageContent:>=2.0",
"navigation:>=1.0"
],
deny:
[
]
},
public:
{
allow:
[
"login:>=3.0"
]
}
}
}
module.exports=ACLConfig;
tdpahacl.init(api, ACLConfig);
##Usage TDPAHACL has a number of functions and variables, most of these are private to the instance itself, you can see them by looking at the source code of the main module. The public functions are as follows:
###TDPAHACL.init(api, configString, relativeToPath) ####Overview TDPAHACL.init() is the (synchronous) initialisation function which sets up the instance, importing config and creating the ACL map (a private object) which controls access to actions.
####Parameters
- api - (Object) the actionHero API main object
- configString - (String or Object) either a Javascript configuration object literal or a path to a config file which contains a NodeJS module as configuration - both as per the above
- relativeToPath - (String) if configString is a path to a config file, this is (optionally) a path to which the configString is relative
####Example usage See the example initialiser for an example use (or the example above).
###TDPAHACL.roleHasPermissionsOnAction(role, actionName, actionVersion, callback)
####Overview TDPAHACL.init() is the (asynchronous) ACL rule check function which determines whether or not a role has permission to run an action/version.
####Parameters
- role - (String) the role of the user for whom permissions are being checked
- actionName - (String) the name (action.name) of the actionHero action which is being checked
- actionVersion - (String / Semantic version number or integer) the version (action.version) of the ationHero action being checked. Note: integer numbers will be automatically converted to semantic version numbers e.g. 1 will become 1.0
- callback - (Function) a callback function which will be passed one boolean parameter, roleAllowed which is true if the role has permission to run the action/version being tested, false otherwise (including when no relevant rule exists)
####Example usage See the example initialiser for an example use (or the example above).
###TDPAHACL.normaliseActionName(actionName) ####Overview This (synchronous) function is not expected to be used often but is a simple method which normalises action.name's passed in to this module to try to ensure consistency and reduce the likelihood of error. It currently simply trims whitespace from the beginning and end of the actionName.
####Parameters
- actionName - (String) the action.name being processed
####Example usage var normalisedActionName=TDPAHACL.normaliseActionName(actionName);
###TDPAHACL.normaliseActionVersion(actionVersion) ####Overview This (synchronous) function is not expected to be used often but is a simple method which normalises action.version's passed in to this module to try to ensure consistency and reduce the likelihood of error. It converts integer action.versions into semantic version numbers e.g. 1 becomes '1.0'.
####Parameters
- actionVersion - (String) the action.version being processed
####Example usage var normalisedActionVersion=TDPAHACL.normaliseActionVersion(actionVersion);
##Logging TDPAHACL uses the built-in winston logging from actionHero (configuration information). The bundled tests however just use console.log (and not in a very clever way) for simplicity.
##How it works Since actionHero API apps are well-defined in terms of their available endpoints (publicly available API methods), TDPAHACL works a little differently to most other ACLs which do not have such strong definition - at least it's different to those I have previously used. The major setup is performed by the init() method, no real surprises there, what init() does is roughly:
- Read in config files and overload userland config onto the default config (for uniformity and default config inheritance)
- Runs through each API action (and version) and recursively calculates which roles are allowed to access them, creating an in-memory map/hash of this
This means we offload the vast majority of the processing work to the initialisation stage, meaning our runtime process is fast, really fast - all that's needed is a test on the ACL map hash for each action/version requested.
If you need to know more, you're probably best looking through the main module library and the example actionHero initialiser.
##Why is TDPAHACL not param(eter) aware? This is something I considered however, as a general rule, your API should be granular enough that you can appropriately restrict permissions based on the action name only. For example, a well-modularised API should have endpoints roughly as follows:
- auth/login
- auth/register
- auth/delete
- auth/logout
Rather than a single auth endpoint which might receive a parameter which determined the action (login/register/delete/logout) to perform.
incidentally, you can achieve the above in actionHero by creating a directory/folder inside the "actions" directory/folder called "auth", adding actions inside the "auth" directory/folder called login, register, delete and logout whose action.name is "auth/login", "auth/register", "auth/delete" and "auth/logout".
Parameter-awareness may appear at some later stage in TDPAHACL but don't count on it! Relying on the action name and version alone is faster and simpler yet still works for most use-cases. You're welcome to raise an issue if you really need parameter awareneness (or fork the project).
##Security
Security is obviously (being an ACL) a pretty high priority for this module. A key part of the security is in the public/private methods of the module, achieved by using function-scope, only listing public methods where strictly necessary.
The security of the module will be subject to continuous evaluation and suggestions/pull requests which improve the situation are very much appreciated.
##To do
- SHOULD BE DONE: installer script using TDPGlobFileCopier
- Test usage as NPM module - does defaultConfig.js work? (run tests to verify)
- Finalise logging
- Finalise tests
- Need to add test(s) to verify:
- ruleProcessingOrder
- allowReinitialisation
- exitOnRoleProcessingError
- Need to add test(s) to verify:
- Move getConfigObject and overloadJSObject out to a module
##Change log Major changes are listed below:
- v0.3 - getACLMapForActionVersion() is now more comprehensive and fully recursive (with role inheritance), consequently some parameters changed. Strict mode.
- v0.2 - roleHasPermissionsOnAction() is now asynchronous
- v0.1 - Initial version
##License TDPAHACL is issued under a Creative Commons attribution share-alike license. This means you can share and adapt the code provided you attribute the original author(s) and you share your resulting source code. If, for some specific reason you need to use this library under a different license then please contact me and i'll see what I can do - though I should mention that I am committed to all my code being open-source so closed licenses will almost certainly not be possible.