Last active
December 17, 2015 19:09
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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)); | |
} | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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