Skip to content

Instantly share code, notes, and snippets.

@dasher
Last active December 17, 2015 19:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dasher/5658045 to your computer and use it in GitHub Desktop.
Save dasher/5658045 to your computer and use it in GitHub Desktop.
Nested Policy Support Allows a policySet to be created that depends on a number of factors - in the current state this allows the path & the controller::action to define the policy set - it also allows the target of the policy to be either the policy file (the existing sails policy system) or a controller::action
'use strict';
var _ = require('lodash');
_.string = require("underscore.string");
// enhance the console a little
require("consoleplusplus");
// Timestamps not needed
console.disableTimestamp();
// Mock
var sails = {
"log": {
"debug": debug,
"error": debug,
"info": debug,
"verbose": debug
}
}
// Given the following policy
// - build a policy chain from the bottom up
// - allowing lower level policies to be applied before a more specific controller:action
var policies = require("./testcase-policies.js").policies;
var tests = require("./testcase-policies.js").tests;
// Just a helper for debugging
function debug() {
if (arguments.length>1) {
if (!_.first(arguments).match(/%s/)) {
arguments[0] = "#cyan{"+arguments[0]+":} #yellow{%s}";
}
console.debug(_.string.sprintf(_.first(arguments), _.rest(arguments)));
} else {
console.log(arguments[0]);
}
}
function recursePolicyRoute(route, policies) {
sails.log.info("recursePolicyRoute [%s][%s]", JSON.stringify(route), JSON.stringify(policies));
var matchedPolicies = [];
var routePath = [];
var routeParts = route.split(/\//);
var policyPath = policies;
// First deal with the route
_.each(routeParts,
function(r){
sails.log.verbose("#cyan{r}",r);
// Special case - usually at the start
if (policyPath[routePath.join("/")+"*"]) {
sails.log.verbose("#red{::1}");
// Only string or array policies allowed for *
if (_.isString(policyPath[routePath.join("/")+"*"])
|| (_.isArray(policyPath[routePath.join("/")+"*"]))) {
matchedPolicies.push(policyPath[routePath.join("/")+"*"]);
} else {
sails.log.error('Invalid policy configuration: The target of a * policy MUST be a string or string array');
sails.log.info(policyPath[routePath.join("/")+"*"]);
process.exit(1);
}
}
sails.log.verbose("#red{1.2} ["+routePath.join("/")+"*"+"]",matchedPolicies);
// Special case - usually at the start
if (policyPath[routePath.join("/")+"/*"]) {
if (_.isString(policyPath[routePath.join("/")+"/*"]) || (_.isArray(policyPath[routePath.join("/")+"/*"]))) {
sails.log.debug("#red{::1-1}");
matchedPolicies.push(policyPath[routePath.join("/")+"/*"]);
} else {
sails.log.error('Invalid policy configuration: The target of a /* policy MUST be a string or string array');
sails.log.info(policyPath[routePath.join("/")+"/*"]);
process.exit(1);
}
}
sails.log.verbose("#red{1-1.2} ["+routePath.join("/")+"*"+"]",matchedPolicies);
if (policyPath[r]) {
sails.log.verbose("#red{::2}");
if (_.isString(policyPath[r]) || _.isArray(policyPath[r])) {
// If the target is a simple policy - apply and move on
matchedPolicies.push(policyPath[r]);
} else {
// With complex policies - we need to recurse
matchedPolicies.push(route.replace(routePath.join("/"),""), recursePolicyRoute(policyPath[r]));
}
}
sails.log.verbose("#red{2} ["+r+"]",matchedPolicies);
// build a composition of the route path processed so far
sails.log.verbose("#green{routePath} ["+r+"]", routePath, routePath.join("/"));
// Append the current path part to the tracking list
routePath.push(r);
if (policyPath[routePath.join("/")]) {
sails.log.verbose("#red{::3}");
if (_.isString(policyPath[routePath.join("/")]) || _.isArray(policyPath[routePath.join("/")])) {
// If the target is a simple policy - apply and move on
matchedPolicies.push(policyPath[routePath.join("/")]);
} else {
// otherwise with complex policies - we need to recurse
sails.log.verbose("#red{::3.2}", routePath.join("/"), route, route.replace(routePath.join("/"),""));
matchedPolicies.push(recursePolicyRoute(route.replace(routePath.join("/"),""), policyPath[routePath.join("/")]));
}
}
sails.log.verbose("#red{3} ["+routePath.join("/")+"]",matchedPolicies);
}
)
return matchedPolicies;
}
function recursePolicyController(controller, actionName, verb, policies) {
// Normalise the controller name
var camelController = require('inflection').camelize(controller) + "Controller";
var matchedPolicies = [];
var policyPath = policies;
sails.log.verbose("Camel", camelController);
// We have a much simpler process with controllers & actions - so lets apply a little help to speed things along
var matchedControllers = _.intersection(_.keys(policies), [camelController, camelController+"::"+actionName]);
sails.log.debug("matchedControllers", matchedControllers, [camelController, camelController+"::"+actionName]);
if (matchedControllers) { // We have a controller definition
_.each(matchedControllers,
function (matchedController) {
// _.isObject(policyPath[matchedController])
if (_.isString(policyPath[matchedController]) || _.isArray(policyPath[matchedController])) {
sails.log.info("Simple String for "+matchedController);
// String or string array
matchedPolicies.push(policyPath[matchedController]);
} else {
sails.log.info("We have a complex object", policyPath[matchedController])
policyPath = policyPath[matchedController];
if (policyPath['*']) {
matchedPolicies.push(policyPath['*']);
sails.log.debug("Controller Matched: *")
}
if (policyPath["verbs"] && policyPath["verbs"][verb.toUpperCase()]) {
sails.log.debug("Controller verb matched "+verb);
matchedPolicies.push(policyPath["verbs"][verb.toUpperCase()]);
}
if (policyPath[actionName]) {
sails.log.debug("Controller action matched "+actionName);
matchedPolicies.push(policyPath[actionName]);
}
}
}
)
}
return matchedPolicies;
}
function matchRouteToPolicyPlan(route, policies, controller, actionName, verb) {
var matchedPolicies = [];
var routeParts = route.split(/\//);
var routePath = [];
var policyPath = policies;
// First iterate through the url path
matchedPolicies.push(recursePolicyRoute(route, policyPath));
// Now deal with the controller, action & verbs
// Reset the controller
policyPath = policies;
matchedPolicies.push(recursePolicyController(controller, actionName, verb, policyPath));
if (!_.isEmpty(matchedPolicies)) {
matchedPolicies = _.uniq(_.flatten(matchedPolicies));
return matchedPolicies;
} else {
return null;
}
}
function isValidControllerAction(controller, action) {
return (sails.controllers[controller.toLowerCase()])
&& (sails.controllers[controller.toLowerCase()][action.toLowerCase()])
&& (_.isFunction(sails.controllers[controller.toLowerCase()][action.toLowerCase()]));
}
function convertPlanToChainable(policyPlan) {
_.each(policyPlan,
function(planItem){
if (!planItem.match(/::/)) {
// Handle policy files first
// If we're here - then we don't have a controller::action as this planItem
} else {
// we have a controller::action
planItem = planItem.split(/::/);
if (isValidControllerAction(planItem[0], planItem[1])) {
// We have a winner
} else {
console.log(planItem[0]+":L:"+planItem[1]+" Invalid ")
}
}
}
);
}
var testIndex = 6;
var route = tests[testIndex].path;
var policyPlan = matchRouteToPolicyPlan(route, policies, tests[testIndex].controller, tests[testIndex].action, "get");
//convertPlanToChainable(policyPlan);
sails.log.info("Route ["+route+"] results in "+policyPlan.length+" items");
_.each(policyPlan,
function(policyItem){
sails.log.info("\t"+JSON.stringify(policyItem));
}
)
module.exports.policies = {
'*': ['base-all-*'],
// examples of path based policies
'/admin/*': ['paths-admin-*'],
'/admin/one': ['paths-admin-one'],
'/admin/one/one': ['paths-admin-one-one'],
'/admin/two/one': ['paths-admin-two-one'],
'/pathA/pathB': ['paths-pathA-pathB'],
// paths expressed as more complex objects
'/users': {
'*': 'object-users-*',
'/first': {
'/second': ['object-first-second']
}
},
// Examples of controller policies
'TestController': ['inline-controller-testController'],
'TestController::index': ['inline-controller-testController-index'], // Yep - worked with controller::action compositions
// and again in object form - much the same as with sails currently - but allowing for verbs - and policies are inherited
'EditorController': {
'*': ['object-controller-editController-*'],
'verbs': {
'GET': ['object-controller--editController-verbs-get'],
'POST': ['object-controller-editorController-verbs-post']
},
'page': ['object-controller-editorController-page']
}
};
module.exports.tests = [
{path: "/admin/one/one", controller: "test", action: "index", expect: 6},
{path: "/users/first/second", controller: "editor", action: "page", expect: 6},
{path: "/admin/two/one", controller: "test", action: "index", expect: 5},
{path: "/users/first", controller: "editor", action: "page", expect: 5},
{path: "/NotFound/first", controller: "editor", action: "page", expect: 4},
{path: "/NotFound/second", controller: "noController", action: "index", expect: 1},
{path: "/NotFound/second", controller: "test", action: "second", expect: 2},
];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment