Skip to content

Commit

Permalink
feat: add BuildIncrementalRoleLinks
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
**model** addPolicies, removePolicies and removeFilteredPolicy returns [boolean, string[][]]
  • Loading branch information
nodece committed May 17, 2020
1 parent 57de7b2 commit b565005
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 70 deletions.
12 changes: 11 additions & 1 deletion src/coreEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import { compile, compileAsync } from 'expression-eval';

import { DefaultEffector, Effect, Effector } from './effect';
import { FunctionMap, Model, newModel } from './model';
import { FunctionMap, Model, newModel, PolicyOp } from './model';
import { Adapter, Filter, FilteredAdapter, Watcher } from './persist';
import { DefaultRoleManager, RoleManager } from './rbac';
import { escapeAssertion, generateGFunction, getEvalValue, hasEval, replaceEval } from './util';
Expand Down Expand Up @@ -260,6 +260,16 @@ export class CoreEnforcer {
return this.buildRoleLinksInternal();
}

/**
* buildIncrementalRoleLinks provides incremental build the role inheritance relations.
* @param op policy operation
* @param ptype g
* @param rules policies
*/
public async buildIncrementalRoleLinks(op: PolicyOp, ptype: string, rules: string[][]): Promise<void> {
await this.model.buildIncrementalRoleLinks(this.rm, op, 'g', ptype, rules);
}

