Skip to content

Commit d8a7cba

Browse files
committed
feat: add 4 new Ex APIs: addPoliciesEx, addGroupingPoliciesEx, addNamedPoliciesEx, addNamedGroupingPoliciesEx (#535)
1 parent c4b289f commit d8a7cba

File tree

3 files changed

+181
-0
lines changed

3 files changed

+181
-0
lines changed

src/internalEnforcer.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,52 @@ export class InternalEnforcer extends CoreEnforcer {
9999
return ok;
100100
}
101101

102+
/**
103+
* addPoliciesInternalEx adds rules to the current policy.
104+
* Unlike addPoliciesInternal, this method will filter out rules that already exist
105+
* and continue to add the remaining rules instead of returning false immediately.
106+
*/
107+
protected async addPoliciesInternalEx(sec: string, ptype: string, rules: string[][], useWatcher: boolean): Promise<boolean> {
108+
// Filter out existing rules
109+
const newRules = rules.filter((rule) => !this.model.hasPolicy(sec, ptype, rule));
110+
111+
// If no new rules to add, return false
112+
if (newRules.length === 0) {
113+
return false;
114+
}
115+
116+
if (this.autoSave) {
117+
if ('addPolicies' in this.adapter) {
118+
try {
119+
await this.adapter.addPolicies(sec, ptype, newRules);
120+
} catch (e) {
121+
if (e.message !== 'not implemented') {
122+
throw e;
123+
}
124+
}
125+
} else {
126+
throw new Error('cannot save policy, the adapter does not implement the BatchAdapter');
127+
}
128+
}
129+
130+
if (useWatcher) {
131+
if (this.autoNotifyWatcher) {
132+
// error intentionally ignored
133+
if (this.watcherEx) {
134+
this.watcherEx.updateForAddPolicies(sec, ptype, ...newRules);
135+
} else if (this.watcher) {
136+
this.watcher.update();
137+
}
138+
}
139+
}
140+
141+
const [ok, effects] = await this.model.addPolicies(sec, ptype, newRules);
142+
if (sec === 'g' && ok && effects?.length) {
143+
await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, effects);
144+
}
145+
return ok;
146+
}
147+
102148
/**
103149
* updatePolicyInternal updates a rule from the current policy.
104150
*/

src/managementEnforcer.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,31 @@ export class ManagementEnforcer extends InternalEnforcer {
276276
return this.addPoliciesInternal('p', ptype, rules, true);
277277
}
278278

