From ebd07a621fe57cbee3f78e0e1e56c4ba61302a18 Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Sun, 1 Apr 2018 18:11:46 -0700 Subject: [PATCH 1/9] replace ipcheck package with one that works in browsers --- conditions.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/conditions.js b/conditions.js index 0f855ee..69783c1 100644 --- a/conditions.js +++ b/conditions.js @@ -1,5 +1,5 @@ 'use strict'; -const ipcheck = require('ipcheck'); +const ipRangeCheck = require('ip-range-check'); const isString = require('lodash/isString'), isBoolean = require('lodash/isBoolean'), @@ -97,7 +97,7 @@ const conditions = { return b ? isUndefined(a) : !isUndefined(a); }, IpAddress(a, b) { - return ipcheck.match(a, b); + return ipRangeCheck(a, b || ''); }, NotIpAddress() { return !this.conditions.IpAddress.apply(this, arguments); diff --git a/package.json b/package.json index 5a8627f..c52b537 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.2.0", "description": "AWS IAM Policy inspired and (mostly) compatible evaluation engine", "dependencies": { - "ipcheck": "^0.1.0", + "ip-range-check": "0.0.2", "lodash": "^4.17.5", "z-schema": "^3.19.1" }, From eaf3e80dcd6325c8d1bf613b362ac9abefb735e7 Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Sun, 1 Apr 2018 18:13:57 -0700 Subject: [PATCH 2/9] require less modules so they're easier to externalize in bundles --- conditions.js | 19 ++++++++++--------- pbac.js | 23 +++++++++++------------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/conditions.js b/conditions.js index 69783c1..f292f52 100644 --- a/conditions.js +++ b/conditions.js @@ -1,15 +1,16 @@ 'use strict'; const ipRangeCheck = require('ip-range-check'); -const isString = require('lodash/isString'), - isBoolean = require('lodash/isBoolean'), - isNumber = require('lodash/isNumber'), - isArray = require('lodash/isArray'), - isUndefined = require('lodash/isUndefined'), - isEmpty = require('lodash/isEmpty'), - forEach = require('lodash/forEach'), - every = require('lodash/every'); - +const { + isString, + isBoolean, + isNumber, + isArray, + isUndefined, + isEmpty, + forEach, + every, +} = require('lodash'); const conditions = { NumericEquals(a, b) { diff --git a/pbac.js b/pbac.js index 2c962aa..b0b290f 100644 --- a/pbac.js +++ b/pbac.js @@ -4,19 +4,18 @@ const conditions = require('./conditions'); const ZSchema = require('z-schema'); const util = require('util'); -const isPlainObject = require('lodash/isPlainObject'); -const isBoolean = require('lodash/isBoolean'); -const isArray = require('lodash/isArray'); -const isUndefined = require('lodash/isUndefined'); -const isEmpty = require('lodash/isEmpty'); -const forEach = require('lodash/forEach'); -const every = require('lodash/every'); -const get = require('lodash/get'); +const { + isPlainObject, + isBoolean, + isArray, + isUndefined, + isEmpty, + forEach, + every, + get, +} = require('lodash'); -const flow = require('lodash/fp/flow'); -const map = require('lodash/fp/map'); -const flatten = require('lodash/fp/flatten'); -const find = require('lodash/fp/find'); +const { flow, map, flatten, find } = require('lodash/fp'); const PBAC = function constructor(policies, options) { options = isPlainObject(options) ? options : {}; From 68eec5d0d3492d047469bbca3efcedcf1856c73a Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Sun, 1 Apr 2018 18:16:30 -0700 Subject: [PATCH 3/9] avoid using util to format --- pbac.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pbac.js b/pbac.js index b0b290f..303adec 100644 --- a/pbac.js +++ b/pbac.js @@ -2,7 +2,6 @@ const policySchema = require('./schema.json'); const conditions = require('./conditions'); const ZSchema = require('z-schema'); -const util = require('util'); const { isPlainObject, @@ -51,7 +50,7 @@ Object.assign(PBAC.prototype, { _validateSchema() { const validator = new ZSchema(); if (!validator.validateSchema(this.schema)) - this.throw('schema validation failed with', validator.getLastError()); + this.throw('schema validation failed with ' + validator.getLastError()); }, validate(policies) { policies = isArray(policies) ? policies : [policies]; @@ -60,8 +59,7 @@ Object.assign(PBAC.prototype, { }); return every(policies, policy => { const result = validator.validate(policy, this.schema); - if (!result) - this.throw('policy validation failed with', validator.getLastError()); + if (!result) this.throw('policy validation failed with ' + validator.getLastError()); return result; }); }, @@ -170,11 +168,9 @@ Object.assign(PBAC.prototype, { }); }, throw(name, message) { - const args = [].slice.call(arguments, 2); - args.unshift(message); const e = new Error(); e.name = name; - e.message = util.format.apply(util, args); + e.message = message; throw e; }, }); From 53d677eac8661ca4d7c272ce42bc9b37fb91a62c Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Sun, 1 Apr 2018 18:16:57 -0700 Subject: [PATCH 4/9] avoid using Buffer in browser builds --- browser.js | 1 + conditions.js | 20 ++++++++++++++++---- package.json | 3 +++ t/conditions.js | 14 +++++++++++--- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 browser.js diff --git a/browser.js b/browser.js new file mode 100644 index 0000000..7103c68 --- /dev/null +++ b/browser.js @@ -0,0 +1 @@ +global.atob = require('atob'); diff --git a/conditions.js b/conditions.js index f292f52..c88eddf 100644 --- a/conditions.js +++ b/conditions.js @@ -68,12 +68,24 @@ const conditions = { return !this.conditions.DateGreaterThan.apply(this, arguments); }, BinaryEquals(a, b) { - if (!isString(b) || !(a instanceof Buffer)) return false; - return a.equals(new Buffer(b, 'base64')); + if (process.env.BROWSER) { + if (!isString(b) || !(a instanceof Uint8Array)) return false; + const buf = new Uint8Array(atob(b).split('').map(function(s) { return s.charCodeAt(0); })); + return a.every(function(x, i) { return x === buf[i]; }); + } else { + if (!isString(b) || !(a instanceof Buffer)) return false; + return a.equals(new Buffer(b, 'base64')); + } }, BinaryNotEquals(a, b) { - if (!isString(b) || !(a instanceof Buffer)) return false; - return !a.equals(new Buffer(b, 'base64')); + if (process.env.BROWSER) { + if (!isString(b) || !(a instanceof Uint8Array)) return false; + const buf = new Uint8Array(atob(b).split('').map(function(s) { return s.charCodeAt(0); })); + return !a.every(function(x, i) { return x === buf[i]; }); + } else { + if (!isString(b) || !(a instanceof Buffer)) return false; + return !a.equals(new Buffer(b, 'base64')); + } }, ArnLike: function ArnLike(a, b) { if (!isString(b)) return false; diff --git a/package.json b/package.json index c52b537..7d59e6e 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,12 @@ }, "scripts": { "test": "mocha --bail t/", + "test:browser": "cross-env BROWSER=1 mocha --bail -r ./browser.js t/", "docs": "cat output/intro.md LICENSE > README.md && doctoc --title Contents README.md" }, "devDependencies": { + "atob": "2.1.0", + "cross-env": "^5.1.4", "doctoc": "^1.3.1", "jsdox": "^0.4.9", "mocha": "^2.2.5" diff --git a/t/conditions.js b/t/conditions.js index 16910b5..4cf4588 100644 --- a/t/conditions.js +++ b/t/conditions.js @@ -93,16 +93,24 @@ var tests = { ['2015-07-07T14 :00:00.123Z', '2015-07-07T15:00:00.123Z', false], ], BinaryEquals: [ - [new Buffer('SGVsbG8gV29ybGQ=', 'base64'), 'SGVsbG8gV29ybGQ=', true], + [fromBase64('SGVsbG8gV29ybGQ='), 'SGVsbG8gV29ybGQ=', true], ['SGVsbG8gV29ybGQ=', 'SGVsbG8gV29ybGQ=', false], ], BinaryNotEquals: [ - [new Buffer('SGVsbG8gV29ybGQ=', 'base64'), 'SGVsbG8gV29ybGQ=', false], - [new Buffer('SGVsbG8gV29ybGQ=', 'base64'), 'SGVsbG8gV29ybGq=', true], + [fromBase64('SGVsbG8gV29ybGQ='), 'SGVsbG8gV29ybGQ=', false], + [fromBase64('SGVsbG8gV29ybGQ='), 'SGVsbG8gV29ybGq=', true], ['SGVsbG8gV29ybGQ=', 'SGVsbG8gV29ybGQ=', false], ] }; +function fromBase64(str) { + if (process.env.BROWSER) { + return new Uint8Array(atob(str).split('').map(function(s) { return s.charCodeAt(0); })); + } else { + return new Buffer(str, 'base64'); + } +} + describe('conditions', function() { _.forEach(tests, function(list, fn) { it(fn, function() { From 2418a545998fba15cd22ee82c182a1dbd14345aa Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Mon, 2 Apr 2018 17:55:38 -0700 Subject: [PATCH 5/9] webpack commands --- .gitignore | 3 +++ package.json | 10 ++++++++-- webpack.config.js | 23 +++++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index d532efc..8e4ff7e 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ build/Release node_modules .idea + +/dist +/package-lock.json diff --git a/package.json b/package.json index 7d59e6e..0b48036 100644 --- a/package.json +++ b/package.json @@ -10,15 +10,21 @@ "scripts": { "test": "mocha --bail t/", "test:browser": "cross-env BROWSER=1 mocha --bail -r ./browser.js t/", - "docs": "cat output/intro.md LICENSE > README.md && doctoc --title Contents README.md" + "docs": "cat output/intro.md LICENSE > README.md && doctoc --title Contents README.md", + "build": "webpack" }, "devDependencies": { "atob": "2.1.0", "cross-env": "^5.1.4", "doctoc": "^1.3.1", "jsdox": "^0.4.9", - "mocha": "^2.2.5" + "mocha": "^2.2.5", + "webpack": "4.4.1", + "webpack-cli": "2.0.13" }, + "files": [ + "dist/*" + ], "repository": { "type": "git", "url": "monken/node-pbac" diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..4630545 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,23 @@ +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + entry: './pbac.js', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'pbac.js', + library: 'pbac', + libraryTarget: 'umd', + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.BROWSER': JSON.stringify(true), + }), + ], + externals: { + 'ip-range-check': 'ip-range-check', + lodash: 'lodash', + 'lodash/fp': 'lodash/fp', + 'z-schema': 'z-schema', + }, +}; From fe4a17d0d7fa28385261253ad1b06e3630a2cfb5 Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Tue, 3 Apr 2018 17:18:44 -0700 Subject: [PATCH 6/9] only use lodash/fp --- conditions.js | 12 ++++++------ pbac.js | 22 ++++++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/conditions.js b/conditions.js index c88eddf..2aca45a 100644 --- a/conditions.js +++ b/conditions.js @@ -10,7 +10,7 @@ const { isEmpty, forEach, every, -} = require('lodash'); +} = require('lodash/fp'); const conditions = { NumericEquals(a, b) { @@ -149,18 +149,19 @@ const conditions = { }, }; -forEach(conditions, function (fn, condition) { +forEach(function (condition) { + const fn = conditions[condition]; conditions[condition + 'IfExists'] = function (a, b) { if (isUndefined(a)) return true; else return fn.apply(this, arguments); }; conditions['ForAllValues:' + condition] = function (a, b) { if (!isArray(a)) a = [a]; - return every(a, value => { + return every(value => { return b.find(key => { return fn.call(this, value, key); }); - }); + }, a); }; conditions['ForAnyValue:' + condition] = function (a, b) { if (!isArray(a)) a = [a]; @@ -170,7 +171,6 @@ forEach(conditions, function (fn, condition) { }); }); }; - -}); +}, Object.keys(conditions)); module.exports = conditions; diff --git a/pbac.js b/pbac.js index 303adec..863138d 100644 --- a/pbac.js +++ b/pbac.js @@ -12,9 +12,11 @@ const { forEach, every, get, -} = require('lodash'); - -const { flow, map, flatten, find } = require('lodash/fp'); + flow, + map, + flatten, + find, +} = require('lodash/fp'); const PBAC = function constructor(policies, options) { options = isPlainObject(options) ? options : {}; @@ -38,14 +40,14 @@ Object.assign(PBAC.prototype, { this.policies.push.apply(this.policies, policies); }, addConditionsToSchema: function addConditionsToSchema() { - const definition = get(this.schema, 'definitions.Condition'); + const definition = get('definitions.Condition', this.schema); if (!definition) return; const props = definition.properties = {}; - forEach(this.conditions, function(condition, name) { + forEach(function(name) { props[name] = { type: 'object' }; - }, this); + }, Object.keys(this.conditions)); }, _validateSchema() { const validator = new ZSchema(); @@ -57,11 +59,11 @@ Object.assign(PBAC.prototype, { const validator = new ZSchema({ noExtraKeywords: true, }); - return every(policies, policy => { + return every(policy => { const result = validator.validate(policy, this.schema); if (!result) this.throw('policy validation failed with ' + validator.getLastError()); return result; - }); + }, policies); }, evaluate(options) { options = Object.assign({ @@ -150,7 +152,7 @@ Object.assign(PBAC.prototype, { evaluateCondition(condition, context) { if (!isPlainObject(condition)) return true; const conditions = this.conditions; - return every(Object.keys(condition), key => { + return every(key => { const expression = condition[key]; const contextKey = Object.keys(expression)[0]; let values = expression[contextKey]; @@ -165,7 +167,7 @@ Object.assign(PBAC.prototype, { } else { return values.find(value => conditions[key].call(this, this.getContextValue(contextKey, context), value)); } - }); + }, Object.keys(condition)); }, throw(name, message) { const e = new Error(); From 7b2baf7231ffde532b70dce8280a72ba17a01f27 Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Tue, 3 Apr 2018 17:19:51 -0700 Subject: [PATCH 7/9] use ipaddr.js --- conditions.js | 19 +++++++++++++++++-- package.json | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/conditions.js b/conditions.js index 2aca45a..06c8867 100644 --- a/conditions.js +++ b/conditions.js @@ -1,5 +1,5 @@ 'use strict'; -const ipRangeCheck = require('ip-range-check'); +const ipaddr = require('ipaddr.js'); const { isString, @@ -110,7 +110,22 @@ const conditions = { return b ? isUndefined(a) : !isUndefined(a); }, IpAddress(a, b) { - return ipRangeCheck(a, b || ''); + try { + if (!a || !b) return false; + + const addr = ipaddr.parse(a); + + if (b.indexOf('/') !== -1) { + const range = ipaddr.parseCIDR(b); + return addr.match(range); + } + + const bddr = ipaddr.parse(b); + + return addr.toString() === bddr.toString(); + } catch(error) { + return false; + } }, NotIpAddress() { return !this.conditions.IpAddress.apply(this, arguments); diff --git a/package.json b/package.json index 0b48036..3a0e40f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.2.0", "description": "AWS IAM Policy inspired and (mostly) compatible evaluation engine", "dependencies": { - "ip-range-check": "0.0.2", + "ipaddr.js": "^1.6.0", "lodash": "^4.17.5", "z-schema": "^3.19.1" }, From 74f65308308f6c4860bc779dbb643e589e222b79 Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Tue, 3 Apr 2018 17:20:05 -0700 Subject: [PATCH 8/9] browser example --- browser/example.html | 52 ++++++++++++++++++++++++++++++++++++++++++++ browser/example.js | 27 +++++++++++++++++++++++ webpack.config.js | 17 +++++++++++---- 3 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 browser/example.html create mode 100644 browser/example.js diff --git a/browser/example.html b/browser/example.html new file mode 100644 index 0000000..2103c76 --- /dev/null +++ b/browser/example.html @@ -0,0 +1,52 @@ + + + + + + + PBAC + + +

Policies:

+ + +

Action:

+ + +

Resource:

+ + +

Context:

+ + +

+ +

Result: ?

+ + + + + + + diff --git a/browser/example.js b/browser/example.js new file mode 100644 index 0000000..b9519df --- /dev/null +++ b/browser/example.js @@ -0,0 +1,27 @@ +checkPolicies(); + +function checkPolicies() { + try { + const policies = JSON.parse(document.all.policies.value); + const action = document.all.action.value; + const resource = document.all.resource.value; + const context = JSON.parse(document.all.context.value); + + var pbac = new PBAC(policies, { + validateSchema: false, + validatePolicies: false, + }); + + var result = pbac.evaluate({ + action, + resource, + context, + }); + + document.all.result.textContent = JSON.stringify(result); + } catch (error) { + console.error(error); + + document.all.result.textContent = error.message; + } +} diff --git a/webpack.config.js b/webpack.config.js index 4630545..2543f31 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,7 +6,7 @@ module.exports = { output: { path: path.resolve(__dirname, 'dist'), filename: 'pbac.js', - library: 'pbac', + library: 'PBAC', libraryTarget: 'umd', }, plugins: [ @@ -15,9 +15,18 @@ module.exports = { }), ], externals: { - 'ip-range-check': 'ip-range-check', - lodash: 'lodash', - 'lodash/fp': 'lodash/fp', + 'ipaddr.js': { + root: 'ipaddr', + commonjs: 'ipaddr.js', + commonjs2: 'ipaddr.js', + amd: 'ipaddr.js', + }, + 'lodash/fp': { + root: '_', + commonjs: 'lodash/fp', + commonjs2: 'lodash/fp', + amd: 'lodash/fp', + }, 'z-schema': 'z-schema', }, }; From ed45236a0f066f6a12ec8d5daf75fdd8a82d94df Mon Sep 17 00:00:00 2001 From: Jason Diamond Date: Tue, 3 Apr 2018 17:29:11 -0700 Subject: [PATCH 9/9] build dist on publish --- package.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 3a0e40f..6e64118 100644 --- a/package.json +++ b/package.json @@ -11,18 +11,22 @@ "test": "mocha --bail t/", "test:browser": "cross-env BROWSER=1 mocha --bail -r ./browser.js t/", "docs": "cat output/intro.md LICENSE > README.md && doctoc --title Contents README.md", - "build": "webpack" + "build": "webpack", + "prepublishOnly": "npm test && npm run test:browser && npm run build" }, "devDependencies": { - "atob": "2.1.0", + "atob": "^2.1.0", "cross-env": "^5.1.4", "doctoc": "^1.3.1", "jsdox": "^0.4.9", "mocha": "^2.2.5", - "webpack": "4.4.1", - "webpack-cli": "2.0.13" + "webpack": "^4.4.1", + "webpack-cli": "^2.0.13" }, "files": [ + "pbac.js", + "conditions.js", + "schema.json", "dist/*" ], "repository": {