Skip to content

Commit

Permalink
Added vanilla javascript version of the RBAC implementation that uses…
Browse files Browse the repository at this point in the history
… Redis as a backing store
  • Loading branch information
bhatti committed Sep 25, 2017
1 parent 0ac619a commit 6366f6e
Show file tree
Hide file tree
Showing 101 changed files with 8,875 additions and 1,663 deletions.
706 changes: 2 additions & 704 deletions README.md

Large diffs are not rendered by default.

File renamed without changes.
File renamed without changes.
File renamed without changes.
718 changes: 718 additions & 0 deletions flow/README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions config.js → flow/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ module.exports = {
name: 'PlexRBACJS API',
version: '0.0.1',
env: process.env.NODE_ENV || 'development',
port: process.env.PORT || 3000,
base_url: process.env.BASE_URL || 'http://localhost:3000',
port: process.env.PORT || 9355,
base_url: process.env.BASE_URL || 'http://localhost:9355',
}
13 changes: 9 additions & 4 deletions package.json → flow/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@
"build": "babel src/ -d lib/",
"prepublish": "yarn run build",
"lib": "yarn run babel -- src/ -d lib/",
"mocha": "mocha --timeout=3000 --compilers js:babel-register --require babel-polyfill --recursive",
"x": "mocha --timeout=3000 --compilers js:babel-register --require babel-polyfill test/repository/claim_repository_test.js",
"y": "mocha --timeout=3000 --compilers js:babel-register --require babel-polyfill test/routes/security_service_test.js",
"mocha": "mocha --timeout=3124 --compilers js:babel-register --require babel-polyfill --recursive",
"audit_repo": "mocha --timeout=3124 --compilers js:babel-register --require babel-polyfill test/repository/audit_record_repository_test.js",
"acct_repo": "mocha --timeout=3124 --compilers js:babel-register --require babel-polyfill test/repository/accounting_repository_test.js",
"x": "mocha --timeout=3124 --compilers js:babel-register --require babel-polyfill test/manager/security_manager_test.js",
"y": "mocha --timeout=3124 --compilers js:babel-register --require babel-polyfill test/repository/limit_repository_test.js",
"z": "mocha --timeout=3124 --compilers js:babel-register --require babel-polyfill test/routes/saas_rbac_test.js",
"test": "nyc --reporter=html --reporter=text mocha --timeout=4000 --recursive --compilers js:babel-register --require babel-polyfill",
"dstart": "NODE_ENV=dev && babel-node lib/app.js --presets es2015,stage-2",
"start": "NODE_ENV=dev && babel src/ -d lib/ && node lib/app.js",
"sample": "babel src/ -d lib/ && node lib/sample/app.js",
"cli": "babel src/ -d lib/ && node lib/cli/rbac_cli.js",
"coverage": "nyc report --reporter=text-lcov | coveralls"
"coverage": "nyc report --reporter=text-lcov | coveralls",
"_coverage": "./node_modules/.bin/nyc report --reporter=text-lcov|./node_modules/.bin/coveralls"
},
"dependencies": {
"babel-plugin-transform-regenerator": "^6.24.1",
Expand All @@ -45,6 +49,7 @@
"babel-plugin-transform-flow-strip-types": "^6.22.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.6.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-flow": "^6.23.0",
"babel-preset-stage-0": "^6.24.1",
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
49 changes: 49 additions & 0 deletions flow/src/domain/audit_record.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*@flow*/
import type {IAuditRecord, IRealm} from './interface';
import type {UniqueIdentifier} from '../util/unique_id';

const assert = require('assert');
const _realm = Symbol('realm');

/**
* AuditRecord is used to record changes to the system
*/
export class AuditRecord implements IAuditRecord, UniqueIdentifier {
id: number; // unique database id

realmName: string; // realm-name

principalName: string; // principal-name

type: string

action: string;

resource: string;

constructor(theRealm: IRealm,
thePrincipalName: string,
theType: string,
theAction: string,
theResource: string) {
//
assert(theRealm, 'realm is required');
assert(thePrincipalName, 'principal-name is required');
assert(theType, 'type is required');
assert(theAction, 'action is required');
assert(theResource, 'resource is required');
assert(theRealm.id, 'realm-id not specified');

//
this.action = theAction;
this.principalName = thePrincipalName;
this.type = theType;
this.action = theAction;
this.resource = theResource;
(this: any)[_realm] = theRealm;
}

realm(): IRealm {
return (this: any)[_realm];
}
}
File renamed without changes.
92 changes: 92 additions & 0 deletions flow/src/domain/claim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*@flow*/

import type {IClaim, IRealm, ClaimEffects} from './interface';
import type {UniqueIdentifier} from '../util/unique_id';

const assert = require('assert');

const _realm = Symbol('realm');
const _startDate = Symbol('startDate');
const _endDate = Symbol('endDate');

/**
* Claim implements IClaim for defining access attributes
*/
export class Claim implements IClaim, UniqueIdentifier {
static allow = 'allow';
static deny = 'deny';
static defaultDeny = 'defaultDeny';

id: number; // unique database id

action: string; // This can be a single operation or regex based multiple operations

resource: string; // target resource

condition: string; // This is optional for specifying runtime condition

effect: ClaimEffects; // This is optional for specifying allow/deny

constructor(theRealm: IRealm,
theAction: string,
theResource: string,
theCondition: string,
theEffect: ?ClaimEffects,
theStartDate: ?Date,
theEndDate: ?Date) {
//
assert(theRealm, 'realm is required');
assert(theAction, 'action is required');
assert(theResource, 'resource is required');
assert(theRealm.id, 'realm-id not specified');

//
this.action = theAction;
this.resource = theResource;
this.condition = theCondition;
this.effect = theEffect || Claim.allow;
(this: any)[_realm] = theRealm;
(this: any)[_startDate] = theStartDate || new Date();
(this: any)[_endDate] = theEndDate || new Date(new Date().setFullYear(new Date().getFullYear() + 5));
}

uniqueKey(): string {
return `${this.realm().realmName}_${this.action}_${this.resource}_${this.condition}`;
}

hasCondition(): boolean {
return this.condition != null && this.condition != undefined && this.condition.length > 0;
}

realm(): IRealm {
return (this: any)[_realm];
}

startDate(): string {
return (this: any)[_startDate].toISOString().split('T')[0];
}

endDate(): string {
return (this: any)[_endDate].toISOString().split('T')[0];
}

/**
* This method checks if given action and resource matches internal action and action.
* It tries to compare action and resource directly or using regex
*
* @param {*} action
* @param {*} resource
*/
implies(theAction: string, theResource: string): boolean {
return (this.action == theAction ||
theAction.match(this.action) != null) &&
this.resource == theResource;
}

/**
* returns textual representation
*/
toString() {
return `(${this.id}, ${this.action}, ${this.resource}, ${this.condition})`;
}
}
44 changes: 43 additions & 1 deletion src/domain/interface.js → flow/src/domain/interface.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {Enum} from '../util/enum';

export type ClaimEffects = 'allow' | 'deny' | 'defaultDeny';


/**
* Realm represents domain of application, each application will have a unique
* realm.
Expand Down Expand Up @@ -61,6 +60,8 @@ export interface IPrincipal {
*/
principalName: string;

properties: Map<string, any>;

/**
* set of claims
*/
Expand All @@ -71,6 +72,8 @@ export interface IPrincipal {
*/
roles: UniqueArray<IRole>;

limits: UniqueArray<ILimit>;

/**
* This method returns all claims including roles
*/
Expand Down Expand Up @@ -143,3 +146,42 @@ export interface IClaim {
*/
endDate(): string;
}

/**
* IAuditRecord is used to record changes to the system
*/
export interface IAuditRecord {
id: number; // unique database id

principalName: string; // principal-name

type: string;

action: string;

resource: string;

realm(): IRealm;
}


/**
* ILimit is used to record usage of resources
*/
export interface ILimit {
id: number; // unique database id

type: string;

resource: string;

maxAllowed: number;

value: number;

expirationDate: Date;

principal: IPrincipal;

expirationISODate(): string;
}
55 changes: 55 additions & 0 deletions flow/src/domain/limit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*@flow*/

import type {ILimit, IRealm, IPrincipal} from './interface';
import type {UniqueIdentifier} from '../util/unique_id';

const assert = require('assert');

const _realm = Symbol('realm');

/**
* Limit tracks resource usage by principal
*/
export class Limit implements ILimit, UniqueIdentifier {
id: number; // unique database id

type: string;

resource: string;

maxAllowed: number;

value: number;

expirationDate: Date;

principal: IPrincipal;

constructor(theType: string,
theResource: string,
theMaxLimit: number,
theUsed: number,
theExpirationDate: ?Date) {
//
assert(theType, 'type is required');
assert(theResource, 'resource is required');
//
this.type = theType;
this.resource = theResource;
this.maxAllowed = theMaxLimit;
this.value = theUsed || 0;
this.expirationDate = theExpirationDate || new Date(new Date().setFullYear(new Date().getFullYear() + 50));
}

uniqueKey(): string {
return `${this.principal.id}_${this.type}_${this.resource}_${this.id}`;
}

expirationISODate(): string {
return this.expirationDate.toISOString().split('T')[0] + 'T23:59:59';
}

valid(): boolean {
return this.value <= this.maxAllowed && Date.parse(this.expirationISODate()) >= new Date().getTime();
}
}
86 changes: 86 additions & 0 deletions flow/src/domain/principal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*@flow*/

import type {IPrincipal, IClaim, IRole, IRealm, ILimit} from './interface';
import {UniqueArray} from '../util/unique_array';
import type {UniqueIdentifier} from '../util/unique_id';

const assert = require('assert');

const _realm = Symbol('realm');

export class Principal implements IPrincipal, UniqueIdentifier {
/**
* unique database id
*/
id: number;

/**
* principal name such as username
*/
principalName: string;

/**
* set of Claims
*/
claims: UniqueArray<IClaim>;

limits: UniqueArray<ILimit>;
/**
* set of roles
*/
roles: UniqueArray<IRole>;

properties: Map<string, any>;

constructor(theRealm: IRealm,
thePrincipalName: string,
theProps: ?Map<string, any>) {
//
assert(theRealm, 'realm is required');
assert(thePrincipalName, 'principal is required');
assert(theRealm.id, 'realm-id not specified');

//
(this: any)[_realm] = theRealm;
this.principalName = thePrincipalName;
this.claims = new UniqueArray();
this.roles = new UniqueArray();
this.limits = new UniqueArray();
this.properties = theProps || new Map();
}

realm(): IRealm {
return (this: any)[_realm];
}

uniqueKey(): string {
return `${this.realm().realmName}_${this.principalName}`;
}

allClaims(): UniqueArray<IClaim> {
let allClaims: UniqueArray<IClaim> = new UniqueArray();
this.claims.forEach(claim => {
allClaims.add(claim);
});
this.roles.forEach(role => {
this.___loadRoleClaims(role, allClaims);
});
return allClaims;
}

___loadRoleClaims(role: IRole, allClaims: UniqueArray<IClaim>):void {
role.claims.forEach(claim => {
allClaims.add(claim);
});
role.parents.forEach(parentRole => {
this.___loadRoleClaims(parentRole, allClaims);
});
}

/**
* returns textual representation
*/
toString() {
return `(${this.id}, ${this.principalName}, ${String(this.claims)}, ${String(this.roles)})`;
}
}
File renamed without changes.
Loading

0 comments on commit 6366f6e

Please sign in to comment.