Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/internalEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,52 @@ export class InternalEnforcer extends CoreEnforcer {
return ok;
}

/**
* addPoliciesInternalEx adds rules to the current policy.
* Unlike addPoliciesInternal, this method will filter out rules that already exist
* and continue to add the remaining rules instead of returning false immediately.
*/
protected async addPoliciesInternalEx(sec: string, ptype: string, rules: string[][], useWatcher: boolean): Promise<boolean> {
// Filter out existing rules
const newRules = rules.filter((rule) => !this.model.hasPolicy(sec, ptype, rule));

// If no new rules to add, return false
if (newRules.length === 0) {
return false;
}

if (this.autoSave) {
if ('addPolicies' in this.adapter) {
try {
await this.adapter.addPolicies(sec, ptype, newRules);
} catch (e) {
if (e.message !== 'not implemented') {
throw e;
}
}
} else {
throw new Error('cannot save policy, the adapter does not implement the BatchAdapter');
}
}

if (useWatcher) {
if (this.autoNotifyWatcher) {
// error intentionally ignored
if (this.watcherEx) {
this.watcherEx.updateForAddPolicies(sec, ptype, ...newRules);
} else if (this.watcher) {
this.watcher.update();
}
}
}

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

/**
* updatePolicyInternal updates a rule from the current policy.
*/
Expand Down
50 changes: 50 additions & 0 deletions src/managementEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,31 @@ export class ManagementEnforcer extends InternalEnforcer {
return this.addPoliciesInternal('p', ptype, rules, true);
}

/**
* addPoliciesEx adds authorization rules to the current policy.
* If a rule already exists, the function will skip it and continue to add the remaining rules.
* The function returns true if at least one rule was added successfully.
*
* @param rules the "p" policy rules, ptype "p" is implicitly used.
* @return succeeds or not.
*/
public async addPoliciesEx(rules: string[][]): Promise<boolean> {
return this.addNamedPoliciesEx('p', rules);
}

/**
* addNamedPoliciesEx adds authorization rules to the current named policy.
* If a rule already exists, the function will skip it and continue to add the remaining rules.
* The function returns true if at least one rule was added successfully.
*
* @param ptype the policy type, can be "p", "p2", "p3", ..
* @param rules the "p" policy rules.
* @return succeeds or not.
*/
public async addNamedPoliciesEx(ptype: string, rules: string[][]): Promise<boolean> {
return this.addPoliciesInternalEx('p', ptype, rules, true);
}

/**
* updatePolicy updates an authorization rule from the current policy.
* If the rule not exists, the function returns false.
Expand Down Expand Up @@ -441,6 +466,31 @@ export class ManagementEnforcer extends InternalEnforcer {
return this.addPoliciesInternal('g', ptype, rules, true);
}

/**
* addGroupingPoliciesEx adds role inheritance rules to the current policy.
* If a rule already exists, the function will skip it and continue to add the remaining rules.
* The function returns true if at least one rule was added successfully.
*
* @param rules the "g" policy rules, ptype "g" is implicitly used.
* @return succeeds or not.
*/
public async addGroupingPoliciesEx(rules: string[][]): Promise<boolean> {
return this.addNamedGroupingPoliciesEx('g', rules);
}

/**
* addNamedGroupingPoliciesEx adds named role inheritance rules to the current policy.
* If a rule already exists, the function will skip it and continue to add the remaining rules.
* The function returns true if at least one rule was added successfully.
*
* @param ptype the policy type, can be "g", "g2", "g3", ..
* @param rules the "g" policy rules.
* @return succeeds or not.
*/
public async addNamedGroupingPoliciesEx(ptype: string, rules: string[][]): Promise<boolean> {
return this.addPoliciesInternalEx('g', ptype, rules, true);
}

/**
* removeGroupingPolicy removes a role inheritance rule from the current policy.
*
Expand Down
85 changes: 85 additions & 0 deletions test/managementAPI.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,88 @@ test('updateNamedGroupingPolicy', async () => {
groupingPolicy = await e.getGroupingPolicy();
testArray2DEquals(groupingPolicy, [['alice', 'update_test']]);
});

test('addPoliciesEx', async () => {
const a = new FileAdapter('examples/rbac_policy.csv');
e.setAdapter(a);
const rules = [
['alice', 'data1', 'read'], // already exists
['bob', 'data2', 'write'], // already exists
['jack', 'data4', 'read'], // new rule
['katy', 'data4', 'write'], // new rule
];
const added = await e.addPoliciesEx(rules);
expect(added).toBe(true);
// Check that new rules were added
expect(await e.hasPolicy('jack', 'data4', 'read')).toBe(true);
expect(await e.hasPolicy('katy', 'data4', 'write')).toBe(true);
// Check that existing rules are still there
expect(await e.hasPolicy('alice', 'data1', 'read')).toBe(true);
expect(await e.hasPolicy('bob', 'data2', 'write')).toBe(true);
});

test('addPoliciesEx - all existing rules', async () => {
const a = new FileAdapter('examples/rbac_policy.csv');
e.setAdapter(a);
const rules = [
['alice', 'data1', 'read'], // already exists
['bob', 'data2', 'write'], // already exists
];
const added = await e.addPoliciesEx(rules);
expect(added).toBe(false);
});

test('addNamedPoliciesEx', async () => {
const a = new FileAdapter('examples/rbac_policy.csv');
e.setAdapter(a);
const rules = [
['alice', 'data1', 'read'], // already exists
['jack', 'data4', 'read'], // new rule
['katy', 'data4', 'write'], // new rule
];
const added = await e.addNamedPoliciesEx('p', rules);
expect(added).toBe(true);
expect(await e.hasPolicy('jack', 'data4', 'read')).toBe(true);
expect(await e.hasPolicy('katy', 'data4', 'write')).toBe(true);
expect(await e.hasPolicy('alice', 'data1', 'read')).toBe(true);
});

test('addGroupingPoliciesEx', async () => {
const a = new FileAdapter('examples/rbac_policy.csv');
e.setAdapter(a);
const groupingRules = [
['alice', 'data2_admin'], // already exists
['ham', 'data4_admin'], // new rule
['jack', 'data5_admin'], // new rule
];
const added = await e.addGroupingPoliciesEx(groupingRules);
expect(added).toBe(true);
expect(await e.hasGroupingPolicy('ham', 'data4_admin')).toBe(true);
expect(await e.hasGroupingPolicy('jack', 'data5_admin')).toBe(true);
expect(await e.hasGroupingPolicy('alice', 'data2_admin')).toBe(true);
});

test('addGroupingPoliciesEx - all existing rules', async () => {
const a = new FileAdapter('examples/rbac_policy.csv');
e.setAdapter(a);
const groupingRules = [
['alice', 'data2_admin'], // already exists
];
const added = await e.addGroupingPoliciesEx(groupingRules);
expect(added).toBe(false);
});

test('addNamedGroupingPoliciesEx', async () => {
const a = new FileAdapter('examples/rbac_policy.csv');
e.setAdapter(a);
const groupingRules = [
['alice', 'data2_admin'], // already exists
['ham', 'data4_admin'], // new rule
['jack', 'data5_admin'], // new rule
];
const added = await e.addNamedGroupingPoliciesEx('g', groupingRules);
expect(added).toBe(true);
expect(await e.hasGroupingPolicy('ham', 'data4_admin')).toBe(true);
expect(await e.hasGroupingPolicy('jack', 'data5_admin')).toBe(true);
expect(await e.hasGroupingPolicy('alice', 'data2_admin')).toBe(true);
});