279+
/**
280+
* addPoliciesEx adds authorization rules to the current policy.
281+
* If a rule already exists, the function will skip it and continue to add the remaining rules.
282+
* The function returns true if at least one rule was added successfully.
283+
*
284+
* @param rules the "p" policy rules, ptype "p" is implicitly used.
285+
* @return succeeds or not.
286+
*/
287+
public async addPoliciesEx(rules: string[][]): Promise<boolean> {
288+
return this.addNamedPoliciesEx('p', rules);
289+
}
290+
291+
/**
292+
* addNamedPoliciesEx adds authorization rules to the current named policy.
293+
* If a rule already exists, the function will skip it and continue to add the remaining rules.
294+
* The function returns true if at least one rule was added successfully.
295+
*
296+
* @param ptype the policy type, can be "p", "p2", "p3", ..
297+
* @param rules the "p" policy rules.
298+
* @return succeeds or not.
299+
*/
300+
public async addNamedPoliciesEx(ptype: string, rules: string[][]): Promise<boolean> {
301+
return this.addPoliciesInternalEx('p', ptype, rules, true);
302+
}
303+
279304
/**
280305
* updatePolicy updates an authorization rule from the current policy.
281306
* If the rule not exists, the function returns false.
@@ -441,6 +466,31 @@ export class ManagementEnforcer extends InternalEnforcer {
441466
return this.addPoliciesInternal('g', ptype, rules, true);
442467
}
443468

469+
/**
470+
* addGroupingPoliciesEx adds role inheritance rules to the current policy.
471+
* If a rule already exists, the function will skip it and continue to add the remaining rules.
472+
* The function returns true if at least one rule was added successfully.
473+
*
474+
* @param rules the "g" policy rules, ptype "g" is implicitly used.
475+
* @return succeeds or not.
476+
*/
477+
public async addGroupingPoliciesEx(rules: string[][]): Promise<boolean> {
478+
return this.addNamedGroupingPoliciesEx('g', rules);
479+
}
480+
481+
/**
482+
* addNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.
483+
* If a rule already exists, the function will skip it and continue to add the remaining rules.
484+
* The function returns true if at least one rule was added successfully.
485+
*
486+
* @param ptype the policy type, can be "g", "g2", "g3", ..
487+
* @param rules the "g" policy rules.
488+
* @return succeeds or not.
489+
*/
490+
public async addNamedGroupingPoliciesEx(ptype: string, rules: string[][]): Promise<boolean> {
491+
return this.addPoliciesInternalEx('g', ptype, rules, true);
492+
}
493+
444494
/**
445495
* removeGroupingPolicy removes a role inheritance rule from the current policy.
446496
*

test/managementAPI.test.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,88 @@ test('updateNamedGroupingPolicy', async () => {
377377
groupingPolicy = await e.getGroupingPolicy();
378378
testArray2DEquals(groupingPolicy, [['alice', 'update_test']]);
379379
});
380+
381+
test('addPoliciesEx', async () => {
382+
const a = new FileAdapter('examples/rbac_policy.csv');
383+
e.setAdapter(a);
384+
const rules = [
385+
['alice', 'data1', 'read'], // already exists
386+
['bob', 'data2', 'write'], // already exists
387+
['jack', 'data4', 'read'], // new rule
388+
['katy', 'data4', 'write'], // new rule
389+
];
390+
const added = await e.addPoliciesEx(rules);
391+
expect(added).toBe(true);
392+
// Check that new rules were added
393+
expect(await e.hasPolicy('jack', 'data4', 'read')).toBe(true);
394+
expect(await e.hasPolicy('katy', 'data4', 'write')).toBe(true);
395+
// Check that existing rules are still there
396+
expect(await e.hasPolicy('alice', 'data1', 'read')).toBe(true);
397+
expect(await e.hasPolicy('bob', 'data2', 'write')).toBe(true);
398+
});
399+
400+
test('addPoliciesEx - all existing rules', async () => {
401+
const a = new FileAdapter('examples/rbac_policy.csv');
402+
e.setAdapter(a);
403+
const rules = [
404+
['alice', 'data1', 'read'], // already exists
405+
['bob', 'data2', 'write'], // already exists
406+
];
407+
const added = await e.addPoliciesEx(rules);
408+
expect(added).toBe(false);
409+
});
410+
411+
test('addNamedPoliciesEx', async () => {
412+
const a = new FileAdapter('examples/rbac_policy.csv');
413+
e.setAdapter(a);
414+
const rules = [
415+
['alice', 'data1', 'read'], // already exists
416+
['jack', 'data4', 'read'], // new rule
417+
['katy', 'data4', 'write'], // new rule
418+
];
419+
const added = await e.addNamedPoliciesEx('p', rules);
420+
expect(added).toBe(true);
421+
expect(await e.hasPolicy('jack', 'data4', 'read')).toBe(true);
422+
expect(await e.hasPolicy('katy', 'data4', 'write')).toBe(true);
423+
expect(await e.hasPolicy('alice', 'data1', 'read')).toBe(true);
424+
});
425+
426+
test('addGroupingPoliciesEx', async () => {
427+
const a = new FileAdapter('examples/rbac_policy.csv');
428+
e.setAdapter(a);
429+
const groupingRules = [
430+
['alice', 'data2_admin'], // already exists
431+
['ham', 'data4_admin'], // new rule
432+
['jack', 'data5_admin'], // new rule
433+
];
434+
const added = await e.addGroupingPoliciesEx(groupingRules);
435+
expect(added).toBe(true);
436+
expect(await e.hasGroupingPolicy('ham', 'data4_admin')).toBe(true);
437+
expect(await e.hasGroupingPolicy('jack', 'data5_admin')).toBe(true);
438+
expect(await e.hasGroupingPolicy('alice', 'data2_admin')).toBe(true);
439+
});
440+
441+
test('addGroupingPoliciesEx - all existing rules', async () => {
442+
const a = new FileAdapter('examples/rbac_policy.csv');
443+
e.setAdapter(a);
444+
const groupingRules = [
445+
['alice', 'data2_admin'], // already exists
446+
];
447+
const added = await e.addGroupingPoliciesEx(groupingRules);
448+
expect(added).toBe(false);
449+
});
450+
451+
test('addNamedGroupingPoliciesEx', async () => {
452+
const a = new FileAdapter('examples/rbac_policy.csv');
453+
e.setAdapter(a);
454+
const groupingRules = [
455+
['alice', 'data2_admin'], // already exists
456+
['ham', 'data4_admin'], // new rule
457+
['jack', 'data5_admin'], // new rule
458+
];
459+
const added = await e.addNamedGroupingPoliciesEx('g', groupingRules);
460+
expect(added).toBe(true);
461+
expect(await e.hasGroupingPolicy('ham', 'data4_admin')).toBe(true);
462+
expect(await e.hasGroupingPolicy('jack', 'data5_admin')).toBe(true);
463+
expect(await e.hasGroupingPolicy('alice', 'data2_admin')).toBe(true);
464+
});

0 commit comments

Comments
 (0)