From b5650055a6e8c47da49dc3b7eb8646bb5bda90d9 Mon Sep 17 00:00:00 2001 From: Zixuan Liu Date: Mon, 18 May 2020 00:37:32 +0800 Subject: [PATCH] feat: add BuildIncrementalRoleLinks BREAKING CHANGE: **model** addPolicies, removePolicies and removeFilteredPolicy returns [boolean, string[][]] --- src/coreEnforcer.ts | 12 +++++++++- src/internalEnforcer.ts | 31 +++++++++++++++++++++---- src/managementEnforcer.ts | 40 ++++---------------------------- src/model/assertion.ts | 49 +++++++++++++++++++++++++-------------- src/model/model.ts | 48 ++++++++++++++++++++++++++++---------- 5 files changed, 110 insertions(+), 70 deletions(-) diff --git a/src/coreEnforcer.ts b/src/coreEnforcer.ts index 081c156..525d8d8 100644 --- a/src/coreEnforcer.ts +++ b/src/coreEnforcer.ts @@ -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'; @@ -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 { + await this.model.buildIncrementalRoleLinks(this.rm, op, 'g', ptype, rules); + } + protected async buildRoleLinksInternal(): Promise { await this.rm.clear(); await this.model.buildRoleLinks(this.rm); diff --git a/src/internalEnforcer.ts b/src/internalEnforcer.ts index 054a445..e2e801c 100644 --- a/src/internalEnforcer.ts +++ b/src/internalEnforcer.ts @@ -14,6 +14,7 @@ import { CoreEnforcer } from './coreEnforcer'; import { BatchAdapter } from './persist/batchAdapter'; +import { PolicyOp } from './model'; /** * InternalEnforcer = CoreEnforcer + Internal API. @@ -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. @@ -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; } /** @@ -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. @@ -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; } /** @@ -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; } } diff --git a/src/managementEnforcer.ts b/src/managementEnforcer.ts index 7cdb33a..b86f5b5 100644 --- a/src/managementEnforcer.ts +++ b/src/managementEnforcer.ts @@ -397,13 +397,7 @@ export class ManagementEnforcer extends InternalEnforcer { * @return succeeds or not. */ public async addNamedGroupingPolicy(ptype: string, ...params: string[]): Promise { - const ruleadded = await this.addPolicyInternal('g', ptype, params); - - if (this.autoBuildRoleLinks) { - await this.buildRoleLinksInternal(); - } - - return ruleadded; + return this.addPolicyInternal('g', ptype, params); } /** @@ -416,13 +410,7 @@ export class ManagementEnforcer extends InternalEnforcer { * @return succeeds or not. */ public async addNamedGroupingPolicies(ptype: string, rules: string[][]): Promise { - const rulesAdded = await this.addPoliciesInternal('g', ptype, rules); - - if (this.autoBuildRoleLinks) { - await this.buildRoleLinksInternal(); - } - - return rulesAdded; + return this.addPoliciesInternal('g', ptype, rules); } /** @@ -465,13 +453,7 @@ export class ManagementEnforcer extends InternalEnforcer { * @return succeeds or not. */ public async removeNamedGroupingPolicy(ptype: string, ...params: string[]): Promise { - const ruleRemoved = await this.removePolicyInternal('g', ptype, params); - - if (this.autoBuildRoleLinks) { - await this.buildRoleLinksInternal(); - } - - return ruleRemoved; + return this.removePolicyInternal('g', ptype, params); } /** @@ -482,13 +464,7 @@ export class ManagementEnforcer extends InternalEnforcer { * @return succeeds or not. */ public async removeNamedGroupingPolicies(ptype: string, rules: string[][]): Promise { - const rulesRemoved = this.removePoliciesInternal('g', ptype, rules); - - if (this.autoBuildRoleLinks) { - await this.buildRoleLinksInternal(); - } - - return rulesRemoved; + return this.removePoliciesInternal('g', ptype, rules); } /** @@ -501,13 +477,7 @@ export class ManagementEnforcer extends InternalEnforcer { * @return succeeds or not. */ public async removeFilteredNamedGroupingPolicy(ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise { - const ruleRemoved = await this.removeFilteredPolicyInternal('g', ptype, fieldIndex, fieldValues); - - if (this.autoBuildRoleLinks) { - await this.buildRoleLinksInternal(); - } - - return ruleRemoved; + return this.removeFilteredPolicyInternal('g', ptype, fieldIndex, fieldValues); } /** diff --git a/src/model/assertion.ts b/src/model/assertion.ts index fa0b2ef..38bd7fa 100644 --- a/src/model/assertion.ts +++ b/src/model/assertion.ts @@ -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 @@ -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 { + public async buildIncrementalRoleLinks(rm: rbac.RoleManager, op: PolicyOp, rules: string[][]): Promise { 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 { + 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(); } diff --git a/src/model/model.ts b/src/model/model.ts index caa96e4..04b23fd 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -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 { @@ -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 { + 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 { const astMap = this.model.get('g'); @@ -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. @@ -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. @@ -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; @@ -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.