Skip to content

Commit

Permalink
feat: support subjectPriority (#417)
Browse files Browse the repository at this point in the history
* feat: support subjectPriority

* fix: remove wrong param
  • Loading branch information
imp2002 committed Jan 31, 2023
1 parent a2ab183 commit e83d505
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 2 deletions.
14 changes: 14 additions & 0 deletions examples/subject_priority_model.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act, eft

[role_definition]
g = _, _

[policy_effect]
e = subjectPriority(p.eft) || deny

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
14 changes: 14 additions & 0 deletions examples/subject_priority_model_with_domain.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[request_definition]
r = sub, obj, dom, act

[policy_definition]
p = sub, obj, dom, act, eft

[role_definition]
g = _, _, _

[policy_effect]
e = subjectPriority(p.eft) || deny

[matchers]
m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
16 changes: 16 additions & 0 deletions examples/subject_priority_policy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
p, root, data1, read, deny
p, admin, data1, read, deny

p, editor, data1, read, deny
p, subscriber, data1, deny

p, jane, data1, read, allow
p, alice, data1, read, allow

g, admin, root

g, editor, admin
g, subscriber, admin

g, jane, editor
g, alice, subscriber
7 changes: 7 additions & 0 deletions examples/subject_priority_policy_with_domain.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
p, admin, data1, domain1, write, deny
p, alice, data1, domain1, write, allow
p, admin, data2, domain2, write, deny
p, bob, data2, domain2, write, allow

g, alice, admin, domain1
g, bob, admin, domain2
4 changes: 4 additions & 0 deletions src/coreEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ export class CoreEnforcer {
await this.adapter.loadPolicy(this.model);

this.sortPolicies();
this.model.sortPoliciesBySubjectHierarchy();

if (this.autoBuildRoleLinks) {
await this.buildRoleLinksInternal();
Expand All @@ -233,6 +234,9 @@ export class CoreEnforcer {
public async loadFilteredPolicy(filter: any): Promise<boolean> {
this.model.clearPolicy();

this.sortPolicies();
this.model.sortPoliciesBySubjectHierarchy();

return this.loadIncrementalFilteredPolicy(filter);
}

Expand Down
1 change: 1 addition & 0 deletions src/effect/defaultEffectorStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export class DefaultEffectorStream implements EffectorStream {
}
break;
case EffectExpress.PRIORITY:
case EffectExpress.SUBJECT_PRIORITY:
if (eft !== Effect.Indeterminate) {
this.res = eft === Effect.Allow;
this.done = true;
Expand Down
87 changes: 87 additions & 0 deletions src/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { Config, ConfigInterface } from '../config';
import { Assertion } from './assertion';
import { getLogger, logPrint } from '../log';
import { DefaultRoleManager } from '../rbac';
import { EffectExpress, FieldIndex } from '../constants';

const defaultDomain = '';
const defaultSeparator = '::';

export const sectionNameMap: { [index: string]: string } = {
r: 'request_definition',
Expand Down Expand Up @@ -479,6 +483,89 @@ export class Model {
assertion.fieldIndexMap.set(field, index);
return index;
}

/**
* sort policies by subject hieraichy
*/
public sortPoliciesBySubjectHierarchy(): void {
if (this.model.get('e')?.get('e')?.value !== EffectExpress.SUBJECT_PRIORITY) {
return;
}

this.model.get('p')?.forEach((assertion, ptype) => {
const domainIndex = this.getFieldIndex(ptype, FieldIndex.Domain);
const subIndex = this.getFieldIndex(ptype, FieldIndex.Subject);
// eslint-disable-next-line
const subjectHierarchyMap = this.getSubjectHierarchyMap(this.model.get('g')!.get('g')!.policy);

assertion.policy.sort((policyA, policyB) => {
const domainA = domainIndex === -1 ? defaultDomain : policyA[domainIndex];
const domainB = domainIndex === -1 ? defaultDomain : policyB[domainIndex];
// eslint-disable-next-line
const priorityA = subjectHierarchyMap.get(this.getNameWithDomain(domainA, policyA[subIndex]))!;
// eslint-disable-next-line
const priorityB = subjectHierarchyMap.get(this.getNameWithDomain(domainB, policyB[subIndex]))!;
return priorityB - priorityA;
});
});
}

/**
* Calculate the priority of each policy store in Map<string, number>
*/
getSubjectHierarchyMap(groupPolicies: string[][]): Map<string, number> {
const subjectHierarchyMap = new Map<string, number>();
if (!groupPolicies) {
return subjectHierarchyMap;
}

const policyMap = new Map<string, string>();
let domain = defaultDomain;

groupPolicies.forEach((policy) => {
if (policy.length !== 2) domain = policy[this.getFieldIndex('p', FieldIndex.Domain)];
const child = this.getNameWithDomain(domain, policy[this.getFieldIndex('p', FieldIndex.Subject)]);
const parent = this.getNameWithDomain(domain, policy[this.getFieldIndex('p', FieldIndex.Object)]);
policyMap.set(child, parent);
if (!subjectHierarchyMap.has(child)) {
subjectHierarchyMap.set(child, 0);
}
if (!subjectHierarchyMap.has(parent)) {
subjectHierarchyMap.set(parent, 0);
}
subjectHierarchyMap.set(child, 1);
});

const set = new Set<string>();
subjectHierarchyMap.forEach((_, key) => {
if (subjectHierarchyMap.get(key) !== 0) set.add(key);
});
while (set.size !== 0) {
for (const child of set.values()) {
this.findHierarchy(policyMap, subjectHierarchyMap, set, child);
}
}
return subjectHierarchyMap;
}

findHierarchy(policyMap: Map<string, string>, subjectHierarchyMap: Map<string, number>, set: Set<string>, child: string): void {
set.delete(child);
// eslint-disable-next-line
const parent = policyMap.get(child)!;

if (set.has(parent)) {
this.findHierarchy(policyMap, subjectHierarchyMap, set, parent);
}
// eslint-disable-next-line
subjectHierarchyMap.set(child, subjectHierarchyMap.get(parent)! + 10);
}

/**
* get full name with domain
*/
getNameWithDomain(domain: string, name: string): string {
return domain + defaultSeparator + name;
}
}

/**
Expand Down
20 changes: 18 additions & 2 deletions test/enforcer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ function testEnforceSync(e: Enforcer, sub: any, obj: string, act: string, res: b
expect(e.enforceSync(sub, obj, act)).toBe(res);
}

async function testEnforceEx(e: Enforcer, sub: any, obj: string, act: string, res: [boolean, string[]]): Promise<void> {
await expect(e.enforceEx(sub, obj, act)).resolves.toEqual(res);
async function testEnforceEx(e: Enforcer, sub: any, obj: string, act: string, res: [boolean, string[]], domain?: string): Promise<void> {
if (domain) {
await expect(e.enforceEx(sub, obj, domain, act)).resolves.toEqual(res);
} else {
await expect(e.enforceEx(sub, obj, act)).resolves.toEqual(res);
}
}

function testEnforceExSync(e: Enforcer, sub: any, obj: string, act: string, res: [boolean, string[]]): void {
Expand Down Expand Up @@ -697,3 +701,15 @@ test('TestEnforceExWithPriorityModel', async () => {
testEnforceEx(e, 'bob', 'data2', 'read', [true, ['data2_allow_group', 'data2', 'read', 'allow']]);
testEnforceEx(e, 'alice', 'data2', 'read', [false, []]);
});

test('TestSubjectPriority', async () => {
const e = await newEnforcer('examples/subject_priority_model.conf', 'examples/subject_priority_policy.csv');
testEnforceEx(e, 'jane', 'data1', 'read', [true, ['jane', 'data1', 'read', 'allow']]);
testEnforceEx(e, 'alice', 'data1', 'read', [true, ['alice', 'data1', 'read', 'allow']]);
});

test('TestSubjectPriorityWithDomain', async () => {
const e = await newEnforcer('examples/subject_priority_model_with_domain.conf', 'examples/subject_priority_policy_with_domain.csv');
testEnforceEx(e, 'alice', 'data1', 'write', [true, ['alice', 'data1', 'domain1', 'write', 'allow']], 'domain1');
testEnforceEx(e, 'bob', 'data2', 'write', [true, ['bob', 'data2', 'domain2', 'write', 'allow']], 'domain2');
});

0 comments on commit e83d505

Please sign in to comment.