From 3420d871d98c7bc62ecef9f147479228640edba3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sat, 17 Oct 2020 18:55:15 -0500 Subject: [PATCH 01/13] feat(actionbasedpolicy): allowing set and get context --- src/ActionBasedPolicy.test.ts | 139 +++++++++++++++++++-------------- src/ActionBasedPolicy.ts | 47 +++++++---- src/Policy.test.ts | 26 ++++++ src/Policy.ts | 21 +++++ src/Statement.ts | 6 +- src/types.ts | 12 ++- src/utils/stringToPath.test.ts | 37 ++++++--- 7 files changed, 196 insertions(+), 92 deletions(-) create mode 100644 src/Policy.test.ts create mode 100644 src/Policy.ts diff --git a/src/ActionBasedPolicy.test.ts b/src/ActionBasedPolicy.test.ts index 07f67c8..ea4188c 100644 --- a/src/ActionBasedPolicy.test.ts +++ b/src/ActionBasedPolicy.test.ts @@ -5,21 +5,36 @@ describe('ActionBasedPolicy Class', () => { it("doesn't throw an error", () => { expect( () => - new ActionBasedPolicy([ - { - action: ['read', 'write'] - } - ]) + new ActionBasedPolicy({ + statements: [ + { + action: ['read', 'write'] + } + ] + }) + ).not.toThrow(); + expect( + () => + new ActionBasedPolicy({ + statements: [ + { + notAction: ['write'] + } + ], + context: {} + }) ).not.toThrow(); }); it('returns an ActionBasedPolicy instance', () => { expect( - new ActionBasedPolicy([ - { - action: ['read', 'write'] - } - ]) + new ActionBasedPolicy({ + statements: [ + { + action: ['read', 'write'] + } + ] + }) ).toBeInstanceOf(ActionBasedPolicy); }); }); @@ -28,10 +43,10 @@ describe('ActionBasedPolicy Class', () => { it('returns those statements', () => { const statements = [ { - action: ['read'] + action: 'read' } ]; - const policy = new ActionBasedPolicy(statements); + const policy = new ActionBasedPolicy({ statements }); const exportedStatements = policy.getStatements(); expect(exportedStatements).toMatchObject(statements); expect(exportedStatements[0].sid).not.toBeFalsy(); @@ -40,11 +55,13 @@ describe('ActionBasedPolicy Class', () => { describe('when match actions', () => { it('returns true or false', () => { - const policy = new ActionBasedPolicy([ - { - action: ['read'] - } - ]); + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['read'] + } + ] + }); expect( policy.evaluate({ @@ -61,11 +78,13 @@ describe('ActionBasedPolicy Class', () => { describe('when match not actions', () => { it('returns true or false', () => { - const policy = new ActionBasedPolicy([ - { - notAction: 'read' - } - ]); + const policy = new ActionBasedPolicy({ + statements: [ + { + notAction: 'read' + } + ] + }); expect( policy.evaluate({ @@ -82,19 +101,21 @@ describe('ActionBasedPolicy Class', () => { describe('when match based on context', () => { it('returns true or false', () => { - const policy = new ActionBasedPolicy([ - { - action: ['getUser/${user.id}', 'updateUser/${user.id}'] - }, - { - action: 'getAllProjects' - } - ]); + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['getUser/${user.id}', 'updateUser/${user.id}'] + }, + { + action: 'getAllProjects' + } + ], + context: { user: { id: 123 } } + }); expect( policy.evaluate({ - action: 'getUser/123', - context: { user: { id: 123 } } + action: 'getUser/123' }) ).toBe(true); expect( @@ -123,14 +144,14 @@ describe('ActionBasedPolicy Class', () => { describe('when match based on conditions', () => { it('returns true or false', () => { - const conditions = { + const conditionResolver = { greaterThan: (data: number, expected: number): boolean => { return data > expected; } }; - const policy = new ActionBasedPolicy( - [ + const policy = new ActionBasedPolicy({ + statements: [ { action: ['read'] }, @@ -143,8 +164,8 @@ describe('ActionBasedPolicy Class', () => { } } ], - conditions - ); + conditionResolver + }); expect( policy.evaluate({ @@ -169,16 +190,18 @@ describe('ActionBasedPolicy Class', () => { describe('can and cannot', () => { it('can should return false when not found and true for when matched with allow', () => { - const policy = new ActionBasedPolicy([ - { - effect: 'allow', - action: [ - 'createProject', - 'getUser/${user.id}', - 'updateUser/${user.id}' - ] - } - ]); + const policy = new ActionBasedPolicy({ + statements: [ + { + effect: 'allow', + action: [ + 'createProject', + 'getUser/${user.id}', + 'updateUser/${user.id}' + ] + } + ] + }); expect( policy.can({ action: 'getUser/123', @@ -199,16 +222,18 @@ describe('ActionBasedPolicy Class', () => { }); it('cannot should return false when not found and true for when matched with deny', () => { - const policy = new ActionBasedPolicy([ - { - effect: 'deny', - action: [ - 'createProject', - 'getUser/${user.id}', - 'updateUser/${user.id}' - ] - } - ]); + const policy = new ActionBasedPolicy({ + statements: [ + { + effect: 'deny', + action: [ + 'createProject', + 'getUser/${user.id}', + 'updateUser/${user.id}' + ] + } + ] + }); expect( policy.cannot({ action: 'getUser/123', diff --git a/src/ActionBasedPolicy.ts b/src/ActionBasedPolicy.ts index 2d681da..1b746a6 100644 --- a/src/ActionBasedPolicy.ts +++ b/src/ActionBasedPolicy.ts @@ -1,50 +1,67 @@ import { ActionBasedType, EvaluateActionBasedInterface, - ConditionResolver + ConditionResolver, + Context } from './types'; import { ActionBased } from './ActionBasedStatement'; +import { Policy } from './Policy'; -export class ActionBasedPolicy { +interface ActionBasedPolicyInterface { + statements: ActionBasedType[]; + conditionResolver?: ConditionResolver; + context?: Context; +} + +export class ActionBasedPolicy extends Policy { private denyStatements: ActionBased[]; private allowStatements: ActionBased[]; private conditionResolver?: ConditionResolver; private statements: ActionBasedType[]; - constructor( - config: ActionBasedType[], - conditionResolver?: ConditionResolver - ) { - const statementInstances = config.map( - statement => new ActionBased(statement) + + constructor({ + statements, + conditionResolver, + context + }: ActionBasedPolicyInterface) { + super({ context }); + const statementInstances = statements.map( + (statement) => new ActionBased(statement) + ); + this.allowStatements = statementInstances.filter( + (s) => s.effect === 'allow' ); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); this.conditionResolver = conditionResolver; - this.statements = this.statements = statementInstances.map(statement => + this.statements = this.statements = statementInstances.map((statement) => statement.getStatement() ); } + getStatements(): ActionBasedType[] { return this.statements; } + evaluate({ action, context }: EvaluateActionBasedInterface): boolean { const args = { action, context }; return !this.cannot(args) && this.can(args); } + can({ action, context }: EvaluateActionBasedInterface): boolean { - return this.allowStatements.some(s => + return this.allowStatements.some((s) => s.matches({ action, - context, + context: context || this.context, conditionResolver: this.conditionResolver }) ); } + cannot({ action, context }: EvaluateActionBasedInterface): boolean { - return this.denyStatements.some(s => + return this.denyStatements.some((s) => s.matches({ action, - context, + context: context || this.context, conditionResolver: this.conditionResolver }) ); diff --git a/src/Policy.test.ts b/src/Policy.test.ts new file mode 100644 index 0000000..e6ef992 --- /dev/null +++ b/src/Policy.test.ts @@ -0,0 +1,26 @@ +import { Policy } from './Policy'; + +describe('Policy Class', () => { + it('returns a Policy instance', () => { + expect(new Policy({})).toBeInstanceOf(Policy); + expect(new Policy({ context: { user: { age: 31 } } })).toBeInstanceOf( + Policy + ); + }); + + describe('when getContext', () => { + it('returns context attribute', () => { + const context = { user: { age: 31 } }; + expect(new Policy({ context }).getContext()).toBe(context); + }); + }); + + describe('when setContext', () => { + it('sets context attribute', () => { + const context = { user: { age: 31 } }; + const policy = new Policy({}); + policy.setContext(context); + expect(policy.getContext()).toBe(context); + }); + }); +}); diff --git a/src/Policy.ts b/src/Policy.ts new file mode 100644 index 0000000..67bbdcd --- /dev/null +++ b/src/Policy.ts @@ -0,0 +1,21 @@ +import { PolicyInterface, Context } from './types'; + +class Policy { + protected context: Context; + + constructor({ context }: PolicyInterface) { + if (context) { + this.context = context; + } + } + + setContext(context: Context): void { + this.context = context; + } + + getContext(): Context { + return this.context; + } +} + +export { Policy }; diff --git a/src/Statement.ts b/src/Statement.ts index 12f13b5..3b97895 100644 --- a/src/Statement.ts +++ b/src/Statement.ts @@ -47,13 +47,13 @@ class Statement { conditionResolver }: MatchConditionInterface): boolean { return conditionResolver && this.condition && context - ? Object.keys(this.condition).every(condition => + ? Object.keys(this.condition).every((condition) => Object.keys(this.condition ? this.condition[condition] : {}).every( - path => { + (path) => { if (this.condition) { const conditionValues = this.condition[condition][path]; if (conditionValues instanceof Array) { - return conditionValues.some(value => + return conditionValues.some((value) => conditionResolver[condition]( getValueFromPath(context, path), value diff --git a/src/types.ts b/src/types.ts index 04d9308..af30caa 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,6 +31,10 @@ interface NotResourceBlock { export type ConditionKey = string | number | boolean; +export interface Context { + [key: string]: ConditionKey | Context | string[] | number[]; +} + export interface ConditionMap { [key: string]: ConditionKey[] | ConditionKey; } @@ -45,6 +49,10 @@ export interface StatementInterface { condition?: ConditionBlock; } +export interface PolicyInterface { + context?: Context; +} + export interface DecomposeString { start: number; end: number; @@ -59,10 +67,6 @@ export interface ConditionResolver { [key: string]: Resolver; } -export interface Context { - [key: string]: ConditionKey | Context | string[] | number[]; -} - export interface MatchConditionInterface { context?: Context; conditionResolver?: ConditionResolver; diff --git a/src/utils/stringToPath.test.ts b/src/utils/stringToPath.test.ts index 2249cae..d94afdc 100644 --- a/src/utils/stringToPath.test.ts +++ b/src/utils/stringToPath.test.ts @@ -1,19 +1,30 @@ import { stringToPath } from './stringToPath'; describe('stringToPath', () => { - it("should match anything that isn't a dot or bracket", () => { - expect(stringToPath('hi')).toEqual(['hi']); - expect(stringToPath.cache.size).toEqual(1); - expect(stringToPath('attribute?')).toEqual(['attribute?']); - expect(stringToPath.cache.size).toEqual(2); + describe("when matching anything that isn't a dot or bracket", () => { + it('returns an array of strings', () => { + expect(stringToPath('hi')).toEqual(['hi']); + expect(stringToPath.cache.size).toEqual(1); + expect(stringToPath('attribute?')).toEqual(['attribute?']); + expect(stringToPath.cache.size).toEqual(2); + }); }); - it('should match property names within brackets', () => { - expect(stringToPath('first[second].third')).toEqual([ - 'first', - 'second', - 'third' - ]); - expect(stringToPath('first[].third')).toEqual(['first', '', 'third']); - expect(stringToPath("first['second\x02]")).toEqual(['first', 'second']); + describe('when matching property names within brackets', () => { + it('returns an array of strings', () => { + expect(stringToPath('first[second].third')).toEqual([ + 'first', + 'second', + 'third' + ]); + expect(stringToPath('first[].third')).toEqual(['first', '', 'third']); + expect(stringToPath("first['second\x02]")).toEqual(['first', 'second']); + }); + }); + describe('when matching dots', () => { + it('return a white space in the returned array path', () => { + expect(stringToPath('.hi')).toEqual(['', 'hi']); + expect(stringToPath('.hi.')).toEqual(['', 'hi', '']); + expect(stringToPath('.hi..')).toEqual(['', 'hi', '', '']); + }); }); }); From 10bee7935e7aa72d59995d170fe0f801acc5dc20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sat, 17 Oct 2020 20:47:15 -0500 Subject: [PATCH 02/13] feat(actionbasedpolicy): allowing set and get conditionResolver --- src/Policy.test.ts | 26 +++++++++++++++ src/Policy.ts | 18 ++++++++--- src/Statement.test.ts | 1 + src/types.ts | 4 --- src/utils/memoize.ts | 4 +-- src/utils/mersenneTwister.test.ts | 54 ++++++++++++++++++++----------- src/utils/mersenneTwister.ts | 2 +- 7 files changed, 79 insertions(+), 30 deletions(-) diff --git a/src/Policy.test.ts b/src/Policy.test.ts index e6ef992..6b55128 100644 --- a/src/Policy.test.ts +++ b/src/Policy.test.ts @@ -23,4 +23,30 @@ describe('Policy Class', () => { expect(policy.getContext()).toBe(context); }); }); + + describe('when getConditionResolver', () => { + it('returns conditionResolver attribute', () => { + const conditionResolver = { + greaterThan: (data: number, expected: number): boolean => { + return data > expected; + } + }; + expect(new Policy({ conditionResolver }).getConditionResolver()).toBe( + conditionResolver + ); + }); + }); + + describe('when setConditionResolver', () => { + it('sets conditionResolver attribute', () => { + const conditionResolver = { + greaterThan: (data: number, expected: number): boolean => { + return data > expected; + } + }; + const policy = new Policy({}); + policy.setConditionResolver(conditionResolver); + expect(policy.getConditionResolver()).toBe(conditionResolver); + }); + }); }); diff --git a/src/Policy.ts b/src/Policy.ts index 67bbdcd..eb2f342 100644 --- a/src/Policy.ts +++ b/src/Policy.ts @@ -1,12 +1,12 @@ -import { PolicyInterface, Context } from './types'; +import { MatchConditionInterface, ConditionResolver, Context } from './types'; class Policy { protected context: Context; + protected conditionResolver: ConditionResolver; - constructor({ context }: PolicyInterface) { - if (context) { - this.context = context; - } + constructor({ context, conditionResolver }: MatchConditionInterface) { + this.context = context; + this.conditionResolver = conditionResolver; } setContext(context: Context): void { @@ -16,6 +16,14 @@ class Policy { getContext(): Context { return this.context; } + + setConditionResolver(conditionResolver: ConditionResolver): void { + this.conditionResolver = conditionResolver; + } + + getConditionResolver(): ConditionResolver { + return this.conditionResolver; + } } export { Policy }; diff --git a/src/Statement.test.ts b/src/Statement.test.ts index cadc600..e03dd05 100644 --- a/src/Statement.test.ts +++ b/src/Statement.test.ts @@ -51,6 +51,7 @@ describe('Statement Class', () => { describe('when match conditions', () => { it('returns true', () => { const firstStatementConfig = { + sid: 'first', condition: { greaterThan: { 'user.age': 30 } } diff --git a/src/types.ts b/src/types.ts index af30caa..ebb9a61 100644 --- a/src/types.ts +++ b/src/types.ts @@ -49,10 +49,6 @@ export interface StatementInterface { condition?: ConditionBlock; } -export interface PolicyInterface { - context?: Context; -} - export interface DecomposeString { start: number; end: number; diff --git a/src/utils/memoize.ts b/src/utils/memoize.ts index 03cc737..00b1d6d 100644 --- a/src/utils/memoize.ts +++ b/src/utils/memoize.ts @@ -35,7 +35,7 @@ import { MemoizeInterface } from '../types'; * ``` */ export function memoize(func: Function, resolver?: Function): MemoizeInterface { - const memoized = function(this: Function, ...args: any): MemoizeInterface { + const memoized = function (this: Function, ...args: any): MemoizeInterface { const key = resolver ? resolver.apply(this, args) : args[0]; const cache = memoized.cache; @@ -43,7 +43,7 @@ export function memoize(func: Function, resolver?: Function): MemoizeInterface { return cache.get(key); } const result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; + cache.set(key, result); return result; }; memoized.cache = new Map(); diff --git a/src/utils/mersenneTwister.test.ts b/src/utils/mersenneTwister.test.ts index 328e850..2f736cf 100644 --- a/src/utils/mersenneTwister.test.ts +++ b/src/utils/mersenneTwister.test.ts @@ -1,24 +1,42 @@ import { MersenneTwister } from './mersenneTwister'; describe('MersenneTwister', () => { - it('should repeat random sequence on same seed', () => { - const mersenne = new MersenneTwister(); + describe('when using an empty array as seed', () => { + it('sets 5489 as seed', () => { + const mersenne = new MersenneTwister([]); - const seed = 123; + expect(mersenne.randomInt32()).toEqual(3499211612); + }); + }); + + describe('when using empty with length greater or equal than 624', () => { + it('returns a MersenneTwister instance', () => { + expect(new MersenneTwister(new Array(624))).toBeInstanceOf( + MersenneTwister + ); + }); + }); + + describe('when using initSeed', () => { + it('repeats random sequence on same seed', () => { + const mersenne = new MersenneTwister(); - mersenne.initSeed(seed); - const first1 = mersenne.randomReal2(); - const first2 = mersenne.randomReal2(); + const seed = 123; - mersenne.initSeed(seed); - const second1 = mersenne.randomReal2(); - const second2 = mersenne.randomReal2(); + mersenne.initSeed(seed); + const first1 = mersenne.randomReal2(); + const first2 = mersenne.randomReal2(); - expect(first1).toEqual(second1); - expect(first2).toEqual(second2); + mersenne.initSeed(seed); + const second1 = mersenne.randomReal2(); + const second2 = mersenne.randomReal2(); + + expect(first1).toEqual(second1); + expect(first2).toEqual(second2); + }); }); - it('should allow seeding via constructor', () => { + it('allows seeding via constructor', () => { const seed = 325; const mersenne1 = new MersenneTwister(seed); const mersenne2 = new MersenneTwister(seed); @@ -36,7 +54,7 @@ describe('MersenneTwister', () => { Modify the mtTest.c file to try each function with the proper seed */ describe('when comparing with results from C language', () => { - it('should generate 100 Int32 values as C', () => { + it('generates 100 Int32 values as C', () => { const seeds = [0x123, 0x234, 0x345, 0x456]; const mersenne = new MersenneTwister(seeds); @@ -148,7 +166,7 @@ describe('MersenneTwister', () => { } }); - it('should generate 100 Int31 values as C', () => { + it('generates 100 Int31 values as C', () => { const seeds = [0x123, 0x234, 0x345, 0x456]; const mersenne = new MersenneTwister(seeds); @@ -260,7 +278,7 @@ describe('MersenneTwister', () => { } }); - it('should generate 100 Real1 values as C', () => { + it('generates 100 Real1 values as C', () => { const seeds = [0x123, 0x234, 0x345, 0x456]; const mersenne = new MersenneTwister(seeds); @@ -372,7 +390,7 @@ describe('MersenneTwister', () => { } }); - it('should generate 100 Real2 values as C', () => { + it('generates 100 Real2 values as C', () => { const seeds = [0x123, 0x234, 0x345, 0x456]; const mersenne = new MersenneTwister(seeds); @@ -484,7 +502,7 @@ describe('MersenneTwister', () => { } }); - it('should generate 100 Real3 values as C', () => { + it('generates 100 Real3 values as C', () => { const seeds = [0x123, 0x234, 0x345, 0x456]; const mersenne = new MersenneTwister(seeds); @@ -596,7 +614,7 @@ describe('MersenneTwister', () => { } }); - it('should generate 100 Real3 values as C', () => { + it('generates 100 Real3 values as C', () => { const seeds = [0x123, 0x234, 0x345, 0x456]; const mersenne = new MersenneTwister(seeds); diff --git a/src/utils/mersenneTwister.ts b/src/utils/mersenneTwister.ts index 4ddd639..ebffabe 100644 --- a/src/utils/mersenneTwister.ts +++ b/src/utils/mersenneTwister.ts @@ -69,7 +69,7 @@ export class MersenneTwister { this.mti = this.N + 1; /* mti==N+1 means mt[N] is not initialized */ if (Array.isArray(seed)) { - this.initByArray(seed, seed.length); + if (seed.length > 0) this.initByArray(seed, seed.length); } else { if (seed === undefined) { this.initSeed(new Date().getTime()); From 061afe2564f9d1f9d0b599cd33487262d02a239b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sat, 17 Oct 2020 20:57:24 -0500 Subject: [PATCH 03/13] chore(dist): updating dist files --- dist/main.d.ts | 26 +++++++++++++++++----- dist/main.es.js | 48 ++++++++++++++++++++++++++++------------ dist/main.es.js.map | 2 +- dist/main.js | 48 ++++++++++++++++++++++++++++------------ dist/main.js.map | 2 +- src/ActionBasedPolicy.ts | 4 +--- src/Policy.ts | 8 +++---- 7 files changed, 95 insertions(+), 43 deletions(-) diff --git a/dist/main.d.ts b/dist/main.d.ts index c64ad5c..e5e9ea1 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -65,6 +65,9 @@ interface NotResourceBlock { notResource: Patterns; } declare type ConditionKey = string | number | boolean; +interface Context { + [key: string]: ConditionKey | Context | string[] | number[]; +} interface ConditionMap { [key: string]: ConditionKey[] | ConditionKey; } @@ -80,9 +83,6 @@ declare type Resolver = (data: any, expected: any) => boolean; interface ConditionResolver { [key: string]: Resolver; } -interface Context { - [key: string]: ConditionKey | Context | string[] | number[]; -} interface MatchConditionInterface { context?: Context; conditionResolver?: ConditionResolver; @@ -166,12 +166,26 @@ declare class ResourceBased extends Statement { matchNotResources(resource: string, context?: Context): boolean; } -declare class ActionBasedPolicy { +declare class Policy { + protected context?: Context; + protected conditionResolver?: ConditionResolver; + constructor({ context, conditionResolver }: MatchConditionInterface); + setContext(context: Context): void; + getContext(): Context | undefined; + setConditionResolver(conditionResolver: ConditionResolver): void; + getConditionResolver(): ConditionResolver | undefined; +} + +interface ActionBasedPolicyInterface { + statements: ActionBasedType[]; + conditionResolver?: ConditionResolver; + context?: Context; +} +declare class ActionBasedPolicy extends Policy { private denyStatements; private allowStatements; - private conditionResolver?; private statements; - constructor(config: ActionBasedType[], conditionResolver?: ConditionResolver); + constructor({ statements, conditionResolver, context }: ActionBasedPolicyInterface); getStatements(): ActionBasedType[]; evaluate({ action, context }: EvaluateActionBasedInterface): boolean; can({ action, context }: EvaluateActionBasedInterface): boolean; diff --git a/dist/main.es.js b/dist/main.es.js index b6ac0b7..77af809 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -157,7 +157,7 @@ function memoize(func, resolver) { return cache.get(key); } const result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; + cache.set(key, result); return result; }; memoized.cache = new Map(); @@ -360,7 +360,8 @@ class MersenneTwister { this.mt = new Array(this.N); /* the array for the state vector */ this.mti = this.N + 1; /* mti==N+1 means mt[N] is not initialized */ if (Array.isArray(seed)) { - this.initByArray(seed, seed.length); + if (seed.length > 0) + this.initByArray(seed, seed.length); } else { if (seed === undefined) { @@ -556,11 +557,11 @@ class Statement { } matchConditions({ context, conditionResolver }) { return conditionResolver && this.condition && context - ? Object.keys(this.condition).every(condition => Object.keys(this.condition ? this.condition[condition] : {}).every(path => { + ? Object.keys(this.condition).every((condition) => Object.keys(this.condition ? this.condition[condition] : {}).every((path) => { if (this.condition) { const conditionValues = this.condition[condition][path]; if (conditionValues instanceof Array) { - return conditionValues.some(value => conditionResolver[condition](getValueFromPath(context, path), value)); + return conditionValues.some((value) => conditionResolver[condition](getValueFromPath(context, path), value)); } return conditionResolver[condition](getValueFromPath(context, path), conditionValues); } @@ -1019,13 +1020,32 @@ class ResourceBased extends Statement { } } -class ActionBasedPolicy { - constructor(config, conditionResolver) { - const statementInstances = config.map(statement => new ActionBased(statement)); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); +class Policy { + constructor({ context, conditionResolver }) { + this.context = context; + this.conditionResolver = conditionResolver; + } + setContext(context) { + this.context = context; + } + getContext() { + return this.context; + } + setConditionResolver(conditionResolver) { this.conditionResolver = conditionResolver; - this.statements = this.statements = statementInstances.map(statement => statement.getStatement()); + } + getConditionResolver() { + return this.conditionResolver; + } +} + +class ActionBasedPolicy extends Policy { + constructor({ statements, conditionResolver, context }) { + super({ context, conditionResolver }); + const statementInstances = statements.map((statement) => new ActionBased(statement)); + this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); + this.statements = this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1035,16 +1055,16 @@ class ActionBasedPolicy { return !this.cannot(args) && this.can(args); } can({ action, context }) { - return this.allowStatements.some(s => s.matches({ + return this.allowStatements.some((s) => s.matches({ action, - context, + context: context || this.context, conditionResolver: this.conditionResolver })); } cannot({ action, context }) { - return this.denyStatements.some(s => s.matches({ + return this.denyStatements.some((s) => s.matches({ action, - context, + context: context || this.context, conditionResolver: this.conditionResolver })); } diff --git a/dist/main.es.js.map b/dist/main.es.js.map index 1c8514e..f406b20 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index aadb18a..4451c02 100644 --- a/dist/main.js +++ b/dist/main.js @@ -161,7 +161,7 @@ function memoize(func, resolver) { return cache.get(key); } const result = func.apply(this, args); - memoized.cache = cache.set(key, result) || cache; + cache.set(key, result); return result; }; memoized.cache = new Map(); @@ -364,7 +364,8 @@ class MersenneTwister { this.mt = new Array(this.N); /* the array for the state vector */ this.mti = this.N + 1; /* mti==N+1 means mt[N] is not initialized */ if (Array.isArray(seed)) { - this.initByArray(seed, seed.length); + if (seed.length > 0) + this.initByArray(seed, seed.length); } else { if (seed === undefined) { @@ -560,11 +561,11 @@ class Statement { } matchConditions({ context, conditionResolver }) { return conditionResolver && this.condition && context - ? Object.keys(this.condition).every(condition => Object.keys(this.condition ? this.condition[condition] : {}).every(path => { + ? Object.keys(this.condition).every((condition) => Object.keys(this.condition ? this.condition[condition] : {}).every((path) => { if (this.condition) { const conditionValues = this.condition[condition][path]; if (conditionValues instanceof Array) { - return conditionValues.some(value => conditionResolver[condition](getValueFromPath(context, path), value)); + return conditionValues.some((value) => conditionResolver[condition](getValueFromPath(context, path), value)); } return conditionResolver[condition](getValueFromPath(context, path), conditionValues); } @@ -1023,13 +1024,32 @@ class ResourceBased extends Statement { } } -class ActionBasedPolicy { - constructor(config, conditionResolver) { - const statementInstances = config.map(statement => new ActionBased(statement)); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); +class Policy { + constructor({ context, conditionResolver }) { + this.context = context; + this.conditionResolver = conditionResolver; + } + setContext(context) { + this.context = context; + } + getContext() { + return this.context; + } + setConditionResolver(conditionResolver) { this.conditionResolver = conditionResolver; - this.statements = this.statements = statementInstances.map(statement => statement.getStatement()); + } + getConditionResolver() { + return this.conditionResolver; + } +} + +class ActionBasedPolicy extends Policy { + constructor({ statements, conditionResolver, context }) { + super({ context, conditionResolver }); + const statementInstances = statements.map((statement) => new ActionBased(statement)); + this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); + this.statements = this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1039,16 +1059,16 @@ class ActionBasedPolicy { return !this.cannot(args) && this.can(args); } can({ action, context }) { - return this.allowStatements.some(s => s.matches({ + return this.allowStatements.some((s) => s.matches({ action, - context, + context: context || this.context, conditionResolver: this.conditionResolver })); } cannot({ action, context }) { - return this.denyStatements.some(s => s.matches({ + return this.denyStatements.some((s) => s.matches({ action, - context, + context: context || this.context, conditionResolver: this.conditionResolver })); } diff --git a/dist/main.js.map b/dist/main.js.map index c796dd1..50ceebc 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/src/ActionBasedPolicy.ts b/src/ActionBasedPolicy.ts index 1b746a6..7fc41a7 100644 --- a/src/ActionBasedPolicy.ts +++ b/src/ActionBasedPolicy.ts @@ -16,7 +16,6 @@ interface ActionBasedPolicyInterface { export class ActionBasedPolicy extends Policy { private denyStatements: ActionBased[]; private allowStatements: ActionBased[]; - private conditionResolver?: ConditionResolver; private statements: ActionBasedType[]; constructor({ @@ -24,7 +23,7 @@ export class ActionBasedPolicy extends Policy { conditionResolver, context }: ActionBasedPolicyInterface) { - super({ context }); + super({ context, conditionResolver }); const statementInstances = statements.map( (statement) => new ActionBased(statement) ); @@ -32,7 +31,6 @@ export class ActionBasedPolicy extends Policy { (s) => s.effect === 'allow' ); this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); - this.conditionResolver = conditionResolver; this.statements = this.statements = statementInstances.map((statement) => statement.getStatement() ); diff --git a/src/Policy.ts b/src/Policy.ts index eb2f342..562c954 100644 --- a/src/Policy.ts +++ b/src/Policy.ts @@ -1,8 +1,8 @@ import { MatchConditionInterface, ConditionResolver, Context } from './types'; class Policy { - protected context: Context; - protected conditionResolver: ConditionResolver; + protected context?: Context; + protected conditionResolver?: ConditionResolver; constructor({ context, conditionResolver }: MatchConditionInterface) { this.context = context; @@ -13,7 +13,7 @@ class Policy { this.context = context; } - getContext(): Context { + getContext(): Context | undefined { return this.context; } @@ -21,7 +21,7 @@ class Policy { this.conditionResolver = conditionResolver; } - getConditionResolver(): ConditionResolver { + getConditionResolver(): ConditionResolver | undefined { return this.conditionResolver; } } From f78553e307ed58b6823b05da3408d5fc5a3c1097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sat, 17 Oct 2020 23:30:44 -0500 Subject: [PATCH 04/13] fix(resourcebasedstatement): return true in matchNotPrincipals when notPrincipal is an object and principalType is undefined, false should be returned --- dist/main.es.js | 40 ++++++++++++++------------------- dist/main.es.js.map | 2 +- dist/main.js | 40 ++++++++++++++------------------- dist/main.js.map | 2 +- src/IdentityBasedPolicy.test.ts | 4 ++-- src/ResourceBasedPolicy.test.ts | 26 +++++++++++++++------ src/ResourceBasedStatement.ts | 34 +++++++++++----------------- src/Statement.test.ts | 10 +++++++++ src/Statement.ts | 39 +++++++++++++------------------- 9 files changed, 96 insertions(+), 101 deletions(-) diff --git a/dist/main.es.js b/dist/main.es.js index 77af809..f7aa5ca 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -540,7 +540,7 @@ function applyContext(str, context) { if (Array.isArray(value)) return `{${value}}`; if (value instanceof Object) - return String(undefined); + return 'undefined'; return String(value); })); } @@ -556,16 +556,14 @@ class Statement { this.condition = condition; } matchConditions({ context, conditionResolver }) { - return conditionResolver && this.condition && context - ? Object.keys(this.condition).every((condition) => Object.keys(this.condition ? this.condition[condition] : {}).every((path) => { - if (this.condition) { - const conditionValues = this.condition[condition][path]; - if (conditionValues instanceof Array) { - return conditionValues.some((value) => conditionResolver[condition](getValueFromPath(context, path), value)); - } - return conditionResolver[condition](getValueFromPath(context, path), conditionValues); + const { condition: conditions } = this; + return conditionResolver && conditions && context + ? Object.keys(conditions).every((condition) => Object.keys(conditions[condition]).every((path) => { + const conditionValues = conditions[condition][path]; + if (conditionValues instanceof Array) { + return conditionValues.some((value) => conditionResolver[condition](getValueFromPath(context, path), value)); } - return conditionResolver[condition](getValueFromPath(context, path), ''); + return conditionResolver[condition](getValueFromPath(context, path), conditionValues); })) : true; } @@ -959,15 +957,13 @@ class ResourceBased extends Statement { if (this.principal instanceof Array) { return principalType ? false - : this.principal.some(a => new Matcher(applyContext(a, context)).match(principal)); + : this.principal.some((a) => new Matcher(applyContext(a, context)).match(principal)); } else { if (principalType) { const principalValues = this.principal[principalType]; if (principalValues instanceof Array) { - return typeof principalValues === 'string' - ? [principalValues].some(a => new Matcher(applyContext(a, context)).match(principal)) - : principalValues.some(a => new Matcher(applyContext(a, context)).match(principal)); + return principalValues.some((a) => new Matcher(applyContext(a, context)).match(principal)); } return new Matcher(applyContext(principalValues, context)).match(principal); } @@ -981,41 +977,39 @@ class ResourceBased extends Statement { if (this.notPrincipal instanceof Array) { return principalType ? true - : !this.notPrincipal.some(a => new Matcher(applyContext(a, context)).match(principal)); + : !this.notPrincipal.some((a) => new Matcher(applyContext(a, context)).match(principal)); } else { if (principalType) { const principalValues = this.notPrincipal[principalType]; if (principalValues instanceof Array) { - return typeof principalValues === 'string' - ? ![principalValues].some(a => new Matcher(applyContext(a, context)).match(principal)) - : !principalValues.some(a => new Matcher(applyContext(a, context)).match(principal)); + return !principalValues.some((a) => new Matcher(applyContext(a, context)).match(principal)); } return !new Matcher(applyContext(principalValues, context)).match(principal); } - return false; + return true; } } return true; } matchActions(action, context) { return this.action - ? this.action.some(a => new Matcher(applyContext(a, context)).match(action)) + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchNotActions(action, context) { return this.notAction - ? !this.notAction.some(a => new Matcher(applyContext(a, context)).match(action)) + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchResources(resource, context) { return this.resource - ? this.resource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } matchNotResources(resource, context) { return this.notResource - ? !this.notResource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } } diff --git a/dist/main.es.js.map b/dist/main.es.js.map index f406b20..2d1f2ee 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index 4451c02..ed517b1 100644 --- a/dist/main.js +++ b/dist/main.js @@ -544,7 +544,7 @@ function applyContext(str, context) { if (Array.isArray(value)) return `{${value}}`; if (value instanceof Object) - return String(undefined); + return 'undefined'; return String(value); })); } @@ -560,16 +560,14 @@ class Statement { this.condition = condition; } matchConditions({ context, conditionResolver }) { - return conditionResolver && this.condition && context - ? Object.keys(this.condition).every((condition) => Object.keys(this.condition ? this.condition[condition] : {}).every((path) => { - if (this.condition) { - const conditionValues = this.condition[condition][path]; - if (conditionValues instanceof Array) { - return conditionValues.some((value) => conditionResolver[condition](getValueFromPath(context, path), value)); - } - return conditionResolver[condition](getValueFromPath(context, path), conditionValues); + const { condition: conditions } = this; + return conditionResolver && conditions && context + ? Object.keys(conditions).every((condition) => Object.keys(conditions[condition]).every((path) => { + const conditionValues = conditions[condition][path]; + if (conditionValues instanceof Array) { + return conditionValues.some((value) => conditionResolver[condition](getValueFromPath(context, path), value)); } - return conditionResolver[condition](getValueFromPath(context, path), ''); + return conditionResolver[condition](getValueFromPath(context, path), conditionValues); })) : true; } @@ -963,15 +961,13 @@ class ResourceBased extends Statement { if (this.principal instanceof Array) { return principalType ? false - : this.principal.some(a => new Matcher(applyContext(a, context)).match(principal)); + : this.principal.some((a) => new Matcher(applyContext(a, context)).match(principal)); } else { if (principalType) { const principalValues = this.principal[principalType]; if (principalValues instanceof Array) { - return typeof principalValues === 'string' - ? [principalValues].some(a => new Matcher(applyContext(a, context)).match(principal)) - : principalValues.some(a => new Matcher(applyContext(a, context)).match(principal)); + return principalValues.some((a) => new Matcher(applyContext(a, context)).match(principal)); } return new Matcher(applyContext(principalValues, context)).match(principal); } @@ -985,41 +981,39 @@ class ResourceBased extends Statement { if (this.notPrincipal instanceof Array) { return principalType ? true - : !this.notPrincipal.some(a => new Matcher(applyContext(a, context)).match(principal)); + : !this.notPrincipal.some((a) => new Matcher(applyContext(a, context)).match(principal)); } else { if (principalType) { const principalValues = this.notPrincipal[principalType]; if (principalValues instanceof Array) { - return typeof principalValues === 'string' - ? ![principalValues].some(a => new Matcher(applyContext(a, context)).match(principal)) - : !principalValues.some(a => new Matcher(applyContext(a, context)).match(principal)); + return !principalValues.some((a) => new Matcher(applyContext(a, context)).match(principal)); } return !new Matcher(applyContext(principalValues, context)).match(principal); } - return false; + return true; } } return true; } matchActions(action, context) { return this.action - ? this.action.some(a => new Matcher(applyContext(a, context)).match(action)) + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchNotActions(action, context) { return this.notAction - ? !this.notAction.some(a => new Matcher(applyContext(a, context)).match(action)) + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchResources(resource, context) { return this.resource - ? this.resource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } matchNotResources(resource, context) { return this.notResource - ? !this.notResource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } } diff --git a/dist/main.js.map b/dist/main.js.map index 50ceebc..442e3f2 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/src/IdentityBasedPolicy.test.ts b/src/IdentityBasedPolicy.test.ts index 7cb9f41..0c69126 100644 --- a/src/IdentityBasedPolicy.test.ts +++ b/src/IdentityBasedPolicy.test.ts @@ -18,8 +18,8 @@ describe('IdentityBasedPolicy Class', () => { expect( new IdentityBasedPolicy([ { - resource: 'some:glob:*:string/word', - action: ['read', 'write'] + notResource: ['some:glob:*:string/word'], + notAction: ['read', 'write'] } ]) ).toBeInstanceOf(IdentityBasedPolicy); diff --git a/src/ResourceBasedPolicy.test.ts b/src/ResourceBasedPolicy.test.ts index b2aecc9..e6a9a57 100644 --- a/src/ResourceBasedPolicy.test.ts +++ b/src/ResourceBasedPolicy.test.ts @@ -20,8 +20,8 @@ describe('ResourceBasedPolicy Class', () => { new ResourceBasedPolicy([ { notPrincipal: 'rogger', - resource: 'some:glob:*:string/example', - action: ['read', 'write'] + notResource: 'some:glob:*:string/example', + notAction: ['read', 'write'] } ]) ).toBeInstanceOf(ResourceBasedPolicy); @@ -102,12 +102,12 @@ describe('ResourceBasedPolicy Class', () => { it('returns true or false', () => { const policy = new ResourceBasedPolicy([ { - notPrincipal: 'andre', + notPrincipal: { id: 'andre' }, resource: 'books:horror:*', action: ['read'] }, { - notPrincipal: { id: 'rogger' }, + notPrincipal: { id: ['rogger'] }, resource: 'secrets:admin:*', action: 'read' } @@ -119,7 +119,7 @@ describe('ResourceBasedPolicy Class', () => { action: 'read', resource: 'books:horror:The Call of Cthulhu' }) - ).toBe(false); + ).toBe(true); expect( policy.evaluate({ principal: 'rogger', @@ -150,9 +150,14 @@ describe('ResourceBasedPolicy Class', () => { it('returns true or false', () => { const policy = new ResourceBasedPolicy([ { - principal: { id: '123' }, + principal: { id: ['123'] }, resource: ['books:horror:*'], action: ['read'] + }, + { + notPrincipal: ['124'], + resource: ['books:science:*'], + action: ['write'] } ]); @@ -172,6 +177,13 @@ describe('ResourceBasedPolicy Class', () => { principalType: 'id' }) ).toBe(false); + expect( + policy.evaluate({ + principal: '125', + action: 'write', + resource: 'books:science:Chemistry' + }) + ).toBe(true); }); }); @@ -236,7 +248,7 @@ describe('ResourceBasedPolicy Class', () => { const policy = new ResourceBasedPolicy([ { principal: ['rogger', 'andre'], - notResource: 'books:horror:*', + notResource: ['books:horror:*'], action: ['read'] } ]); diff --git a/src/ResourceBasedStatement.ts b/src/ResourceBasedStatement.ts index 17b969a..5d60cfa 100644 --- a/src/ResourceBasedStatement.ts +++ b/src/ResourceBasedStatement.ts @@ -92,20 +92,16 @@ class ResourceBased extends Statement { if (this.principal instanceof Array) { return principalType ? false - : this.principal.some(a => + : this.principal.some((a) => new Matcher(applyContext(a, context)).match(principal) ); } else { if (principalType) { const principalValues = this.principal[principalType]; if (principalValues instanceof Array) { - return typeof principalValues === 'string' - ? [principalValues].some(a => - new Matcher(applyContext(a, context)).match(principal) - ) - : principalValues.some(a => - new Matcher(applyContext(a, context)).match(principal) - ); + return principalValues.some((a) => + new Matcher(applyContext(a, context)).match(principal) + ); } return new Matcher(applyContext(principalValues, context)).match( principal @@ -126,26 +122,22 @@ class ResourceBased extends Statement { if (this.notPrincipal instanceof Array) { return principalType ? true - : !this.notPrincipal.some(a => + : !this.notPrincipal.some((a) => new Matcher(applyContext(a, context)).match(principal) ); } else { if (principalType) { const principalValues = this.notPrincipal[principalType]; if (principalValues instanceof Array) { - return typeof principalValues === 'string' - ? ![principalValues].some(a => - new Matcher(applyContext(a, context)).match(principal) - ) - : !principalValues.some(a => - new Matcher(applyContext(a, context)).match(principal) - ); + return !principalValues.some((a) => + new Matcher(applyContext(a, context)).match(principal) + ); } return !new Matcher(applyContext(principalValues, context)).match( principal ); } - return false; + return true; } } return true; @@ -153,7 +145,7 @@ class ResourceBased extends Statement { matchActions(action: string, context?: Context): boolean { return this.action - ? this.action.some(a => + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action) ) : true; @@ -161,7 +153,7 @@ class ResourceBased extends Statement { matchNotActions(action: string, context?: Context): boolean { return this.notAction - ? !this.notAction.some(a => + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action) ) : true; @@ -169,7 +161,7 @@ class ResourceBased extends Statement { matchResources(resource: string, context?: Context): boolean { return this.resource - ? this.resource.some(a => + ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource) ) : true; @@ -177,7 +169,7 @@ class ResourceBased extends Statement { matchNotResources(resource: string, context?: Context): boolean { return this.notResource - ? !this.notResource.some(a => + ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource) ) : true; diff --git a/src/Statement.test.ts b/src/Statement.test.ts index e03dd05..7c6254d 100644 --- a/src/Statement.test.ts +++ b/src/Statement.test.ts @@ -38,6 +38,16 @@ describe('Statement Class', () => { 'secrets:undefined:account' ); }); + + it('can match object values', () => { + const context = { + user: { id: 456, address: { lat: 11, long: 52 } } + }; + + expect(applyContext('secrets:${user.address}:account', context)).toBe( + 'secrets:undefined:account' + ); + }); }); it('returns a Statement instance', () => { diff --git a/src/Statement.ts b/src/Statement.ts index 3b97895..4fadd77 100644 --- a/src/Statement.ts +++ b/src/Statement.ts @@ -20,7 +20,7 @@ export function applyContext(str: string, context?: Context): string { str.replace(reDelimiters, (_, path: string) => { const value = getValueFromPath(context, path); if (Array.isArray(value)) return `{${value}}`; - if (value instanceof Object) return String(undefined); + if (value instanceof Object) return 'undefined'; return String(value); }) @@ -46,31 +46,24 @@ class Statement { context, conditionResolver }: MatchConditionInterface): boolean { - return conditionResolver && this.condition && context - ? Object.keys(this.condition).every((condition) => - Object.keys(this.condition ? this.condition[condition] : {}).every( - (path) => { - if (this.condition) { - const conditionValues = this.condition[condition][path]; - if (conditionValues instanceof Array) { - return conditionValues.some((value) => - conditionResolver[condition]( - getValueFromPath(context, path), - value - ) - ); - } - return conditionResolver[condition]( + const { condition: conditions } = this; + return conditionResolver && conditions && context + ? Object.keys(conditions).every((condition) => + Object.keys(conditions[condition]).every((path) => { + const conditionValues = conditions[condition][path]; + if (conditionValues instanceof Array) { + return conditionValues.some((value) => + conditionResolver[condition]( getValueFromPath(context, path), - conditionValues - ); - } - return conditionResolver[condition]( - getValueFromPath(context, path), - '' + value + ) ); } - ) + return conditionResolver[condition]( + getValueFromPath(context, path), + conditionValues + ); + }) ) : true; } From 2e4409dd87cad000c85a0d8d2c6941b936293487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 18 Oct 2020 12:42:34 -0500 Subject: [PATCH 05/13] fix(actionbasedstatement): throw TypeError when action and notAction are present --- src/ActionBasedStatement.test.ts | 32 ++++++++++++++++++++++++++ src/ActionBasedStatement.ts | 22 +++++++++++++----- src/types.ts | 11 +++++---- src/utils/instanceOfInterfaces.test.ts | 15 +++++++++++- src/utils/instanceOfInterfaces.ts | 23 ++++++++++++++++++ 5 files changed, 91 insertions(+), 12 deletions(-) create mode 100644 src/ActionBasedStatement.test.ts diff --git a/src/ActionBasedStatement.test.ts b/src/ActionBasedStatement.test.ts new file mode 100644 index 0000000..6552f4f --- /dev/null +++ b/src/ActionBasedStatement.test.ts @@ -0,0 +1,32 @@ +import { ActionBased } from './ActionBasedStatement'; + +describe('ActionBased Class', () => { + describe('when creating action based statement', () => { + it("doesn't throw an error", () => { + expect( + () => + new ActionBased({ + action: ['read', 'write'] + }) + ).not.toThrow(); + expect( + () => + new ActionBased({ + notAction: ['write'] + }) + ).not.toThrow(); + }); + + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ActionBased statement should have an action or a notAction attribute, no both' + ); + expect(() => { + new ActionBased({ + action: ['read', 'write'], + notAction: 'delete' + }); + }).toThrow(expectedError); + }); + }); +}); diff --git a/src/ActionBasedStatement.ts b/src/ActionBasedStatement.ts index 74b93cf..ef14e08 100644 --- a/src/ActionBasedStatement.ts +++ b/src/ActionBasedStatement.ts @@ -1,7 +1,10 @@ -import { Context, ActionBasedType, MatchActionBasedInterface } from './types'; -import { instanceOfActionBlock } from './utils/instanceOfInterfaces'; +import { ActionBasedType, Context, MatchActionBasedInterface } from './types'; +import { + instanceOfActionBlock, + instanceOfNotActionBlock +} from './utils/instanceOfInterfaces'; import { Matcher } from './Matcher'; -import { Statement, applyContext } from './Statement'; +import { applyContext, Statement } from './Statement'; class ActionBased extends Statement { private action?: string[]; @@ -10,7 +13,14 @@ class ActionBased extends Statement { constructor(action: ActionBasedType) { super(action); - if (instanceOfActionBlock(action)) { + const hasAction = instanceOfActionBlock(action); + const hasNotAction = instanceOfNotActionBlock(action); + if (hasAction && hasNotAction) { + throw new TypeError( + 'ActionBased statement should have an action or a notAction attribute, no both' + ); + } + if (hasAction) { this.action = typeof action.action === 'string' ? [action.action] : action.action; } else { @@ -40,7 +50,7 @@ class ActionBased extends Statement { private matchActions(action: string, context?: Context): boolean { return this.action - ? this.action.some(a => + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action) ) : true; @@ -48,7 +58,7 @@ class ActionBased extends Statement { private matchNotActions(action: string, context?: Context): boolean { return this.notAction - ? !this.notAction.some(a => + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action) ) : true; diff --git a/src/types.ts b/src/types.ts index ebb9a61..9f598e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -115,14 +115,15 @@ type ResourceBasedType = StatementInterface & export { ActionBasedType, + ActionBlock, IdentityBasedType, - ResourceBasedType, - PrincipalMap, Patterns, + PrincipalBlock, + PrincipalMap, + ResourceBasedType, ResourceBlock, - NotResourceBlock, - ActionBlock, - PrincipalBlock + NotActionBlock, + NotResourceBlock }; /* diff --git a/src/utils/instanceOfInterfaces.test.ts b/src/utils/instanceOfInterfaces.test.ts index 8608701..2598592 100644 --- a/src/utils/instanceOfInterfaces.test.ts +++ b/src/utils/instanceOfInterfaces.test.ts @@ -1,7 +1,8 @@ import { + instanceOfActionBlock, instanceOfPrincipalBlock, instanceOfResourceBlock, - instanceOfActionBlock, + instanceOfNotActionBlock, instanceOfNotResourceBlock } from './instanceOfInterfaces'; @@ -44,6 +45,18 @@ describe('Util functions', () => { }); }); + describe('instanceOfNotActionBlock', () => { + it("doesn't throw an error", () => { + expect(() => + instanceOfNotActionBlock({ + notAction: 'something' + }) + ).not.toThrow(); + expect(instanceOfNotActionBlock({ notAction: 'something' })).toBe(true); + expect(instanceOfNotActionBlock({ action: 'something' })).toBe(false); + }); + }); + describe('instanceOfNotResourceBlock', () => { it("doesn't throw an error", () => { expect(() => diff --git a/src/utils/instanceOfInterfaces.ts b/src/utils/instanceOfInterfaces.ts index b55d955..e7f2e34 100644 --- a/src/utils/instanceOfInterfaces.ts +++ b/src/utils/instanceOfInterfaces.ts @@ -1,5 +1,6 @@ import { ActionBlock, + NotActionBlock, PrincipalBlock, NotResourceBlock, ResourceBlock @@ -13,12 +14,34 @@ import { * ```javascript * instanceOfActionBlock({ action: 'something' }) * // => true + * + * instanceOfActionBlock({ notAction: 'something' }) + * // => false * ``` */ export function instanceOfActionBlock(object: object): object is ActionBlock { return 'action' in object; } +/** + * Validate if an `object` is an instance of `NotActionBlock`. + * @param {Object} object Object to validate + * @returns {boolean} Returns true if `object` has `notAction` attribute. + * @example + * ```javascript + * instanceOfNotActionBlock({ notAction: 'something' }) + * // => true + * + * instanceOfNotActionBlock({ action: 'something' }) + * // => false + * ``` + */ +export function instanceOfNotActionBlock( + object: object +): object is NotActionBlock { + return 'notAction' in object; +} + /** * Validate if an `object` is an instance of `PrincipalBlock`. * @param {Object} object Object to validate From 78c52ca50a4edf84758d4d96e8d422ade6d3f1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 18 Oct 2020 14:43:07 -0500 Subject: [PATCH 06/13] fix: throw Type Error in Identity and Resource statements for actions and resources --- dist/main.d.ts | 16 +-- dist/main.es.js | 163 ++++++++++++++++++++--------- dist/main.es.js.map | 2 +- dist/main.js | 163 ++++++++++++++++++++--------- dist/main.js.map | 2 +- src/ActionBasedPolicy.ts | 4 +- src/ActionBasedStatement.test.ts | 33 ++++-- src/ActionBasedStatement.ts | 41 +++++--- src/IdentityBasedStatement.test.ts | 77 ++++++++++++++ src/IdentityBasedStatement.ts | 92 +++++++++++----- src/ResourceBasedStatement.test.ts | 49 +++++++++ src/ResourceBasedStatement.ts | 59 +++++++---- src/utils/instanceOfInterfaces.ts | 6 ++ 13 files changed, 527 insertions(+), 180 deletions(-) create mode 100644 src/IdentityBasedStatement.test.ts create mode 100644 src/ResourceBasedStatement.test.ts diff --git a/dist/main.d.ts b/dist/main.d.ts index e5e9ea1..06f926a 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -128,6 +128,7 @@ declare class ActionBased extends Statement { constructor(action: ActionBasedType); getStatement(): ActionBasedType; matches({ action, context, conditionResolver }: MatchActionBasedInterface): boolean; + private checkAndAssignActions; private matchActions; private matchNotActions; } @@ -141,6 +142,8 @@ declare class IdentityBased extends Statement { constructor(identity: IdentityBasedType); getStatement(): IdentityBasedType; matches({ action, resource, context, conditionResolver }: MatchIdentityBasedInterface): boolean; + private checkAndAssignActions; + private checkAndAssignResources; private matchActions; private matchNotActions; private matchResources; @@ -158,12 +161,13 @@ declare class ResourceBased extends Statement { constructor(identity: ResourceBasedType); getStatement(): ResourceBasedType; matches({ principal, action, resource, principalType, context, conditionResolver }: MatchResourceBasedInterface): boolean; - matchPrincipals(principal: string, principalType?: string, context?: Context): boolean; - matchNotPrincipals(principal: string, principalType?: string, context?: Context): boolean; - matchActions(action: string, context?: Context): boolean; - matchNotActions(action: string, context?: Context): boolean; - matchResources(resource: string, context?: Context): boolean; - matchNotResources(resource: string, context?: Context): boolean; + private checkAndAssignActions; + private matchPrincipals; + private matchNotPrincipals; + private matchActions; + private matchNotActions; + private matchResources; + private matchNotResources; } declare class Policy { diff --git a/dist/main.es.js b/dist/main.es.js index f7aa5ca..2ff2773 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -577,11 +577,30 @@ class Statement { * ```javascript * instanceOfActionBlock({ action: 'something' }) * // => true + * + * instanceOfActionBlock({ notAction: 'something' }) + * // => false * ``` */ function instanceOfActionBlock(object) { return 'action' in object; } +/** + * Validate if an `object` is an instance of `NotActionBlock`. + * @param {Object} object Object to validate + * @returns {boolean} Returns true if `object` has `notAction` attribute. + * @example + * ```javascript + * instanceOfNotActionBlock({ notAction: 'something' }) + * // => true + * + * instanceOfNotActionBlock({ action: 'something' }) + * // => false + * ``` + */ +function instanceOfNotActionBlock(object) { + return 'notAction' in object; +} /** * Validate if an `object` is an instance of `PrincipalBlock`. * @param {Object} object Object to validate @@ -603,6 +622,9 @@ function instanceOfPrincipalBlock(object) { * ```javascript * instanceOfNotResourceBlock({ notResource: 'something' }) * // => true + * + * instanceOfNotResourceBlock({ resource: 'something' }) + * // => false * ``` */ function instanceOfNotResourceBlock(object) { @@ -616,6 +638,9 @@ function instanceOfNotResourceBlock(object) { * ```javascript * instanceOfResourceBlock({ resource: 'something' }) * // => true + * + * instanceOfResourceBlock({ notResource: 'something' }) + * // => false * ``` */ function instanceOfResourceBlock(object) { @@ -806,6 +831,26 @@ class Matcher { class ActionBased extends Statement { constructor(action) { super(action); + this.checkAndAssignActions(action); + this.statement = Object.assign(Object.assign({}, action), { sid: this.sid }); + } + getStatement() { + return this.statement; + } + matches({ action, context, conditionResolver }) { + return (this.matchActions(action, context) && + this.matchNotActions(action, context) && + this.matchConditions({ context, conditionResolver })); + } + checkAndAssignActions(action) { + const hasAction = instanceOfActionBlock(action); + const hasNotAction = instanceOfNotActionBlock(action); + if (hasAction && hasNotAction) { + throw new TypeError('ActionBased statement should have an action or a notAction attribute, no both'); + } + if (!hasAction && !hasNotAction) { + throw new TypeError('ActionBased statement should have an action or a notAction attribute'); + } if (instanceOfActionBlock(action)) { this.action = typeof action.action === 'string' ? [action.action] : action.action; @@ -816,24 +861,15 @@ class ActionBased extends Statement { ? [action.notAction] : action.notAction; } - this.statement = Object.assign(Object.assign({}, action), { sid: this.sid }); - } - getStatement() { - return this.statement; - } - matches({ action, context, conditionResolver }) { - return (this.matchActions(action, context) && - this.matchNotActions(action, context) && - this.matchConditions({ context, conditionResolver })); } matchActions(action, context) { return this.action - ? this.action.some(a => new Matcher(applyContext(a, context)).match(action)) + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchNotActions(action, context) { return this.notAction - ? !this.notAction.some(a => new Matcher(applyContext(a, context)).match(action)) + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } } @@ -841,17 +877,28 @@ class ActionBased extends Statement { class IdentityBased extends Statement { constructor(identity) { super(identity); - if (instanceOfResourceBlock(identity)) { - this.resource = - typeof identity.resource === 'string' - ? [identity.resource] - : identity.resource; + this.checkAndAssignActions(identity); + this.checkAndAssignResources(identity); + this.statement = Object.assign(Object.assign({}, identity), { sid: this.sid }); + } + getStatement() { + return this.statement; + } + matches({ action, resource, context, conditionResolver }) { + return (this.matchActions(action, context) && + this.matchNotActions(action, context) && + this.matchResources(resource, context) && + this.matchNotResources(resource, context) && + this.matchConditions({ context, conditionResolver })); + } + checkAndAssignActions(identity) { + const hasAction = instanceOfActionBlock(identity); + const hasNotAction = instanceOfNotActionBlock(identity); + if (hasAction && hasNotAction) { + throw new TypeError('IdentityBased statement should have an action or a notAction attribute, no both'); } - else { - this.notResource = - typeof identity.notResource === 'string' - ? [identity.notResource] - : identity.notResource; + if (!hasAction && !hasNotAction) { + throw new TypeError('IdentityBased statement should have an action or a notAction attribute'); } if (instanceOfActionBlock(identity)) { this.action = @@ -865,36 +912,47 @@ class IdentityBased extends Statement { ? [identity.notAction] : identity.notAction; } - this.statement = Object.assign(Object.assign({}, identity), { sid: this.sid }); } - getStatement() { - return this.statement; - } - matches({ action, resource, context, conditionResolver }) { - return (this.matchActions(action, context) && - this.matchNotActions(action, context) && - this.matchResources(resource, context) && - this.matchNotResources(resource, context) && - this.matchConditions({ context, conditionResolver })); + checkAndAssignResources(identity) { + const hasResource = instanceOfResourceBlock(identity); + const hasNotResource = instanceOfNotResourceBlock(identity); + if (hasResource && hasNotResource) { + throw new TypeError('IdentityBased statement should have a resource or a notResource attribute, no both'); + } + if (!hasResource && !hasNotResource) { + throw new TypeError('IdentityBased statement should have a resource or a notResource attribute'); + } + if (instanceOfResourceBlock(identity)) { + this.resource = + typeof identity.resource === 'string' + ? [identity.resource] + : identity.resource; + } + else { + this.notResource = + typeof identity.notResource === 'string' + ? [identity.notResource] + : identity.notResource; + } } matchActions(action, context) { return this.action - ? this.action.some(a => new Matcher(applyContext(a, context)).match(action)) + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchNotActions(action, context) { return this.notAction - ? !this.notAction.some(a => new Matcher(applyContext(a, context)).match(action)) + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchResources(resource, context) { return this.resource - ? this.resource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } matchNotResources(resource, context) { return this.notResource - ? !this.notResource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } } @@ -902,6 +960,7 @@ class IdentityBased extends Statement { class ResourceBased extends Statement { constructor(identity) { super(identity); + this.checkAndAssignActions(identity); if (instanceOfResourceBlock(identity)) { this.resource = typeof identity.resource === 'string' @@ -914,18 +973,6 @@ class ResourceBased extends Statement { ? [identity.notResource] : identity.notResource; } - if (instanceOfActionBlock(identity)) { - this.action = - typeof identity.action === 'string' - ? [identity.action] - : identity.action; - } - else { - this.notAction = - typeof identity.notAction === 'string' - ? [identity.notAction] - : identity.notAction; - } if (instanceOfPrincipalBlock(identity)) { this.principal = typeof identity.principal === 'string' @@ -952,6 +999,28 @@ class ResourceBased extends Statement { this.matchNotResources(resource, context) && this.matchConditions({ context, conditionResolver })); } + checkAndAssignActions(identity) { + const hasAction = instanceOfActionBlock(identity); + const hasNotAction = instanceOfNotActionBlock(identity); + if (hasAction && hasNotAction) { + throw new TypeError('ResourceBased statement should have an action or a notAction attribute, no both'); + } + if (!hasAction && !hasNotAction) { + throw new TypeError('ResourceBased statement should have an action or a notAction attribute'); + } + if (instanceOfActionBlock(identity)) { + this.action = + typeof identity.action === 'string' + ? [identity.action] + : identity.action; + } + else { + this.notAction = + typeof identity.notAction === 'string' + ? [identity.notAction] + : identity.notAction; + } + } matchPrincipals(principal, principalType, context) { if (this.principal) { if (this.principal instanceof Array) { diff --git a/dist/main.es.js.map b/dist/main.es.js.map index 2d1f2ee..8602e51 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index ed517b1..f4213f4 100644 --- a/dist/main.js +++ b/dist/main.js @@ -581,11 +581,30 @@ class Statement { * ```javascript * instanceOfActionBlock({ action: 'something' }) * // => true + * + * instanceOfActionBlock({ notAction: 'something' }) + * // => false * ``` */ function instanceOfActionBlock(object) { return 'action' in object; } +/** + * Validate if an `object` is an instance of `NotActionBlock`. + * @param {Object} object Object to validate + * @returns {boolean} Returns true if `object` has `notAction` attribute. + * @example + * ```javascript + * instanceOfNotActionBlock({ notAction: 'something' }) + * // => true + * + * instanceOfNotActionBlock({ action: 'something' }) + * // => false + * ``` + */ +function instanceOfNotActionBlock(object) { + return 'notAction' in object; +} /** * Validate if an `object` is an instance of `PrincipalBlock`. * @param {Object} object Object to validate @@ -607,6 +626,9 @@ function instanceOfPrincipalBlock(object) { * ```javascript * instanceOfNotResourceBlock({ notResource: 'something' }) * // => true + * + * instanceOfNotResourceBlock({ resource: 'something' }) + * // => false * ``` */ function instanceOfNotResourceBlock(object) { @@ -620,6 +642,9 @@ function instanceOfNotResourceBlock(object) { * ```javascript * instanceOfResourceBlock({ resource: 'something' }) * // => true + * + * instanceOfResourceBlock({ notResource: 'something' }) + * // => false * ``` */ function instanceOfResourceBlock(object) { @@ -810,6 +835,26 @@ class Matcher { class ActionBased extends Statement { constructor(action) { super(action); + this.checkAndAssignActions(action); + this.statement = Object.assign(Object.assign({}, action), { sid: this.sid }); + } + getStatement() { + return this.statement; + } + matches({ action, context, conditionResolver }) { + return (this.matchActions(action, context) && + this.matchNotActions(action, context) && + this.matchConditions({ context, conditionResolver })); + } + checkAndAssignActions(action) { + const hasAction = instanceOfActionBlock(action); + const hasNotAction = instanceOfNotActionBlock(action); + if (hasAction && hasNotAction) { + throw new TypeError('ActionBased statement should have an action or a notAction attribute, no both'); + } + if (!hasAction && !hasNotAction) { + throw new TypeError('ActionBased statement should have an action or a notAction attribute'); + } if (instanceOfActionBlock(action)) { this.action = typeof action.action === 'string' ? [action.action] : action.action; @@ -820,24 +865,15 @@ class ActionBased extends Statement { ? [action.notAction] : action.notAction; } - this.statement = Object.assign(Object.assign({}, action), { sid: this.sid }); - } - getStatement() { - return this.statement; - } - matches({ action, context, conditionResolver }) { - return (this.matchActions(action, context) && - this.matchNotActions(action, context) && - this.matchConditions({ context, conditionResolver })); } matchActions(action, context) { return this.action - ? this.action.some(a => new Matcher(applyContext(a, context)).match(action)) + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchNotActions(action, context) { return this.notAction - ? !this.notAction.some(a => new Matcher(applyContext(a, context)).match(action)) + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } } @@ -845,17 +881,28 @@ class ActionBased extends Statement { class IdentityBased extends Statement { constructor(identity) { super(identity); - if (instanceOfResourceBlock(identity)) { - this.resource = - typeof identity.resource === 'string' - ? [identity.resource] - : identity.resource; + this.checkAndAssignActions(identity); + this.checkAndAssignResources(identity); + this.statement = Object.assign(Object.assign({}, identity), { sid: this.sid }); + } + getStatement() { + return this.statement; + } + matches({ action, resource, context, conditionResolver }) { + return (this.matchActions(action, context) && + this.matchNotActions(action, context) && + this.matchResources(resource, context) && + this.matchNotResources(resource, context) && + this.matchConditions({ context, conditionResolver })); + } + checkAndAssignActions(identity) { + const hasAction = instanceOfActionBlock(identity); + const hasNotAction = instanceOfNotActionBlock(identity); + if (hasAction && hasNotAction) { + throw new TypeError('IdentityBased statement should have an action or a notAction attribute, no both'); } - else { - this.notResource = - typeof identity.notResource === 'string' - ? [identity.notResource] - : identity.notResource; + if (!hasAction && !hasNotAction) { + throw new TypeError('IdentityBased statement should have an action or a notAction attribute'); } if (instanceOfActionBlock(identity)) { this.action = @@ -869,36 +916,47 @@ class IdentityBased extends Statement { ? [identity.notAction] : identity.notAction; } - this.statement = Object.assign(Object.assign({}, identity), { sid: this.sid }); } - getStatement() { - return this.statement; - } - matches({ action, resource, context, conditionResolver }) { - return (this.matchActions(action, context) && - this.matchNotActions(action, context) && - this.matchResources(resource, context) && - this.matchNotResources(resource, context) && - this.matchConditions({ context, conditionResolver })); + checkAndAssignResources(identity) { + const hasResource = instanceOfResourceBlock(identity); + const hasNotResource = instanceOfNotResourceBlock(identity); + if (hasResource && hasNotResource) { + throw new TypeError('IdentityBased statement should have a resource or a notResource attribute, no both'); + } + if (!hasResource && !hasNotResource) { + throw new TypeError('IdentityBased statement should have a resource or a notResource attribute'); + } + if (instanceOfResourceBlock(identity)) { + this.resource = + typeof identity.resource === 'string' + ? [identity.resource] + : identity.resource; + } + else { + this.notResource = + typeof identity.notResource === 'string' + ? [identity.notResource] + : identity.notResource; + } } matchActions(action, context) { return this.action - ? this.action.some(a => new Matcher(applyContext(a, context)).match(action)) + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchNotActions(action, context) { return this.notAction - ? !this.notAction.some(a => new Matcher(applyContext(a, context)).match(action)) + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action)) : true; } matchResources(resource, context) { return this.resource - ? this.resource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } matchNotResources(resource, context) { return this.notResource - ? !this.notResource.some(a => new Matcher(applyContext(a, context)).match(resource)) + ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource)) : true; } } @@ -906,6 +964,7 @@ class IdentityBased extends Statement { class ResourceBased extends Statement { constructor(identity) { super(identity); + this.checkAndAssignActions(identity); if (instanceOfResourceBlock(identity)) { this.resource = typeof identity.resource === 'string' @@ -918,18 +977,6 @@ class ResourceBased extends Statement { ? [identity.notResource] : identity.notResource; } - if (instanceOfActionBlock(identity)) { - this.action = - typeof identity.action === 'string' - ? [identity.action] - : identity.action; - } - else { - this.notAction = - typeof identity.notAction === 'string' - ? [identity.notAction] - : identity.notAction; - } if (instanceOfPrincipalBlock(identity)) { this.principal = typeof identity.principal === 'string' @@ -956,6 +1003,28 @@ class ResourceBased extends Statement { this.matchNotResources(resource, context) && this.matchConditions({ context, conditionResolver })); } + checkAndAssignActions(identity) { + const hasAction = instanceOfActionBlock(identity); + const hasNotAction = instanceOfNotActionBlock(identity); + if (hasAction && hasNotAction) { + throw new TypeError('ResourceBased statement should have an action or a notAction attribute, no both'); + } + if (!hasAction && !hasNotAction) { + throw new TypeError('ResourceBased statement should have an action or a notAction attribute'); + } + if (instanceOfActionBlock(identity)) { + this.action = + typeof identity.action === 'string' + ? [identity.action] + : identity.action; + } + else { + this.notAction = + typeof identity.notAction === 'string' + ? [identity.notAction] + : identity.notAction; + } + } matchPrincipals(principal, principalType, context) { if (this.principal) { if (this.principal instanceof Array) { diff --git a/dist/main.js.map b/dist/main.js.map index 442e3f2..263dd86 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/src/ActionBasedPolicy.ts b/src/ActionBasedPolicy.ts index 7fc41a7..bb1a3fd 100644 --- a/src/ActionBasedPolicy.ts +++ b/src/ActionBasedPolicy.ts @@ -1,8 +1,8 @@ import { ActionBasedType, - EvaluateActionBasedInterface, ConditionResolver, - Context + Context, + EvaluateActionBasedInterface } from './types'; import { ActionBased } from './ActionBasedStatement'; import { Policy } from './Policy'; diff --git a/src/ActionBasedStatement.test.ts b/src/ActionBasedStatement.test.ts index 6552f4f..677910f 100644 --- a/src/ActionBasedStatement.test.ts +++ b/src/ActionBasedStatement.test.ts @@ -17,16 +17,29 @@ describe('ActionBased Class', () => { ).not.toThrow(); }); - it('throws a TypeError', () => { - const expectedError = new TypeError( - 'ActionBased statement should have an action or a notAction attribute, no both' - ); - expect(() => { - new ActionBased({ - action: ['read', 'write'], - notAction: 'delete' - }); - }).toThrow(expectedError); + describe('when creating action based statement with no actions', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ActionBased statement should have an action or a notAction attribute' + ); + expect(() => { + new ActionBased({}); + }).toThrow(expectedError); + }); + }); + + describe('when creating action based statement with action and notAction attributes', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ActionBased statement should have an action or a notAction attribute, no both' + ); + expect(() => { + new ActionBased({ + action: ['read', 'write'], + notAction: 'delete' + }); + }).toThrow(expectedError); + }); }); }); }); diff --git a/src/ActionBasedStatement.ts b/src/ActionBasedStatement.ts index ef14e08..f271844 100644 --- a/src/ActionBasedStatement.ts +++ b/src/ActionBasedStatement.ts @@ -13,22 +13,7 @@ class ActionBased extends Statement { constructor(action: ActionBasedType) { super(action); - const hasAction = instanceOfActionBlock(action); - const hasNotAction = instanceOfNotActionBlock(action); - if (hasAction && hasNotAction) { - throw new TypeError( - 'ActionBased statement should have an action or a notAction attribute, no both' - ); - } - if (hasAction) { - this.action = - typeof action.action === 'string' ? [action.action] : action.action; - } else { - this.notAction = - typeof action.notAction === 'string' - ? [action.notAction] - : action.notAction; - } + this.checkAndAssignActions(action); this.statement = { ...action, sid: this.sid }; } @@ -48,6 +33,30 @@ class ActionBased extends Statement { ); } + private checkAndAssignActions(action: ActionBasedType): void { + const hasAction = instanceOfActionBlock(action); + const hasNotAction = instanceOfNotActionBlock(action); + if (hasAction && hasNotAction) { + throw new TypeError( + 'ActionBased statement should have an action or a notAction attribute, no both' + ); + } + if (!hasAction && !hasNotAction) { + throw new TypeError( + 'ActionBased statement should have an action or a notAction attribute' + ); + } + if (instanceOfActionBlock(action)) { + this.action = + typeof action.action === 'string' ? [action.action] : action.action; + } else { + this.notAction = + typeof action.notAction === 'string' + ? [action.notAction] + : action.notAction; + } + } + private matchActions(action: string, context?: Context): boolean { return this.action ? this.action.some((a) => diff --git a/src/IdentityBasedStatement.test.ts b/src/IdentityBasedStatement.test.ts new file mode 100644 index 0000000..a9d32cd --- /dev/null +++ b/src/IdentityBasedStatement.test.ts @@ -0,0 +1,77 @@ +import { IdentityBased } from './IdentityBasedStatement'; + +describe('IdentityBased Class', () => { + describe('when creating identity based statement', () => { + it("doesn't throw an error", () => { + expect( + () => + new IdentityBased({ + action: ['read', 'write'], + resource: 'secret' + }) + ).not.toThrow(); + expect( + () => + new IdentityBased({ + notAction: ['write'], + notResource: ['secret'] + }) + ).not.toThrow(); + }); + + describe('when creating identity based statement with no actions', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'IdentityBased statement should have an action or a notAction attribute' + ); + expect(() => { + new IdentityBased({ + resource: 'secret' + }); + }).toThrow(expectedError); + }); + }); + + describe('when creating identity based statement with no resources', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'IdentityBased statement should have a resource or a notResource attribute' + ); + expect(() => { + new IdentityBased({ + action: 'write' + }); + }).toThrow(expectedError); + }); + }); + + describe('when creating identity based statement with action and notAction attributes', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'IdentityBased statement should have an action or a notAction attribute, no both' + ); + expect(() => { + new IdentityBased({ + action: ['read', 'write'], + notAction: 'delete' + }); + }).toThrow(expectedError); + }); + }); + + describe('when creating identity based statement with resource and notResource attributes', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'IdentityBased statement should have a resource or a notResource attribute, no both' + ); + expect(() => { + new IdentityBased({ + action: ['read', 'write'], + resource: ['secret'], + notResource: 'topSecret' + }); + }).toThrow(expectedError); + }); + }); + }); +}); diff --git a/src/IdentityBasedStatement.ts b/src/IdentityBasedStatement.ts index 78636c9..7c30d08 100644 --- a/src/IdentityBasedStatement.ts +++ b/src/IdentityBasedStatement.ts @@ -4,11 +4,13 @@ import { MatchIdentityBasedInterface } from './types'; import { - instanceOfResourceBlock, - instanceOfActionBlock + instanceOfActionBlock, + instanceOfNotActionBlock, + instanceOfNotResourceBlock, + instanceOfResourceBlock } from './utils/instanceOfInterfaces'; import { Matcher } from './Matcher'; -import { Statement, applyContext } from './Statement'; +import { applyContext, Statement } from './Statement'; class IdentityBased extends Statement { private resource?: string[]; @@ -19,28 +21,8 @@ class IdentityBased extends Statement { constructor(identity: IdentityBasedType) { super(identity); - if (instanceOfResourceBlock(identity)) { - this.resource = - typeof identity.resource === 'string' - ? [identity.resource] - : identity.resource; - } else { - this.notResource = - typeof identity.notResource === 'string' - ? [identity.notResource] - : identity.notResource; - } - if (instanceOfActionBlock(identity)) { - this.action = - typeof identity.action === 'string' - ? [identity.action] - : identity.action; - } else { - this.notAction = - typeof identity.notAction === 'string' - ? [identity.notAction] - : identity.notAction; - } + this.checkAndAssignActions(identity); + this.checkAndAssignResources(identity); this.statement = { ...identity, sid: this.sid }; } @@ -63,9 +45,61 @@ class IdentityBased extends Statement { ); } + private checkAndAssignActions(identity: IdentityBasedType): void { + const hasAction = instanceOfActionBlock(identity); + const hasNotAction = instanceOfNotActionBlock(identity); + if (hasAction && hasNotAction) { + throw new TypeError( + 'IdentityBased statement should have an action or a notAction attribute, no both' + ); + } + if (!hasAction && !hasNotAction) { + throw new TypeError( + 'IdentityBased statement should have an action or a notAction attribute' + ); + } + if (instanceOfActionBlock(identity)) { + this.action = + typeof identity.action === 'string' + ? [identity.action] + : identity.action; + } else { + this.notAction = + typeof identity.notAction === 'string' + ? [identity.notAction] + : identity.notAction; + } + } + + private checkAndAssignResources(identity: IdentityBasedType): void { + const hasResource = instanceOfResourceBlock(identity); + const hasNotResource = instanceOfNotResourceBlock(identity); + if (hasResource && hasNotResource) { + throw new TypeError( + 'IdentityBased statement should have a resource or a notResource attribute, no both' + ); + } + if (!hasResource && !hasNotResource) { + throw new TypeError( + 'IdentityBased statement should have a resource or a notResource attribute' + ); + } + if (instanceOfResourceBlock(identity)) { + this.resource = + typeof identity.resource === 'string' + ? [identity.resource] + : identity.resource; + } else { + this.notResource = + typeof identity.notResource === 'string' + ? [identity.notResource] + : identity.notResource; + } + } + private matchActions(action: string, context?: Context): boolean { return this.action - ? this.action.some(a => + ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action) ) : true; @@ -73,7 +107,7 @@ class IdentityBased extends Statement { private matchNotActions(action: string, context?: Context): boolean { return this.notAction - ? !this.notAction.some(a => + ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action) ) : true; @@ -81,7 +115,7 @@ class IdentityBased extends Statement { private matchResources(resource: string, context?: Context): boolean { return this.resource - ? this.resource.some(a => + ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource) ) : true; @@ -89,7 +123,7 @@ class IdentityBased extends Statement { private matchNotResources(resource: string, context?: Context): boolean { return this.notResource - ? !this.notResource.some(a => + ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource) ) : true; diff --git a/src/ResourceBasedStatement.test.ts b/src/ResourceBasedStatement.test.ts new file mode 100644 index 0000000..c44e80c --- /dev/null +++ b/src/ResourceBasedStatement.test.ts @@ -0,0 +1,49 @@ +import { ResourceBased } from './ResourceBasedStatement'; + +describe('ResourceBased Class', () => { + describe('when creating resource based statement', () => { + it("doesn't throw an error", () => { + expect( + () => + new ResourceBased({ + action: ['read', 'write'], + resource: 'secret' + }) + ).not.toThrow(); + expect( + () => + new ResourceBased({ + notAction: ['write'], + notResource: ['secret'] + }) + ).not.toThrow(); + }); + + describe('when creating resource based statement with no actions', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ResourceBased statement should have an action or a notAction attribute' + ); + expect(() => { + new ResourceBased({ + resource: 'secret' + }); + }).toThrow(expectedError); + }); + }); + + describe('when creating resource based statement with action and notAction attributes', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ResourceBased statement should have an action or a notAction attribute, no both' + ); + expect(() => { + new ResourceBased({ + action: ['read', 'write'], + notAction: 'delete' + }); + }).toThrow(expectedError); + }); + }); + }); +}); diff --git a/src/ResourceBasedStatement.ts b/src/ResourceBasedStatement.ts index 5d60cfa..95dcf01 100644 --- a/src/ResourceBasedStatement.ts +++ b/src/ResourceBasedStatement.ts @@ -5,13 +5,14 @@ import { MatchResourceBasedInterface } from './types'; import { - instanceOfPrincipalBlock, - instanceOfResourceBlock, + instanceOfActionBlock, + instanceOfNotActionBlock, instanceOfNotResourceBlock, - instanceOfActionBlock + instanceOfPrincipalBlock, + instanceOfResourceBlock } from './utils/instanceOfInterfaces'; import { Matcher } from './Matcher'; -import { Statement, applyContext } from './Statement'; +import { applyContext, Statement } from './Statement'; class ResourceBased extends Statement { private principal?: PrincipalMap | string[]; @@ -24,6 +25,7 @@ class ResourceBased extends Statement { constructor(identity: ResourceBasedType) { super(identity); + this.checkAndAssignActions(identity); if (instanceOfResourceBlock(identity)) { this.resource = typeof identity.resource === 'string' @@ -35,17 +37,6 @@ class ResourceBased extends Statement { ? [identity.notResource] : identity.notResource; } - if (instanceOfActionBlock(identity)) { - this.action = - typeof identity.action === 'string' - ? [identity.action] - : identity.action; - } else { - this.notAction = - typeof identity.notAction === 'string' - ? [identity.notAction] - : identity.notAction; - } if (instanceOfPrincipalBlock(identity)) { this.principal = typeof identity.principal === 'string' @@ -83,7 +74,33 @@ class ResourceBased extends Statement { ); } - matchPrincipals( + private checkAndAssignActions(identity: ResourceBasedType): void { + const hasAction = instanceOfActionBlock(identity); + const hasNotAction = instanceOfNotActionBlock(identity); + if (hasAction && hasNotAction) { + throw new TypeError( + 'ResourceBased statement should have an action or a notAction attribute, no both' + ); + } + if (!hasAction && !hasNotAction) { + throw new TypeError( + 'ResourceBased statement should have an action or a notAction attribute' + ); + } + if (instanceOfActionBlock(identity)) { + this.action = + typeof identity.action === 'string' + ? [identity.action] + : identity.action; + } else { + this.notAction = + typeof identity.notAction === 'string' + ? [identity.notAction] + : identity.notAction; + } + } + + private matchPrincipals( principal: string, principalType?: string, context?: Context @@ -113,7 +130,7 @@ class ResourceBased extends Statement { return true; } - matchNotPrincipals( + private matchNotPrincipals( principal: string, principalType?: string, context?: Context @@ -143,7 +160,7 @@ class ResourceBased extends Statement { return true; } - matchActions(action: string, context?: Context): boolean { + private matchActions(action: string, context?: Context): boolean { return this.action ? this.action.some((a) => new Matcher(applyContext(a, context)).match(action) @@ -151,7 +168,7 @@ class ResourceBased extends Statement { : true; } - matchNotActions(action: string, context?: Context): boolean { + private matchNotActions(action: string, context?: Context): boolean { return this.notAction ? !this.notAction.some((a) => new Matcher(applyContext(a, context)).match(action) @@ -159,7 +176,7 @@ class ResourceBased extends Statement { : true; } - matchResources(resource: string, context?: Context): boolean { + private matchResources(resource: string, context?: Context): boolean { return this.resource ? this.resource.some((a) => new Matcher(applyContext(a, context)).match(resource) @@ -167,7 +184,7 @@ class ResourceBased extends Statement { : true; } - matchNotResources(resource: string, context?: Context): boolean { + private matchNotResources(resource: string, context?: Context): boolean { return this.notResource ? !this.notResource.some((a) => new Matcher(applyContext(a, context)).match(resource) diff --git a/src/utils/instanceOfInterfaces.ts b/src/utils/instanceOfInterfaces.ts index e7f2e34..d31447e 100644 --- a/src/utils/instanceOfInterfaces.ts +++ b/src/utils/instanceOfInterfaces.ts @@ -66,6 +66,9 @@ export function instanceOfPrincipalBlock( * ```javascript * instanceOfNotResourceBlock({ notResource: 'something' }) * // => true + * + * instanceOfNotResourceBlock({ resource: 'something' }) + * // => false * ``` */ export function instanceOfNotResourceBlock( @@ -82,6 +85,9 @@ export function instanceOfNotResourceBlock( * ```javascript * instanceOfResourceBlock({ resource: 'something' }) * // => true + * + * instanceOfResourceBlock({ notResource: 'something' }) + * // => false * ``` */ export function instanceOfResourceBlock( From 2aee00caf6ac720213cbb7bcc1da2c5292f41acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 18 Oct 2020 20:08:04 -0500 Subject: [PATCH 07/13] fix(resourcebasedstatement): validate principal, notPrincipal, resource and notResource attributes check when there aren't principal and notPrincipal attributes, and when there aren't resource and notResource attributes --- dist/main.d.ts | 6 + dist/main.es.js | 166 +++++++++++++++++-------- dist/main.es.js.map | 2 +- dist/main.js | 166 +++++++++++++++++-------- dist/main.js.map | 2 +- src/Matcher.test.ts | 22 +++- src/Matcher.ts | 36 ++---- src/ResourceBasedPolicy.test.ts | 37 +++++- src/ResourceBasedStatement.test.ts | 30 +++++ src/ResourceBasedStatement.ts | 132 +++++++++++++++----- src/types.ts | 1 + src/utils/instanceOfInterfaces.test.ts | 23 +++- src/utils/instanceOfInterfaces.ts | 25 +++- 13 files changed, 480 insertions(+), 168 deletions(-) diff --git a/dist/main.d.ts b/dist/main.d.ts index 06f926a..cd44e02 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -158,10 +158,16 @@ declare class ResourceBased extends Statement { private notResource?; private notAction?; private statement; + private hasPrincipals; + private hasResources; constructor(identity: ResourceBasedType); getStatement(): ResourceBasedType; matches({ principal, action, resource, principalType, context, conditionResolver }: MatchResourceBasedInterface): boolean; + private matchPrincipalAndNotPrincipal; + private matchResourceAndNotResource; private checkAndAssignActions; + private checkAndAssignPrincipals; + private checkAndAssignResources; private matchPrincipals; private matchNotPrincipals; private matchActions; diff --git a/dist/main.es.js b/dist/main.es.js index 2ff2773..4cd1e54 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -609,11 +609,30 @@ function instanceOfNotActionBlock(object) { * ```javascript * instanceOfPrincipalBlock({ principal: 'something' }) * // => true + * + * instanceOfPrincipalBlock({ notPrincipal: 'something' }) + * // => false * ``` */ function instanceOfPrincipalBlock(object) { return 'principal' in object; } +/** + * Validate if an `object` is an instance of `NotPrincipalBlock`. + * @param {Object} object Object to validate + * @returns {boolean} Returns true if `object` has `notPrincipal` attribute. + * @example + * ```javascript + * instanceOfNotPrincipalBlock({ notPrincipal: 'something' }) + * // => true + * + * instanceOfNotPrincipalBlock({ principal: 'something' }) + * // => false + * ``` + */ +function instanceOfNotPrincipalBlock(object) { + return 'notPrincipal' in object; +} /** * Validate if an `object` is an instance of `NotResourceBlock`. * @param {Object} object Object to validate @@ -734,35 +753,27 @@ function decomposeString(initialSeparator, finalSeparator, str) { } class Matcher { - constructor(pattern) { + constructor(pattern, maxLength = 1024 * 64) { this.set = []; this.pattern = pattern.trim(); + this.maxLength = maxLength; this.empty = !this.pattern ? true : false; const set = this.braceExpand(); - this.set = set.map(val => this.parse(val)); - this.set = this.set.filter(s => { + this.set = set.map((val) => this.parse(val)); + this.set = this.set.filter((s) => { return Boolean(s); }); } braceExpand() { - let pattern = this.pattern; + const pattern = this.pattern; if (!pattern.match(/{.*}/)) { return [pattern]; } - // I don't know why Bash 4.3 does this, but it does. - // Anything starting with {} will have the first two bytes preserved - // but only at the top level, so {},a}b will not expand to anything, - // but a{},b}c will be expanded to [a}c,abc]. - // One could argue that this is a bug in Bash, but since the goal of - // this module is to match Bash's rules, we escape a leading {} - if (pattern.substr(0, 2) === '{}') { - pattern = '\\{\\}' + pattern.substr(2); - } return this.expand(pattern, true); } parse(pattern) { - if (pattern.length > 1024 * 64) { - throw new TypeError('pattern is too long'); + if (pattern.length > this.maxLength) { + throw new TypeError('Pattern is too long'); } let regExp; let hasSpecialCharacter = false; @@ -793,18 +804,14 @@ class Matcher { const balance = decomposeString('{', '}', str); if (balance.start < 0 || /\$$/.test(balance.pre)) return [str]; - let parts; - if (!balance.body) - parts = ['']; - else - parts = balance.body.split(','); + const parts = balance.body.split(','); // no need to expand pre, since it is guaranteed to be free of brace-sets const pre = balance.pre; const postParts = balance.post.length ? this.expand(balance.post, false) : ['']; parts.forEach((part) => { - postParts.forEach(postPart => { + postParts.forEach((postPart) => { const expansion = pre + part + postPart; if (!isTop || expansion) expansions.push(expansion); @@ -815,12 +822,9 @@ class Matcher { match(str) { if (this.empty) return str === ''; - const set = this.set; - return set.some(pattern => this.matchOne(str, pattern)); + return this.set.some((pattern) => this.matchOne(str, pattern)); } matchOne(str, pattern) { - if (!pattern) - return false; if (typeof pattern === 'string') { return str === pattern; } @@ -960,45 +964,59 @@ class IdentityBased extends Statement { class ResourceBased extends Statement { constructor(identity) { super(identity); + this.hasPrincipals = false; + this.hasResources = false; this.checkAndAssignActions(identity); - if (instanceOfResourceBlock(identity)) { - this.resource = - typeof identity.resource === 'string' - ? [identity.resource] - : identity.resource; - } - else if (instanceOfNotResourceBlock(identity)) { - this.notResource = - typeof identity.notResource === 'string' - ? [identity.notResource] - : identity.notResource; - } - if (instanceOfPrincipalBlock(identity)) { - this.principal = - typeof identity.principal === 'string' - ? [identity.principal] - : identity.principal; - } - else { - this.notPrincipal = - typeof identity.notPrincipal === 'string' - ? [identity.notPrincipal] - : identity.notPrincipal; - } + this.checkAndAssignPrincipals(identity); + this.checkAndAssignResources(identity); this.statement = Object.assign(Object.assign({}, identity), { sid: this.sid }); } getStatement() { return this.statement; } matches({ principal, action, resource, principalType, context, conditionResolver }) { - return (this.matchPrincipals(principal, principalType, context) && - this.matchNotPrincipals(principal, principalType, context) && + return (this.matchPrincipalAndNotPrincipal(principal, principalType, context) && this.matchActions(action, context) && this.matchNotActions(action, context) && - this.matchResources(resource, context) && - this.matchNotResources(resource, context) && + this.matchResourceAndNotResource(resource, context) && this.matchConditions({ context, conditionResolver })); } + /*valueComing principal noPrincipal + true false false false + true true false true or false + true false true true or false + false false false true + false true false false + false false true false*/ + matchPrincipalAndNotPrincipal(principal, principalType, context) { + if (principal) { + if (this.hasPrincipals) + return (this.matchPrincipals(principal, principalType, context) && + this.matchNotPrincipals(principal, principalType, context)); + return false; + } + if (this.hasPrincipals) + return false; + return true; + } + /*valueComing resource noResource + true false false false + true true false true or false + true false true true or false + false false false true + false true false false + false false true false*/ + matchResourceAndNotResource(resource, context) { + if (resource) { + if (this.hasResources) + return (this.matchResources(resource, context) && + this.matchNotResources(resource, context)); + return false; + } + if (this.hasResources) + return false; + return true; + } checkAndAssignActions(identity) { const hasAction = instanceOfActionBlock(identity); const hasNotAction = instanceOfNotActionBlock(identity); @@ -1021,6 +1039,48 @@ class ResourceBased extends Statement { : identity.notAction; } } + checkAndAssignPrincipals(identity) { + const hasPrincipal = instanceOfPrincipalBlock(identity); + const hasNotPrincipal = instanceOfNotPrincipalBlock(identity); + if (hasPrincipal && hasNotPrincipal) { + throw new TypeError('ResourceBased statement could have a principal or a notPrincipal attribute, no both'); + } + if (instanceOfPrincipalBlock(identity)) { + this.principal = + typeof identity.principal === 'string' + ? [identity.principal] + : identity.principal; + this.hasPrincipals = true; + } + else if (instanceOfNotPrincipalBlock(identity)) { + this.notPrincipal = + typeof identity.notPrincipal === 'string' + ? [identity.notPrincipal] + : identity.notPrincipal; + this.hasPrincipals = true; + } + } + checkAndAssignResources(identity) { + const hasResource = instanceOfResourceBlock(identity); + const hasNotResource = instanceOfNotResourceBlock(identity); + if (hasResource && hasNotResource) { + throw new TypeError('ResourceBased statement could have a resource or a notResource attribute, no both'); + } + if (instanceOfResourceBlock(identity)) { + this.resource = + typeof identity.resource === 'string' + ? [identity.resource] + : identity.resource; + this.hasResources = true; + } + else if (instanceOfNotResourceBlock(identity)) { + this.notResource = + typeof identity.notResource === 'string' + ? [identity.notResource] + : identity.notResource; + this.hasResources = true; + } + } matchPrincipals(principal, principalType, context) { if (this.principal) { if (this.principal instanceof Array) { diff --git a/dist/main.es.js.map b/dist/main.es.js.map index 8602e51..c9aa7c5 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index f4213f4..ddcc02e 100644 --- a/dist/main.js +++ b/dist/main.js @@ -613,11 +613,30 @@ function instanceOfNotActionBlock(object) { * ```javascript * instanceOfPrincipalBlock({ principal: 'something' }) * // => true + * + * instanceOfPrincipalBlock({ notPrincipal: 'something' }) + * // => false * ``` */ function instanceOfPrincipalBlock(object) { return 'principal' in object; } +/** + * Validate if an `object` is an instance of `NotPrincipalBlock`. + * @param {Object} object Object to validate + * @returns {boolean} Returns true if `object` has `notPrincipal` attribute. + * @example + * ```javascript + * instanceOfNotPrincipalBlock({ notPrincipal: 'something' }) + * // => true + * + * instanceOfNotPrincipalBlock({ principal: 'something' }) + * // => false + * ``` + */ +function instanceOfNotPrincipalBlock(object) { + return 'notPrincipal' in object; +} /** * Validate if an `object` is an instance of `NotResourceBlock`. * @param {Object} object Object to validate @@ -738,35 +757,27 @@ function decomposeString(initialSeparator, finalSeparator, str) { } class Matcher { - constructor(pattern) { + constructor(pattern, maxLength = 1024 * 64) { this.set = []; this.pattern = pattern.trim(); + this.maxLength = maxLength; this.empty = !this.pattern ? true : false; const set = this.braceExpand(); - this.set = set.map(val => this.parse(val)); - this.set = this.set.filter(s => { + this.set = set.map((val) => this.parse(val)); + this.set = this.set.filter((s) => { return Boolean(s); }); } braceExpand() { - let pattern = this.pattern; + const pattern = this.pattern; if (!pattern.match(/{.*}/)) { return [pattern]; } - // I don't know why Bash 4.3 does this, but it does. - // Anything starting with {} will have the first two bytes preserved - // but only at the top level, so {},a}b will not expand to anything, - // but a{},b}c will be expanded to [a}c,abc]. - // One could argue that this is a bug in Bash, but since the goal of - // this module is to match Bash's rules, we escape a leading {} - if (pattern.substr(0, 2) === '{}') { - pattern = '\\{\\}' + pattern.substr(2); - } return this.expand(pattern, true); } parse(pattern) { - if (pattern.length > 1024 * 64) { - throw new TypeError('pattern is too long'); + if (pattern.length > this.maxLength) { + throw new TypeError('Pattern is too long'); } let regExp; let hasSpecialCharacter = false; @@ -797,18 +808,14 @@ class Matcher { const balance = decomposeString('{', '}', str); if (balance.start < 0 || /\$$/.test(balance.pre)) return [str]; - let parts; - if (!balance.body) - parts = ['']; - else - parts = balance.body.split(','); + const parts = balance.body.split(','); // no need to expand pre, since it is guaranteed to be free of brace-sets const pre = balance.pre; const postParts = balance.post.length ? this.expand(balance.post, false) : ['']; parts.forEach((part) => { - postParts.forEach(postPart => { + postParts.forEach((postPart) => { const expansion = pre + part + postPart; if (!isTop || expansion) expansions.push(expansion); @@ -819,12 +826,9 @@ class Matcher { match(str) { if (this.empty) return str === ''; - const set = this.set; - return set.some(pattern => this.matchOne(str, pattern)); + return this.set.some((pattern) => this.matchOne(str, pattern)); } matchOne(str, pattern) { - if (!pattern) - return false; if (typeof pattern === 'string') { return str === pattern; } @@ -964,45 +968,59 @@ class IdentityBased extends Statement { class ResourceBased extends Statement { constructor(identity) { super(identity); + this.hasPrincipals = false; + this.hasResources = false; this.checkAndAssignActions(identity); - if (instanceOfResourceBlock(identity)) { - this.resource = - typeof identity.resource === 'string' - ? [identity.resource] - : identity.resource; - } - else if (instanceOfNotResourceBlock(identity)) { - this.notResource = - typeof identity.notResource === 'string' - ? [identity.notResource] - : identity.notResource; - } - if (instanceOfPrincipalBlock(identity)) { - this.principal = - typeof identity.principal === 'string' - ? [identity.principal] - : identity.principal; - } - else { - this.notPrincipal = - typeof identity.notPrincipal === 'string' - ? [identity.notPrincipal] - : identity.notPrincipal; - } + this.checkAndAssignPrincipals(identity); + this.checkAndAssignResources(identity); this.statement = Object.assign(Object.assign({}, identity), { sid: this.sid }); } getStatement() { return this.statement; } matches({ principal, action, resource, principalType, context, conditionResolver }) { - return (this.matchPrincipals(principal, principalType, context) && - this.matchNotPrincipals(principal, principalType, context) && + return (this.matchPrincipalAndNotPrincipal(principal, principalType, context) && this.matchActions(action, context) && this.matchNotActions(action, context) && - this.matchResources(resource, context) && - this.matchNotResources(resource, context) && + this.matchResourceAndNotResource(resource, context) && this.matchConditions({ context, conditionResolver })); } + /*valueComing principal noPrincipal + true false false false + true true false true or false + true false true true or false + false false false true + false true false false + false false true false*/ + matchPrincipalAndNotPrincipal(principal, principalType, context) { + if (principal) { + if (this.hasPrincipals) + return (this.matchPrincipals(principal, principalType, context) && + this.matchNotPrincipals(principal, principalType, context)); + return false; + } + if (this.hasPrincipals) + return false; + return true; + } + /*valueComing resource noResource + true false false false + true true false true or false + true false true true or false + false false false true + false true false false + false false true false*/ + matchResourceAndNotResource(resource, context) { + if (resource) { + if (this.hasResources) + return (this.matchResources(resource, context) && + this.matchNotResources(resource, context)); + return false; + } + if (this.hasResources) + return false; + return true; + } checkAndAssignActions(identity) { const hasAction = instanceOfActionBlock(identity); const hasNotAction = instanceOfNotActionBlock(identity); @@ -1025,6 +1043,48 @@ class ResourceBased extends Statement { : identity.notAction; } } + checkAndAssignPrincipals(identity) { + const hasPrincipal = instanceOfPrincipalBlock(identity); + const hasNotPrincipal = instanceOfNotPrincipalBlock(identity); + if (hasPrincipal && hasNotPrincipal) { + throw new TypeError('ResourceBased statement could have a principal or a notPrincipal attribute, no both'); + } + if (instanceOfPrincipalBlock(identity)) { + this.principal = + typeof identity.principal === 'string' + ? [identity.principal] + : identity.principal; + this.hasPrincipals = true; + } + else if (instanceOfNotPrincipalBlock(identity)) { + this.notPrincipal = + typeof identity.notPrincipal === 'string' + ? [identity.notPrincipal] + : identity.notPrincipal; + this.hasPrincipals = true; + } + } + checkAndAssignResources(identity) { + const hasResource = instanceOfResourceBlock(identity); + const hasNotResource = instanceOfNotResourceBlock(identity); + if (hasResource && hasNotResource) { + throw new TypeError('ResourceBased statement could have a resource or a notResource attribute, no both'); + } + if (instanceOfResourceBlock(identity)) { + this.resource = + typeof identity.resource === 'string' + ? [identity.resource] + : identity.resource; + this.hasResources = true; + } + else if (instanceOfNotResourceBlock(identity)) { + this.notResource = + typeof identity.notResource === 'string' + ? [identity.notResource] + : identity.notResource; + this.hasResources = true; + } + } matchPrincipals(principal, principalType, context) { if (this.principal) { if (this.principal instanceof Array) { diff --git a/dist/main.js.map b/dist/main.js.map index 263dd86..6c6ff22 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/src/Matcher.test.ts b/src/Matcher.test.ts index 9b7b746..6d8ff73 100644 --- a/src/Matcher.test.ts +++ b/src/Matcher.test.ts @@ -1,19 +1,38 @@ import { Matcher } from './Matcher'; describe('Matcher Class', () => { - describe('when creating identity based policy', () => { + describe('when creating a Matcher instance', () => { it("doesn't throw an error", () => { expect(() => new Matcher('secrets:123:*')).not.toThrow(); }); + it('throws a TypeError', () => { + const expectedError = new TypeError('Pattern is too long'); + expect(() => new Matcher('secrets:123:*', 5)).toThrow(expectedError); + }); + it('returns a Matcher instance', () => { expect(new Matcher('secrets:123:*')).toBeInstanceOf(Matcher); }); }); describe('when match strings', () => { + describe('when using an invalid regex pattern', () => { + it('returns false with any string', () => { + expect(new Matcher('\\(*)').match('')).toBe(false); + expect(new Matcher('\\(*)').match('word')).toBe(false); + }); + }); + + describe('when pattern is an empty string', () => { + it('returns true', () => { + expect(new Matcher('').match('')).toBe(true); + }); + }); + it('returns true', () => { expect(new Matcher('*').match('secrets::999/image')).toBe(true); + expect(new Matcher('{125,126}').match('125')).toBe(true); expect(new Matcher('secrets:123').match('secrets:123')).toBe(true); expect( new Matcher('secrets:*:something').match('secrets:123:something') @@ -21,6 +40,7 @@ describe('Matcher Class', () => { }); it('returns false', () => { + expect(new Matcher('{,}').match('')).toBe(false); expect(new Matcher('secrets:123').match('secrets:124')).toBe(false); expect(new Matcher('secrets:123:*').match('secrets:123')).toBe(false); expect(new Matcher('secrets:123:*').match('secrets:124:something')).toBe( diff --git a/src/Matcher.ts b/src/Matcher.ts index a941d1c..e4fd907 100644 --- a/src/Matcher.ts +++ b/src/Matcher.ts @@ -2,42 +2,35 @@ import { decomposeString } from './utils/decomposeString'; export class Matcher { private readonly pattern: string; + private readonly maxLength: number; private readonly set: (string | RegExp)[]; private readonly empty: boolean; - constructor(pattern: string) { + constructor(pattern: string, maxLength = 1024 * 64) { this.set = []; this.pattern = pattern.trim(); + this.maxLength = maxLength; this.empty = !this.pattern ? true : false; const set = this.braceExpand(); - this.set = set.map(val => this.parse(val)); - this.set = this.set.filter(s => { + this.set = set.map((val) => this.parse(val)); + this.set = this.set.filter((s) => { return Boolean(s); }); } private braceExpand(): string[] { - let pattern = this.pattern; + const pattern = this.pattern; if (!pattern.match(/{.*}/)) { return [pattern]; } - // I don't know why Bash 4.3 does this, but it does. - // Anything starting with {} will have the first two bytes preserved - // but only at the top level, so {},a}b will not expand to anything, - // but a{},b}c will be expanded to [a}c,abc]. - // One could argue that this is a bug in Bash, but since the goal of - // this module is to match Bash's rules, we escape a leading {} - if (pattern.substr(0, 2) === '{}') { - pattern = '\\{\\}' + pattern.substr(2); - } return this.expand(pattern, true); } private parse(pattern: string): string | RegExp { - if (pattern.length > 1024 * 64) { - throw new TypeError('pattern is too long'); + if (pattern.length > this.maxLength) { + throw new TypeError('Pattern is too long'); } let regExp; let hasSpecialCharacter = false; @@ -70,11 +63,8 @@ export class Matcher { const expansions = [] as string[]; const balance = decomposeString('{', '}', str); if (balance.start < 0 || /\$$/.test(balance.pre)) return [str]; - let parts; - - if (!balance.body) parts = ['']; - else parts = balance.body.split(','); + const parts = balance.body.split(','); // no need to expand pre, since it is guaranteed to be free of brace-sets const pre = balance.pre; const postParts = balance.post.length @@ -82,7 +72,7 @@ export class Matcher { : ['']; parts.forEach((part: string) => { - postParts.forEach(postPart => { + postParts.forEach((postPart) => { const expansion = pre + part + postPart; if (!isTop || expansion) expansions.push(expansion); }); @@ -94,14 +84,10 @@ export class Matcher { match(str: string): boolean { if (this.empty) return str === ''; - const set = this.set; - - return set.some(pattern => this.matchOne(str, pattern)); + return this.set.some((pattern) => this.matchOne(str, pattern)); } private matchOne(str: string, pattern: string | RegExp): boolean { - if (!pattern) return false; - if (typeof pattern === 'string') { return str === pattern; } diff --git a/src/ResourceBasedPolicy.test.ts b/src/ResourceBasedPolicy.test.ts index e6a9a57..41451ae 100644 --- a/src/ResourceBasedPolicy.test.ts +++ b/src/ResourceBasedPolicy.test.ts @@ -56,6 +56,10 @@ describe('ResourceBasedPolicy Class', () => { principal: { id: '1' }, resource: ['books:horror:*'], action: 'write' + }, + { + resource: ['books:science:*'], + action: 'read' } ]); @@ -95,6 +99,19 @@ describe('ResourceBasedPolicy Class', () => { resource: 'books:horror:The Call of Cthulhu' }) ).toBe(false); + expect( + policy.evaluate({ + action: 'read', + resource: 'books:science:Chemistry' + }) + ).toBe(true); + expect( + policy.evaluate({ + principal: 'id1', + action: 'read', + resource: 'books:science:Chemistry' + }) + ).toBe(false); }); }); @@ -223,6 +240,10 @@ describe('ResourceBasedPolicy Class', () => { principal: ['rogger', 'andre'], resource: 'books:horror:*', action: ['read'] + }, + { + principal: 'andre', + action: ['write'] } ]); @@ -236,8 +257,20 @@ describe('ResourceBasedPolicy Class', () => { expect( policy.evaluate({ principal: 'andre', - action: 'read', - resource: 'books:fantasy:Brisingr' + action: 'read' + }) + ).toBe(false); + expect( + policy.evaluate({ + principal: 'andre', + action: 'write' + }) + ).toBe(true); + expect( + policy.evaluate({ + principal: 'andre', + resource: 'books', + action: 'write' }) ).toBe(false); }); diff --git a/src/ResourceBasedStatement.test.ts b/src/ResourceBasedStatement.test.ts index c44e80c..fbf54a6 100644 --- a/src/ResourceBasedStatement.test.ts +++ b/src/ResourceBasedStatement.test.ts @@ -45,5 +45,35 @@ describe('ResourceBased Class', () => { }).toThrow(expectedError); }); }); + + describe('when creating resource based statement with resource and notResource attributes', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ResourceBased statement could have a resource or a notResource attribute, no both' + ); + expect(() => { + new ResourceBased({ + action: ['read', 'write'], + resource: ['account'], + notResource: 'profile' + }); + }).toThrow(expectedError); + }); + }); + + describe('when creating resource based statement with principal and notPrincipal attributes', () => { + it('throws a TypeError', () => { + const expectedError = new TypeError( + 'ResourceBased statement could have a principal or a notPrincipal attribute, no both' + ); + expect(() => { + new ResourceBased({ + action: ['read', 'write'], + principal: ['id1'], + notPrincipal: 'id2' + }); + }).toThrow(expectedError); + }); + }); }); }); diff --git a/src/ResourceBasedStatement.ts b/src/ResourceBasedStatement.ts index 95dcf01..a22fbc9 100644 --- a/src/ResourceBasedStatement.ts +++ b/src/ResourceBasedStatement.ts @@ -1,12 +1,13 @@ import { PrincipalMap, Context, - ResourceBasedType, - MatchResourceBasedInterface + MatchResourceBasedInterface, + ResourceBasedType } from './types'; import { instanceOfActionBlock, instanceOfNotActionBlock, + instanceOfNotPrincipalBlock, instanceOfNotResourceBlock, instanceOfPrincipalBlock, instanceOfResourceBlock @@ -22,32 +23,16 @@ class ResourceBased extends Statement { private notResource?: string[]; private notAction?: string[]; private statement: ResourceBasedType; + private hasPrincipals: boolean; + private hasResources: boolean; constructor(identity: ResourceBasedType) { super(identity); + this.hasPrincipals = false; + this.hasResources = false; this.checkAndAssignActions(identity); - if (instanceOfResourceBlock(identity)) { - this.resource = - typeof identity.resource === 'string' - ? [identity.resource] - : identity.resource; - } else if (instanceOfNotResourceBlock(identity)) { - this.notResource = - typeof identity.notResource === 'string' - ? [identity.notResource] - : identity.notResource; - } - if (instanceOfPrincipalBlock(identity)) { - this.principal = - typeof identity.principal === 'string' - ? [identity.principal] - : identity.principal; - } else { - this.notPrincipal = - typeof identity.notPrincipal === 'string' - ? [identity.notPrincipal] - : identity.notPrincipal; - } + this.checkAndAssignPrincipals(identity); + this.checkAndAssignResources(identity); this.statement = { ...identity, sid: this.sid }; } @@ -64,16 +49,61 @@ class ResourceBased extends Statement { conditionResolver }: MatchResourceBasedInterface): boolean { return ( - this.matchPrincipals(principal, principalType, context) && - this.matchNotPrincipals(principal, principalType, context) && + this.matchPrincipalAndNotPrincipal(principal, principalType, context) && this.matchActions(action, context) && this.matchNotActions(action, context) && - this.matchResources(resource, context) && - this.matchNotResources(resource, context) && + this.matchResourceAndNotResource(resource, context) && this.matchConditions({ context, conditionResolver }) ); } + /*valueComing principal noPrincipal + true false false false + true true false true or false + true false true true or false + false false false true + false true false false + false false true false*/ + private matchPrincipalAndNotPrincipal( + principal?: string, + principalType?: string, + context?: Context + ): boolean { + if (principal) { + if (this.hasPrincipals) + return ( + this.matchPrincipals(principal, principalType, context) && + this.matchNotPrincipals(principal, principalType, context) + ); + return false; + } + if (this.hasPrincipals) return false; + return true; + } + + /*valueComing resource noResource + true false false false + true true false true or false + true false true true or false + false false false true + false true false false + false false true false*/ + private matchResourceAndNotResource( + resource?: string, + context?: Context + ): boolean { + if (resource) { + if (this.hasResources) + return ( + this.matchResources(resource, context) && + this.matchNotResources(resource, context) + ); + return false; + } + if (this.hasResources) return false; + return true; + } + private checkAndAssignActions(identity: ResourceBasedType): void { const hasAction = instanceOfActionBlock(identity); const hasNotAction = instanceOfNotActionBlock(identity); @@ -100,6 +130,52 @@ class ResourceBased extends Statement { } } + private checkAndAssignPrincipals(identity: ResourceBasedType): void { + const hasPrincipal = instanceOfPrincipalBlock(identity); + const hasNotPrincipal = instanceOfNotPrincipalBlock(identity); + if (hasPrincipal && hasNotPrincipal) { + throw new TypeError( + 'ResourceBased statement could have a principal or a notPrincipal attribute, no both' + ); + } + if (instanceOfPrincipalBlock(identity)) { + this.principal = + typeof identity.principal === 'string' + ? [identity.principal] + : identity.principal; + this.hasPrincipals = true; + } else if (instanceOfNotPrincipalBlock(identity)) { + this.notPrincipal = + typeof identity.notPrincipal === 'string' + ? [identity.notPrincipal] + : identity.notPrincipal; + this.hasPrincipals = true; + } + } + + private checkAndAssignResources(identity: ResourceBasedType): void { + const hasResource = instanceOfResourceBlock(identity); + const hasNotResource = instanceOfNotResourceBlock(identity); + if (hasResource && hasNotResource) { + throw new TypeError( + 'ResourceBased statement could have a resource or a notResource attribute, no both' + ); + } + if (instanceOfResourceBlock(identity)) { + this.resource = + typeof identity.resource === 'string' + ? [identity.resource] + : identity.resource; + this.hasResources = true; + } else if (instanceOfNotResourceBlock(identity)) { + this.notResource = + typeof identity.notResource === 'string' + ? [identity.notResource] + : identity.notResource; + this.hasResources = true; + } + } + private matchPrincipals( principal: string, principalType?: string, diff --git a/src/types.ts b/src/types.ts index 9f598e4..7e574f6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -123,6 +123,7 @@ export { ResourceBasedType, ResourceBlock, NotActionBlock, + NotPrincipalBlock, NotResourceBlock }; diff --git a/src/utils/instanceOfInterfaces.test.ts b/src/utils/instanceOfInterfaces.test.ts index 2598592..0e0ab04 100644 --- a/src/utils/instanceOfInterfaces.test.ts +++ b/src/utils/instanceOfInterfaces.test.ts @@ -1,9 +1,10 @@ import { instanceOfActionBlock, - instanceOfPrincipalBlock, - instanceOfResourceBlock, instanceOfNotActionBlock, - instanceOfNotResourceBlock + instanceOfNotPrincipalBlock, + instanceOfNotResourceBlock, + instanceOfPrincipalBlock, + instanceOfResourceBlock } from './instanceOfInterfaces'; describe('Util functions', () => { @@ -21,6 +22,22 @@ describe('Util functions', () => { }); }); + describe('instanceOfNotPrincipalBlock', () => { + it("doesn't throw an error", () => { + expect(() => + instanceOfNotPrincipalBlock({ + notPrincipal: 'something' + }) + ).not.toThrow(); + expect(instanceOfNotPrincipalBlock({ notPrincipal: 'something' })).toBe( + true + ); + expect(instanceOfNotPrincipalBlock({ principal: 'something' })).toBe( + false + ); + }); + }); + describe('instanceOfResourceBlock', () => { it("doesn't throw an error", () => { expect(() => diff --git a/src/utils/instanceOfInterfaces.ts b/src/utils/instanceOfInterfaces.ts index d31447e..9bb2451 100644 --- a/src/utils/instanceOfInterfaces.ts +++ b/src/utils/instanceOfInterfaces.ts @@ -1,8 +1,9 @@ import { ActionBlock, NotActionBlock, - PrincipalBlock, + NotPrincipalBlock, NotResourceBlock, + PrincipalBlock, ResourceBlock } from '../types'; @@ -50,6 +51,9 @@ export function instanceOfNotActionBlock( * ```javascript * instanceOfPrincipalBlock({ principal: 'something' }) * // => true + * + * instanceOfPrincipalBlock({ notPrincipal: 'something' }) + * // => false * ``` */ export function instanceOfPrincipalBlock( @@ -58,6 +62,25 @@ export function instanceOfPrincipalBlock( return 'principal' in object; } +/** + * Validate if an `object` is an instance of `NotPrincipalBlock`. + * @param {Object} object Object to validate + * @returns {boolean} Returns true if `object` has `notPrincipal` attribute. + * @example + * ```javascript + * instanceOfNotPrincipalBlock({ notPrincipal: 'something' }) + * // => true + * + * instanceOfNotPrincipalBlock({ principal: 'something' }) + * // => false + * ``` + */ +export function instanceOfNotPrincipalBlock( + object: object +): object is NotPrincipalBlock { + return 'notPrincipal' in object; +} + /** * Validate if an `object` is an instance of `NotResourceBlock`. * @param {Object} object Object to validate From bd06fc6ec22177e0fca38308bda7601e4c47fd1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 18 Oct 2020 20:43:19 -0500 Subject: [PATCH 08/13] feat: extending Policy in Identity and Resource Based Policies allow get and set context and conditionResolver BREAKING CHANGE: new way to contruct Policies instances with a json --- src/ActionBasedPolicy.test.ts | 3 + src/ActionBasedPolicy.ts | 2 +- src/ActionBasedStatement.test.ts | 2 + src/IdentityBasedPolicy.test.ts | 155 ++++++++++-------- src/IdentityBasedPolicy.ts | 44 +++-- src/IdentityBasedStatement.test.ts | 4 + src/Matcher.test.ts | 1 + src/Policy.test.ts | 4 + src/ResourceBasedPolicy.test.ts | 251 ++++++++++++++++------------- src/ResourceBasedPolicy.ts | 44 +++-- src/ResourceBasedStatement.test.ts | 4 + src/Statement.test.ts | 6 +- src/utils/decomposeString.test.ts | 1 + src/utils/getTag.test.ts | 3 + src/utils/getValueFromPath.test.ts | 4 + src/utils/toKey.test.ts | 1 + 16 files changed, 317 insertions(+), 212 deletions(-) diff --git a/src/ActionBasedPolicy.test.ts b/src/ActionBasedPolicy.test.ts index ea4188c..ca29e92 100644 --- a/src/ActionBasedPolicy.test.ts +++ b/src/ActionBasedPolicy.test.ts @@ -48,6 +48,7 @@ describe('ActionBasedPolicy Class', () => { ]; const policy = new ActionBasedPolicy({ statements }); const exportedStatements = policy.getStatements(); + expect(exportedStatements).toMatchObject(statements); expect(exportedStatements[0].sid).not.toBeFalsy(); }); @@ -202,6 +203,7 @@ describe('ActionBasedPolicy Class', () => { } ] }); + expect( policy.can({ action: 'getUser/123', @@ -234,6 +236,7 @@ describe('ActionBasedPolicy Class', () => { } ] }); + expect( policy.cannot({ action: 'getUser/123', diff --git a/src/ActionBasedPolicy.ts b/src/ActionBasedPolicy.ts index bb1a3fd..23b47df 100644 --- a/src/ActionBasedPolicy.ts +++ b/src/ActionBasedPolicy.ts @@ -31,7 +31,7 @@ export class ActionBasedPolicy extends Policy { (s) => s.effect === 'allow' ); this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); - this.statements = this.statements = statementInstances.map((statement) => + this.statements = statementInstances.map((statement) => statement.getStatement() ); } diff --git a/src/ActionBasedStatement.test.ts b/src/ActionBasedStatement.test.ts index 677910f..0a28ff9 100644 --- a/src/ActionBasedStatement.test.ts +++ b/src/ActionBasedStatement.test.ts @@ -22,6 +22,7 @@ describe('ActionBased Class', () => { const expectedError = new TypeError( 'ActionBased statement should have an action or a notAction attribute' ); + expect(() => { new ActionBased({}); }).toThrow(expectedError); @@ -33,6 +34,7 @@ describe('ActionBased Class', () => { const expectedError = new TypeError( 'ActionBased statement should have an action or a notAction attribute, no both' ); + expect(() => { new ActionBased({ action: ['read', 'write'], diff --git a/src/IdentityBasedPolicy.test.ts b/src/IdentityBasedPolicy.test.ts index 0c69126..1634f88 100644 --- a/src/IdentityBasedPolicy.test.ts +++ b/src/IdentityBasedPolicy.test.ts @@ -5,23 +5,27 @@ describe('IdentityBasedPolicy Class', () => { it("doesn't throw an error", () => { expect( () => - new IdentityBasedPolicy([ - { - resource: 'some:glob:*:string/word', - action: ['read', 'write'] - } - ]) + new IdentityBasedPolicy({ + statements: [ + { + resource: 'some:glob:*:string/word', + action: ['read', 'write'] + } + ] + }) ).not.toThrow(); }); it('returns an IdentityBasedPolicy instance', () => { expect( - new IdentityBasedPolicy([ - { - notResource: ['some:glob:*:string/word'], - notAction: ['read', 'write'] - } - ]) + new IdentityBasedPolicy({ + statements: [ + { + notResource: ['some:glob:*:string/word'], + notAction: ['read', 'write'] + } + ] + }) ).toBeInstanceOf(IdentityBasedPolicy); }); }); @@ -34,8 +38,9 @@ describe('IdentityBasedPolicy Class', () => { action: ['read'] } ]; - const policy = new IdentityBasedPolicy(statements); + const policy = new IdentityBasedPolicy({ statements }); const exportedStatements = policy.getStatements(); + expect(exportedStatements).toMatchObject(statements); expect(exportedStatements[0].sid).not.toBeFalsy(); }); @@ -43,12 +48,14 @@ describe('IdentityBasedPolicy Class', () => { describe('when match actions', () => { it('returns true or false', () => { - const policy = new IdentityBasedPolicy([ - { - resource: ['books:horror:*'], - action: ['read'] - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + resource: ['books:horror:*'], + action: ['read'] + } + ] + }); expect( policy.evaluate({ @@ -67,12 +74,14 @@ describe('IdentityBasedPolicy Class', () => { describe('when match not actions', () => { it('returns true or false', () => { - const policy = new IdentityBasedPolicy([ - { - resource: 'books:horror:*', - notAction: 'read' - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + resource: 'books:horror:*', + notAction: 'read' + } + ] + }); expect( policy.evaluate({ @@ -91,12 +100,14 @@ describe('IdentityBasedPolicy Class', () => { describe('when match resources', () => { it('returns true or false', () => { - const policy = new IdentityBasedPolicy([ - { - resource: 'books:horror:*', - action: 'read' - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + resource: 'books:horror:*', + action: 'read' + } + ] + }); expect( policy.evaluate({ @@ -115,12 +126,14 @@ describe('IdentityBasedPolicy Class', () => { describe('when match not resources', () => { it('returns true or false', () => { - const policy = new IdentityBasedPolicy([ - { - notResource: 'books:horror:*', - action: 'read' - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + notResource: 'books:horror:*', + action: 'read' + } + ] + }); expect( policy.evaluate({ @@ -139,16 +152,18 @@ describe('IdentityBasedPolicy Class', () => { describe('when match based on context', () => { it('returns true or false', () => { - const policy = new IdentityBasedPolicy([ - { - resource: ['secrets:${user.id}:*'], - action: ['read', 'write'] - }, - { - resource: ['secrets:${user.bestFriends}:*'], - action: 'read' - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + resource: ['secrets:${user.id}:*'], + action: ['read', 'write'] + }, + { + resource: ['secrets:${user.bestFriends}:*'], + action: 'read' + } + ] + }); expect( policy.evaluate({ @@ -188,14 +203,13 @@ describe('IdentityBasedPolicy Class', () => { describe('when match based on conditions', () => { it('returns true or false', () => { - const conditions = { + const conditionResolver = { greaterThan: (data: number, expected: number): boolean => { return data > expected; } }; - - const policy = new IdentityBasedPolicy( - [ + const policy = new IdentityBasedPolicy({ + statements: [ { resource: 'secrets:*', action: ['read', 'write'] @@ -210,8 +224,8 @@ describe('IdentityBasedPolicy Class', () => { } } ], - conditions - ); + conditionResolver + }); expect( policy.evaluate({ @@ -236,15 +250,19 @@ describe('IdentityBasedPolicy Class', () => { ).toBe(true); }); }); + describe('can and cannot', () => { it('can should return false when not found and true for when matched with allow', () => { - const policy = new IdentityBasedPolicy([ - { - effect: 'allow', - resource: ['posts:${user.id}:*'], - action: ['write', 'read', 'update'] - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + effect: 'allow', + resource: ['posts:${user.id}:*'], + action: ['write', 'read', 'update'] + } + ] + }); + expect( policy.can({ action: 'read', @@ -262,13 +280,16 @@ describe('IdentityBasedPolicy Class', () => { }); it('cannot should return false when not found and true for when matched with deny', () => { - const policy = new IdentityBasedPolicy([ - { - effect: 'deny', - resource: ['posts:${user.id}:*'], - action: ['write', 'read', 'update'] - } - ]); + const policy = new IdentityBasedPolicy({ + statements: [ + { + effect: 'deny', + resource: ['posts:${user.id}:*'], + action: ['write', 'read', 'update'] + } + ] + }); + expect( policy.cannot({ action: 'read', diff --git a/src/IdentityBasedPolicy.ts b/src/IdentityBasedPolicy.ts index b5fe2eb..665469f 100644 --- a/src/IdentityBasedPolicy.ts +++ b/src/IdentityBasedPolicy.ts @@ -1,32 +1,46 @@ import { - IdentityBasedType, + ConditionResolver, + Context, EvaluateIdentityBasedInterface, - ConditionResolver + IdentityBasedType } from './types'; import { IdentityBased } from './IdentityBasedStatement'; +import { Policy } from './Policy'; -export class IdentityBasedPolicy { +interface IdentityBasedPolicyInterface { + statements: IdentityBasedType[]; + conditionResolver?: ConditionResolver; + context?: Context; +} + +export class IdentityBasedPolicy extends Policy { private denyStatements: IdentityBased[]; private allowStatements: IdentityBased[]; private conditionResolver?: ConditionResolver; private statements: IdentityBasedType[]; - constructor( - config: IdentityBasedType[], - conditionResolver?: ConditionResolver - ) { - const statementInstances = config.map( - statement => new IdentityBased(statement) + constructor({ + statements, + conditionResolver, + context + }: IdentityBasedPolicyInterface) { + super({ context, conditionResolver }); + const statementInstances = statements.map( + (statement) => new IdentityBased(statement) + ); + this.allowStatements = statementInstances.filter( + (s) => s.effect === 'allow' ); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); this.conditionResolver = conditionResolver; - this.statements = statementInstances.map(statement => + this.statements = statementInstances.map((statement) => statement.getStatement() ); } + getStatements(): IdentityBasedType[] { return this.statements; } + evaluate({ action, resource, @@ -35,8 +49,9 @@ export class IdentityBasedPolicy { const args = { action, resource, context }; return !this.cannot(args) && this.can(args); } + can({ action, resource, context }: EvaluateIdentityBasedInterface): boolean { - return this.allowStatements.some(s => + return this.allowStatements.some((s) => s.matches({ action, resource, @@ -45,12 +60,13 @@ export class IdentityBasedPolicy { }) ); } + cannot({ action, resource, context }: EvaluateIdentityBasedInterface): boolean { - return this.denyStatements.some(s => + return this.denyStatements.some((s) => s.matches({ action, resource, diff --git a/src/IdentityBasedStatement.test.ts b/src/IdentityBasedStatement.test.ts index a9d32cd..b5322d3 100644 --- a/src/IdentityBasedStatement.test.ts +++ b/src/IdentityBasedStatement.test.ts @@ -24,6 +24,7 @@ describe('IdentityBased Class', () => { const expectedError = new TypeError( 'IdentityBased statement should have an action or a notAction attribute' ); + expect(() => { new IdentityBased({ resource: 'secret' @@ -37,6 +38,7 @@ describe('IdentityBased Class', () => { const expectedError = new TypeError( 'IdentityBased statement should have a resource or a notResource attribute' ); + expect(() => { new IdentityBased({ action: 'write' @@ -50,6 +52,7 @@ describe('IdentityBased Class', () => { const expectedError = new TypeError( 'IdentityBased statement should have an action or a notAction attribute, no both' ); + expect(() => { new IdentityBased({ action: ['read', 'write'], @@ -64,6 +67,7 @@ describe('IdentityBased Class', () => { const expectedError = new TypeError( 'IdentityBased statement should have a resource or a notResource attribute, no both' ); + expect(() => { new IdentityBased({ action: ['read', 'write'], diff --git a/src/Matcher.test.ts b/src/Matcher.test.ts index 6d8ff73..6068a0e 100644 --- a/src/Matcher.test.ts +++ b/src/Matcher.test.ts @@ -8,6 +8,7 @@ describe('Matcher Class', () => { it('throws a TypeError', () => { const expectedError = new TypeError('Pattern is too long'); + expect(() => new Matcher('secrets:123:*', 5)).toThrow(expectedError); }); diff --git a/src/Policy.test.ts b/src/Policy.test.ts index 6b55128..cf70bf6 100644 --- a/src/Policy.test.ts +++ b/src/Policy.test.ts @@ -11,6 +11,7 @@ describe('Policy Class', () => { describe('when getContext', () => { it('returns context attribute', () => { const context = { user: { age: 31 } }; + expect(new Policy({ context }).getContext()).toBe(context); }); }); @@ -20,6 +21,7 @@ describe('Policy Class', () => { const context = { user: { age: 31 } }; const policy = new Policy({}); policy.setContext(context); + expect(policy.getContext()).toBe(context); }); }); @@ -31,6 +33,7 @@ describe('Policy Class', () => { return data > expected; } }; + expect(new Policy({ conditionResolver }).getConditionResolver()).toBe( conditionResolver ); @@ -46,6 +49,7 @@ describe('Policy Class', () => { }; const policy = new Policy({}); policy.setConditionResolver(conditionResolver); + expect(policy.getConditionResolver()).toBe(conditionResolver); }); }); diff --git a/src/ResourceBasedPolicy.test.ts b/src/ResourceBasedPolicy.test.ts index 41451ae..1535cc4 100644 --- a/src/ResourceBasedPolicy.test.ts +++ b/src/ResourceBasedPolicy.test.ts @@ -5,25 +5,29 @@ describe('ResourceBasedPolicy Class', () => { it("doesn't throw an error", () => { expect( () => - new ResourceBasedPolicy([ - { - principal: 'rogger', - resource: 'some:glob:*:string/example', - action: ['read', 'write'] - } - ]) + new ResourceBasedPolicy({ + statements: [ + { + principal: 'rogger', + resource: 'some:glob:*:string/example', + action: ['read', 'write'] + } + ] + }) ).not.toThrow(); }); it('returns a ResourceBasedPolicy instance', () => { expect( - new ResourceBasedPolicy([ - { - notPrincipal: 'rogger', - notResource: 'some:glob:*:string/example', - notAction: ['read', 'write'] - } - ]) + new ResourceBasedPolicy({ + statements: [ + { + notPrincipal: 'rogger', + notResource: 'some:glob:*:string/example', + notAction: ['read', 'write'] + } + ] + }) ).toBeInstanceOf(ResourceBasedPolicy); }); }); @@ -37,7 +41,7 @@ describe('ResourceBasedPolicy Class', () => { action: ['read'] } ]; - const policy = new ResourceBasedPolicy(statements); + const policy = new ResourceBasedPolicy({ statements }); const exportedStatements = policy.getStatements(); expect(exportedStatements).toMatchObject(statements); expect(exportedStatements[0].sid).not.toBeFalsy(); @@ -46,22 +50,24 @@ describe('ResourceBasedPolicy Class', () => { describe('when match principal', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - principal: 'andre', - resource: 'books:horror:*', - action: ['read'] - }, - { - principal: { id: '1' }, - resource: ['books:horror:*'], - action: 'write' - }, - { - resource: ['books:science:*'], - action: 'read' - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + principal: 'andre', + resource: 'books:horror:*', + action: ['read'] + }, + { + principal: { id: '1' }, + resource: ['books:horror:*'], + action: 'write' + }, + { + resource: ['books:science:*'], + action: 'read' + } + ] + }); expect( policy.evaluate({ @@ -117,18 +123,20 @@ describe('ResourceBasedPolicy Class', () => { describe('when match not principal', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - notPrincipal: { id: 'andre' }, - resource: 'books:horror:*', - action: ['read'] - }, - { - notPrincipal: { id: ['rogger'] }, - resource: 'secrets:admin:*', - action: 'read' - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + notPrincipal: { id: 'andre' }, + resource: 'books:horror:*', + action: ['read'] + }, + { + notPrincipal: { id: ['rogger'] }, + resource: 'secrets:admin:*', + action: 'read' + } + ] + }); expect( policy.evaluate({ @@ -165,18 +173,20 @@ describe('ResourceBasedPolicy Class', () => { describe('when match actions', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - principal: { id: ['123'] }, - resource: ['books:horror:*'], - action: ['read'] - }, - { - notPrincipal: ['124'], - resource: ['books:science:*'], - action: ['write'] - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + principal: { id: ['123'] }, + resource: ['books:horror:*'], + action: ['read'] + }, + { + notPrincipal: ['124'], + resource: ['books:science:*'], + action: ['write'] + } + ] + }); expect( policy.evaluate({ @@ -206,13 +216,15 @@ describe('ResourceBasedPolicy Class', () => { describe('when match not actions', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - principal: { id: '123' }, - resource: ['books:horror:*'], - notAction: 'get*' - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + principal: { id: '123' }, + resource: ['books:horror:*'], + notAction: 'get*' + } + ] + }); expect( policy.evaluate({ @@ -235,17 +247,19 @@ describe('ResourceBasedPolicy Class', () => { describe('when match resources', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - principal: ['rogger', 'andre'], - resource: 'books:horror:*', - action: ['read'] - }, - { - principal: 'andre', - action: ['write'] - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + principal: ['rogger', 'andre'], + resource: 'books:horror:*', + action: ['read'] + }, + { + principal: 'andre', + action: ['write'] + } + ] + }); expect( policy.evaluate({ @@ -278,13 +292,15 @@ describe('ResourceBasedPolicy Class', () => { describe('when match not resources', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - principal: ['rogger', 'andre'], - notResource: ['books:horror:*'], - action: ['read'] - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + principal: ['rogger', 'andre'], + notResource: ['books:horror:*'], + action: ['read'] + } + ] + }); expect( policy.evaluate({ @@ -305,18 +321,20 @@ describe('ResourceBasedPolicy Class', () => { describe('when match based on context', () => { it('returns true or false', () => { - const policy = new ResourceBasedPolicy([ - { - principal: { id: 'rogger' }, - resource: ['secrets:${user.id}:*'], - action: ['read', 'write'] - }, - { - principal: { id: 'rogger' }, - resource: ['secrets:${user.bestFriends}:*'], - action: 'read' - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + principal: { id: 'rogger' }, + resource: ['secrets:${user.id}:*'], + action: ['read', 'write'] + }, + { + principal: { id: 'rogger' }, + resource: ['secrets:${user.bestFriends}:*'], + action: 'read' + } + ] + }); expect( policy.evaluate({ @@ -368,14 +386,14 @@ describe('ResourceBasedPolicy Class', () => { describe('when match based on conditions', () => { it('returns true or false', () => { - const conditions = { + const conditionResolver = { greaterThan: (data: number, expected: number): boolean => { return data > expected; } }; - const policy = new ResourceBasedPolicy( - [ + const policy = new ResourceBasedPolicy({ + statements: [ { principal: { id: 'rogger' }, resource: 'secrets:*', @@ -392,8 +410,8 @@ describe('ResourceBasedPolicy Class', () => { } } ], - conditions - ); + conditionResolver + }); expect( policy.evaluate({ @@ -424,16 +442,20 @@ describe('ResourceBasedPolicy Class', () => { ).toBe(true); }); }); + describe('can and cannot', () => { it('can should return false when not found and true for when matched with allow', () => { - const policy = new ResourceBasedPolicy([ - { - effect: 'allow', - principal: { id: 'rogger' }, - resource: ['posts:${user.id}:*'], - action: ['write', 'read', 'update'] - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + effect: 'allow', + principal: { id: 'rogger' }, + resource: ['posts:${user.id}:*'], + action: ['write', 'read', 'update'] + } + ] + }); + expect( policy.can({ principal: 'rogger', @@ -455,14 +477,17 @@ describe('ResourceBasedPolicy Class', () => { }); it('cannot should return false when not found and true for when matched with deny', () => { - const policy = new ResourceBasedPolicy([ - { - effect: 'deny', - principal: { id: 'rogger' }, - resource: ['posts:${user.id}:*'], - action: ['write', 'read', 'update'] - } - ]); + const policy = new ResourceBasedPolicy({ + statements: [ + { + effect: 'deny', + principal: { id: 'rogger' }, + resource: ['posts:${user.id}:*'], + action: ['write', 'read', 'update'] + } + ] + }); + expect( policy.cannot({ principal: 'rogger', diff --git a/src/ResourceBasedPolicy.ts b/src/ResourceBasedPolicy.ts index d1a90b6..2c4e804 100644 --- a/src/ResourceBasedPolicy.ts +++ b/src/ResourceBasedPolicy.ts @@ -1,32 +1,46 @@ import { - ResourceBasedType, + ConditionResolver, + Context, EvaluateResourceBasedInterface, - ConditionResolver + ResourceBasedType } from './types'; import { ResourceBased } from './ResourceBasedStatement'; +import { Policy } from './Policy'; -export class ResourceBasedPolicy { +interface ResourceBasedPolicyInterface { + statements: ResourceBasedType[]; + conditionResolver?: ConditionResolver; + context?: Context; +} + +export class ResourceBasedPolicy extends Policy { private denyStatements: ResourceBased[]; private allowStatements: ResourceBased[]; private conditionResolver?: ConditionResolver; private statements: ResourceBasedType[]; - constructor( - config: ResourceBasedType[], - conditionResolver?: ConditionResolver - ) { - const statementInstances = config.map( - statement => new ResourceBased(statement) + constructor({ + statements, + conditionResolver, + context + }: IdentityBasedPolicyInterface) { + super({ context, conditionResolver }); + const statementInstances = statements.map( + (statement) => new ResourceBased(statement) + ); + this.allowStatements = statementInstances.filter( + (s) => s.effect === 'allow' ); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); this.conditionResolver = conditionResolver; - this.statements = statementInstances.map(statement => + this.statements = statementInstances.map((statement) => statement.getStatement() ); } + getStatements(): ResourceBasedType[] { return this.statements; } + evaluate({ principal, action, @@ -37,6 +51,7 @@ export class ResourceBasedPolicy { const args = { principal, action, resource, principalType, context }; return !this.cannot(args) && this.can(args); } + can({ principal, action, @@ -44,7 +59,7 @@ export class ResourceBasedPolicy { principalType, context }: EvaluateResourceBasedInterface): boolean { - return this.allowStatements.some(s => + return this.allowStatements.some((s) => s.matches({ principal, action, @@ -55,6 +70,7 @@ export class ResourceBasedPolicy { }) ); } + cannot({ principal, action, @@ -62,7 +78,7 @@ export class ResourceBasedPolicy { principalType, context }: EvaluateResourceBasedInterface): boolean { - return this.denyStatements.some(s => + return this.denyStatements.some((s) => s.matches({ principal, action, diff --git a/src/ResourceBasedStatement.test.ts b/src/ResourceBasedStatement.test.ts index fbf54a6..a1a7d7d 100644 --- a/src/ResourceBasedStatement.test.ts +++ b/src/ResourceBasedStatement.test.ts @@ -24,6 +24,7 @@ describe('ResourceBased Class', () => { const expectedError = new TypeError( 'ResourceBased statement should have an action or a notAction attribute' ); + expect(() => { new ResourceBased({ resource: 'secret' @@ -37,6 +38,7 @@ describe('ResourceBased Class', () => { const expectedError = new TypeError( 'ResourceBased statement should have an action or a notAction attribute, no both' ); + expect(() => { new ResourceBased({ action: ['read', 'write'], @@ -51,6 +53,7 @@ describe('ResourceBased Class', () => { const expectedError = new TypeError( 'ResourceBased statement could have a resource or a notResource attribute, no both' ); + expect(() => { new ResourceBased({ action: ['read', 'write'], @@ -66,6 +69,7 @@ describe('ResourceBased Class', () => { const expectedError = new TypeError( 'ResourceBased statement could have a principal or a notPrincipal attribute, no both' ); + expect(() => { new ResourceBased({ action: ['read', 'write'], diff --git a/src/Statement.test.ts b/src/Statement.test.ts index 7c6254d..867e176 100644 --- a/src/Statement.test.ts +++ b/src/Statement.test.ts @@ -6,18 +6,16 @@ describe('Statement Class', () => { const context = { user: { id: 456, bestFriends: [123, 532, 987] } }; + expect(applyContext('secrets:${user.id}:*', context)).toBe( 'secrets:456:*' ); - expect(applyContext('secrets:${user.bestFriends}:*', context)).toBe( 'secrets:{123,532,987}:*' ); - expect(applyContext('secrets:${user.bestFriends}:account', context)).toBe( 'secrets:{123,532,987}:account' ); - expect( applyContext( 'secrets:${user.id}:bestFriends:${user.bestFriends}', @@ -79,6 +77,7 @@ describe('Statement Class', () => { return data < expected; } }; + expect( new Statement(firstStatementConfig).matchConditions({ context: { user: { age: 31 } }, @@ -112,6 +111,7 @@ describe('Statement Class', () => { return data < expected; } }; + expect( new Statement(firstStatementConfig).matchConditions({ context: { user: { age: 31 } }, diff --git a/src/utils/decomposeString.test.ts b/src/utils/decomposeString.test.ts index eda3873..9946482 100644 --- a/src/utils/decomposeString.test.ts +++ b/src/utils/decomposeString.test.ts @@ -17,6 +17,7 @@ describe('decomposeString', () => { post: '**' }); }); + it('should find only one separator', () => { expect(decomposeString('first', 'second', 'firstAndSecond')).toEqual({ start: -1, diff --git a/src/utils/getTag.test.ts b/src/utils/getTag.test.ts index 18abca7..1420bda 100644 --- a/src/utils/getTag.test.ts +++ b/src/utils/getTag.test.ts @@ -4,12 +4,15 @@ describe('getTag', () => { it('should get Number tag', () => { expect(getTag(1)).toEqual('[object Number]'); }); + it('should get String tag', () => { expect(getTag('hello')).toEqual('[object String]'); }); + it('should get Undefined tag', () => { expect(getTag(undefined)).toEqual('[object Undefined]'); }); + it('should get Null tag', () => { expect(getTag(null)).toEqual('[object Null]'); }); diff --git a/src/utils/getValueFromPath.test.ts b/src/utils/getValueFromPath.test.ts index 1191d0d..726aa23 100644 --- a/src/utils/getValueFromPath.test.ts +++ b/src/utils/getValueFromPath.test.ts @@ -23,6 +23,7 @@ describe('getValueFromPath', () => { const context = { user: { id: 456, bestFriends: [123, 532], '56': 50 } }; + expect(getValueFromPath(context, 'user.bestFriends')).toEqual([ 123, 532 @@ -40,6 +41,7 @@ describe('getValueFromPath', () => { const context = { user: { id: 456, bestFriends: [123, 532], '56': 50 } }; + expect(getValueFromPath(context, ['user', 'bestFriends'])).toEqual([ 123, 532 @@ -60,6 +62,7 @@ describe('getValueFromPath', () => { const context = { user: { id: 456, bestFriends: [123] } }; + expect(getValueFromPath(context, 'user.id.pets')).toBe(undefined); expect(getValueFromPath(context, 'company')).toBe(undefined); expect(getValueFromPath(context, 'company.address')).toBe(undefined); @@ -71,6 +74,7 @@ describe('getValueFromPath', () => { const context = { user: { id: 456, bestFriends: [123] } }; + expect(getValueFromPath(context, 'user.id.pets', 'default')).toBe( 'default' ); diff --git a/src/utils/toKey.test.ts b/src/utils/toKey.test.ts index 368eaa4..3be19fd 100644 --- a/src/utils/toKey.test.ts +++ b/src/utils/toKey.test.ts @@ -4,6 +4,7 @@ describe('toKey', () => { describe('when converting value to a string key', () => { it('should get true', () => { const symbol = Symbol(1); + expect(toKey(1)).toEqual('1'); expect(toKey(symbol)).toEqual(symbol); expect(toKey(-0)).toEqual('-0'); From e6de53b2ba3fb9d4fea8d3856b33eb6aaa6140f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 18 Oct 2020 20:56:39 -0500 Subject: [PATCH 09/13] chore(example): updating example directory --- dist/main.d.ts | 20 +++-- dist/main.es.js | 38 ++++---- dist/main.js | 38 ++++---- example/index.js | 176 ++++++++++++++++++++----------------- src/IdentityBasedPolicy.ts | 3 +- src/ResourceBasedPolicy.ts | 5 +- 6 files changed, 151 insertions(+), 129 deletions(-) diff --git a/dist/main.d.ts b/dist/main.d.ts index cd44e02..8c33fca 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -202,24 +202,32 @@ declare class ActionBasedPolicy extends Policy { cannot({ action, context }: EvaluateActionBasedInterface): boolean; } -declare class IdentityBasedPolicy { +interface IdentityBasedPolicyInterface { + statements: IdentityBasedType[]; + conditionResolver?: ConditionResolver; + context?: Context; +} +declare class IdentityBasedPolicy extends Policy { private denyStatements; private allowStatements; - private conditionResolver?; private statements; - constructor(config: IdentityBasedType[], conditionResolver?: ConditionResolver); + constructor({ statements, conditionResolver, context }: IdentityBasedPolicyInterface); getStatements(): IdentityBasedType[]; evaluate({ action, resource, context }: EvaluateIdentityBasedInterface): boolean; can({ action, resource, context }: EvaluateIdentityBasedInterface): boolean; cannot({ action, resource, context }: EvaluateIdentityBasedInterface): boolean; } -declare class ResourceBasedPolicy { +interface ResourceBasedPolicyInterface { + statements: ResourceBasedType[]; + conditionResolver?: ConditionResolver; + context?: Context; +} +declare class ResourceBasedPolicy extends Policy { private denyStatements; private allowStatements; - private conditionResolver?; private statements; - constructor(config: ResourceBasedType[], conditionResolver?: ConditionResolver); + constructor({ statements, conditionResolver, context }: ResourceBasedPolicyInterface); getStatements(): ResourceBasedType[]; evaluate({ principal, action, resource, principalType, context }: EvaluateResourceBasedInterface): boolean; can({ principal, action, resource, principalType, context }: EvaluateResourceBasedInterface): boolean; diff --git a/dist/main.es.js b/dist/main.es.js index 4cd1e54..d42f575 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -1168,7 +1168,7 @@ class ActionBasedPolicy extends Policy { const statementInstances = statements.map((statement) => new ActionBased(statement)); this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); - this.statements = this.statements = statementInstances.map((statement) => statement.getStatement()); + this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1193,13 +1193,13 @@ class ActionBasedPolicy extends Policy { } } -class IdentityBasedPolicy { - constructor(config, conditionResolver) { - const statementInstances = config.map(statement => new IdentityBased(statement)); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); - this.conditionResolver = conditionResolver; - this.statements = statementInstances.map(statement => statement.getStatement()); +class IdentityBasedPolicy extends Policy { + constructor({ statements, conditionResolver, context }) { + super({ context, conditionResolver }); + const statementInstances = statements.map((statement) => new IdentityBased(statement)); + this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); + this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1209,7 +1209,7 @@ class IdentityBasedPolicy { return !this.cannot(args) && this.can(args); } can({ action, resource, context }) { - return this.allowStatements.some(s => s.matches({ + return this.allowStatements.some((s) => s.matches({ action, resource, context, @@ -1217,7 +1217,7 @@ class IdentityBasedPolicy { })); } cannot({ action, resource, context }) { - return this.denyStatements.some(s => s.matches({ + return this.denyStatements.some((s) => s.matches({ action, resource, context, @@ -1226,13 +1226,13 @@ class IdentityBasedPolicy { } } -class ResourceBasedPolicy { - constructor(config, conditionResolver) { - const statementInstances = config.map(statement => new ResourceBased(statement)); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); - this.conditionResolver = conditionResolver; - this.statements = statementInstances.map(statement => statement.getStatement()); +class ResourceBasedPolicy extends Policy { + constructor({ statements, conditionResolver, context }) { + super({ context, conditionResolver }); + const statementInstances = statements.map((statement) => new ResourceBased(statement)); + this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); + this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1242,7 +1242,7 @@ class ResourceBasedPolicy { return !this.cannot(args) && this.can(args); } can({ principal, action, resource, principalType, context }) { - return this.allowStatements.some(s => s.matches({ + return this.allowStatements.some((s) => s.matches({ principal, action, resource, @@ -1252,7 +1252,7 @@ class ResourceBasedPolicy { })); } cannot({ principal, action, resource, principalType, context }) { - return this.denyStatements.some(s => s.matches({ + return this.denyStatements.some((s) => s.matches({ principal, action, resource, diff --git a/dist/main.js b/dist/main.js index ddcc02e..c3d9a30 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1172,7 +1172,7 @@ class ActionBasedPolicy extends Policy { const statementInstances = statements.map((statement) => new ActionBased(statement)); this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); - this.statements = this.statements = statementInstances.map((statement) => statement.getStatement()); + this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1197,13 +1197,13 @@ class ActionBasedPolicy extends Policy { } } -class IdentityBasedPolicy { - constructor(config, conditionResolver) { - const statementInstances = config.map(statement => new IdentityBased(statement)); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); - this.conditionResolver = conditionResolver; - this.statements = statementInstances.map(statement => statement.getStatement()); +class IdentityBasedPolicy extends Policy { + constructor({ statements, conditionResolver, context }) { + super({ context, conditionResolver }); + const statementInstances = statements.map((statement) => new IdentityBased(statement)); + this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); + this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1213,7 +1213,7 @@ class IdentityBasedPolicy { return !this.cannot(args) && this.can(args); } can({ action, resource, context }) { - return this.allowStatements.some(s => s.matches({ + return this.allowStatements.some((s) => s.matches({ action, resource, context, @@ -1221,7 +1221,7 @@ class IdentityBasedPolicy { })); } cannot({ action, resource, context }) { - return this.denyStatements.some(s => s.matches({ + return this.denyStatements.some((s) => s.matches({ action, resource, context, @@ -1230,13 +1230,13 @@ class IdentityBasedPolicy { } } -class ResourceBasedPolicy { - constructor(config, conditionResolver) { - const statementInstances = config.map(statement => new ResourceBased(statement)); - this.allowStatements = statementInstances.filter(s => s.effect === 'allow'); - this.denyStatements = statementInstances.filter(s => s.effect === 'deny'); - this.conditionResolver = conditionResolver; - this.statements = statementInstances.map(statement => statement.getStatement()); +class ResourceBasedPolicy extends Policy { + constructor({ statements, conditionResolver, context }) { + super({ context, conditionResolver }); + const statementInstances = statements.map((statement) => new ResourceBased(statement)); + this.allowStatements = statementInstances.filter((s) => s.effect === 'allow'); + this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); + this.statements = statementInstances.map((statement) => statement.getStatement()); } getStatements() { return this.statements; @@ -1246,7 +1246,7 @@ class ResourceBasedPolicy { return !this.cannot(args) && this.can(args); } can({ principal, action, resource, principalType, context }) { - return this.allowStatements.some(s => s.matches({ + return this.allowStatements.some((s) => s.matches({ principal, action, resource, @@ -1256,7 +1256,7 @@ class ResourceBasedPolicy { })); } cannot({ principal, action, resource, principalType, context }) { - return this.denyStatements.some(s => s.matches({ + return this.denyStatements.some((s) => s.matches({ principal, action, resource, diff --git a/example/index.js b/example/index.js index 2608bec..871ffe1 100644 --- a/example/index.js +++ b/example/index.js @@ -2,17 +2,19 @@ const { IdentityBasedPolicy, ResourceBasedPolicy } = require('iam-policies'); console.log('Allow Example'); -const allowExample = new IdentityBasedPolicy([ - { - effect: 'allow', // optional, defaults to allow - resource: ['secrets:${user.id}:*'], // embedded value by context - action: ['read', 'write'] - }, - { - resource: ['bd:company:*'], - action: 'create' - } -]); +const allowExample = new IdentityBasedPolicy({ + statements: [ + { + effect: 'allow', // optional, defaults to allow + resource: ['secrets:${user.id}:*'], // embedded value by context + action: ['read', 'write'] + }, + { + resource: ['bd:company:*'], + action: 'create' + } + ] +}); const contextForAllowExample = { user: { id: 456 } }; @@ -51,25 +53,27 @@ console.log( console.log('Deny Example'); -const denyExample = new IdentityBasedPolicy([ - { - resource: ['secrets:${user.bestFriends}:*'], - action: 'read' - }, - { - effect: 'deny', - resource: 'secrets:123:*', - action: 'read' - }, - { - resource: 'bd:company:*', - notAction: 'update' - }, - { - notResource: ['bd:roles:*'], - action: 'update' - } -]); +const denyExample = new IdentityBasedPolicy({ + statements: [ + { + resource: ['secrets:${user.bestFriends}:*'], + action: 'read' + }, + { + effect: 'deny', + resource: 'secrets:123:*', + action: 'read' + }, + { + resource: 'bd:company:*', + notAction: 'update' + }, + { + notResource: ['bd:roles:*'], + action: 'update' + } + ] +}); const contextForDenyExample = { user: { bestFriends: [123, 563, 1211] } }; @@ -92,12 +96,14 @@ console.log( console.log('Not Action Example'); -const notActionExample = new IdentityBasedPolicy([ - { - resource: 'bd:company:*', - notAction: 'update' - } -]); +const notActionExample = new IdentityBasedPolicy({ + statements: [ + { + resource: 'bd:company:*', + notAction: 'update' + } + ] +}); // true console.log( @@ -116,12 +122,14 @@ console.log( console.log('Not Resource Example'); -const notResourceExample = new IdentityBasedPolicy([ - { - notResource: ['bd:roles:*'], - action: 'update' - } -]); +const notResourceExample = new IdentityBasedPolicy({ + statements: [ + { + notResource: ['bd:roles:*'], + action: 'update' + } + ] +}); // true console.log( @@ -137,12 +145,14 @@ console.log( console.log('Admin Example'); -const adminExample = new IdentityBasedPolicy([ - { - resource: '*', - action: '*' - } -]); +const adminExample = new IdentityBasedPolicy({ + statements: [ + { + resource: '*', + action: '*' + } + ] +}); // true console.log( @@ -155,14 +165,14 @@ console.log( console.log('Conditions Example'); -const conditions = { - greaterThan: function(data, expected) { +const conditionResolver = { + greaterThan: function (data, expected) { return data > expected; } }; -const conditionExample = new IdentityBasedPolicy( - [ +const conditionExample = new IdentityBasedPolicy({ + statements: [ { effect: 'allow', // optional, defaults to allow resource: 'secrets:*', @@ -174,8 +184,8 @@ const conditionExample = new IdentityBasedPolicy( } } ], - conditions -); + conditionResolver +}); // true console.log( @@ -198,19 +208,21 @@ console.log( console.log('Principal Example'); -const principalExample = new ResourceBasedPolicy([ - { - principal: '1', - effect: 'allow', - resource: ['secrets:user:*'], - action: ['read', 'write'] - }, - { - principal: { id: '2' }, - resource: 'bd:company:*', - notAction: 'update' - } -]); +const principalExample = new ResourceBasedPolicy({ + statements: [ + { + principal: '1', + effect: 'allow', + resource: ['secrets:user:*'], + action: ['read', 'write'] + }, + { + principal: { id: '2' }, + resource: 'bd:company:*', + notAction: 'update' + } + ] +}); // true console.log( @@ -249,18 +261,20 @@ console.log( console.log('Not Principal Example'); -const notPrincipalExample = new ResourceBasedPolicy([ - { - notPrincipal: ['1', '2'], - resource: ['secrets:bd:*'], - action: 'read' - }, - { - notPrincipal: { id: '3' }, - resource: 'secrets:admin:*', - action: 'read' - } -]); +const notPrincipalExample = new ResourceBasedPolicy({ + statements: [ + { + notPrincipal: ['1', '2'], + resource: ['secrets:bd:*'], + action: 'read' + }, + { + notPrincipal: { id: '3' }, + resource: 'secrets:admin:*', + action: 'read' + } + ] +}); // true console.log( @@ -312,7 +326,9 @@ const canAndCannotStatements = [ } ]; -const inclusivePolicy = new IdentityBasedPolicy(canAndCannotStatements); +const inclusivePolicy = new IdentityBasedPolicy({ + statements: canAndCannotStatements +}); const contextCanAndCannot = { division: { diff --git a/src/IdentityBasedPolicy.ts b/src/IdentityBasedPolicy.ts index 665469f..f0da0ea 100644 --- a/src/IdentityBasedPolicy.ts +++ b/src/IdentityBasedPolicy.ts @@ -16,8 +16,8 @@ interface IdentityBasedPolicyInterface { export class IdentityBasedPolicy extends Policy { private denyStatements: IdentityBased[]; private allowStatements: IdentityBased[]; - private conditionResolver?: ConditionResolver; private statements: IdentityBasedType[]; + constructor({ statements, conditionResolver, @@ -31,7 +31,6 @@ export class IdentityBasedPolicy extends Policy { (s) => s.effect === 'allow' ); this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); - this.conditionResolver = conditionResolver; this.statements = statementInstances.map((statement) => statement.getStatement() ); diff --git a/src/ResourceBasedPolicy.ts b/src/ResourceBasedPolicy.ts index 2c4e804..4a6907b 100644 --- a/src/ResourceBasedPolicy.ts +++ b/src/ResourceBasedPolicy.ts @@ -16,13 +16,13 @@ interface ResourceBasedPolicyInterface { export class ResourceBasedPolicy extends Policy { private denyStatements: ResourceBased[]; private allowStatements: ResourceBased[]; - private conditionResolver?: ConditionResolver; private statements: ResourceBasedType[]; + constructor({ statements, conditionResolver, context - }: IdentityBasedPolicyInterface) { + }: ResourceBasedPolicyInterface) { super({ context, conditionResolver }); const statementInstances = statements.map( (statement) => new ResourceBased(statement) @@ -31,7 +31,6 @@ export class ResourceBasedPolicy extends Policy { (s) => s.effect === 'allow' ); this.denyStatements = statementInstances.filter((s) => s.effect === 'deny'); - this.conditionResolver = conditionResolver; this.statements = statementInstances.map((statement) => statement.getStatement() ); From dd97b693a09dcc33c7106cc0fd2c7324843540aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Fri, 23 Oct 2020 11:49:46 -0500 Subject: [PATCH 10/13] build(typescript): upgrade version to 4.0.3 fixing type errors for this upgrade --- dist/main.d.ts | 10 +- dist/main.es.js | 165 ++++--------------------- dist/main.es.js.map | 2 +- dist/main.js | 165 ++++--------------------- dist/main.js.map | 2 +- package.json | 10 +- src/ActionBasedPolicy.ts | 2 +- src/ActionBasedStatement.ts | 10 +- src/IdentityBasedStatement.ts | 18 +-- src/ResourceBasedStatement.ts | 30 ++--- src/types.ts | 2 +- src/utils/getTag.ts | 2 +- src/utils/getValueFromPath.ts | 14 ++- src/utils/instanceOfInterfaces.test.ts | 90 -------------- src/utils/instanceOfInterfaces.ts | 141 --------------------- src/utils/isKey.test.ts | 1 + src/utils/isKey.ts | 22 ++-- src/utils/isSymbol.ts | 2 +- src/utils/memoize.ts | 10 +- src/utils/memoizeCapped.ts | 6 +- yarn.lock | 111 ++++++++++------- 21 files changed, 193 insertions(+), 622 deletions(-) delete mode 100644 src/utils/instanceOfInterfaces.test.ts delete mode 100644 src/utils/instanceOfInterfaces.ts diff --git a/dist/main.d.ts b/dist/main.d.ts index 8c33fca..7b7a0e0 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -6,7 +6,7 @@ * @param {Object} [object] The object to query keys on. * @returns {Array} Returns the cast property path array. */ -declare function castPath(value: any, object: object): Array; +declare function castPath(value: unknown, object: Record): Array; /** * The base implementation of `get` without support for default values. * @@ -15,7 +15,7 @@ declare function castPath(value: any, object: object): Array; * @param {Array|string} path The path of the property to get. * @returns {*} Returns the resolved value. */ -declare function baseGet(object: object, path: Array | string): any; +declare function baseGet(object: Record, path: Array | string): any; /** * Gets the value at `path` of `object`. If the resolved value is * `undefined`, the `defaultValue` is returned in its place. @@ -39,7 +39,7 @@ declare function baseGet(object: object, path: Array | string): any; * getValueFromPath(object, 'a.b.c', 'default') * // => 'default' */ -declare function getValueFromPath(object: object, path: Array | string, defaultValue?: any): any; +declare function getValueFromPath(object: Record, path: Array | string, defaultValue?: unknown): any; declare type EffectBlock = 'allow' | 'deny'; declare type Patterns = string[] | string; @@ -110,7 +110,7 @@ interface EvaluateResourceBasedInterface extends EvaluateIdentityBasedInterface } declare type ActionBasedType = StatementInterface & (ActionBlock | NotActionBlock); declare type IdentityBasedType = StatementInterface & (ActionBlock | NotActionBlock) & (ResourceBlock | NotResourceBlock); -declare type ResourceBasedType = StatementInterface & (PrincipalBlock | NotPrincipalBlock) & (ActionBlock | NotActionBlock) & (ResourceBlock | NotResourceBlock | {}); +declare type ResourceBasedType = StatementInterface & (PrincipalBlock | NotPrincipalBlock) & (ActionBlock | NotActionBlock) & (ResourceBlock | NotResourceBlock); declare function applyContext(str: string, context?: Context): string; declare class Statement { @@ -234,4 +234,4 @@ declare class ResourceBasedPolicy extends Policy { cannot({ principal, action, resource, principalType, context }: EvaluateResourceBasedInterface): boolean; } -export { ActionBased, ActionBasedPolicy, IdentityBased, IdentityBasedPolicy, ResourceBased, ResourceBasedPolicy, Statement, applyContext, baseGet, castPath, getValueFromPath }; +export { ActionBased, ActionBasedPolicy, ActionBasedPolicyInterface, IdentityBased, IdentityBasedPolicy, ResourceBased, ResourceBasedPolicy, Statement, applyContext, baseGet, castPath, getValueFromPath }; diff --git a/dist/main.es.js b/dist/main.es.js index d42f575..e555dce 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -110,9 +110,12 @@ function isKey(value, object) { isSymbol(value)) { return true; } - return (reIsPlainProp.test(value) || - !reIsDeepProp.test(value) || - (object !== null && value in Object(object))); + if (typeof value === 'string') { + return (reIsPlainProp.test(value) || + !reIsDeepProp.test(value) || + (object !== null && value in Object(object))); + } + return false; } /** @@ -569,122 +572,6 @@ class Statement { } } -/** - * Validate if an `object` is an instance of `ActionBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `action` attribute. - * @example - * ```javascript - * instanceOfActionBlock({ action: 'something' }) - * // => true - * - * instanceOfActionBlock({ notAction: 'something' }) - * // => false - * ``` - */ -function instanceOfActionBlock(object) { - return 'action' in object; -} -/** - * Validate if an `object` is an instance of `NotActionBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notAction` attribute. - * @example - * ```javascript - * instanceOfNotActionBlock({ notAction: 'something' }) - * // => true - * - * instanceOfNotActionBlock({ action: 'something' }) - * // => false - * ``` - */ -function instanceOfNotActionBlock(object) { - return 'notAction' in object; -} -/** - * Validate if an `object` is an instance of `PrincipalBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `principal` attribute. - * @example - * ```javascript - * instanceOfPrincipalBlock({ principal: 'something' }) - * // => true - * - * instanceOfPrincipalBlock({ notPrincipal: 'something' }) - * // => false - * ``` - */ -function instanceOfPrincipalBlock(object) { - return 'principal' in object; -} -/** - * Validate if an `object` is an instance of `NotPrincipalBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notPrincipal` attribute. - * @example - * ```javascript - * instanceOfNotPrincipalBlock({ notPrincipal: 'something' }) - * // => true - * - * instanceOfNotPrincipalBlock({ principal: 'something' }) - * // => false - * ``` - */ -function instanceOfNotPrincipalBlock(object) { - return 'notPrincipal' in object; -} -/** - * Validate if an `object` is an instance of `NotResourceBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notResource` attribute. - * @example - * ```javascript - * instanceOfNotResourceBlock({ notResource: 'something' }) - * // => true - * - * instanceOfNotResourceBlock({ resource: 'something' }) - * // => false - * ``` - */ -function instanceOfNotResourceBlock(object) { - return 'notResource' in object; -} -/** - * Validate if an `object` is an instance of `ResourceBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `resource` attribute. - * @example - * ```javascript - * instanceOfResourceBlock({ resource: 'something' }) - * // => true - * - * instanceOfResourceBlock({ notResource: 'something' }) - * // => false - * ``` - */ -function instanceOfResourceBlock(object) { - return 'resource' in object; -} -//export { IdentityBasedType, ResourceBasedType, PrincipalMap, Patterns, ResourceBlock, ActionBlock}; -/* -type Message = MessageWithText | MessageWithAttachment | (MessageWithText & MessageWithAttachment);*/ -/* = "Condition" : { } - = { - : { : }, - : { : }, ... -} - = [, , ...] - = ("string" | "number" | "Boolean") - -//ConditionBlock -condition: {//ConditionMap - ConditionTypeString greaterThan: { - ConditionKeyString 'user.age': 18 //ConditionValueList, - }, - } - -*/ - /** * Get index range where separators are found. * @@ -847,15 +734,15 @@ class ActionBased extends Statement { this.matchConditions({ context, conditionResolver })); } checkAndAssignActions(action) { - const hasAction = instanceOfActionBlock(action); - const hasNotAction = instanceOfNotActionBlock(action); + const hasAction = 'action' in action; + const hasNotAction = 'notAction' in action; if (hasAction && hasNotAction) { throw new TypeError('ActionBased statement should have an action or a notAction attribute, no both'); } if (!hasAction && !hasNotAction) { throw new TypeError('ActionBased statement should have an action or a notAction attribute'); } - if (instanceOfActionBlock(action)) { + if ('action' in action) { this.action = typeof action.action === 'string' ? [action.action] : action.action; } @@ -896,15 +783,15 @@ class IdentityBased extends Statement { this.matchConditions({ context, conditionResolver })); } checkAndAssignActions(identity) { - const hasAction = instanceOfActionBlock(identity); - const hasNotAction = instanceOfNotActionBlock(identity); + const hasAction = 'action' in identity; + const hasNotAction = 'notAction' in identity; if (hasAction && hasNotAction) { throw new TypeError('IdentityBased statement should have an action or a notAction attribute, no both'); } if (!hasAction && !hasNotAction) { throw new TypeError('IdentityBased statement should have an action or a notAction attribute'); } - if (instanceOfActionBlock(identity)) { + if ("action" in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -918,15 +805,15 @@ class IdentityBased extends Statement { } } checkAndAssignResources(identity) { - const hasResource = instanceOfResourceBlock(identity); - const hasNotResource = instanceOfNotResourceBlock(identity); + const hasResource = "resource" in identity; + const hasNotResource = "notResource" in identity; if (hasResource && hasNotResource) { throw new TypeError('IdentityBased statement should have a resource or a notResource attribute, no both'); } if (!hasResource && !hasNotResource) { throw new TypeError('IdentityBased statement should have a resource or a notResource attribute'); } - if (instanceOfResourceBlock(identity)) { + if ("resource" in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] @@ -1018,15 +905,15 @@ class ResourceBased extends Statement { return true; } checkAndAssignActions(identity) { - const hasAction = instanceOfActionBlock(identity); - const hasNotAction = instanceOfNotActionBlock(identity); + const hasAction = 'action' in identity; + const hasNotAction = 'notAction' in identity; if (hasAction && hasNotAction) { throw new TypeError('ResourceBased statement should have an action or a notAction attribute, no both'); } if (!hasAction && !hasNotAction) { throw new TypeError('ResourceBased statement should have an action or a notAction attribute'); } - if (instanceOfActionBlock(identity)) { + if ('action' in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -1040,19 +927,19 @@ class ResourceBased extends Statement { } } checkAndAssignPrincipals(identity) { - const hasPrincipal = instanceOfPrincipalBlock(identity); - const hasNotPrincipal = instanceOfNotPrincipalBlock(identity); + const hasPrincipal = 'principal' in identity; + const hasNotPrincipal = 'notPrincipal' in identity; if (hasPrincipal && hasNotPrincipal) { throw new TypeError('ResourceBased statement could have a principal or a notPrincipal attribute, no both'); } - if (instanceOfPrincipalBlock(identity)) { + if ('principal' in identity) { this.principal = typeof identity.principal === 'string' ? [identity.principal] : identity.principal; this.hasPrincipals = true; } - else if (instanceOfNotPrincipalBlock(identity)) { + else if ('notPrincipal' in identity) { this.notPrincipal = typeof identity.notPrincipal === 'string' ? [identity.notPrincipal] @@ -1061,19 +948,19 @@ class ResourceBased extends Statement { } } checkAndAssignResources(identity) { - const hasResource = instanceOfResourceBlock(identity); - const hasNotResource = instanceOfNotResourceBlock(identity); + const hasResource = 'resource' in identity; + const hasNotResource = 'notResource' in identity; if (hasResource && hasNotResource) { throw new TypeError('ResourceBased statement could have a resource or a notResource attribute, no both'); } - if (instanceOfResourceBlock(identity)) { + if ('resource' in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] : identity.resource; this.hasResources = true; } - else if (instanceOfNotResourceBlock(identity)) { + else if ('notResource' in identity) { this.notResource = typeof identity.notResource === 'string' ? [identity.notResource] diff --git a/dist/main.es.js.map b/dist/main.es.js.map index c9aa7c5..2d7f8ec 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index c3d9a30..5e8cc59 100644 --- a/dist/main.js +++ b/dist/main.js @@ -114,9 +114,12 @@ function isKey(value, object) { isSymbol(value)) { return true; } - return (reIsPlainProp.test(value) || - !reIsDeepProp.test(value) || - (object !== null && value in Object(object))); + if (typeof value === 'string') { + return (reIsPlainProp.test(value) || + !reIsDeepProp.test(value) || + (object !== null && value in Object(object))); + } + return false; } /** @@ -573,122 +576,6 @@ class Statement { } } -/** - * Validate if an `object` is an instance of `ActionBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `action` attribute. - * @example - * ```javascript - * instanceOfActionBlock({ action: 'something' }) - * // => true - * - * instanceOfActionBlock({ notAction: 'something' }) - * // => false - * ``` - */ -function instanceOfActionBlock(object) { - return 'action' in object; -} -/** - * Validate if an `object` is an instance of `NotActionBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notAction` attribute. - * @example - * ```javascript - * instanceOfNotActionBlock({ notAction: 'something' }) - * // => true - * - * instanceOfNotActionBlock({ action: 'something' }) - * // => false - * ``` - */ -function instanceOfNotActionBlock(object) { - return 'notAction' in object; -} -/** - * Validate if an `object` is an instance of `PrincipalBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `principal` attribute. - * @example - * ```javascript - * instanceOfPrincipalBlock({ principal: 'something' }) - * // => true - * - * instanceOfPrincipalBlock({ notPrincipal: 'something' }) - * // => false - * ``` - */ -function instanceOfPrincipalBlock(object) { - return 'principal' in object; -} -/** - * Validate if an `object` is an instance of `NotPrincipalBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notPrincipal` attribute. - * @example - * ```javascript - * instanceOfNotPrincipalBlock({ notPrincipal: 'something' }) - * // => true - * - * instanceOfNotPrincipalBlock({ principal: 'something' }) - * // => false - * ``` - */ -function instanceOfNotPrincipalBlock(object) { - return 'notPrincipal' in object; -} -/** - * Validate if an `object` is an instance of `NotResourceBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notResource` attribute. - * @example - * ```javascript - * instanceOfNotResourceBlock({ notResource: 'something' }) - * // => true - * - * instanceOfNotResourceBlock({ resource: 'something' }) - * // => false - * ``` - */ -function instanceOfNotResourceBlock(object) { - return 'notResource' in object; -} -/** - * Validate if an `object` is an instance of `ResourceBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `resource` attribute. - * @example - * ```javascript - * instanceOfResourceBlock({ resource: 'something' }) - * // => true - * - * instanceOfResourceBlock({ notResource: 'something' }) - * // => false - * ``` - */ -function instanceOfResourceBlock(object) { - return 'resource' in object; -} -//export { IdentityBasedType, ResourceBasedType, PrincipalMap, Patterns, ResourceBlock, ActionBlock}; -/* -type Message = MessageWithText | MessageWithAttachment | (MessageWithText & MessageWithAttachment);*/ -/* = "Condition" : { } - = { - : { : }, - : { : }, ... -} - = [, , ...] - = ("string" | "number" | "Boolean") - -//ConditionBlock -condition: {//ConditionMap - ConditionTypeString greaterThan: { - ConditionKeyString 'user.age': 18 //ConditionValueList, - }, - } - -*/ - /** * Get index range where separators are found. * @@ -851,15 +738,15 @@ class ActionBased extends Statement { this.matchConditions({ context, conditionResolver })); } checkAndAssignActions(action) { - const hasAction = instanceOfActionBlock(action); - const hasNotAction = instanceOfNotActionBlock(action); + const hasAction = 'action' in action; + const hasNotAction = 'notAction' in action; if (hasAction && hasNotAction) { throw new TypeError('ActionBased statement should have an action or a notAction attribute, no both'); } if (!hasAction && !hasNotAction) { throw new TypeError('ActionBased statement should have an action or a notAction attribute'); } - if (instanceOfActionBlock(action)) { + if ('action' in action) { this.action = typeof action.action === 'string' ? [action.action] : action.action; } @@ -900,15 +787,15 @@ class IdentityBased extends Statement { this.matchConditions({ context, conditionResolver })); } checkAndAssignActions(identity) { - const hasAction = instanceOfActionBlock(identity); - const hasNotAction = instanceOfNotActionBlock(identity); + const hasAction = 'action' in identity; + const hasNotAction = 'notAction' in identity; if (hasAction && hasNotAction) { throw new TypeError('IdentityBased statement should have an action or a notAction attribute, no both'); } if (!hasAction && !hasNotAction) { throw new TypeError('IdentityBased statement should have an action or a notAction attribute'); } - if (instanceOfActionBlock(identity)) { + if ("action" in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -922,15 +809,15 @@ class IdentityBased extends Statement { } } checkAndAssignResources(identity) { - const hasResource = instanceOfResourceBlock(identity); - const hasNotResource = instanceOfNotResourceBlock(identity); + const hasResource = "resource" in identity; + const hasNotResource = "notResource" in identity; if (hasResource && hasNotResource) { throw new TypeError('IdentityBased statement should have a resource or a notResource attribute, no both'); } if (!hasResource && !hasNotResource) { throw new TypeError('IdentityBased statement should have a resource or a notResource attribute'); } - if (instanceOfResourceBlock(identity)) { + if ("resource" in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] @@ -1022,15 +909,15 @@ class ResourceBased extends Statement { return true; } checkAndAssignActions(identity) { - const hasAction = instanceOfActionBlock(identity); - const hasNotAction = instanceOfNotActionBlock(identity); + const hasAction = 'action' in identity; + const hasNotAction = 'notAction' in identity; if (hasAction && hasNotAction) { throw new TypeError('ResourceBased statement should have an action or a notAction attribute, no both'); } if (!hasAction && !hasNotAction) { throw new TypeError('ResourceBased statement should have an action or a notAction attribute'); } - if (instanceOfActionBlock(identity)) { + if ('action' in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -1044,19 +931,19 @@ class ResourceBased extends Statement { } } checkAndAssignPrincipals(identity) { - const hasPrincipal = instanceOfPrincipalBlock(identity); - const hasNotPrincipal = instanceOfNotPrincipalBlock(identity); + const hasPrincipal = 'principal' in identity; + const hasNotPrincipal = 'notPrincipal' in identity; if (hasPrincipal && hasNotPrincipal) { throw new TypeError('ResourceBased statement could have a principal or a notPrincipal attribute, no both'); } - if (instanceOfPrincipalBlock(identity)) { + if ('principal' in identity) { this.principal = typeof identity.principal === 'string' ? [identity.principal] : identity.principal; this.hasPrincipals = true; } - else if (instanceOfNotPrincipalBlock(identity)) { + else if ('notPrincipal' in identity) { this.notPrincipal = typeof identity.notPrincipal === 'string' ? [identity.notPrincipal] @@ -1065,19 +952,19 @@ class ResourceBased extends Statement { } } checkAndAssignResources(identity) { - const hasResource = instanceOfResourceBlock(identity); - const hasNotResource = instanceOfNotResourceBlock(identity); + const hasResource = 'resource' in identity; + const hasNotResource = 'notResource' in identity; if (hasResource && hasNotResource) { throw new TypeError('ResourceBased statement could have a resource or a notResource attribute, no both'); } - if (instanceOfResourceBlock(identity)) { + if ('resource' in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] : identity.resource; this.hasResources = true; } - else if (instanceOfNotResourceBlock(identity)) { + else if ('notResource' in identity) { this.notResource = typeof identity.notResource === 'string' ? [identity.notResource] diff --git a/dist/main.js.map b/dist/main.js.map index 6c6ff22..837a3c1 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/package.json b/package.json index 0199645..f27272f 100644 --- a/package.json +++ b/package.json @@ -75,8 +75,8 @@ "@semantic-release/npm": "^7.0.6", "@semantic-release/release-notes-generator": "^9.0.1", "@types/jest": "^24.0.22", - "@typescript-eslint/eslint-plugin": "^2.17.0", - "@typescript-eslint/parser": "^2.17.0", + "@typescript-eslint/eslint-plugin": "4.5.0", + "@typescript-eslint/parser": "4.5.0", "babel-eslint": "^10.0.3", "babel-jest": "26.2.2", "commitizen": "^4.0.3", @@ -94,13 +94,13 @@ "prettier": "2.1.2", "pretty-quick": "3.1.0", "rimraf": "^3.0.0", - "rollup": "2.23.1", - "rollup-plugin-dts": "1.4.10", + "rollup": "2.32.1", + "rollup-plugin-dts": "1.4.13", "rollup-plugin-peer-deps-external": "^2.2.0", "rollup-plugin-typescript2": "^0.25.3", "semantic-release": "^17.1.1", "tslib": "^1.10.0", - "typescript": "^3.9.3" + "typescript": "4.0.3" }, "files": [ "dist" diff --git a/src/ActionBasedPolicy.ts b/src/ActionBasedPolicy.ts index 23b47df..9a1a7c3 100644 --- a/src/ActionBasedPolicy.ts +++ b/src/ActionBasedPolicy.ts @@ -7,7 +7,7 @@ import { import { ActionBased } from './ActionBasedStatement'; import { Policy } from './Policy'; -interface ActionBasedPolicyInterface { +export interface ActionBasedPolicyInterface { statements: ActionBasedType[]; conditionResolver?: ConditionResolver; context?: Context; diff --git a/src/ActionBasedStatement.ts b/src/ActionBasedStatement.ts index f271844..3d7177a 100644 --- a/src/ActionBasedStatement.ts +++ b/src/ActionBasedStatement.ts @@ -1,8 +1,4 @@ import { ActionBasedType, Context, MatchActionBasedInterface } from './types'; -import { - instanceOfActionBlock, - instanceOfNotActionBlock -} from './utils/instanceOfInterfaces'; import { Matcher } from './Matcher'; import { applyContext, Statement } from './Statement'; @@ -34,8 +30,8 @@ class ActionBased extends Statement { } private checkAndAssignActions(action: ActionBasedType): void { - const hasAction = instanceOfActionBlock(action); - const hasNotAction = instanceOfNotActionBlock(action); + const hasAction = 'action' in action; + const hasNotAction = 'notAction' in action; if (hasAction && hasNotAction) { throw new TypeError( 'ActionBased statement should have an action or a notAction attribute, no both' @@ -46,7 +42,7 @@ class ActionBased extends Statement { 'ActionBased statement should have an action or a notAction attribute' ); } - if (instanceOfActionBlock(action)) { + if ('action' in action) { this.action = typeof action.action === 'string' ? [action.action] : action.action; } else { diff --git a/src/IdentityBasedStatement.ts b/src/IdentityBasedStatement.ts index 7c30d08..eae0490 100644 --- a/src/IdentityBasedStatement.ts +++ b/src/IdentityBasedStatement.ts @@ -3,12 +3,6 @@ import { IdentityBasedType, MatchIdentityBasedInterface } from './types'; -import { - instanceOfActionBlock, - instanceOfNotActionBlock, - instanceOfNotResourceBlock, - instanceOfResourceBlock -} from './utils/instanceOfInterfaces'; import { Matcher } from './Matcher'; import { applyContext, Statement } from './Statement'; @@ -46,8 +40,8 @@ class IdentityBased extends Statement { } private checkAndAssignActions(identity: IdentityBasedType): void { - const hasAction = instanceOfActionBlock(identity); - const hasNotAction = instanceOfNotActionBlock(identity); + const hasAction = 'action' in identity; + const hasNotAction = 'notAction' in identity; if (hasAction && hasNotAction) { throw new TypeError( 'IdentityBased statement should have an action or a notAction attribute, no both' @@ -58,7 +52,7 @@ class IdentityBased extends Statement { 'IdentityBased statement should have an action or a notAction attribute' ); } - if (instanceOfActionBlock(identity)) { + if ('action' in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -72,8 +66,8 @@ class IdentityBased extends Statement { } private checkAndAssignResources(identity: IdentityBasedType): void { - const hasResource = instanceOfResourceBlock(identity); - const hasNotResource = instanceOfNotResourceBlock(identity); + const hasResource = 'resource' in identity; + const hasNotResource = 'notResource' in identity; if (hasResource && hasNotResource) { throw new TypeError( 'IdentityBased statement should have a resource or a notResource attribute, no both' @@ -84,7 +78,7 @@ class IdentityBased extends Statement { 'IdentityBased statement should have a resource or a notResource attribute' ); } - if (instanceOfResourceBlock(identity)) { + if ('resource' in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] diff --git a/src/ResourceBasedStatement.ts b/src/ResourceBasedStatement.ts index a22fbc9..9516c09 100644 --- a/src/ResourceBasedStatement.ts +++ b/src/ResourceBasedStatement.ts @@ -4,14 +4,6 @@ import { MatchResourceBasedInterface, ResourceBasedType } from './types'; -import { - instanceOfActionBlock, - instanceOfNotActionBlock, - instanceOfNotPrincipalBlock, - instanceOfNotResourceBlock, - instanceOfPrincipalBlock, - instanceOfResourceBlock -} from './utils/instanceOfInterfaces'; import { Matcher } from './Matcher'; import { applyContext, Statement } from './Statement'; @@ -105,8 +97,8 @@ class ResourceBased extends Statement { } private checkAndAssignActions(identity: ResourceBasedType): void { - const hasAction = instanceOfActionBlock(identity); - const hasNotAction = instanceOfNotActionBlock(identity); + const hasAction = 'action' in identity; + const hasNotAction = 'notAction' in identity; if (hasAction && hasNotAction) { throw new TypeError( 'ResourceBased statement should have an action or a notAction attribute, no both' @@ -117,7 +109,7 @@ class ResourceBased extends Statement { 'ResourceBased statement should have an action or a notAction attribute' ); } - if (instanceOfActionBlock(identity)) { + if ('action' in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -131,20 +123,20 @@ class ResourceBased extends Statement { } private checkAndAssignPrincipals(identity: ResourceBasedType): void { - const hasPrincipal = instanceOfPrincipalBlock(identity); - const hasNotPrincipal = instanceOfNotPrincipalBlock(identity); + const hasPrincipal = 'principal' in identity; + const hasNotPrincipal = 'notPrincipal' in identity; if (hasPrincipal && hasNotPrincipal) { throw new TypeError( 'ResourceBased statement could have a principal or a notPrincipal attribute, no both' ); } - if (instanceOfPrincipalBlock(identity)) { + if ('principal' in identity) { this.principal = typeof identity.principal === 'string' ? [identity.principal] : identity.principal; this.hasPrincipals = true; - } else if (instanceOfNotPrincipalBlock(identity)) { + } else if ('notPrincipal' in identity) { this.notPrincipal = typeof identity.notPrincipal === 'string' ? [identity.notPrincipal] @@ -154,20 +146,20 @@ class ResourceBased extends Statement { } private checkAndAssignResources(identity: ResourceBasedType): void { - const hasResource = instanceOfResourceBlock(identity); - const hasNotResource = instanceOfNotResourceBlock(identity); + const hasResource = 'resource' in identity; + const hasNotResource = 'notResource' in identity; if (hasResource && hasNotResource) { throw new TypeError( 'ResourceBased statement could have a resource or a notResource attribute, no both' ); } - if (instanceOfResourceBlock(identity)) { + if ('resource' in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] : identity.resource; this.hasResources = true; - } else if (instanceOfNotResourceBlock(identity)) { + } else if ('notResource' in identity) { this.notResource = typeof identity.notResource === 'string' ? [identity.notResource] diff --git a/src/types.ts b/src/types.ts index 7e574f6..e430f7a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -111,7 +111,7 @@ type IdentityBasedType = StatementInterface & type ResourceBasedType = StatementInterface & (PrincipalBlock | NotPrincipalBlock) & (ActionBlock | NotActionBlock) & - (ResourceBlock | NotResourceBlock | {}); + (ResourceBlock | NotResourceBlock); export { ActionBasedType, diff --git a/src/utils/getTag.ts b/src/utils/getTag.ts index 44086c4..b4796e1 100644 --- a/src/utils/getTag.ts +++ b/src/utils/getTag.ts @@ -14,6 +14,6 @@ * // => '[object Null]' * ``` */ -export function getTag(value: any): string { +export function getTag(value: unknown): string { return Object.prototype.toString.call(value); } diff --git a/src/utils/getValueFromPath.ts b/src/utils/getValueFromPath.ts index 67d7db7..af87edc 100644 --- a/src/utils/getValueFromPath.ts +++ b/src/utils/getValueFromPath.ts @@ -10,7 +10,10 @@ import { stringToPath } from './stringToPath'; * @param {Object} [object] The object to query keys on. * @returns {Array} Returns the cast property path array. */ -export function castPath(value: any, object: object): Array { +export function castPath( + value: unknown, + object: Record +): Array { if (Array.isArray(value)) { return value; } @@ -26,7 +29,10 @@ export function castPath(value: any, object: object): Array { * @param {Array|string} path The path of the property to get. * @returns {*} Returns the resolved value. */ -export function baseGet(object: object, path: Array | string): any { +export function baseGet( + object: Record, + path: Array | string +): any { const newPath = castPath(path, object); let index = 0; @@ -64,9 +70,9 @@ export function baseGet(object: object, path: Array | string): any { * // => 'default' */ export function getValueFromPath( - object: object, + object: Record, path: Array | string, - defaultValue?: any + defaultValue?: unknown ): any { const result = object === null ? undefined : baseGet(object, path); diff --git a/src/utils/instanceOfInterfaces.test.ts b/src/utils/instanceOfInterfaces.test.ts deleted file mode 100644 index 0e0ab04..0000000 --- a/src/utils/instanceOfInterfaces.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { - instanceOfActionBlock, - instanceOfNotActionBlock, - instanceOfNotPrincipalBlock, - instanceOfNotResourceBlock, - instanceOfPrincipalBlock, - instanceOfResourceBlock -} from './instanceOfInterfaces'; - -describe('Util functions', () => { - describe('instanceOfPrincipalBlock', () => { - it("doesn't throw an error", () => { - expect(() => - instanceOfPrincipalBlock({ - principal: 'something' - }) - ).not.toThrow(); - expect(instanceOfPrincipalBlock({ principal: 'something' })).toBe(true); - expect(instanceOfPrincipalBlock({ notPrincipal: 'something' })).toBe( - false - ); - }); - }); - - describe('instanceOfNotPrincipalBlock', () => { - it("doesn't throw an error", () => { - expect(() => - instanceOfNotPrincipalBlock({ - notPrincipal: 'something' - }) - ).not.toThrow(); - expect(instanceOfNotPrincipalBlock({ notPrincipal: 'something' })).toBe( - true - ); - expect(instanceOfNotPrincipalBlock({ principal: 'something' })).toBe( - false - ); - }); - }); - - describe('instanceOfResourceBlock', () => { - it("doesn't throw an error", () => { - expect(() => - instanceOfResourceBlock({ - resource: 'something' - }) - ).not.toThrow(); - expect(instanceOfResourceBlock({ resource: 'something' })).toBe(true); - expect(instanceOfResourceBlock({ notResource: 'something' })).toBe(false); - }); - }); - - describe('instanceOfActionBlock', () => { - it("doesn't throw an error", () => { - expect(() => - instanceOfActionBlock({ - action: 'something' - }) - ).not.toThrow(); - expect(instanceOfActionBlock({ action: 'something' })).toBe(true); - expect(instanceOfActionBlock({ notAction: 'something' })).toBe(false); - }); - }); - - describe('instanceOfNotActionBlock', () => { - it("doesn't throw an error", () => { - expect(() => - instanceOfNotActionBlock({ - notAction: 'something' - }) - ).not.toThrow(); - expect(instanceOfNotActionBlock({ notAction: 'something' })).toBe(true); - expect(instanceOfNotActionBlock({ action: 'something' })).toBe(false); - }); - }); - - describe('instanceOfNotResourceBlock', () => { - it("doesn't throw an error", () => { - expect(() => - instanceOfNotResourceBlock({ - notResource: 'something' - }) - ).not.toThrow(); - expect(instanceOfNotResourceBlock({ notResource: 'something' })).toBe( - true - ); - expect(instanceOfNotResourceBlock({ resource: 'something' })).toBe(false); - }); - }); -}); diff --git a/src/utils/instanceOfInterfaces.ts b/src/utils/instanceOfInterfaces.ts deleted file mode 100644 index 9bb2451..0000000 --- a/src/utils/instanceOfInterfaces.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { - ActionBlock, - NotActionBlock, - NotPrincipalBlock, - NotResourceBlock, - PrincipalBlock, - ResourceBlock -} from '../types'; - -/** - * Validate if an `object` is an instance of `ActionBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `action` attribute. - * @example - * ```javascript - * instanceOfActionBlock({ action: 'something' }) - * // => true - * - * instanceOfActionBlock({ notAction: 'something' }) - * // => false - * ``` - */ -export function instanceOfActionBlock(object: object): object is ActionBlock { - return 'action' in object; -} - -/** - * Validate if an `object` is an instance of `NotActionBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notAction` attribute. - * @example - * ```javascript - * instanceOfNotActionBlock({ notAction: 'something' }) - * // => true - * - * instanceOfNotActionBlock({ action: 'something' }) - * // => false - * ``` - */ -export function instanceOfNotActionBlock( - object: object -): object is NotActionBlock { - return 'notAction' in object; -} - -/** - * Validate if an `object` is an instance of `PrincipalBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `principal` attribute. - * @example - * ```javascript - * instanceOfPrincipalBlock({ principal: 'something' }) - * // => true - * - * instanceOfPrincipalBlock({ notPrincipal: 'something' }) - * // => false - * ``` - */ -export function instanceOfPrincipalBlock( - object: object -): object is PrincipalBlock { - return 'principal' in object; -} - -/** - * Validate if an `object` is an instance of `NotPrincipalBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notPrincipal` attribute. - * @example - * ```javascript - * instanceOfNotPrincipalBlock({ notPrincipal: 'something' }) - * // => true - * - * instanceOfNotPrincipalBlock({ principal: 'something' }) - * // => false - * ``` - */ -export function instanceOfNotPrincipalBlock( - object: object -): object is NotPrincipalBlock { - return 'notPrincipal' in object; -} - -/** - * Validate if an `object` is an instance of `NotResourceBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `notResource` attribute. - * @example - * ```javascript - * instanceOfNotResourceBlock({ notResource: 'something' }) - * // => true - * - * instanceOfNotResourceBlock({ resource: 'something' }) - * // => false - * ``` - */ -export function instanceOfNotResourceBlock( - object: object -): object is NotResourceBlock { - return 'notResource' in object; -} - -/** - * Validate if an `object` is an instance of `ResourceBlock`. - * @param {Object} object Object to validate - * @returns {boolean} Returns true if `object` has `resource` attribute. - * @example - * ```javascript - * instanceOfResourceBlock({ resource: 'something' }) - * // => true - * - * instanceOfResourceBlock({ notResource: 'something' }) - * // => false - * ``` - */ -export function instanceOfResourceBlock( - object: object -): object is ResourceBlock { - return 'resource' in object; -} - -//export { IdentityBasedType, ResourceBasedType, PrincipalMap, Patterns, ResourceBlock, ActionBlock}; - -/* -type Message = MessageWithText | MessageWithAttachment | (MessageWithText & MessageWithAttachment);*/ -/* = "Condition" : { } - = { - : { : }, - : { : }, ... -} - = [, , ...] - = ("string" | "number" | "Boolean") - -//ConditionBlock -condition: {//ConditionMap - ConditionTypeString greaterThan: { - ConditionKeyString 'user.age': 18 //ConditionValueList, - }, - } - -*/ diff --git a/src/utils/isKey.test.ts b/src/utils/isKey.test.ts index 5644a31..c1d2018 100644 --- a/src/utils/isKey.test.ts +++ b/src/utils/isKey.test.ts @@ -13,6 +13,7 @@ describe('isKey', () => { }); it('should get false', () => { expect(isKey([1, ''])).toEqual(false); + expect(isKey({})).toEqual(false); expect(isKey('something[test]')).toEqual(false); expect(isKey('a.b')).toEqual(false); }); diff --git a/src/utils/isKey.ts b/src/utils/isKey.ts index abd73e4..760c164 100644 --- a/src/utils/isKey.ts +++ b/src/utils/isKey.ts @@ -35,22 +35,26 @@ const reIsPlainProp = /^\w*$/; //matches any word caracter (alphanumeric and und * // => true * ``` */ -export function isKey(value: any, object?: object): boolean { - if (Array.isArray(value)) { - return false; - } +export function isKey( + value: unknown, + object?: Record +): boolean { const type = typeof value; if ( type === 'number' || type === 'boolean' || value === null || + value === undefined || isSymbol(value) ) { return true; } - return ( - reIsPlainProp.test(value) || - !reIsDeepProp.test(value) || - (object !== null && value in Object(object)) - ); + if (typeof value === 'string') { + return ( + reIsPlainProp.test(value) || + !reIsDeepProp.test(value) || + (object !== null && value in Object(object)) + ); + } + return false; } diff --git a/src/utils/isSymbol.ts b/src/utils/isSymbol.ts index 14a0da6..acaa78d 100644 --- a/src/utils/isSymbol.ts +++ b/src/utils/isSymbol.ts @@ -16,7 +16,7 @@ import { getTag } from './getTag'; * // => false * ``` */ -export function isSymbol(value?: any): boolean { +export function isSymbol(value?: unknown): value is symbol { const type = typeof value; return ( type === 'symbol' || diff --git a/src/utils/memoize.ts b/src/utils/memoize.ts index 00b1d6d..0252d4f 100644 --- a/src/utils/memoize.ts +++ b/src/utils/memoize.ts @@ -34,8 +34,14 @@ import { MemoizeInterface } from '../types'; * // => ['a', 'b'] * ``` */ -export function memoize(func: Function, resolver?: Function): MemoizeInterface { - const memoized = function (this: Function, ...args: any): MemoizeInterface { +export function memoize( + func: (...args: unknown[]) => any, + resolver?: (...args: unknown[]) => any +): MemoizeInterface { + const memoized = function ( + this: (...args: unknown[]) => any, + ...args: unknown[] + ): MemoizeInterface { const key = resolver ? resolver.apply(this, args) : args[0]; const cache = memoized.cache; diff --git a/src/utils/memoizeCapped.ts b/src/utils/memoizeCapped.ts index c41087a..d59d40e 100644 --- a/src/utils/memoizeCapped.ts +++ b/src/utils/memoizeCapped.ts @@ -13,8 +13,10 @@ export const MAX_MEMOIZE_SIZE = 500; * @param {Function} func The function to have its output memoized. * @returns {Function} Returns the new memoized function. */ -export function memoizeCapped(func: Function): MemoizeInterface { - const result = memoize(func, (key: any) => { +export function memoizeCapped( + func: (...args: any) => unknown +): MemoizeInterface { + const result = memoize(func, (key: unknown) => { const { cache } = result; if (cache.size === MAX_MEMOIZE_SIZE) { cache.clear(); diff --git a/yarn.lock b/yarn.lock index 2646de3..e2767f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1598,11 +1598,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/eslint-visitor-keys@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" - integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== - "@types/estree@*": version "0.0.45" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" @@ -1725,49 +1720,76 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^2.17.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" - integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== +"@typescript-eslint/eslint-plugin@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.5.0.tgz#4ff9c1d8535ae832e239f0ef6d7210592d9b0b07" + integrity sha512-mjb/gwNcmDKNt+6mb7Aj/TjKzIJjOPcoCJpjBQC9ZnTRnBt1p4q5dJSSmIqAtsZ/Pff5N+hJlbiPc5bl6QN4OQ== dependencies: - "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/experimental-utils" "4.5.0" + "@typescript-eslint/scope-manager" "4.5.0" + debug "^4.1.1" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" + semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" - integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== +"@typescript-eslint/experimental-utils@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.5.0.tgz#547fe1158609143ce60645383aa1d6f83ada28df" + integrity sha512-bW9IpSAKYvkqDGRZzayBXIgPsj2xmmVHLJ+flGSoN0fF98pGoKFhbunIol0VF2Crka7z984EEhFi623Rl7e6gg== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.34.0" + "@typescript-eslint/scope-manager" "4.5.0" + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/typescript-estree" "4.5.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^2.17.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" - integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== +"@typescript-eslint/parser@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.5.0.tgz#b2d659f25eec0041c7bc5660b91db1eefe8d7122" + integrity sha512-xb+gmyhQcnDWe+5+xxaQk5iCw6KqXd8VQxGiTeELTMoYeRjpocZYYRP1gFVM2C8Yl0SpUvLa1lhprwqZ00w3Iw== dependencies: - "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.34.0" - "@typescript-eslint/typescript-estree" "2.34.0" - eslint-visitor-keys "^1.1.0" + "@typescript-eslint/scope-manager" "4.5.0" + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/typescript-estree" "4.5.0" + debug "^4.1.1" -"@typescript-eslint/typescript-estree@2.34.0": - version "2.34.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" - integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== +"@typescript-eslint/scope-manager@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.5.0.tgz#8dfd53c3256d4357e7d66c2fc8956835f4d239be" + integrity sha512-C0cEO0cTMPJ/w4RA/KVe4LFFkkSh9VHoFzKmyaaDWAnPYIEzVCtJ+Un8GZoJhcvq+mPFXEsXa01lcZDHDG6Www== dependencies: + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/visitor-keys" "4.5.0" + +"@typescript-eslint/types@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.5.0.tgz#98256e07bad1c8d15d0c9627ebec82fd971bb3c3" + integrity sha512-n2uQoXnyWNk0Les9MtF0gCK3JiWd987JQi97dMSxBOzVoLZXCNtxFckVqt1h8xuI1ix01t+iMY4h4rFMj/303g== + +"@typescript-eslint/typescript-estree@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.5.0.tgz#d50cf91ae3a89878401111031eb6fb6d03554f64" + integrity sha512-gN1mffq3zwRAjlYWzb5DanarOPdajQwx5MEWkWCk0XvqC8JpafDTeioDoow2L4CA/RkYZu7xEsGZRhqrTsAG8w== + dependencies: + "@typescript-eslint/types" "4.5.0" + "@typescript-eslint/visitor-keys" "4.5.0" debug "^4.1.1" - eslint-visitor-keys "^1.1.0" - glob "^7.1.6" + globby "^11.0.1" is-glob "^4.0.1" lodash "^4.17.15" semver "^7.3.2" tsutils "^3.17.1" +"@typescript-eslint/visitor-keys@4.5.0": + version "4.5.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.5.0.tgz#b59f26213ac597efe87f6b13cf2aabee70542af0" + integrity sha512-UHq4FSa55NDZqscRU//O5ROFhHa9Hqn9KWTEvJGTArtTQp5GKv9Zqf6d/Q3YXXcFv4woyBml7fJQlQ+OuqRcHA== + dependencies: + "@typescript-eslint/types" "4.5.0" + eslint-visitor-keys "^2.0.0" + JSONStream@^1.0.4, JSONStream@^1.3.4, JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -3490,6 +3512,11 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + eslint@^6.8.0: version "6.8.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" @@ -4217,7 +4244,7 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -globby@^11.0.0: +globby@^11.0.0, globby@^11.0.1: version "11.0.1" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== @@ -7916,10 +7943,10 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" -rollup-plugin-dts@1.4.10: - version "1.4.10" - resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-1.4.10.tgz#0373c4284a2ba4d2d72df69c289271a816bc2736" - integrity sha512-bL6MBXc8lK7D5b/tYbHaglxs4ZxMQTQilGA6Xm9KQBEj4h9ZwIDlAsvDooGjJ/cOw23r3POTRtSCEyTHxtzHJg== +rollup-plugin-dts@1.4.13: + version "1.4.13" + resolved "https://registry.yarnpkg.com/rollup-plugin-dts/-/rollup-plugin-dts-1.4.13.tgz#4f086e84f4fdcc1f49160799ebc66f6b09db292b" + integrity sha512-7mxoQ6PcmCkBE5ZhrjGDL4k42XLy8BkSqpiRi1MipwiGs+7lwi4mQkp2afX+OzzLjJp/TGM8llfe8uayIUhPEw== optionalDependencies: "@babel/code-frame" "^7.10.4" @@ -7946,10 +7973,10 @@ rollup-pluginutils@2.8.1: dependencies: estree-walker "^0.6.1" -rollup@2.23.1: - version "2.23.1" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.23.1.tgz#d458d28386dc7660c2e8a4978bea6f9494046c20" - integrity sha512-Heyl885+lyN/giQwxA8AYT2GY3U+gOlTqVLrMQYno8Z1X9lAOpfXPiKiZCyPc25e9BLJM3Zlh957dpTlO4pa8A== +rollup@2.32.1: + version "2.32.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.32.1.tgz#625a92c54f5b4d28ada12d618641491d4dbb548c" + integrity sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw== optionalDependencies: fsevents "~2.1.2" @@ -8960,10 +8987,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.9.3: - version "3.9.7" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== +typescript@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" + integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.6" From 72f40920e7869fd22b3ea3dbaa9dcaccc75dfd92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 25 Oct 2020 21:16:39 -0500 Subject: [PATCH 11/13] feat(actionbasedpolicy): allow to generate proxy from Action Based Policy updating types and Statement class errors --- dist/main.d.ts | 37 ++++++-- dist/main.es.js | 55 +++++++---- dist/main.es.js.map | 2 +- dist/main.js | 55 +++++++---- dist/main.js.map | 2 +- src/ActionBasedPolicy.test.ts | 148 +++++++++++++++++++++++++++++ src/ActionBasedPolicy.ts | 47 ++++++++- src/ActionBasedStatement.test.ts | 12 --- src/ActionBasedStatement.ts | 5 - src/IdentityBasedStatement.test.ts | 31 +----- src/IdentityBasedStatement.ts | 10 -- src/ResourceBasedStatement.test.ts | 14 --- src/ResourceBasedStatement.ts | 5 - src/types.ts | 49 ++++++---- 14 files changed, 330 insertions(+), 142 deletions(-) diff --git a/dist/main.d.ts b/dist/main.d.ts index 7b7a0e0..8e8aa93 100644 --- a/dist/main.d.ts +++ b/dist/main.d.ts @@ -46,11 +46,11 @@ declare type Patterns = string[] | string; interface PrincipalMap { [key: string]: Patterns; } -interface PrincipalBlock { - principal: PrincipalMap | Patterns; +interface OptionalPrincipalBlock { + principal?: PrincipalMap | Patterns; } -interface NotPrincipalBlock { - notPrincipal: PrincipalMap | Patterns; +interface OptionalNotPrincipalBlock { + notPrincipal?: PrincipalMap | Patterns; } interface ActionBlock { action: Patterns; @@ -64,6 +64,12 @@ interface ResourceBlock { interface NotResourceBlock { notResource: Patterns; } +interface OptionalResourceBlock { + resource?: Patterns; +} +interface OptionalNotResourceBlock { + notResource?: Patterns; +} declare type ConditionKey = string | number | boolean; interface Context { [key: string]: ConditionKey | Context | string[] | number[]; @@ -93,9 +99,10 @@ interface MatchActionBasedInterface extends MatchConditionInterface { interface MatchIdentityBasedInterface extends MatchActionBasedInterface { resource: string; } -interface MatchResourceBasedInterface extends MatchIdentityBasedInterface { - principal: string; +interface MatchResourceBasedInterface extends MatchActionBasedInterface { + principal?: string; principalType?: string; + resource?: string; } interface EvaluateActionBasedInterface { action: string; @@ -104,13 +111,24 @@ interface EvaluateActionBasedInterface { interface EvaluateIdentityBasedInterface extends EvaluateActionBasedInterface { resource: string; } -interface EvaluateResourceBasedInterface extends EvaluateIdentityBasedInterface { - principal: string; +interface EvaluateResourceBasedInterface extends EvaluateActionBasedInterface { + principal?: string; principalType?: string; + resource?: string; } declare type ActionBasedType = StatementInterface & (ActionBlock | NotActionBlock); declare type IdentityBasedType = StatementInterface & (ActionBlock | NotActionBlock) & (ResourceBlock | NotResourceBlock); -declare type ResourceBasedType = StatementInterface & (PrincipalBlock | NotPrincipalBlock) & (ActionBlock | NotActionBlock) & (ResourceBlock | NotResourceBlock); +declare type ResourceBasedType = StatementInterface & (OptionalPrincipalBlock | OptionalNotPrincipalBlock) & (ActionBlock | NotActionBlock) & (OptionalResourceBlock | OptionalNotResourceBlock); +interface ProxyOptions { + get?: { + allow?: boolean; + propertyMap?: Record; + }; + set?: { + allow?: boolean; + propertyMap?: Record; + }; +} declare function applyContext(str: string, context?: Context): string; declare class Statement { @@ -200,6 +218,7 @@ declare class ActionBasedPolicy extends Policy { evaluate({ action, context }: EvaluateActionBasedInterface): boolean; can({ action, context }: EvaluateActionBasedInterface): boolean; cannot({ action, context }: EvaluateActionBasedInterface): boolean; + generateProxy(obj: unknown, options?: ProxyOptions): T | undefined; } interface IdentityBasedPolicyInterface { diff --git a/dist/main.es.js b/dist/main.es.js index e555dce..885af9c 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -100,13 +100,11 @@ const reIsPlainProp = /^\w*$/; //matches any word caracter (alphanumeric and und * ``` */ function isKey(value, object) { - if (Array.isArray(value)) { - return false; - } const type = typeof value; if (type === 'number' || type === 'boolean' || value === null || + value === undefined || isSymbol(value)) { return true; } @@ -739,9 +737,6 @@ class ActionBased extends Statement { if (hasAction && hasNotAction) { throw new TypeError('ActionBased statement should have an action or a notAction attribute, no both'); } - if (!hasAction && !hasNotAction) { - throw new TypeError('ActionBased statement should have an action or a notAction attribute'); - } if ('action' in action) { this.action = typeof action.action === 'string' ? [action.action] : action.action; @@ -788,10 +783,7 @@ class IdentityBased extends Statement { if (hasAction && hasNotAction) { throw new TypeError('IdentityBased statement should have an action or a notAction attribute, no both'); } - if (!hasAction && !hasNotAction) { - throw new TypeError('IdentityBased statement should have an action or a notAction attribute'); - } - if ("action" in identity) { + if ('action' in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -805,15 +797,12 @@ class IdentityBased extends Statement { } } checkAndAssignResources(identity) { - const hasResource = "resource" in identity; - const hasNotResource = "notResource" in identity; + const hasResource = 'resource' in identity; + const hasNotResource = 'notResource' in identity; if (hasResource && hasNotResource) { throw new TypeError('IdentityBased statement should have a resource or a notResource attribute, no both'); } - if (!hasResource && !hasNotResource) { - throw new TypeError('IdentityBased statement should have a resource or a notResource attribute'); - } - if ("resource" in identity) { + if ('resource' in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] @@ -910,9 +899,6 @@ class ResourceBased extends Statement { if (hasAction && hasNotAction) { throw new TypeError('ResourceBased statement should have an action or a notAction attribute, no both'); } - if (!hasAction && !hasNotAction) { - throw new TypeError('ResourceBased statement should have an action or a notAction attribute'); - } if ('action' in identity) { this.action = typeof identity.action === 'string' @@ -1078,6 +1064,37 @@ class ActionBasedPolicy extends Policy { conditionResolver: this.conditionResolver })); } + generateProxy(obj, options = {}) { + const { get = {}, set = {} } = options; + const { allow: allowGet = true, propertyMap: propertyMapGet = {} } = get; + const { allow: allowSet = true, propertyMap: propertyMapSet = {} } = set; + const handler = Object.assign(Object.assign({}, (allowGet ? { get: (target, prop) => { + if (prop in target) { + if (typeof prop === 'string') { + const property = propertyMapGet[prop] || prop; + if (this.evaluate({ action: property })) + return target[prop]; + throw new Error(`Unauthorize to get ${prop} property`); + } + } + return target[prop]; + } } : {})), (allowSet ? { set: (target, prop, value) => { + if (typeof prop === 'string') { + const property = propertyMapSet[prop] || prop; + if (this.evaluate({ action: property })) { + target[prop] = value; + return true; + } + else + throw new Error(`Unauthorize to set ${prop} property`); + } + return true; + } } : {})); + if (obj instanceof Object) { + return new Proxy(obj, handler); + } + return undefined; + } } class IdentityBasedPolicy extends Policy { diff --git a/dist/main.es.js.map b/dist/main.es.js.map index 2d7f8ec..928da08 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index 5e8cc59..05539cd 100644 --- a/dist/main.js +++ b/dist/main.js @@ -104,13 +104,11 @@ const reIsPlainProp = /^\w*$/; //matches any word caracter (alphanumeric and und * ``` */ function isKey(value, object) { - if (Array.isArray(value)) { - return false; - } const type = typeof value; if (type === 'number' || type === 'boolean' || value === null || + value === undefined || isSymbol(value)) { return true; } @@ -743,9 +741,6 @@ class ActionBased extends Statement { if (hasAction && hasNotAction) { throw new TypeError('ActionBased statement should have an action or a notAction attribute, no both'); } - if (!hasAction && !hasNotAction) { - throw new TypeError('ActionBased statement should have an action or a notAction attribute'); - } if ('action' in action) { this.action = typeof action.action === 'string' ? [action.action] : action.action; @@ -792,10 +787,7 @@ class IdentityBased extends Statement { if (hasAction && hasNotAction) { throw new TypeError('IdentityBased statement should have an action or a notAction attribute, no both'); } - if (!hasAction && !hasNotAction) { - throw new TypeError('IdentityBased statement should have an action or a notAction attribute'); - } - if ("action" in identity) { + if ('action' in identity) { this.action = typeof identity.action === 'string' ? [identity.action] @@ -809,15 +801,12 @@ class IdentityBased extends Statement { } } checkAndAssignResources(identity) { - const hasResource = "resource" in identity; - const hasNotResource = "notResource" in identity; + const hasResource = 'resource' in identity; + const hasNotResource = 'notResource' in identity; if (hasResource && hasNotResource) { throw new TypeError('IdentityBased statement should have a resource or a notResource attribute, no both'); } - if (!hasResource && !hasNotResource) { - throw new TypeError('IdentityBased statement should have a resource or a notResource attribute'); - } - if ("resource" in identity) { + if ('resource' in identity) { this.resource = typeof identity.resource === 'string' ? [identity.resource] @@ -914,9 +903,6 @@ class ResourceBased extends Statement { if (hasAction && hasNotAction) { throw new TypeError('ResourceBased statement should have an action or a notAction attribute, no both'); } - if (!hasAction && !hasNotAction) { - throw new TypeError('ResourceBased statement should have an action or a notAction attribute'); - } if ('action' in identity) { this.action = typeof identity.action === 'string' @@ -1082,6 +1068,37 @@ class ActionBasedPolicy extends Policy { conditionResolver: this.conditionResolver })); } + generateProxy(obj, options = {}) { + const { get = {}, set = {} } = options; + const { allow: allowGet = true, propertyMap: propertyMapGet = {} } = get; + const { allow: allowSet = true, propertyMap: propertyMapSet = {} } = set; + const handler = Object.assign(Object.assign({}, (allowGet ? { get: (target, prop) => { + if (prop in target) { + if (typeof prop === 'string') { + const property = propertyMapGet[prop] || prop; + if (this.evaluate({ action: property })) + return target[prop]; + throw new Error(`Unauthorize to get ${prop} property`); + } + } + return target[prop]; + } } : {})), (allowSet ? { set: (target, prop, value) => { + if (typeof prop === 'string') { + const property = propertyMapSet[prop] || prop; + if (this.evaluate({ action: property })) { + target[prop] = value; + return true; + } + else + throw new Error(`Unauthorize to set ${prop} property`); + } + return true; + } } : {})); + if (obj instanceof Object) { + return new Proxy(obj, handler); + } + return undefined; + } } class IdentityBasedPolicy extends Policy { diff --git a/dist/main.js.map b/dist/main.js.map index 837a3c1..7cde2eb 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/src/ActionBasedPolicy.test.ts b/src/ActionBasedPolicy.test.ts index ca29e92..5c19605 100644 --- a/src/ActionBasedPolicy.test.ts +++ b/src/ActionBasedPolicy.test.ts @@ -256,4 +256,152 @@ describe('ActionBasedPolicy Class', () => { ).toBe(false); }); }); + + describe('generate Proxy', () => { + describe('when use default options', () => { + it('returns an instance of a class', () => { + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['upper', 'lastName', 'age'] + } + ] + }); + class User { + firstName: string; + age: number; + private lastName: string; + constructor(firstName, lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + upper(): string { + return this.lastName.toUpperCase(); + } + } + const user = new User('John', 'Wick'); + const proxy = policy.generateProxy(user) as User; + const getExpectedError = new Error( + 'Unauthorize to get firstName property' + ); + const setExpectedError = new Error( + 'Unauthorize to set firstName property' + ); + + expect(proxy.upper()).toBe('WICK'); + expect(proxy.upper()).toBe('WICK'); + expect(() => { + proxy.age = 20; + }).not.toThrow(); + expect(proxy.age).toBe(20); + expect(() => (proxy.firstName = 'Nancy')).toThrow(setExpectedError); + expect(() => proxy.firstName).toThrow(getExpectedError); + }); + + it('returns a json', () => { + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['lastName'] + } + ] + }); + const sym: string = (Symbol('id') as unknown) as string; + const user = { + firstName: 'John', + lastName: 'Wick', + [sym]: 1 + }; + const proxy = policy.generateProxy(user) as Record< + string | symbol, + unknown + >; + const expectedError = new Error( + 'Unauthorize to get firstName property' + ); + + expect(proxy.lastName).toBe('Wick'); + expect(() => (proxy[sym] = 2)).not.toThrow(); + expect(proxy[sym]).toBe(1); + expect(proxy.otherValue).toBe(undefined); + expect(() => proxy.firstName).toThrow(expectedError); + }); + + it('returns undefined', () => { + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['lastName'] + } + ] + }); + const proxy = policy.generateProxy(8); + + expect(proxy).toBe(undefined); + }); + }); + + describe('when pass options', () => { + it('sets propertyMap', () => { + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['getLastName'] + } + ] + }); + const user = { + firstName: 'John', + lastName: 'Wick' + }; + const proxy = policy.generateProxy(user, { + get: { + propertyMap: { + lastName: 'getLastName' + } + }, + set: { + allow: false + } + }) as Record; + const expectedError = new Error( + 'Unauthorize to get firstName property' + ); + + expect(proxy.lastName).toBe('Wick'); + expect(() => proxy.firstName).toThrow(expectedError); + }); + + it('sets propertyMap', () => { + const policy = new ActionBasedPolicy({ + statements: [ + { + action: ['setLastName'] + } + ] + }); + const user = { + firstName: 'John', + lastName: 'Wick' + }; + const proxy = policy.generateProxy(user, { + set: { + propertyMap: { + lastName: 'setLastName' + } + }, + get: { + allow: false + } + }) as Record; + const expectedError = new Error( + 'Unauthorize to set firstName property' + ); + + expect(() => (proxy.lastName = 'Smith')).not.toThrow(); + expect(proxy.lastName).toBe('Smith'); + expect(() => (proxy.firstName = 'Mario')).toThrow(expectedError); + }); + }); + }); }); diff --git a/src/ActionBasedPolicy.ts b/src/ActionBasedPolicy.ts index 9a1a7c3..68b0db6 100644 --- a/src/ActionBasedPolicy.ts +++ b/src/ActionBasedPolicy.ts @@ -2,7 +2,8 @@ import { ActionBasedType, ConditionResolver, Context, - EvaluateActionBasedInterface + EvaluateActionBasedInterface, + ProxyOptions } from './types'; import { ActionBased } from './ActionBasedStatement'; import { Policy } from './Policy'; @@ -64,4 +65,48 @@ export class ActionBasedPolicy extends Policy { }) ); } + + generateProxy( + obj: unknown, + options: ProxyOptions = {} + ): T | undefined { + const { get = {}, set = {} } = options; + const { allow: allowGet = true, propertyMap: propertyMapGet = {} } = get; + const { allow: allowSet = true, propertyMap: propertyMapSet = {} } = set; + const handler = { + ...(allowGet + ? { + get: (target: T, prop: U): any => { + if (prop in target) { + if (typeof prop === 'string') { + const property = propertyMapGet[prop] || prop; + if (this.evaluate({ action: property })) return target[prop]; + throw new Error(`Unauthorize to get ${prop} property`); + } + } + return target[prop]; + } + } + : {}), + ...(allowSet + ? { + set: (target: T, prop: U, value: any): boolean => { + if (typeof prop === 'string') { + const property = propertyMapSet[prop] || prop; + if (this.evaluate({ action: property })) { + target[prop] = value; + return true; + } else throw new Error(`Unauthorize to set ${prop} property`); + } + return true; + } + } + : {}) + }; + + if (obj instanceof Object) { + return new Proxy(obj, handler) as T; + } + return undefined; + } } diff --git a/src/ActionBasedStatement.test.ts b/src/ActionBasedStatement.test.ts index 0a28ff9..3e89cc7 100644 --- a/src/ActionBasedStatement.test.ts +++ b/src/ActionBasedStatement.test.ts @@ -17,18 +17,6 @@ describe('ActionBased Class', () => { ).not.toThrow(); }); - describe('when creating action based statement with no actions', () => { - it('throws a TypeError', () => { - const expectedError = new TypeError( - 'ActionBased statement should have an action or a notAction attribute' - ); - - expect(() => { - new ActionBased({}); - }).toThrow(expectedError); - }); - }); - describe('when creating action based statement with action and notAction attributes', () => { it('throws a TypeError', () => { const expectedError = new TypeError( diff --git a/src/ActionBasedStatement.ts b/src/ActionBasedStatement.ts index 3d7177a..73adb91 100644 --- a/src/ActionBasedStatement.ts +++ b/src/ActionBasedStatement.ts @@ -37,11 +37,6 @@ class ActionBased extends Statement { 'ActionBased statement should have an action or a notAction attribute, no both' ); } - if (!hasAction && !hasNotAction) { - throw new TypeError( - 'ActionBased statement should have an action or a notAction attribute' - ); - } if ('action' in action) { this.action = typeof action.action === 'string' ? [action.action] : action.action; diff --git a/src/IdentityBasedStatement.test.ts b/src/IdentityBasedStatement.test.ts index b5322d3..82c1b13 100644 --- a/src/IdentityBasedStatement.test.ts +++ b/src/IdentityBasedStatement.test.ts @@ -19,34 +19,6 @@ describe('IdentityBased Class', () => { ).not.toThrow(); }); - describe('when creating identity based statement with no actions', () => { - it('throws a TypeError', () => { - const expectedError = new TypeError( - 'IdentityBased statement should have an action or a notAction attribute' - ); - - expect(() => { - new IdentityBased({ - resource: 'secret' - }); - }).toThrow(expectedError); - }); - }); - - describe('when creating identity based statement with no resources', () => { - it('throws a TypeError', () => { - const expectedError = new TypeError( - 'IdentityBased statement should have a resource or a notResource attribute' - ); - - expect(() => { - new IdentityBased({ - action: 'write' - }); - }).toThrow(expectedError); - }); - }); - describe('when creating identity based statement with action and notAction attributes', () => { it('throws a TypeError', () => { const expectedError = new TypeError( @@ -56,7 +28,8 @@ describe('IdentityBased Class', () => { expect(() => { new IdentityBased({ action: ['read', 'write'], - notAction: 'delete' + notAction: 'delete', + resource: 'books' }); }).toThrow(expectedError); }); diff --git a/src/IdentityBasedStatement.ts b/src/IdentityBasedStatement.ts index eae0490..b6eb383 100644 --- a/src/IdentityBasedStatement.ts +++ b/src/IdentityBasedStatement.ts @@ -47,11 +47,6 @@ class IdentityBased extends Statement { 'IdentityBased statement should have an action or a notAction attribute, no both' ); } - if (!hasAction && !hasNotAction) { - throw new TypeError( - 'IdentityBased statement should have an action or a notAction attribute' - ); - } if ('action' in identity) { this.action = typeof identity.action === 'string' @@ -73,11 +68,6 @@ class IdentityBased extends Statement { 'IdentityBased statement should have a resource or a notResource attribute, no both' ); } - if (!hasResource && !hasNotResource) { - throw new TypeError( - 'IdentityBased statement should have a resource or a notResource attribute' - ); - } if ('resource' in identity) { this.resource = typeof identity.resource === 'string' diff --git a/src/ResourceBasedStatement.test.ts b/src/ResourceBasedStatement.test.ts index a1a7d7d..c687632 100644 --- a/src/ResourceBasedStatement.test.ts +++ b/src/ResourceBasedStatement.test.ts @@ -19,20 +19,6 @@ describe('ResourceBased Class', () => { ).not.toThrow(); }); - describe('when creating resource based statement with no actions', () => { - it('throws a TypeError', () => { - const expectedError = new TypeError( - 'ResourceBased statement should have an action or a notAction attribute' - ); - - expect(() => { - new ResourceBased({ - resource: 'secret' - }); - }).toThrow(expectedError); - }); - }); - describe('when creating resource based statement with action and notAction attributes', () => { it('throws a TypeError', () => { const expectedError = new TypeError( diff --git a/src/ResourceBasedStatement.ts b/src/ResourceBasedStatement.ts index 9516c09..5b759f1 100644 --- a/src/ResourceBasedStatement.ts +++ b/src/ResourceBasedStatement.ts @@ -104,11 +104,6 @@ class ResourceBased extends Statement { 'ResourceBased statement should have an action or a notAction attribute, no both' ); } - if (!hasAction && !hasNotAction) { - throw new TypeError( - 'ResourceBased statement should have an action or a notAction attribute' - ); - } if ('action' in identity) { this.action = typeof identity.action === 'string' diff --git a/src/types.ts b/src/types.ts index e430f7a..995a348 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,12 +5,12 @@ interface PrincipalMap { [key: string]: Patterns; } -interface PrincipalBlock { - principal: PrincipalMap | Patterns; +interface OptionalPrincipalBlock { + principal?: PrincipalMap | Patterns; } -interface NotPrincipalBlock { - notPrincipal: PrincipalMap | Patterns; +interface OptionalNotPrincipalBlock { + notPrincipal?: PrincipalMap | Patterns; } interface ActionBlock { @@ -29,6 +29,14 @@ interface NotResourceBlock { notResource: Patterns; } +interface OptionalResourceBlock { + resource?: Patterns; +} + +interface OptionalNotResourceBlock { + notResource?: Patterns; +} + export type ConditionKey = string | number | boolean; export interface Context { @@ -76,10 +84,10 @@ export interface MatchIdentityBasedInterface extends MatchActionBasedInterface { resource: string; } -export interface MatchResourceBasedInterface - extends MatchIdentityBasedInterface { - principal: string; +export interface MatchResourceBasedInterface extends MatchActionBasedInterface { + principal?: string; principalType?: string; + resource?: string; } export interface EvaluateActionBasedInterface { @@ -93,9 +101,10 @@ export interface EvaluateIdentityBasedInterface } export interface EvaluateResourceBasedInterface - extends EvaluateIdentityBasedInterface { - principal: string; + extends EvaluateActionBasedInterface { + principal?: string; principalType?: string; + resource?: string; } export interface MemoizeInterface extends Function { @@ -109,22 +118,28 @@ type IdentityBasedType = StatementInterface & (ResourceBlock | NotResourceBlock); type ResourceBasedType = StatementInterface & - (PrincipalBlock | NotPrincipalBlock) & + (OptionalPrincipalBlock | OptionalNotPrincipalBlock) & (ActionBlock | NotActionBlock) & - (ResourceBlock | NotResourceBlock); + (OptionalResourceBlock | OptionalNotResourceBlock); + +interface ProxyOptions { + get?: { + allow?: boolean; + propertyMap?: Record; + }; + set?: { + allow?: boolean; + propertyMap?: Record; + }; +} export { ActionBasedType, - ActionBlock, IdentityBasedType, Patterns, - PrincipalBlock, PrincipalMap, ResourceBasedType, - ResourceBlock, - NotActionBlock, - NotPrincipalBlock, - NotResourceBlock + ProxyOptions }; /* From f75f1cd603f5f7c1bb92c9741400e7f8ba5ad903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 25 Oct 2020 22:43:14 -0500 Subject: [PATCH 12/13] docs(en): updating documentation --- README.md | 2 +- translations/README.zh-CN.md | 521 ----------------------------------- www/src/docs/en.md | 353 +++++++++++++++--------- www/src/docs/zh-CN.md | 4 +- 4 files changed, 228 insertions(+), 652 deletions(-) delete mode 100644 translations/README.zh-CN.md diff --git a/README.md b/README.md index ee9fef9..b1c5ebb 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Supports these glob features: Please click on the language that you prefer - Translations: - - [Chinese docs](https://roggervalf.github.io/iam-policies/zh-CN/) by [@mickymao1110](https://github.com/mickymao1110) + - [Chinese docs](https://roggervalf.github.io/iam-policies/zh-CN/) by [@mickymao1110](https://github.com/mickymao1110) (< v3.5.0 ) - [English docs](https://roggervalf.github.io/iam-policies/en/) by [@roggervalf](https://github.com/roggervalf) ## Article diff --git a/translations/README.zh-CN.md b/translations/README.zh-CN.md deleted file mode 100644 index 6acb5b7..0000000 --- a/translations/README.zh-CN.md +++ /dev/null @@ -1,521 +0,0 @@ -# iam-policies - -> - -[![NPM](https://img.shields.io/npm/v/iam-policies.svg)](https://www.npmjs.com/package/iam-policies) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) - -## 介绍 - -自定义 IAM 政策来提供对资源访问的控制,并且可以设置可选的上下文几条件 - -拒绝政策高于认许政策 - -基于 [@ddt/iam](https://www.npmjs.com/package/@ddt/iam) 和 [AWS Reference Policies ](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html). - -## 安装 - -```bash -npm install --save iam-policies -``` - -或者 - -```bash -yarn add iam-policies -``` - -## 功能 - -提供以下功能: - -- 创建政策 ([IdentityBasedPolicy and ResourceBasedPolicy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#access_policy-types)) -- 权限验证 - -## 使用 - -### 例子 - -首先需要得到政策类: - -```js -const { IdentityBasedPolicy, ResourceBasedPolicy } = require('iam-policies'); -``` - -#### 设置允许属性 - -```js -const allowExample = new IdentityBasedPolicy([ - { - effect: 'allow', // 可选的, 默认值'allow' - resource: ['secrets:${user.id}:*'], // 嵌入值 - action: ['read', 'write'] - }, - { - resource: ['bd:company:*'], - action: 'create' - } -]); - -const contextForAllowExample = { user: { id: 456 } }; - -allowExample.evaluate({ - action: 'read', - resource: 'secrets:456:ultraSecret', - context: contextForAllowExample -}); // true -allowExample.evaluate({ - action: 'create', - resource: 'secrets:456:ultraSecret', - context: contextForAllowExample -}); // false -allowExample.evaluate({ - action: 'create', - resource: 'bd:company:account', - context: contextForAllowExample -}); // true -allowExample.evaluate({ - action: 'read', - resource: 'bd:company:account', - context: contextForAllowExample -}); // false -``` - -#### 设置拒绝属性 - -```js -const denyExample = new IdentityBasedPolicy([ - { - resource: ['secrets:${user.bestFriends}:*'], - action: 'read' - }, - { - effect: 'deny', - resource: 'secrets:123:*', - action: 'read' - } -]); - -const contextForDenyExample = { user: { bestFriends: [123, 563, 1211] } }; - -denyExample.evaluate({ - action: 'read', - resource: 'secrets:563:super-secret', - context: contextForDenyExample -}); // true -denyExample.evaluate({ - action: 'read', - resource: 'secrets:123:super-secret', - context: contextForDenyExample -}); // false -``` - -#### 排除的行为属性 - -```js -const notActionExample = new IdentityBasedPolicy([ - { - resource: 'bd:company:*', - notAction: 'update' - } -]); - -notActionExample.evaluate({ - action: 'delete', - resource: 'bd:company:account' -}); // true -notActionExample.evaluate({ - action: 'update', - resource: 'bd:company:account' -}); // false -``` - -#### 排除的资源属性 - -```js -const notResourceExample = new IdentityBasedPolicy([ - { - notResource: ['bd:roles:*'], - action: 'update' - } -]); - -notResourceExample.evaluate({ - action: 'update', - resource: 'photos' -}); // true -notResourceExample.evaluate({ - action: 'update', - resource: 'bd:roles:admin' -}); // false -``` - -#### 允许一切 - -```js -const adminExample = new IdentityBasedPolicy([ - { - resource: '*', - action: '*' - } -]); - -adminExample.evaluate({ - action: 'read', - resource: 'someResource' -}); // true -adminExample.evaluate({ - action: 'write', - resource: 'otherResource' -}); // true -``` - -#### 条件属性 - -```js -const conditions = { - greaterThan: function(data, expected) { - return data > expected; - } -}; - -const conditionExample = new IdentityBasedPolicy( - [ - { - resource: 'secrets:*', - action: ['read', 'write'], - condition: { - greaterThan: { - 'user.age': 18 - } - } - } - ], - conditions -); - -conditionExample.evaluate({ - action: 'read', - resource: 'secrets:code', - context: { user: { age: 19 } } -}); // true -conditionExample.evaluate({ - action: 'read', - resource: 'secrets:admin:super-secret', - context: { - user: { age: 18 } - } -}); // false -``` - -#### 主要的属性 - -```js -const principalExample = new ResourceBasedPolicy([ - { - principal: '1', - effect: 'allow', - resource: ['secrets:user:*'], - action: ['read', 'write'] - }, - { - principal: { id: '2' }, - resource: 'bd:company:*', - notAction: 'update' - } -]); - -principalExample.evaluate({ - principal: '1', - action: 'read', - resource: 'secrets:user:name' -}); // true -principalExample.evaluate({ - principal: '2', - action: 'read', - resource: 'secrets:user:super-secret' -}); // false -principalExample.evaluate({ - principal: '2', - action: 'read', - resource: 'bd:company:name', - principalType: 'id' -}); // true -principalExample.evaluate({ - principal: '2', - action: 'update', - resource: 'bd:company:name', - principalType: 'id' -}); // false -``` - -#### 非主要属性 - -```js -const notPrincipalExample = new ResourceBasedPolicy([ - { - notPrincipal: ['1', '2'], - resource: ['secrets:bd:*'], - action: 'read' - }, - { - notPrincipal: { id: '3' }, - resource: 'secrets:admin:*', - action: 'read' - } -]); - -notPrincipalExample.evaluate({ - principal: '3', - action: 'read', - resource: 'secrets:bd:tables' -}); // true -notPrincipalExample.evaluate({ - principal: '1', - action: 'read', - resource: 'secrets:bd:tables' -}); // false -notPrincipalExample.evaluate({ - principal: '1', - action: 'read', - resource: 'secrets:admin:friends', - principalType: 'id' -}); // true -notPrincipalExample.evaluate({ - principal: '3', - action: 'read', - resource: 'secrets:admin:friends', - principalType: 'id' -}); // false -``` - -#### 使用 `can` 和 `cannot` - -```js -const canAndCannotStatements = [ - { - effect: 'allow', // 可选的, 默认值'allow - resource: ['website:${division.companyId}:${division.countryId}:*/*'], - action: ['create', 'update', 'delete'] - }, - { - effect: 'deny', - resource: ['website:${division.companyId}:${division.countryId}:city/lima'], - action: 'delete' - } -]; - -const inclusivePolicy = new IdentityBasedPolicy(canAndCannotStatements); - -const contextCanAndCannot = { - division: { - companyId: 123, - countryId: 456 - } -}; - -const canAndCannotDeniedArgument = { - action: 'delete', - resource: 'website:123:456:city/lima', - context: contextCanAndCannot -}; - -inclusivePolicy.evaluate(canAndCannotDeniedArgument); // false -// 到这里, 我们还不确定参数是被拒绝还是不存在 - -inclusivePolicy.can(canAndCannotDeniedArgument); // true -// 是一个存在的允许政策, 所以应该是被拒绝了, 是吧? - -inclusivePolicy.cannot(canAndCannotDeniedArgument); // true -// 事实证明, 我是对的。 - -const canAndCannotNotPresentArgument = { - action: 'read', - resource: 'website:123:456:}city/lima', - context: contextCanAndCannot -}; - -inclusivePolicy.evaluate(canAndCannotNotPresentArgument); // false -// 在这里用户没有访问权限, 但是为什么呐, 我们看看 - -inclusivePolicy.can(canAndCannotNotPresentArgument); // false -// 这不是一个存在的允许政策, 但是是被拒绝了吗? - -inclusivePolicy.cannot(canAndCannotNotPresentArgument); // false -// 不是的, 它不在那。 -``` - -## IdentityBasedPolicy 类 - -附加托管和内联政策到个体资源(用户,群, 或者规则)。 基于个体的政策授予权限给个体。 - -```js -const { IdentityBasedPolicy } = require('iam-policies'); - -const identityBasedPolicy = new IdentityBasedPolicy( - Statement, - conditionResolver -); -``` - -### Properties - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| -------------------------------------------------------- | ---------------------------------------------------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Statement` | object[] | undefined | `true` | **_Statement_** 是政策对象中主要的元素,Statement 元素可以包含单个 statement 或者是包含一组 statement. | -| `Statement[].effect` | string | allow | `false` | **_effect_** 元素指明是被允许还是拒绝。 有效的值是 `allow` 和 `deny`. | -| `Statement[].action` | string or string[] | undefined | `false` | **_action_**元素描述行为, Statements 中必须包含`action` 或者 `notAction` 元素. | -| `Statement[].notAction` | string or string[] | undefined | `false` | **_notAction_** 元素是一个高级的政策元素, 它使得 statement 取匹配所有除了列出的所有行为。Statements 必须包含`action` 或者 `notAction` 元素. 通过使用 `notAction`,可以只列出少量的需要匹配的行为, 而不需要列出一个超长的列表。 当使用 `notAction` 的时候, 你需要注意,如果指明允许, 那么所有行为没有在这个元素中列出的将被允许,如果指明拒绝,那么所有不在这个属性里的行为将被拒绝。 | -| `Statement[].resource` | string or string[] | undefined | `true` | **_resource_** 元素指明 statement 包含的对象。Statements 必须包含 `resource` 或者 `notResource` 元素. | -| `Statement[].notResource` | string or string[] | undefined | `true` | **_notResource_** 元素是一个高级政策元素, 它将匹配除了列表指定的所有的资源。Statements 必须包含 `resource` 或 `notResource` 元素。 通过使用 `notResource` 你可以制定相对短的不匹配资源列表。 | -| `Statement[].condition` | object | undefined | `false` | **_condition_** 元素 (或者条件块) 可以让你指定当政策生效时候的条件。 在`condition` 元素里边, 你可以通过条件运算符来创建表达式(equal, less than...) 来匹配在政策中的 keys,values 和在请求中的 keys, values. | -| `Statement[].condition["conditionType"]` | object | undefined | `false` | 对于特定条件的 conditionResolver 元素, **_conditionType_** 应该被替换成一个自定义的字符串 | -| `Statement[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | 对于自定义的 context,**_conditionKey_** 应该是一个自定义的字符串路径。 备注: 属性必须以`.`分隔。 | - -### 方法 - -#### identityBasedPolicy.evaluate({action, resource, context}) - -_public_: 验证针对特定资源的行为是被允许 (`true`) 还是拒绝 (`false`). - -##### Params - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| ---------- | ------ | --------- | ------- | ---------------------- | -| `action` | string | undefined | `true` | 描述行为。 | -| `resource` | string | undefined | `true` | 描述资源。 | -| `context` | object | undefined | `false` | 描述资源中嵌入的属性。 | - -#### identityBasedPolicy.can({action, resource, context}) - -_public_: 验证针对特定资源的行为是被允许 (`true`) 还是拒绝 (`false`). - -##### Params - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| ---------- | ------ | --------- | ------- | ---------------------- | -| `action` | string | undefined | `true` | 描述行为。 | -| `resource` | string | undefined | `true` | 描述资源。 | -| `context` | object | undefined | `false` | 描述资源中嵌入的属性。 | - -#### identityBasedPolicy.cannot({action, resource, context}) - -_public_: 验证针对特定资源的行为是被拒绝 (`true`) 还是不存在 (`false`)。 - -##### 参数 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| ---------- | ------ | --------- | ------- | ---------------------- | -| `action` | string | undefined | `true` | 描述行为。 | -| `resource` | string | undefined | `true` | 描述资源。 | -| `context` | object | undefined | `false` | 描述资源中嵌入的属性。 | - -## ResourceBasedPolicy 类 - -附加内联政策到个体资源。 Resource-based 政策授权给 principal. - -```js -const { ResourceBasedPolicy } = require('iam-policies'); - -const resourceBasedPolicy = new ResourceBasedPolicy( - Statement, - conditionResolver -); -``` - -### 属性 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| -------------------------------------------------------- | ---------------------------------------------------------------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Statement` | object[] | undefined | `true` | **_Statement_** 是政策对象中主要的元素,Statement 元素可以包含单个 statement 或者是包含一组 statement. | -| `Statement[].effect` | string | allow | `false` | **_effect_** 元素指明是被允许还是拒绝。 有效的值是 `allow` 和 `deny`. | -| `Statement[].principal` | string or string[] | undefined | `false` | **_principal_**元素描述针对资源是允许或是拒绝行为, Statements 中必须包含`principal` 或者 `notPrincipal` 元素. | -| `Statement[].notPrincipal` | string or string[] | undefined | `false` | **_notPrincipal_** 元素指定 principal 不被允许或拒绝访问. `notPrincipal` 元素可以让你指定例外。使用这个元素, 可以拒绝所有 principal, 除了指定在`notPrincipal` 元素里边的. | -| `Statement[].action` | string or string[] | undefined | `false` | **_action_**元素描述行为, Statements 中必须包含`action` 或者 `notAction` 元素. | -| `Statement[].notAction` | string or string[] | undefined | `false` | **_notAction_** 元素是一个高级的政策元素, 它使得 statement 取匹配所有除了列出的所有行为。Statements 必须包含`action` 或者 `notAction` 元素. 通过使用 `notAction`,可以只列出少量的需要匹配的行为, 而不需要列出一个超长的列表。 当使用 `notAction` 的时候, 你需要注意,如果指明允许, 那么所有行为没有在这个元素中列出的将被允许,如果指明拒绝,那么所有不在这个属性里的行为将被拒绝。 | -| `Statement[].resource` | string or string[] | undefined | `true` | **_resource_** 元素指明 statement 包含的对象。Statements 必须包含 `resource` 或者 `notResource` 元素. | -| `Statement[].notResource` | string or string[] | undefined | `true` | **_notResource_** 元素是一个高级政策元素, 它将匹配除了列表指定的所有的资源。Statements 必须包含 `resource` 或 `notResource` 元素。 通过使用 `notResource` 你可以制定相对短的不匹配资源列表。 | -| `Statement[].condition` | object | undefined | `false` | **_condition_** 元素 (或者条件块) 可以让你指定当政策生效时候的条件。 在`condition` 元素里边, 你可以通过条件运算符来创建表达式(equal, less than...) 来匹配在政策中的 keys,values 和在请求中的 keys, values. | -| `Statement[].condition["conditionType"]` | object | undefined | `false` | 对于特定条件的 conditionResolver 元素, **_conditionType_** 应该被替换成一个自定义的字符串 | -| `Statement[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | 对于自定义的 context,**_conditionKey_** 应该是一个自定义的字符串路径。 备注: 属性必须以`.`分隔。 | - -### 方法 - -#### resourceBasedPolicy.evaluate({principal, action, resource, context, principalType}) - -_public_: 验证针对特定资源的行为是被允许 (`true`) 还是拒绝 (`false`). - -##### 参数 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| --------------- | ------ | --------- | ------- | ----------------------------------------------------------------------------- | -| `principal` | string | undefined | `true` | 描述 principal。 | -| `action` | string | undefined | `true` | 描述行为。 | -| `resource` | string | undefined | `true` | 描述资源。 | -| `context` | object | undefined | `false` | 描述资源中嵌入的属性。 | -| `principalType` | string | undefined | `true` | 描述 principal 类型 (principal 的属性, 如果 statement 包含 principal 对象)。 | - -#### resourceBasedPolicy.can({principal, action, resource, context, principalType}) - -_public_: 验证针对特定资源的行为是被允许 (`true`) 还是不存在 (`false`)。 - -##### 参数 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| --------------- | ------ | --------- | ------- | ----------------------------------------------------------------------------- | -| `principal` | string | undefined | `true` | 描述 principal。 | -| `action` | string | undefined | `true` | 描述行为。 | -| `resource` | string | undefined | `true` | 描述资源。 | -| `context` | object | undefined | `false` | 描述资源中嵌入的属性。 | -| `principalType` | string | undefined | `true` | 描述 principal 类型 (principal 的属性, 如果 statement 包含 principal 对象)。 | - -#### resourceBasedPolicy.cannot({principal, action, resource, context, principalType}) - -_public_: 验证针对特定资源的行为是被拒绝 (`true`) 还是不存在 (`false`)。 - -##### 参数 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| --------------- | ------ | --------- | ------- | ----------------------------------------------------------------------------- | -| `principal` | string | undefined | `true` | 描述 principal。 | -| `action` | string | undefined | `true` | 描述行为。 | -| `resource` | string | undefined | `true` | 描述资源。 | -| `context` | object | undefined | `false` | 描述资源中嵌入的属性。 | -| `principalType` | string | undefined | `true` | 描述 principal 类型 (principal 的属性, 如果 statement 包含 principal 对象)。 | - -## getValueFromPath(data, path) Function - -通过路径获取对象。 - -```js -const { getValueFromPath } = require('iam-policies'); - -const value = getValueFromPath(data, path); -``` - -### 参数 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| ------ | ------ | --------- | ------ | --------------------------------- | -| `data` | object | undefined | `true` | 上下文。 | -| `path` | string | undefined | `true` | 数据中的路径。属性名通过`.`分隔。 | - -## applyContext(str, context) Function - -获取嵌入到上下文的字符串 - -```js -const { applyContext } = require('iam-policies'); - -const embeddedStr = applyContext(str, context); -``` - -### 参数 - -| 名字 | 类型 | 默认值 | 必须的 | 描述 | -| --------- | ------ | --------- | ------- | ---------------------------------------- | -| `str` | string | undefined | `true` | 它可能包含嵌入的路径, 通过使用 (`${}`). | -| `context` | object | undefined | `false` | 指定上下文, 它应该被嵌入到 `str` 里边. | - -## License - -MIT © [roggervalf](https://github.com/roggervalf) diff --git a/www/src/docs/en.md b/www/src/docs/en.md index cb13348..8aa4e74 100644 --- a/www/src/docs/en.md +++ b/www/src/docs/en.md @@ -5,7 +5,7 @@ date: "14-08-2020" # iam-policies -> [![NPM](https://img.shields.io/npm/v/iam-policies.svg)](https://www.npmjs.com/package/iam-policies) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![Build Status](https://travis-ci.com/roggervalf/iam-policies.svg?branch=master)](https://travis-ci.com/github/roggervalf/iam-policies) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) +> [![NPM](https://img.shields.io/npm/v/iam-policies.svg)](https://www.npmjs.com/package/iam-policies) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![Build Status](https://travis-ci.com/roggervalf/iam-policies.svg?branch=master)](https://travis-ci.com/github/roggervalf/iam-policies) [![NPM downloads](https://img.shields.io/npm/dm/iam-policies)](https://www.npmjs.com/package/iam-policies) [![Coverage Status](https://coveralls.io/repos/github/roggervalf/iam-policies/badge.svg?branch=master)](https://coveralls.io/github/roggervalf/iam-policies?branch=master) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## About @@ -55,6 +55,7 @@ Supports these glob features: - Policies creation ([IdentityBasedPolicy and ResourceBasedPolicy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#access_policy-types)) - Permission verifications +- Generate Proxies ## Usage @@ -73,17 +74,19 @@ const { #### Effect property allow ```js -const allowExample = new IdentityBasedPolicy([ - { - effect: "allow", // optional, defaults to allow - resource: ["secrets:${user.id}:*"], // embedded value by context - action: ["read", "write"] - }, - { - resource: ["bd:company:*"], - action: "create" - } -]) +const allowExample = new IdentityBasedPolicy({ + statements: [ + { + effect: "allow", // optional, defaults to allow + resource: ["secrets:${user.id}:*"], // embedded value by context + action: ["read", "write"] + }, + { + resource: ["bd:company:*"], + action: "create" + } + ] +}) const contextForAllowExample = { user: { id: 456 } } @@ -112,17 +115,19 @@ allowExample.evaluate({ #### Effect property deny ```js -const denyExample = new IdentityBasedPolicy([ - { - resource: ["secrets:${user.bestFriends}:*"], - action: "read" - }, - { - effect: "deny", - resource: "secrets:123:*", - action: "read" - } -]) +const denyExample = new IdentityBasedPolicy({ + statements: [ + { + resource: ["secrets:${user.bestFriends}:*"], + action: "read" + }, + { + effect: "deny", + resource: "secrets:123:*", + action: "read" + } + ] +}) const contextForDenyExample = { user: { bestFriends: [123, 563, 1211] } } @@ -141,12 +146,14 @@ denyExample.evaluate({ #### Not Action property ```js -const notActionExample = new IdentityBasedPolicy([ - { - resource: "bd:company:*", - notAction: "update" - } -]) +const notActionExample = new IdentityBasedPolicy({ + statements: [ + { + resource: "bd:company:*", + notAction: "update" + } + ] +}) notActionExample.evaluate({ action: "delete", @@ -161,12 +168,14 @@ notActionExample.evaluate({ #### Not Resource property ```js -const notResourceExample = new IdentityBasedPolicy([ - { - notResource: ["bd:roles:*"], - action: "update" - } -]) +const notResourceExample = new IdentityBasedPolicy({ + statements: [ + { + notResource: ["bd:roles:*"], + action: "update" + } + ] +}) notResourceExample.evaluate({ action: "update", @@ -181,12 +190,14 @@ notResourceExample.evaluate({ #### Allow everything ```js -const adminExample = new IdentityBasedPolicy([ - { - resource: "*", - action: "*" - } -]) +const adminExample = new IdentityBasedPolicy({ + statements: [ + { + resource: "*", + action: "*" + } + ] +}) adminExample.evaluate({ action: "read", @@ -201,14 +212,14 @@ adminExample.evaluate({ #### Conditions property ```js -const conditions = { - greaterThan: function(data, expected) { +const conditionResolver = { + greaterThan: function (data, expected) { return data > expected } } -const conditionExample = new IdentityBasedPolicy( - [ +const conditionExample = new IdentityBasedPolicy({ + statements: [ { resource: "secrets:*", action: ["read", "write"], @@ -219,8 +230,8 @@ const conditionExample = new IdentityBasedPolicy( } } ], - conditions -) + conditionResolver +}) conditionExample.evaluate({ action: "read", @@ -239,19 +250,21 @@ conditionExample.evaluate({ #### Principal property ```js -const principalExample = new ResourceBasedPolicy([ - { - principal: "1", - effect: "allow", - resource: ["secrets:user:*"], - action: ["read", "write"] - }, - { - principal: { id: "2" }, - resource: "bd:company:*", - notAction: "update" - } -]) +const principalExample = new ResourceBasedPolicy({ + statements: [ + { + principal: "1", + effect: "allow", + resource: ["secrets:user:*"], + action: ["read", "write"] + }, + { + principal: { id: "2" }, + resource: "bd:company:*", + notAction: "update" + } + ] +}) principalExample.evaluate({ principal: "1", @@ -280,18 +293,20 @@ principalExample.evaluate({ #### Not Principal property ```js -const notPrincipalExample = new ResourceBasedPolicy([ - { - notPrincipal: ["1", "2"], - resource: ["secrets:bd:*"], - action: "read" - }, - { - notPrincipal: { id: "3" }, - resource: "secrets:admin:*", - action: "read" - } -]) +const notPrincipalExample = new ResourceBasedPolicy({ + statements: [ + { + notPrincipal: ["1", "2"], + resource: ["secrets:bd:*"], + action: "read" + }, + { + notPrincipal: { id: "3" }, + resource: "secrets:admin:*", + action: "read" + } + ] +}) notPrincipalExample.evaluate({ principal: "3", @@ -333,7 +348,9 @@ const canAndCannotStatements = [ } ] -const inclusivePolicy = new IdentityBasedPolicy(canAndCannotStatements) +const inclusivePolicy = new IdentityBasedPolicy({ + statements: canAndCannotStatements +}) const contextCanAndCannot = { division: { @@ -380,26 +397,50 @@ Attach managed and simple inline policies to grant actions only. ```js const { ActionBasedPolicy } = require("iam-policies") -const actionBasedPolicy = new ActionBasedPolicy(Statement, conditionResolver) +const actionBasedPolicy = new ActionBasedPolicy({ + statements, + conditionResolver, + context +}) ``` ### Properties -| Name | Type | Default | Required | Description | -| -------------------------------------------------------- | ---------------------------------------------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Statement` | object[] | undefined | `true` | The **_Statement_** element is the main element for a policy. The Statement element can contain a single statement or an array of individual statements. | -| `Statement[].sid` | string | _uuid_ | `false` | The **_sid_** element should be a unique identifier. It is automatically generated with a uuid in case it is undefined. | -| `Statement[].effect` | string | allow | `false` | The **_effect_** element specifies whether the statement results in an allow or an explicit deny. Valid values for Effect are `allow` and `deny`. | -| `Statement[].action` | string or string[] | undefined | `false` | The **_action_** element describes the specific action or actions that will be allowed or denied. Statements must include either an `action` or `notAction` element. | -| `Statement[].notAction` | string or string[] | undefined | `false` | The **_notAction_** element is an advanced policy element that explicitly matches everything except the specified list of actions. Statements must include either an `action` or `notAction` element. Using `notAction` can result in a shorter policy by listing only a few actions that should not match, rather than including a long list of actions that will match. When using `notAction`, you should keep in mind that actions specified in this element are the only actions in that are limited. This, in turn, means that all of the applicable actions or services that are not listed are allowed if you use the Allow effect. In addition, such unlisted actions or services are denied if you use the `deny` effect. When you use `notAction` with the `resource` element, you provide scope for the policy. | | `Statement[].condition` | object | undefined | `false` | The **_condition_** element (or Condition block) lets you specify conditions for when a policy is in effect. In the `condition` element, you build expressions in which you use condition operators (equal, less than, etc.) to match the condition keys and values in the policy against keys and values in the request context. | -| `Statement[].condition["conditionType"]` | object | undefined | `false` | The **_conditionType_** name should be replaced with a custom string attribute for a specific condition that should be match with one conditionResolver element. | -| `Statement[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | The **_conditionKey_** should be a custom string path attribute for a specific context attribute. Note: attributes must be separated but dots (`.`). | +| Name | Type | Default | Required | Description | +| --------------------------------------------------------- | ---------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `statements` | object[] | undefined | `true` | The **_statements_** is the main element for a policy. The statements element can contain an array of individual statements. | +| `statements[].sid` | string | _uuid_ | `false` | The **_sid_** element should be a unique identifier. It is automatically generated with a uuid in case it is undefined. | +| `statements[].effect` | string | allow | `false` | The **_effect_** element specifies whether the statement results in an allow or an explicit deny. Valid values for Effect are `allow` and `deny`. | +| `statements[].action` | string or string[] | undefined | `false` | The **_action_** element describes the specific action or actions that will be allowed or denied. Each statement must include either an `action` or `notAction` element. | +| `statements[].notAction` | string or string[] | undefined | `false` | The **_notAction_** element is an advanced policy element that explicitly matches everything except the specified list of actions. Each statement must include either an `action` or `notAction` element. Using `notAction` can result in a shorter policy by listing only a few actions that should not match, rather than including a long list of actions that will match. When using `notAction`, you should keep in mind that actions specified in this element are the only actions in that are limited. This, in turn, means that all of the applicable actions or services that are not listed are allowed if you use the Allow effect. In addition, such unlisted actions or services are denied if you use the `deny` effect. When you use `notAction` with the `resource` element, you provide scope for the policy. | +| `statements[].condition` | object | undefined | `false` | The **_condition_** element (or Condition block) lets you specify conditions for when a policy is in effect. In the `condition` element, you build expressions in which you use condition operators (equal, less than, etc.) to match the condition keys and values in the policy against keys and values in the request context. | +| `statements[].condition["conditionType"]` | object | undefined | `false` | The **_conditionType_** name should be replaced with a custom string attribute for a specific condition that should be match with one conditionResolver element. | +| `statements[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | The **_conditionKey_** should be a custom string path attribute for a specific context attribute. Note: attributes must be separated but dots (`.`). | +| `conditionResolver` | object | undefined | `false` | The **_conditionResolver_** should contain a function in each attribute to resolve an specific embedded condition in our statements. | +| `conditionResolver["conditionKey"]` | function | undefined | `false` | The **_conditionKey_** should match with a function name that will be used as a resolver in condition evaluation. There are 2 parameters for this function: **data** (first parameter) that will be evaluated with **expected** (second parameter), returning a **true** or **false**. | +| `context` | object | undefined | `false` | The **_context_** has those properties that will be embedded in our statements. | ### Methods #### actionBasedPolicy.getStatements() -_public_: Returns `Statement[]` (statements array). +_public_: Returns `statements[]` (statements array). + +#### actionBasedPolicy.getContext() + +_public_: Returns `context` object. + +#### actionBasedPolicy.setContext(context) + +_public_: Sets `context` object. + +#### actionBasedPolicy.getConditionResolver() + +_public_: Returns `conditionResolver` object. + +#### actionBasedPolicy.setConditionResolver(conditionResolver) + +_public_: Sets `conditionResolver` object. #### actionBasedPolicy.evaluate({action, context}) @@ -434,6 +475,23 @@ _public_: Verify if action for specific resource is denied (`true`) or not prese | `action` | string | undefined | `true` | It represents the action you are asking. | | `context` | object | undefined | `false` | It represents the properties that will be embedded into your actions. | +#### actionBasedPolicy.generateProxy(object, options) + +_public_: Generate a Proxy for the object param. + +##### Params + +| Name | Type | Default | Required | Description | +| ------------------------- | ------- | ------------------------------------------ | -------- | ---------------------------------------------------------------------------------- | +| `object` | object | undefined | `true` | It's the object that is going to be wrapped with a Proxy. | +| `options` | object | { get:{ allow:true }, set:{ allow:true } } | `false` | It should contains attributes to modify the default behavior. | +| `options.get` | object | { allow:true } | `false` | It should contain get function handler options for the Proxy instance. | +| `options.get.allow` | boolean | true | `false` | It should allow to use the get function handler. | +| `options.get.propertyMap` | object | {} | `false` | It should serve as a mapping for different property names in get function handler. | +| `options.set` | object | { allow:true } | `false` | It should contain set function handler options for the Proxy instance. | +| `options.set.allow` | boolean | true | `false` | It should allow to use the set function handler. | +| `options.set.propertyMap` | object | {} | `false` | It should serve as a mapping for different property names in set function handler. | + ## IdentityBasedPolicy Class Attach managed and inline policies to identities (users, groups to which users belong, or roles). Identity-based policies grant permissions to an identity. @@ -441,32 +499,51 @@ Attach managed and inline policies to identities (users, groups to which users b ```js const { IdentityBasedPolicy } = require("iam-policies") -const identityBasedPolicy = new IdentityBasedPolicy( - Statement, +const identityBasedPolicy = new IdentityBasedPolicy({ + statements, conditionResolver -) +}) ``` ### Properties -| Name | Type | Default | Required | Description | -| -------------------------------------------------------- | ---------------------------------------------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Statement` | object[] | undefined | `true` | The **_Statement_** element is the main element for a policy. The Statement element can contain a single statement or an array of individual statements. | -| `Statement[].sid` | string | _uuid_ | `false` | The **_sid_** element should be a unique identifier. It is automatically generated with a uuid in case it is undefined. | -| `Statement[].effect` | string | allow | `false` | The **_effect_** element specifies whether the statement results in an allow or an explicit deny. Valid values for Effect are `allow` and `deny`. | -| `Statement[].action` | string or string[] | undefined | `false` | The **_action_** element describes the specific action or actions that will be allowed or denied. Statements must include either an `action` or `notAction` element. | -| `Statement[].notAction` | string or string[] | undefined | `false` | The **_notAction_** element is an advanced policy element that explicitly matches everything except the specified list of actions. Statements must include either an `action` or `notAction` element. Using `notAction` can result in a shorter policy by listing only a few actions that should not match, rather than including a long list of actions that will match. When using `notAction`, you should keep in mind that actions specified in this element are the only actions in that are limited. This, in turn, means that all of the applicable actions or services that are not listed are allowed if you use the Allow effect. In addition, such unlisted actions or services are denied if you use the `deny` effect. When you use `notAction` with the `resource` element, you provide scope for the policy. | -| `Statement[].resource` | string or string[] | undefined | `true` | The **_resource_** element specifies the object or objects that the statement covers. Statements must include either a `resource` or a `notResource` element. | -| `Statement[].notResource` | string or string[] | undefined | `true` | The **_notResource_** element is an advanced policy element that explicitly matches every resource except those specified. Statements must include either an `resource` or `notResource` element. Using `notResource` can result in a shorter policy by listing only a few resources that should not match, rather than including a long list of resources that will match. | -| `Statement[].condition` | object | undefined | `false` | The **_condition_** element (or Condition block) lets you specify conditions for when a policy is in effect. In the `condition` element, you build expressions in which you use condition operators (equal, less than, etc.) to match the condition keys and values in the policy against keys and values in the request context. | -| `Statement[].condition["conditionType"]` | object | undefined | `false` | The **_conditionType_** name should be replaced with a custom string attribute for a specific condition that should be match with one conditionResolver element. | -| `Statement[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | The **_conditionKey_** should be a custom string path attribute for a specific context attribute. Note: attributes must be separated but dots (`.`). | +| Name | Type | Default | Required | Description | +| --------------------------------------------------------- | ---------------------------------------------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `statements` | object[] | undefined | `true` | The **_statements_** element is the main element for a policy. The statements element can contain an array of individual statements. | +| `statements[].sid` | string | _uuid_ | `false` | The **_sid_** element should be a unique identifier. It is automatically generated with a uuid in case it is undefined. | +| `statements[].effect` | string | allow | `false` | The **_effect_** element specifies whether the statement results in an allow or an explicit deny. Valid values for Effect are `allow` and `deny`. | +| `statements[].action` | string or string[] | undefined | `false` | The **_action_** element describes the specific action or actions that will be allowed or denied. Statements must include either an `action` or `notAction` element. | +| `statements[].notAction` | string or string[] | undefined | `false` | The **_notAction_** element is an advanced policy element that explicitly matches everything except the specified list of actions. Statements must include either an `action` or `notAction` element. Using `notAction` can result in a shorter policy by listing only a few actions that should not match, rather than including a long list of actions that will match. When using `notAction`, you should keep in mind that actions specified in this element are the only actions in that are limited. This, in turn, means that all of the applicable actions or services that are not listed are allowed if you use the Allow effect. In addition, such unlisted actions or services are denied if you use the `deny` effect. When you use `notAction` with the `resource` element, you provide scope for the policy. | +| `statements[].resource` | string or string[] | undefined | `true` | The **_resource_** element specifies the object or objects that the statement covers. Each statement must include either a `resource` or a `notResource` element. | +| `statements[].notResource` | string or string[] | undefined | `true` | The **_notResource_** element is an advanced policy element that explicitly matches every resource except those specified. Each statement must include either an `resource` or `notResource` element. Using `notResource` can result in a shorter policy by listing only a few resources that should not match, rather than including a long list of resources that will match. | +| `statements[].condition` | object | undefined | `false` | The **_condition_** element (or Condition block) lets you specify conditions for when a policy is in effect. In the `condition` element, you build expressions in which you use condition operators (equal, less than, etc.) to match the condition keys and values in the policy against keys and values in the request context. | +| `statements[].condition["conditionType"]` | object | undefined | `false` | The **_conditionType_** name should be replaced with a custom string attribute for a specific condition that should be match with one conditionResolver element. | +| `statements[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | The **_conditionKey_** should be a custom string path attribute for a specific context attribute. Note: attributes must be separated but dots (`.`). | +| `conditionResolver` | object | undefined | `false` | The **_conditionResolver_** should contain a function in each attribute to resolve an specific embedded condition in our statements. | +| `conditionResolver["conditionKey"]` | function | undefined | `false` | The **_conditionKey_** should match with a function name that will be used as a resolver in condition evaluation. There are 2 parameters for this function: **data** (first parameter) that will be evaluated with **expected** (second parameter), returning a **true** or **false**. | +| `context` | object | undefined | `false` | The **_context_** has those properties that will be embedded in our statements. | ### Methods #### identityBasedPolicy.getStatements() -_public_: Returns `Statement[]` (statements array). +_public_: Returns `statements[]` (statements array). + +#### identityBasedPolicy.getContext() + +_public_: Returns `context` object. + +#### identityBasedPolicy.setContext(context) + +_public_: Sets `context` object. + +#### identityBasedPolicy.getConditionResolver() + +_public_: Returns `conditionResolver` object. + +#### identityBasedPolicy.setConditionResolver(conditionResolver) + +_public_: Sets `conditionResolver` object. #### identityBasedPolicy.evaluate({action, resource, context}) @@ -511,34 +588,54 @@ Attach inline policies to resources. Resource-based policies grant permissions t ```js const { ResourceBasedPolicy } = require("iam-policies") -const resourceBasedPolicy = new ResourceBasedPolicy( - Statement, - conditionResolver -) +const resourceBasedPolicy = new ResourceBasedPolicy({ + statements, + conditionResolver, + context +}) ``` ### Properties -| Name | Type | Default | Required | Description | -| -------------------------------------------------------- | ---------------------------------------------------------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Statement` | object[] | undefined | `true` | The **_Statement_** element is the main element for a policy. The Statement element can contain a single statement or an array of individual statements. | -| `Statement[].sid` | string | _uuid_ | `false` | The **_sid_** element should be a unique identifier. It is automatically generated with a uuid in case it is undefined. | -| `Statement[].effect` | string | allow | `false` | The **_effect_** element specifies whether the statement results in an allow or an explicit deny. Valid values for Effect are `allow` and `deny`. | -| `Statement[].principal` | string or string[] | undefined | `false` | The **_principal_** element in a policy to specify the principal that is allowed or denied access to a resource. Statements must include either an `principal` or `notPrincipal` element. | -| `Statement[].notPrincipal` | string or string[] | undefined | `false` | The **_notPrincipal_** element specifies the principal that is not allowed or denied access to a resource. The `notPrincipal` element enables you to specify an exception to a list of principals. Use this element to deny access to all principals except the one named in the `notPrincipal` element. Statements must include either an `principal` or `notPrincipal` element. | -| `Statement[].action` | string or string[] | undefined | `false` | The **_action_** element describes the specific action or actions that will be allowed or denied. Statements must include either an `action` or `notAction` element. | -| `Statement[].notAction` | string or string[] | undefined | `false` | The **_notAction_** element is an advanced policy element that explicitly matches everything except the specified list of actions. Statements must include either an `action` or `notAction` element. Using `notAction` can result in a shorter policy by listing only a few actions that should not match, rather than including a long list of actions that will match. When using `notAction`, you should keep in mind that actions specified in this element are the only actions in that are limited. This, in turn, means that all of the applicable actions or services that are not listed are allowed if you use the Allow effect. In addition, such unlisted actions or services are denied if you use the `deny` effect. When you use `notAction` with the `resource` element, you provide scope for the policy. | -| `Statement[].resource` | string or string[] | undefined | `true` | The **_resource_** element specifies the object or objects that the statement covers. Statements could include either a `resource` or a `notResource` element. | -| `Statement[].notResource` | string or string[] | undefined | `true` | The **_notResource_** element is an advanced policy element that explicitly matches every resource except those specified. Statements could include either an `resource` or `notResource` element. Using `notResource` can result in a shorter policy by listing only a few resources that should not match, rather than including a long list of resources that will match. | -| `Statement[].condition` | object | undefined | `false` | The **_condition_** element (or Condition block) lets you specify conditions for when a policy is in effect. In the `condition` element, you build expressions in which you use condition operators (equal, less than, etc.) to match the condition keys and values in the policy against keys and values in the request context. | -| `Statement[].condition["conditionType"]` | object | undefined | `false` | The **_conditionType_** name should be replaced with a custom string attribute for a specific condition that should be match with one conditionResolver element. | -| `Statement[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | The **_conditionKey_** should be a custom string path attribute for a specific context attribute. Note: attributes must be separated but dots (`.`). | +| Name | Type | Default | Required | Description | +| --------------------------------------------------------- | ---------------------------------------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `statements` | object[] | undefined | `true` | The **_statements_** element is the main element for a policy. The statements element can contain an array of individual statements. | +| `statements[].sid` | string | _uuid_ | `false` | The **_sid_** element should be a unique identifier. It is automatically generated with a uuid in case it is undefined. | +| `statements[].effect` | string | allow | `false` | The **_effect_** element specifies whether the statement results in an allow or an explicit deny. Valid values for Effect are `allow` and `deny`. | +| `statements[].principal` | string or string[] | undefined | `false` | The **_principal_** element in a policy to specify the principal that is allowed or denied access to a resource. Each statement could include either an `principal` or `notPrincipal`. | +| `statements[].notPrincipal` | string or string[] | undefined | `false` | The **_notPrincipal_** element specifies the principal that is not allowed or denied access to a resource. The `notPrincipal` element enables you to specify an exception to a list of principals. Use this element to deny access to all principals except the one named in the `notPrincipal` element. Each statement could include either an `principal` or `notPrincipal` element. | +| `statements[].action` | string or string[] | undefined | `false` | The **_action_** element describes the specific action or actions that will be allowed or denied. Each statement must include either an `action` or `notAction` element. | +| `statements[].notAction` | string or string[] | undefined | `false` | The **_notAction_** element is an advanced policy element that explicitly matches everything except the specified list of actions. Each statement must include either an `action` or `notAction` element. Using `notAction` can result in a shorter policy by listing only a few actions that should not match, rather than including a long list of actions that will match. When using `notAction`, you should keep in mind that actions specified in this element are the only actions in that are limited. This, in turn, means that all of the applicable actions or services that are not listed are allowed if you use the Allow effect. In addition, such unlisted actions or services are denied if you use the `deny` effect. When you use `notAction` with the `resource` element, you provide scope for the policy. | +| `statements[].resource` | string or string[] | undefined | `true` | The **_resource_** element specifies the object or objects that the statement covers. Each statement could include either a `resource` or a `notResource` element. | +| `statements[].notResource` | string or string[] | undefined | `true` | The **_notResource_** element is an advanced policy element that explicitly matches every resource except those specified. Each statement could include either an `resource` or `notResource` element. Using `notResource` can result in a shorter policy by listing only a few resources that should not match, rather than including a long list of resources that will match. | +| `statements[].condition` | object | undefined | `false` | The **_condition_** element (or Condition block) lets you specify conditions for when a policy is in effect. In the `condition` element, you build expressions in which you use condition operators (equal, less than, etc.) to match the condition keys and values in the policy against keys and values in the request context. | +| `statements[].condition["conditionType"]` | object | undefined | `false` | The **_conditionType_** name should be replaced with a custom string attribute for a specific condition that should be match with one conditionResolver element. | +| `statements[].condition["conditionType"]["conditionKey"]` | (string or number or boolean) or (string or number or boolean)[] | undefined | `false` | The **_conditionKey_** should be a custom string path attribute for a specific context attribute. Note: attributes must be separated but dots (`.`). | +| `conditionResolver` | object | undefined | `false` | The **_conditionResolver_** should contain a function in each attribute to resolve an specific embedded condition in our statements. | +| `conditionResolver["conditionKey"]` | function | undefined | `false` | The **_conditionKey_** should match with a function name that will be used as a resolver in condition evaluation. There are 2 parameters for this function: **data** (first parameter) that will be evaluated with **expected** (second parameter), returning a **true** or **false**. | +| `context` | object | undefined | `false` | The **_context_** has those properties that will be embedded in our statements. | ### Methods #### resourceBasedPolicy.getStatements() -_public_: Returns `Statement[]` (statements array). +_public_: Returns `statements[]` (statements array). + +#### resourceBasedPolicy.getContext() + +_public_: Returns `context` object. + +#### resourceBasedPolicy.setContext(context) + +_public_: Sets `context` object. + +#### resourceBasedPolicy.getConditionResolver() + +_public_: Returns `conditionResolver` object. + +#### resourceBasedPolicy.setConditionResolver(conditionResolver) + +_public_: Sets `conditionResolver` object. #### resourceBasedPolicy.evaluate({principal, action, resource, context, principalType}) @@ -548,11 +645,11 @@ _public_: Verify if action for specific resource is allowed (`true`) or denied ( | Name | Type | Default | Required | Description | | --------------- | ------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------ | -| `principal` | string | undefined | `true` | It represents the principal you are asking. | +| `principal` | string | undefined | `false` | It represents the principal you are asking. | | `action` | string | undefined | `true` | It represents the action you are asking. | -| `resource` | string | undefined | `true` | It represents the resource for the action you are asking. | +| `resource` | string | undefined | `false` | It represents the resource for the action you are asking. | | `context` | object | undefined | `false` | It represents the properties that will be embedded into your resources. | -| `principalType` | string | undefined | `true` | It represents the principalType (principal attribute if the statement have principal object) you are asking. | +| `principalType` | string | undefined | `false` | It represents the principalType (principal attribute if the statement have principal object) you are asking. | #### resourceBasedPolicy.can({principal, action, resource, context, principalType}) @@ -562,11 +659,11 @@ _public_: Verify if action for specific resource is allowed (`true`) or not pres | Name | Type | Default | Required | Description | | --------------- | ------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------ | -| `principal` | string | undefined | `true` | It represents the principal you are asking. | +| `principal` | string | undefined | `false` | It represents the principal you are asking. | | `action` | string | undefined | `true` | It represents the action you are asking. | -| `resource` | string | undefined | `true` | It represents the resource for the action you are asking. | +| `resource` | string | undefined | `false` | It represents the resource for the action you are asking. | | `context` | object | undefined | `false` | It represents the properties that will be embedded into your resources. | -| `principalType` | string | undefined | `true` | It represents the principalType (principal attribute if the statement have principal object) you are asking. | +| `principalType` | string | undefined | `false` | It represents the principalType (principal attribute if the statement have principal object) you are asking. | #### resourceBasedPolicy.cannot({principal, action, resource, context, principalType}) @@ -576,11 +673,11 @@ _public_: Verify if action for specific resource is denied (`true`) or not prese | Name | Type | Default | Required | Description | | --------------- | ------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------ | -| `principal` | string | undefined | `true` | It represents the principal you are asking. | +| `principal` | string | undefined | `false` | It represents the principal you are asking. | | `action` | string | undefined | `true` | It represents the action you are asking. | -| `resource` | string | undefined | `true` | It represents the resource for the action you are asking. | +| `resource` | string | undefined | `false` | It represents the resource for the action you are asking. | | `context` | object | undefined | `false` | It represents the properties that will be embedded into your resources. | -| `principalType` | string | undefined | `true` | It represents the principalType (principal attribute if the statement have principal object) you are asking. | +| `principalType` | string | undefined | `false` | It represents the principalType (principal attribute if the statement have principal object) you are asking. | ## getValueFromPath(data, path) Function diff --git a/www/src/docs/zh-CN.md b/www/src/docs/zh-CN.md index 57e4503..3483ec3 100644 --- a/www/src/docs/zh-CN.md +++ b/www/src/docs/zh-CN.md @@ -5,7 +5,7 @@ date: "14-08-2020" # iam-policies -> [![NPM](https://img.shields.io/npm/v/iam-policies.svg)](https://www.npmjs.com/package/iam-policies) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![Build Status](https://travis-ci.com/roggervalf/iam-policies.svg?branch=master)](https://travis-ci.com/github/roggervalf/iam-policies) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) +> [![NPM](https://img.shields.io/npm/v/iam-policies.svg)](https://www.npmjs.com/package/iam-policies) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![Build Status](https://travis-ci.com/roggervalf/iam-policies.svg?branch=master)](https://travis-ci.com/github/roggervalf/iam-policies) [![NPM downloads](https://img.shields.io/npm/dm/iam-policies)](https://www.npmjs.com/package/iam-policies) [![Coverage Status](https://coveralls.io/repos/github/roggervalf/iam-policies/badge.svg?branch=master)](https://coveralls.io/github/roggervalf/iam-policies?branch=master) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) ## 介绍 @@ -176,7 +176,7 @@ adminExample.evaluate({ ```js const conditions = { - greaterThan: function(data, expected) { + greaterThan: function (data, expected) { return data > expected } } From 4777d39f1f9c22f8d289b48df6046ae3b9897e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?rogger=20andr=C3=A9=20valverde=20flores?= Date: Sun, 25 Oct 2020 22:46:24 -0500 Subject: [PATCH 13/13] chore(dist): updating files --- dist/main.es.js | 44 ++++++++++++++++++++++++++------------------ dist/main.es.js.map | 2 +- dist/main.js | 44 ++++++++++++++++++++++++++------------------ dist/main.js.map | 2 +- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/dist/main.es.js b/dist/main.es.js index 885af9c..accba1f 100644 --- a/dist/main.es.js +++ b/dist/main.es.js @@ -1068,28 +1068,36 @@ class ActionBasedPolicy extends Policy { const { get = {}, set = {} } = options; const { allow: allowGet = true, propertyMap: propertyMapGet = {} } = get; const { allow: allowSet = true, propertyMap: propertyMapSet = {} } = set; - const handler = Object.assign(Object.assign({}, (allowGet ? { get: (target, prop) => { - if (prop in target) { - if (typeof prop === 'string') { - const property = propertyMapGet[prop] || prop; - if (this.evaluate({ action: property })) - return target[prop]; - throw new Error(`Unauthorize to get ${prop} property`); + const handler = Object.assign(Object.assign({}, (allowGet + ? { + get: (target, prop) => { + if (prop in target) { + if (typeof prop === 'string') { + const property = propertyMapGet[prop] || prop; + if (this.evaluate({ action: property })) + return target[prop]; + throw new Error(`Unauthorize to get ${prop} property`); + } } + return target[prop]; } - return target[prop]; - } } : {})), (allowSet ? { set: (target, prop, value) => { - if (typeof prop === 'string') { - const property = propertyMapSet[prop] || prop; - if (this.evaluate({ action: property })) { - target[prop] = value; - return true; + } + : {})), (allowSet + ? { + set: (target, prop, value) => { + if (typeof prop === 'string') { + const property = propertyMapSet[prop] || prop; + if (this.evaluate({ action: property })) { + target[prop] = value; + return true; + } + else + throw new Error(`Unauthorize to set ${prop} property`); } - else - throw new Error(`Unauthorize to set ${prop} property`); + return true; } - return true; - } } : {})); + } + : {})); if (obj instanceof Object) { return new Proxy(obj, handler); } diff --git a/dist/main.es.js.map b/dist/main.es.js.map index 928da08..50f4732 100644 --- a/dist/main.es.js.map +++ b/dist/main.es.js.map @@ -1 +1 @@ -{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file diff --git a/dist/main.js b/dist/main.js index 05539cd..8b60134 100644 --- a/dist/main.js +++ b/dist/main.js @@ -1072,28 +1072,36 @@ class ActionBasedPolicy extends Policy { const { get = {}, set = {} } = options; const { allow: allowGet = true, propertyMap: propertyMapGet = {} } = get; const { allow: allowSet = true, propertyMap: propertyMapSet = {} } = set; - const handler = Object.assign(Object.assign({}, (allowGet ? { get: (target, prop) => { - if (prop in target) { - if (typeof prop === 'string') { - const property = propertyMapGet[prop] || prop; - if (this.evaluate({ action: property })) - return target[prop]; - throw new Error(`Unauthorize to get ${prop} property`); + const handler = Object.assign(Object.assign({}, (allowGet + ? { + get: (target, prop) => { + if (prop in target) { + if (typeof prop === 'string') { + const property = propertyMapGet[prop] || prop; + if (this.evaluate({ action: property })) + return target[prop]; + throw new Error(`Unauthorize to get ${prop} property`); + } } + return target[prop]; } - return target[prop]; - } } : {})), (allowSet ? { set: (target, prop, value) => { - if (typeof prop === 'string') { - const property = propertyMapSet[prop] || prop; - if (this.evaluate({ action: property })) { - target[prop] = value; - return true; + } + : {})), (allowSet + ? { + set: (target, prop, value) => { + if (typeof prop === 'string') { + const property = propertyMapSet[prop] || prop; + if (this.evaluate({ action: property })) { + target[prop] = value; + return true; + } + else + throw new Error(`Unauthorize to set ${prop} property`); } - else - throw new Error(`Unauthorize to set ${prop} property`); + return true; } - return true; - } } : {})); + } + : {})); if (obj instanceof Object) { return new Proxy(obj, handler); } diff --git a/dist/main.js.map b/dist/main.js.map index 7cde2eb..e515e62 100644 --- a/dist/main.js.map +++ b/dist/main.js.map @@ -1 +1 @@ -{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file +{"version":3,"file":"main.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"} \ No newline at end of file