From d57ca4242954f65ca881641d0508ccf2e6339a76 Mon Sep 17 00:00:00 2001 From: Moritz Onken Date: Thu, 13 Aug 2015 22:33:47 -0400 Subject: [PATCH] support NotResource/NotAction --- README.md | 2 +- output/intro.md | 2 +- pbac.js | 54 +++++++++++------------------ t/notaction.js | 90 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 t/notaction.js diff --git a/README.md b/README.md index 709c91e..4c5c286 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Use the power and flexibility of the AWS IAM Policy syntax in your own application to manage access control. For more details on AWS IAM Policies have a look at https://docs.aws.amazon.com/IAM/latest/UserGuide/policies_overview.html. -**Note:** The policy elements `Principal`, `NotPrincipal`, `NotResource` and conditions `ArnEquals`, `ArnNotEquals`, `ArnLike`, `ArnNotLike` are not supported at the moment. +**Note:** The policy elements `Principal`, `NotPrincipal` and conditions `ArnEquals`, `ArnNotEquals`, `ArnLike`, `ArnNotLike` are not supported at the moment. ## Installation diff --git a/output/intro.md b/output/intro.md index 3b3259f..1423994 100644 --- a/output/intro.md +++ b/output/intro.md @@ -6,7 +6,7 @@ Use the power and flexibility of the AWS IAM Policy syntax in your own application to manage access control. For more details on AWS IAM Policies have a look at https://docs.aws.amazon.com/IAM/latest/UserGuide/policies_overview.html. -**Note:** The policy elements `Principal`, `NotPrincipal`, `NotResource` and conditions `ArnEquals`, `ArnNotEquals`, `ArnLike`, `ArnNotLike` are not supported at the moment. +**Note:** The policy elements `Principal`, `NotPrincipal` and conditions `ArnEquals`, `ArnNotEquals`, `ArnLike`, `ArnNotLike` are not supported at the moment. ## Installation diff --git a/pbac.js b/pbac.js index e6f48ee..464aa71 100644 --- a/pbac.js +++ b/pbac.js @@ -5,8 +5,6 @@ var _ = require('lodash'), ZSchema = require('z-schema'), util = require('util'); - - var PBAC = function constructor(policies, options) { options = _.isPlainObject(options) ? options : {}; var myconditions = _.isPlainObject(options.conditions) ? _.extend(options.conditions, conditions) : conditions; @@ -18,22 +16,24 @@ var PBAC = function constructor(policies, options) { conditions: myconditions, }); this.addConditionsToSchema(); - if(this.validateSchema) this._validateSchema(); + if (this.validateSchema) this._validateSchema(); this.add(policies); }; _.extend(PBAC.prototype, { add: function add(policies) { policies = _.isArray(policies) ? policies : [policies]; - if(this.validatePolicies) this.validate(policies); + if (this.validatePolicies) this.validate(policies); this.policies.push.apply(this.policies, policies); }, addConditionsToSchema: function addConditionsToSchema() { var definition = _.get(this.schema, 'definitions.Condition'); - if(!definition) return; + if (!definition) return; var props = definition.properties = {}; _.forEach(this.conditions, function(condition, name) { - props[name] = { type: 'object' }; + props[name] = { + type: 'object' + }; }, this); }, _validateSchema: function() { @@ -41,13 +41,6 @@ _.extend(PBAC.prototype, { if (!validator.validateSchema(this.schema)) this.throw('schema validation failed with', validator.getLastError()); }, - /** - * Validates one or many policies against the schema provided in the constructor. - * Will throw an error if validation fails. - * - * @param {object} policy - Array of policies or single policy object - * @return {boolean} Returns `true` if the policies are valid - */ validate: function validate(policies) { policies = _.isArray(policies) ? policies : [policies]; var validator = new ZSchema({ @@ -60,17 +53,6 @@ _.extend(PBAC.prototype, { return result; }.bind(this)); }, - /** - * Tests an object against the policies and determines if the object passes. - * The method will first try to find a policy with an explicit `Deny` for the combination of - * `resource`, `action` and `condition` (matching policy). If such policy exists, `evaulate` returns false. - * If there is no explicit deny the method will look for a matching policy with an explicit `Allow`. - * `evaulate` will return `true` if such a policy is found. If no matching can be found at all, - * `evaluate` will return `false`. - * - * @param {object} object - Object to test against the policies - * @return {boolean} Returns `true` if the object passes, `false` otherwise - */ evaluate: function evaluate(options) { options = _.extend({ action: '', @@ -96,15 +78,14 @@ _.extend(PBAC.prototype, { return _(this.policies).pluck('Statement').flatten().find(function(statement, idx) { if (statement.Effect !== options.effect) return false; var actionApplies = false; - if (!this.evaluateResource(statement.Resource, options.resource, options.variables)) + if (statement.Resource && !this.evaluateResource(statement.Resource, options.resource, options.variables)) + return false; + if (statement.NotResource && this.evaluateResource(statement.NotResource, options.resource, options.variables)) + return false; + if (statement.Action && !this.evaluateAction(statement.Action, options.action)) + return false; + if (statement.NotAction && this.evaluateAction(statement.NotAction, options.action)) return false; - if (statement.Action) actionApplies = _.find(statement.Action, function(action) { - return this.conditions.StringLike(action, options.action); - }.bind(this)) ? true : false; - if (statement.NotAction) actionApplies = _.all(statement.NotAction, function(action) { - return this.conditions.StringNotLike(action, options.action); - }.bind(this)); - if (!actionApplies) return false; return this.evaluateCondition(statement.Condition, options.variables); }.bind(this)); }, @@ -113,12 +94,17 @@ _.extend(PBAC.prototype, { return this.getVariableValue(variable, variables); }.bind(this)); }, - getVariableValue: function(variable, variables) { + getVariableValue: function getVariableValue(variable, variables) { var parts = variable.split(':'); if (_.isPlainObject(variables[parts[0]]) && !_.isUndefined(variables[parts[0]][parts[1]])) return variables[parts[0]][parts[1]]; else return variable; }, + evaluateAction: function evaluateAction(actions, reference) { + return _.find(actions, function(action) { + return this.conditions.StringLike.call(this, action, reference); + }.bind(this)); + }, evaluateResource: function evaluateResource(resources, reference, variables) { resources = _.isArray(resources) ? resources : [resources]; return _.find(resources, function(resource) { @@ -139,7 +125,7 @@ _.extend(PBAC.prototype, { }.bind(this)); }.bind(this)); }, - throw: function(name, message) { + throw: function (name, message) { var args = [].slice.call(arguments, 2); args.unshift(message); var e = new Error(); diff --git a/t/notaction.js b/t/notaction.js new file mode 100644 index 0000000..d083b1e --- /dev/null +++ b/t/notaction.js @@ -0,0 +1,90 @@ +var assert = require('assert'), + PBAC = require('../pbac'); + +var tests = [{ + name: 'explicit deny overwrites allow', + policies: [{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Deny", + "Action": [ + "iam:CreateUser" + ], + "Resource": [ + "*" + ] + }, { + "Effect": "Allow", + "Action": [ + "iam:*User" + ], + "Resource": [ + "abc*" + ] + }] + }], + tests: [{ + params: { + action: 'iam:CreateUser', + resource: 'abcfoo', + }, + result: false, + }, { + params: { + action: 'iam:UpdateUser', + resource: 'abcfoo', + }, + result: true, + }, { + params: { + action: 'iam:UpdateUser', + resource: 'foo', + }, + result: false, + }] +}, { + name: 'implicit deny with NotAction', + policies: [{ + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "NotAction": [ + "iam:CreateUser" + ], + "NotResource": [ + "abc*" + ] + }] + }], + tests: [{ + params: { + action: 'iam:CreateUser', + }, + result: false, + }, { + params: { + action: 'iam:UpdateUser', + }, + result: true, + }, { + params: { + resource: 'abcfoo', + action: 'iam:UpdateUser', + }, + result: false, + }] +}]; + + +describe('policies', function() { + tests.forEach(function(test, idx) { + var pbac = new PBAC(test.policies); + it(test.name, function() { + test.tests.forEach(function(params) { + assert.equal(!!pbac.evaluate(params.params), params.result); + }); + }); + + }); + +});