Skip to content

Commit b565005

Browse files
committed
feat: add BuildIncrementalRoleLinks
BREAKING CHANGE: **model** addPolicies, removePolicies and removeFilteredPolicy returns [boolean, string[][]]
1 parent 57de7b2 commit b565005

File tree

5 files changed

+110
-70
lines changed

5 files changed

+110
-70
lines changed

src/coreEnforcer.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import { compile, compileAsync } from 'expression-eval';
1616

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

263+
/**
264+
* buildIncrementalRoleLinks provides incremental build the role inheritance relations.
265+
* @param op policy operation
266+
* @param ptype g
267+
* @param rules policies
268+
*/
269+
public async buildIncrementalRoleLinks(op: PolicyOp, ptype: string, rules: string[][]): Promise<void> {
270+
await this.model.buildIncrementalRoleLinks(this.rm, op, 'g', ptype, rules);
271+
}
272+
263273
protected async buildRoleLinksInternal(): Promise<void> {
264274
await this.rm.clear();
265275
await this.model.buildRoleLinks(this.rm);

src/internalEnforcer.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import { CoreEnforcer } from './coreEnforcer';
1616
import { BatchAdapter } from './persist/batchAdapter';
17+
import { PolicyOp } from './model';
1718

1819
/**
1920
* InternalEnforcer = CoreEnforcer + Internal API.
@@ -42,7 +43,11 @@ export class InternalEnforcer extends CoreEnforcer {
4243
this.watcher.update();
4344
}
4445

45-
return this.model.addPolicy(sec, ptype, rule);
46+
const ok = this.model.addPolicy(sec, ptype, rule);
47+
if (sec === 'g' && ok) {
48+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, [rule]);
49+
}
50+
return ok;
4651
}
4752

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

73-
return this.model.addPolicies(sec, ptype, rules);
78+
const [ok, effects] = await this.model.addPolicies(sec, ptype, rules);
79+
if (sec === 'g' && ok && effects?.length) {
80+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, effects);
81+
}
82+
return ok;
7483
}
7584

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

99-
return this.model.removePolicy(sec, ptype, rule);
108+
const ok = await this.model.removePolicy(sec, ptype, rule);
109+
if (sec === 'g' && ok) {
110+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, [rule]);
111+
}
112+
return ok;
100113
}
101114

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

126-
return this.model.removePolicies(sec, ptype, rules);
139+
const [ok, effects] = this.model.removePolicies(sec, ptype, rules);
140+
if (sec === 'g' && ok && effects?.length) {
141+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, effects);
142+
}
143+
return ok;
127144
}
128145

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

148-
return this.model.removeFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
165+
const [ok, effects] = this.model.removeFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
166+
if (sec === 'g' && ok && effects?.length) {
167+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, effects);
168+
}
169+
return ok;
149170
}
150171
}

src/managementEnforcer.ts

+5-35
Original file line numberDiff line numberDiff line change
@@ -397,13 +397,7 @@ export class ManagementEnforcer extends InternalEnforcer {
397397
* @return succeeds or not.
398398
*/
399399
public async addNamedGroupingPolicy(ptype: string, ...params: string[]): Promise<boolean> {
400-
const ruleadded = await this.addPolicyInternal('g', ptype, params);
401-
402-
if (this.autoBuildRoleLinks) {
403-
await this.buildRoleLinksInternal();
404-
}
405-
406-
return ruleadded;
400+
return this.addPolicyInternal('g', ptype, params);
407401
}
408402

409403
/**
@@ -416,13 +410,7 @@ export class ManagementEnforcer extends InternalEnforcer {
416410
* @return succeeds or not.
417411
*/
418412
public async addNamedGroupingPolicies(ptype: string, rules: string[][]): Promise<boolean> {
419-
const rulesAdded = await this.addPoliciesInternal('g', ptype, rules);
420-
421-
if (this.autoBuildRoleLinks) {
422-
await this.buildRoleLinksInternal();
423-
}
424-
425-
return rulesAdded;
413+
return this.addPoliciesInternal('g', ptype, rules);
426414
}
427415

428416
/**
@@ -465,13 +453,7 @@ export class ManagementEnforcer extends InternalEnforcer {
465453
* @return succeeds or not.
466454
*/
467455
public async removeNamedGroupingPolicy(ptype: string, ...params: string[]): Promise<boolean> {
468-
const ruleRemoved = await this.removePolicyInternal('g', ptype, params);
469-
470-
if (this.autoBuildRoleLinks) {
471-
await this.buildRoleLinksInternal();
472-
}
473-
474-
return ruleRemoved;
456+
return this.removePolicyInternal('g', ptype, params);
475457
}
476458

477459
/**
@@ -482,13 +464,7 @@ export class ManagementEnforcer extends InternalEnforcer {
482464
* @return succeeds or not.
483465
*/
484466
public async removeNamedGroupingPolicies(ptype: string, rules: string[][]): Promise<boolean> {
485-
const rulesRemoved = this.removePoliciesInternal('g', ptype, rules);
486-
487-
if (this.autoBuildRoleLinks) {
488-
await this.buildRoleLinksInternal();
489-
}
490-
491-
return rulesRemoved;
467+
return this.removePoliciesInternal('g', ptype, rules);
492468
}
493469

494470
/**
@@ -501,13 +477,7 @@ export class ManagementEnforcer extends InternalEnforcer {
501477
* @return succeeds or not.
502478
*/
503479
public async removeFilteredNamedGroupingPolicy(ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise<boolean> {
504-
const ruleRemoved = await this.removeFilteredPolicyInternal('g', ptype, fieldIndex, fieldValues);
505-
506-
if (this.autoBuildRoleLinks) {
507-
await this.buildRoleLinksInternal();
508-
}
509-
510-
return ruleRemoved;
480+
return this.removeFilteredPolicyInternal('g', ptype, fieldIndex, fieldValues);
511481
}
512482

513483
/**

src/model/assertion.ts

+32-17
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import * as rbac from '../rbac';
1616
import * as _ from 'lodash';
1717
import { logPrint } from '../log';
18+
import { PolicyOp } from './model';
1819

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

39-
public async buildRoleLinks(rm: rbac.RoleManager): Promise<void> {
40+
public async buildIncrementalRoleLinks(rm: rbac.RoleManager, op: PolicyOp, rules: string[][]): Promise<void> {
4041
this.rm = rm;
4142
const count = _.words(this.value, /_/g).length;
42-
for (const rule of this.policy) {
43-
if (count < 2) {
44-
throw new Error('the number of "_" in role definition should be at least 2');
45-
}
46-
43+
if (count < 2) {
44+
throw new Error('the number of "_" in role definition should be at least 2');
45+
}
46+
for (let rule of rules) {
4747
if (rule.length < count) {
4848
throw new Error('grouping policy elements do not meet role definition');
4949
}
50-
51-
if (count === 2) {
52-
// error intentionally ignored
53-
await this.rm.addLink(rule[0], rule[1]);
54-
} else if (count === 3) {
55-
// error intentionally ignored
56-
await this.rm.addLink(rule[0], rule[1], rule[2]);
57-
} else if (count === 4) {
58-
// error intentionally ignored
59-
await this.rm.addLink(rule[0], rule[1], rule[2], rule[3]);
50+
if (rule.length > count) {
51+
rule = rule.slice(0, count);
52+
}
53+
switch (op) {
54+
case PolicyOp.PolicyAdd:
55+
await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
56+
break;
57+
case PolicyOp.PolicyRemove:
58+
await this.rm.deleteLink(rule[0], rule[1], ...rule.slice(2));
59+
break;
60+
default:
61+
throw new Error('unsupported operation');
6062
}
6163
}
64+
}
6265

66+
public async buildRoleLinks(rm: rbac.RoleManager): Promise<void> {
67+
this.rm = rm;
68+
const count = _.words(this.value, /_/g).length;
69+
if (count < 2) {
70+
throw new Error('the number of "_" in role definition should be at least 2');
71+
}
72+
for (let rule of this.policy) {
73+
if (rule.length > count) {
74+
rule = rule.slice(0, count);
75+
}
76+
await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
77+
}
6378
logPrint(`Role links for: ${this.key}`);
6479
await this.rm.printRoles();
6580
}

src/model/model.ts

+36-12
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ export const sectionNameMap: { [index: string]: string } = {
2727
m: 'matchers'
2828
};
2929

30+
export enum PolicyOp {
31+
PolicyAdd,
32+
PolicyRemove
33+
}
34+
3035
export const requiredSections = ['r', 'p', 'e', 'm'];
3136

3237
export class Model {
@@ -160,6 +165,16 @@ export class Model {
160165
});
161166
}
162167

168+
// buildIncrementalRoleLinks provides incremental build the role inheritance relations.
169+
public async buildIncrementalRoleLinks(rm: rbac.RoleManager, op: PolicyOp, sec: string, ptype: string, rules: string[][]): Promise<void> {
170+
if (sec === 'g') {
171+
await this.model
172+
.get(sec)
173+
?.get(ptype)
174+
?.buildIncrementalRoleLinks(rm, op, rules);
175+
}
176+
}
177+
163178
// buildRoleLinks initializes the roles in RBAC.
164179
public async buildRoleLinks(rm: rbac.RoleManager): Promise<void> {
165180
const astMap = this.model.get('g');
@@ -217,21 +232,21 @@ export class Model {
217232
}
218233

219234
// addPolicies adds policy rules to the model.
220-
public addPolicies(sec: string, ptype: string, rules: string[][]): boolean {
235+
public addPolicies(sec: string, ptype: string, rules: string[][]): [boolean, string[][]] {
221236
const ast = this.model.get(sec)?.get(ptype);
222237
if (!ast) {
223-
return false;
238+
return [false, []];
224239
}
225240

226241
for (const rule of rules) {
227242
if (this.hasPolicy(sec, ptype, rule)) {
228-
return false;
243+
return [false, []];
229244
}
230245
}
231246

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

234-
return true;
249+
return [true, rules];
235250
}
236251

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

251266
// removePolicies removes policy rules from the model.
252-
public removePolicies(sec: string, ptype: string, rules: string[][]): boolean {
267+
public removePolicies(sec: string, ptype: string, rules: string[][]): [boolean, string[][]] {
268+
const effects: string[][] = [];
253269
const ast = this.model.get(sec)?.get(ptype);
254270
if (!ast) {
255-
return false;
271+
return [false, []];
256272
}
257273

258274
for (const rule of rules) {
259275
if (!this.hasPolicy(sec, ptype, rule)) {
260-
return false;
276+
return [false, []];
261277
}
262278
}
263279

264280
for (const rule of rules) {
265-
ast.policy = _.filter(ast.policy, r => !util.arrayEquals(rule, r));
281+
ast.policy = _.filter(ast.policy, (r: string[]) => {
282+
const equals = util.arrayEquals(rule, r);
283+
if (equals) {
284+
effects.push(r);
285+
}
286+
return !equals;
287+
});
266288
}
267289

268-
return true;
290+
return [true, effects];
269291
}
270292

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

296318
// removeFilteredPolicy removes policy rules based on field filters from the model.
297-
public removeFilteredPolicy(sec: string, key: string, fieldIndex: number, ...fieldValues: string[]): boolean {
319+
public removeFilteredPolicy(sec: string, key: string, fieldIndex: number, ...fieldValues: string[]): [boolean, string[][]] {
298320
const res = [];
321+
const effects = [];
299322
let bool = false;
300323
const ast = this.model.get(sec)?.get(key);
301324
if (!ast) {
302-
return bool;
325+
return [false, []];
303326
}
304327
for (const rule of ast.policy) {
305328
let matched = true;
@@ -313,13 +336,14 @@ export class Model {
313336

314337
if (matched) {
315338
bool = true;
339+
effects.push(rule);
316340
} else {
317341
res.push(rule);
318342
}
319343
}
320344
ast.policy = res;
321345

322-
return bool;
346+
return [bool, effects];
323347
}
324348

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

0 commit comments

Comments
 (0)