Skip to content

Commit

Permalink
feat: extending Policy in Identity and Resource Based Policies
Browse files Browse the repository at this point in the history
allow get and set context and conditionResolver

BREAKING CHANGE: new way to contruct Policies instances with a json
  • Loading branch information
roggervalf committed Oct 19, 2020
1 parent 2aee00c commit bd06fc6
Show file tree
Hide file tree
Showing 16 changed files with 317 additions and 212 deletions.
3 changes: 3 additions & 0 deletions src/ActionBasedPolicy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down Expand Up @@ -202,6 +203,7 @@ describe('ActionBasedPolicy Class', () => {
}
]
});

expect(
policy.can({
action: 'getUser/123',
Expand Down Expand Up @@ -234,6 +236,7 @@ describe('ActionBasedPolicy Class', () => {
}
]
});

expect(
policy.cannot({
action: 'getUser/123',
Expand Down
2 changes: 1 addition & 1 deletion src/ActionBasedPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/ActionBasedStatement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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'],
Expand Down
155 changes: 88 additions & 67 deletions src/IdentityBasedPolicy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Expand All @@ -34,21 +38,24 @@ 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();
});
});

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({
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand All @@ -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({
Expand Down Expand Up @@ -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']
Expand All @@ -210,8 +224,8 @@ describe('IdentityBasedPolicy Class', () => {
}
}
],
conditions
);
conditionResolver
});

expect(
policy.evaluate({
Expand All @@ -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',
Expand All @@ -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',
Expand Down
44 changes: 30 additions & 14 deletions src/IdentityBasedPolicy.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit bd06fc6

Please sign in to comment.