Skip to content

Commit

Permalink
Merge pull request #1 from Rogger794/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
roggervalf authored Nov 17, 2019
2 parents b18a4df + 3a4a61a commit b721766
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 10 deletions.
28 changes: 26 additions & 2 deletions readme.md → README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

## About

Define an allowed or denied set of actions against a set of resources with optional context.
Define an allowed or denied set of actions against a set of resources with optional context and conditions.

Deny rules trump allow rules.

This is a fork of [@ddt/iam](https://www.npmjs.com/package/@ddt/iam) updated.
This is a fork of [@ddt/iam](https://www.npmjs.com/package/@ddt/iam) updated with new functionalities.

## Install

Expand Down Expand Up @@ -84,4 +84,28 @@ const adminRole = new Role([
adminRole.can('read', 'someResource')
// true
adminRole.can('write', 'otherResource')

const conditions={
"greatherThan":function(data,expected){
return data>expected
}
}

const roleWithCondition = new Role([
{
effect: 'allow', // optional, defaults to allow
resources: ['secrets:*'],
actions: ['read', 'write'],
conditions: {
"greatherThan":{
'user.age':18
}
}
},
], conditions)

// true
console.log(roleWithCondition.can('read', 'secrets:sshhh', { user: { age: 19 } }))
// false
console.log(roleWithCondition.can('read', 'secrets:admin:super-secret', { user: { age: 18 } }))
```
26 changes: 25 additions & 1 deletion example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const role = new Role([
{
effect: 'deny',
resources: ['secrets:admin:*'],
actions: ['read'],
actions: ['read']
},
])

Expand All @@ -40,3 +40,27 @@ const adminRole = new Role([
console.log(adminRole.can('read', 'someResource'))
// true
console.log(adminRole.can('write', 'otherResource'))

const conditions={
"greatherThan":function(data,expected){
return data>expected
}
}

const roleWithCondition = new Role([
{
effect: 'allow', // optional, defaults to allow
resources: ['secrets:*'],
actions: ['read', 'write'],
conditions: {
"greatherThan":{
'user.age':18
}
}
},
], conditions)

// true
console.log(roleWithCondition.can('read', 'secrets:sshhh', { user: { age: 19 } }))
// false
console.log(roleWithCondition.can('read', 'secrets:admin:super-secret', { user: { age: 18 } }))
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"repository": "Rogger794/iam-policies",
"deprecated": false,
"description": "Identity based policies library",
"version": "1.0.1",
"version": "1.1.0",
"keywords": ["iam", "policies", "iam-policies"],
"name": "iam-policies",
"private": false,
Expand All @@ -18,7 +18,7 @@
"scripts": {
"prepublishOnly": "npm run build",
"test": "jest /tests",
"test:watch": "jest --watch",
"test:watch": "jest /tests --watch",
"clean": "rimraf dist",
"build": "rollup -c",
"build:watch": "rollup -c -w",
Expand Down
8 changes: 5 additions & 3 deletions src/Role.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { Statement, StatementConfig } from './Statement'
export class Role {
private denyStatements: Statement[]
private allowStatements: Statement[]
constructor(config: StatementConfig[]) {
private conditionResolvers?: object
constructor(config: StatementConfig[], conditionResolvers?: object) {
const statements = config.map(s => new Statement(s))
this.allowStatements = statements.filter(s => s.effect === 'allow')
this.denyStatements = statements.filter(s => s.effect === 'deny')
this.conditionResolvers = conditionResolvers
}
can(action: string, resource: string, context?: object): boolean {
return (
!this.denyStatements.some(s => s.matches(action, resource, context)) &&
this.allowStatements.some(s => s.matches(action, resource, context))
!this.denyStatements.some(s => s.matches(action, resource, context, this.conditionResolvers)) &&
this.allowStatements.some(s => s.matches(action, resource, context, this.conditionResolvers ))
)
}
}
40 changes: 38 additions & 2 deletions src/Statement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,51 @@ import template from 'lodash.template'

type StatementEffect = 'allow' | 'deny'
type StatementPattern = string

interface Condition {
[key: string]: any
}

interface StatementConditions {
[key: string]: Condition
}

export type StatementConfig = {
effect?: StatementEffect
resources: StatementPattern[]
actions: StatementPattern[]
conditions?: StatementConditions
}

//"Condition" : { "{condition-operator}" : { "{condition-key}" : "{condition-value}" }}
export class Statement {
effect: StatementEffect
private resources: StatementPattern[]
private actions: StatementPattern[]
constructor({ effect = 'allow', resources, actions }: StatementConfig) {
private conditions: StatementConditions
constructor({ effect = 'allow', resources, actions, conditions }: StatementConfig) {
this.effect = effect
this.resources = resources
this.actions = actions
this.conditions = conditions
}

matches(action: string, resource: string, context?: object, conditionResolvers?: object): boolean {
if(conditionResolvers&&this.conditions&&context){
return (
this.actions.some(a =>
new Minimatch(applyContext(a, context)).match(action)
) &&
this.resources.some(r =>
new Minimatch(applyContext(r, context)).match(resource)
) &&
Object.keys(this.conditions).every(condition =>
Object.keys(this.conditions[condition]).every(path=>
conditionResolvers[condition](getValueFromPath(context,path),this.conditions[condition][path])
)
)
)
}
matches(action: string, resource: string, context?: object): boolean {
return (
this.actions.some(a =>
new Minimatch(applyContext(a, context)).match(action)
Expand All @@ -30,6 +59,13 @@ export class Statement {
}
}

export function getValueFromPath(data, path) {
let value= data
const steps = path.split('.');
steps.forEach(e => value=value[e]);
return value
}

export function applyContext(str: string, context?: object) {
if (context == null) return str
const t = template(str)
Expand Down
35 changes: 35 additions & 0 deletions tests/Role.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,38 @@ it('can match based on context', () => {
).toBe(true)
expect(role.can('write', 'secrets:123:sshhh')).toBe(false)
})

it('can match based on conditions', () => {

const conditions={
"greatherThan":(data,expected)=>{
return data>expected
}
}

const role = new Role([
{
resources: ['secrets:*'],
actions: ['read', 'write'],
},
{
resources: ['posts:${user.id}:*'],
actions: ['write','read','update'],
conditions: {
"greatherThan":{
"user.age": 18
}
}
},
], conditions)

expect(role.can('read', 'secrets:123:sshhh', { user: { id: 123 } })).toBe(
true
)
expect(role.can('write', 'posts:123:sshhh', { user: { id: 123, age: 17 } })).toBe(
false
)
expect(role.can('read', 'posts:456:sshhh', { user: { id: 456, age: 19 } })).toBe(
true
)
})

0 comments on commit b721766

Please sign in to comment.