protected async buildRoleLinksInternal(): Promise<void> {
await this.rm.clear();
await this.model.buildRoleLinks(this.rm);
Expand Down
31 changes: 26 additions & 5 deletions src/internalEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import { CoreEnforcer } from './coreEnforcer';
import { BatchAdapter } from './persist/batchAdapter';
import { PolicyOp } from './model';

/**
* InternalEnforcer = CoreEnforcer + Internal API.
Expand Down Expand Up @@ -42,7 +43,11 @@ export class InternalEnforcer extends CoreEnforcer {
this.watcher.update();
}

return this.model.addPolicy(sec, ptype, rule);
const ok = this.model.addPolicy(sec, ptype, rule);
if (sec === 'g' && ok) {
await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, [rule]);
}
return ok;
}

// addPolicies adds rules to the current policy.
Expand Down Expand Up @@ -70,7 +75,11 @@ export class InternalEnforcer extends CoreEnforcer {
this.watcher.update();
}

return this.model.addPolicies(sec, ptype, rules);
const [ok, effects] = await this.model.addPolicies(sec, ptype, rules);
if (sec === 'g' && ok && effects?.length) {
await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, effects);
}
return ok;
}

/**
Expand All @@ -96,7 +105,11 @@ export class InternalEnforcer extends CoreEnforcer {
this.watcher.update();
}

return this.model.removePolicy(sec, ptype, rule);
const ok = await this.model.removePolicy(sec, ptype, rule);
if (sec === 'g' && ok) {
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, [rule]);
}
return ok;
}

// removePolicies removes rules from the current policy.
Expand All @@ -123,7 +136,11 @@ export class InternalEnforcer extends CoreEnforcer {
this.watcher.update();
}

return this.model.removePolicies(sec, ptype, rules);
const [ok, effects] = this.model.removePolicies(sec, ptype, rules);
if (sec === 'g' && ok && effects?.length) {
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, effects);
}
return ok;
}

/**
Expand All @@ -145,6 +162,10 @@ export class InternalEnforcer extends CoreEnforcer {
this.watcher.update();
}

return this.model.removeFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
const [ok, effects] = this.model.removeFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
if (sec === 'g' && ok && effects?.length) {
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, effects);
}
return ok;
}
}
40 changes: 5 additions & 35 deletions src/managementEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,13 +397,7 @@ export class ManagementEnforcer extends InternalEnforcer {
* @return succeeds or not.
*/
public async addNamedGroupingPolicy(ptype: string, ...params: string[]): Promise<boolean> {
const ruleadded = await this.addPolicyInternal('g', ptype, params);

if (this.autoBuildRoleLinks) {
await this.buildRoleLinksInternal();
}

return ruleadded;
return this.addPolicyInternal('g', ptype, params);
}

/**
Expand All @@ -416,13 +410,7 @@ export class ManagementEnforcer extends InternalEnforcer {
* @return succeeds or not.
*/
public async addNamedGroupingPolicies(ptype: string, rules: string[][]): Promise<boolean> {
const rulesAdded = await this.addPoliciesInternal('g', ptype, rules);

if (this.autoBuildRoleLinks) {
await this.buildRoleLinksInternal();
}

return rulesAdded;
return this.addPoliciesInternal('g', ptype, rules);
}

/**
Expand Down Expand Up @@ -465,13 +453,7 @@ export class ManagementEnforcer extends InternalEnforcer {
* @return succeeds or not.
*/
public async removeNamedGroupingPolicy(ptype: string, ...params: string[]): Promise<boolean> {
const ruleRemoved = await this.removePolicyInternal('g', ptype, params);

if (this.autoBuildRoleLinks) {
await this.buildRoleLinksInternal();
}

return ruleRemoved;
return this.removePolicyInternal('g', ptype, params);
}

/**
Expand All @@ -482,13 +464,7 @@ export class ManagementEnforcer extends InternalEnforcer {
* @return succeeds or not.
*/
public async removeNamedGroupingPolicies(ptype: string, rules: string[][]): Promise<boolean> {
const rulesRemoved = this.removePoliciesInternal('g', ptype, rules);

if (this.autoBuildRoleLinks) {
await this.buildRoleLinksInternal();
}

return rulesRemoved;
return this.removePoliciesInternal('g', ptype, rules);
}

/**
Expand All @@ -501,13 +477,7 @@ export class ManagementEnforcer extends InternalEnforcer {
* @return succeeds or not.
*/
public async removeFilteredNamedGroupingPolicy(ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise<boolean> {
const ruleRemoved = await this.removeFilteredPolicyInternal('g', ptype, fieldIndex, fieldValues);

if (this.autoBuildRoleLinks) {
await this.buildRoleLinksInternal();
}

return ruleRemoved;
return this.removeFilteredPolicyInternal('g', ptype, fieldIndex, fieldValues);
}

/**
Expand Down
49 changes: 32 additions & 17 deletions src/model/assertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as rbac from '../rbac';
import * as _ from 'lodash';
import { logPrint } from '../log';
import { PolicyOp } from './model';

// Assertion represents an expression in a section of the model.
// For example: r = sub, obj, act
Expand All @@ -33,33 +34,47 @@ export class Assertion {
this.value = '';
this.tokens = [];
this.policy = [];
this.rm = new rbac.DefaultRoleManager(0);
this.rm = new rbac.DefaultRoleManager(10);
}

public async buildRoleLinks(rm: rbac.RoleManager): Promise<void> {
public async buildIncrementalRoleLinks(rm: rbac.RoleManager, op: PolicyOp, rules: string[][]): Promise<void> {
this.rm = rm;
const count = _.words(this.value, /_/g).length;
for (const rule of this.policy) {
if (count < 2) {
throw new Error('the number of "_" in role definition should be at least 2');
}

if (count < 2) {
throw new Error('the number of "_" in role definition should be at least 2');
}
for (let rule of rules) {
if (rule.length < count) {
throw new Error('grouping policy elements do not meet role definition');
}

if (count === 2) {
// error intentionally ignored
await this.rm.addLink(rule[0], rule[1]);
} else if (count === 3) {
// error intentionally ignored
await this.rm.addLink(rule[0], rule[1], rule[2]);
} else if (count === 4) {
// error intentionally ignored
await this.rm.addLink(rule[0], rule[1], rule[2], rule[3]);
if (rule.length > count) {
rule = rule.slice(0, count);
}
switch (op) {
case PolicyOp.PolicyAdd:
await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
break;
case PolicyOp.PolicyRemove:
await this.rm.deleteLink(rule[0], rule[1], ...rule.slice(2));
break;
default:
throw new Error('unsupported operation');
}
}
}

public async buildRoleLinks(rm: rbac.RoleManager): Promise<void> {
this.rm = rm;
const count = _.words(this.value, /_/g).length;
if (count < 2) {
throw new Error('the number of "_" in role definition should be at least 2');
}
for (let rule of this.policy) {
if (rule.length > count) {
rule = rule.slice(0, count);
}
await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
}
logPrint(`Role links for: ${this.key}`);
await this.rm.printRoles();
}
Expand Down
48 changes: 36 additions & 12 deletions src/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export const sectionNameMap: { [index: string]: string } = {
m: 'matchers'
};

export enum PolicyOp {
PolicyAdd,
PolicyRemove
}

export const requiredSections = ['r', 'p', 'e', 'm'];

export class Model {
Expand Down Expand Up @@ -160,6 +165,16 @@ export class Model {
});
}

// buildIncrementalRoleLinks provides incremental build the role inheritance relations.
public async buildIncrementalRoleLinks(rm: rbac.RoleManager, op: PolicyOp, sec: string, ptype: string, rules: string[][]): Promise<void> {
if (sec === 'g') {
await this.model
.get(sec)
?.get(ptype)
?.buildIncrementalRoleLinks(rm, op, rules);
}
}

// buildRoleLinks initializes the roles in RBAC.
public async buildRoleLinks(rm: rbac.RoleManager): Promise<void> {
const astMap = this.model.get('g');
Expand Down Expand Up @@ -217,21 +232,21 @@ export class Model {
}

// addPolicies adds policy rules to the model.
public addPolicies(sec: string, ptype: string, rules: string[][]): boolean {
public addPolicies(sec: string, ptype: string, rules: string[][]): [boolean, string[][]] {
const ast = this.model.get(sec)?.get(ptype);
if (!ast) {
return false;
return [false, []];
}

for (const rule of rules) {
if (this.hasPolicy(sec, ptype, rule)) {
return false;
return [false, []];
}
}

ast.policy = ast.policy.concat(rules);

return true;
return [true, rules];
}

// removePolicy removes a policy rule from the model.
Expand All @@ -249,23 +264,30 @@ export class Model {
}

// removePolicies removes policy rules from the model.
public removePolicies(sec: string, ptype: string, rules: string[][]): boolean {
public removePolicies(sec: string, ptype: string, rules: string[][]): [boolean, string[][]] {
const effects: string[][] = [];
const ast = this.model.get(sec)?.get(ptype);
if (!ast) {
return false;
return [false, []];
}

for (const rule of rules) {
if (!this.hasPolicy(sec, ptype, rule)) {
return false;
return [false, []];
}
}

for (const rule of rules) {
ast.policy = _.filter(ast.policy, r => !util.arrayEquals(rule, r));
ast.policy = _.filter(ast.policy, (r: string[]) => {
const equals = util.arrayEquals(rule, r);
if (equals) {
effects.push(r);
}
return !equals;
});
}

return true;
return [true, effects];
}

// getFilteredPolicy gets rules based on field filters from a policy.
Expand Down Expand Up @@ -294,12 +316,13 @@ export class Model {
}

// removeFilteredPolicy removes policy rules based on field filters from the model.
public removeFilteredPolicy(sec: string, key: string, fieldIndex: number, ...fieldValues: string[]): boolean {
public removeFilteredPolicy(sec: string, key: string, fieldIndex: number, ...fieldValues: string[]): [boolean, string[][]] {
const res = [];
const effects = [];
let bool = false;
const ast = this.model.get(sec)?.get(key);
if (!ast) {
return bool;
return [false, []];
}
for (const rule of ast.policy) {
let matched = true;
Expand All @@ -313,13 +336,14 @@ export class Model {

if (matched) {
bool = true;
effects.push(rule);
} else {
res.push(rule);
}
}
ast.policy = res;

return bool;
return [bool, effects];
}

// getValuesForFieldInPolicy gets all values for a field for all rules in a policy, duplicated values are removed.
Expand Down

0 comments on commit b565005

Please sign in to comment.