Skip to content

Commit

Permalink
Merge edf9178 into 0c440b9
Browse files Browse the repository at this point in the history
  • Loading branch information
Sefriol committed Aug 11, 2022
2 parents 0c440b9 + edf9178 commit affc436
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 10 deletions.
35 changes: 25 additions & 10 deletions src/rbac/defaultRoleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export class DefaultRoleManager implements RoleManager {
private maxHierarchyLevel: number;
private hasPattern = false;
private hasDomainPattern = false;
private hasDomainHierarchy = false;
private hierarchyManager: RoleManager;
private matchingFunc: MatchingFunc;
private domainMatchingFunc: MatchingFunc;

Expand Down Expand Up @@ -179,20 +181,30 @@ export class DefaultRoleManager implements RoleManager {
this.domainMatchingFunc = fn;
}

public async addDomainHierarchy(rm: RoleManager): Promise<void> {
this.hasDomainHierarchy = true;
this.hierarchyManager = rm;
}

private generateTempRoles(domain: string): Roles {
loadOrDefault(this.allDomains, domain, new Roles());

const patternDomain = new Set([domain]);
if (this.hasDomainPattern) {
const extraDomain = new Set([domain]);
if (this.hasDomainPattern || this.hasDomainHierarchy) {
this.allDomains.forEach((value, key) => {
if (this.domainMatchingFunc(domain, key)) {
patternDomain.add(key);
if (this.hasDomainPattern && this.domainMatchingFunc(domain, key)) {
extraDomain.add(key);
} else if (this.hasDomainHierarchy) {
if (!this.hierarchyManager.syncedHasLink) throw Error('domain hierarchy must be syncronous');
// Lower domain should inherit higher domain's roles
const test = this.hierarchyManager.syncedHasLink(key, domain);
if (test) extraDomain.add(key);
}
});
}

const allRoles = new Roles();
patternDomain.forEach((domain) => {
extraDomain.forEach((domain) => {
loadOrDefault(this.allDomains, domain, new Roles()).forEach((value, key) => {
const role1 = allRoles.createRole(value.name, this.matchingFunc);
value.getRoles().forEach((n) => {
Expand Down Expand Up @@ -269,7 +281,7 @@ export class DefaultRoleManager implements RoleManager {
}

let allRoles: Roles;
if (this.hasPattern || this.hasDomainPattern) {
if (this.hasPattern || this.hasDomainPattern || this.hasDomainHierarchy) {
allRoles = this.generateTempRoles(domain[0]);
} else {
allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
Expand Down Expand Up @@ -299,7 +311,7 @@ export class DefaultRoleManager implements RoleManager {
}

let allRoles: Roles;
if (this.hasPattern || this.hasDomainPattern) {
if (this.hasPattern || this.hasDomainPattern || this.hasDomainHierarchy) {
allRoles = this.generateTempRoles(domain[0]);
} else {
allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
Expand All @@ -324,7 +336,7 @@ export class DefaultRoleManager implements RoleManager {
}

let allRoles: Roles;
if (this.hasPattern || this.hasDomainPattern) {
if (this.hasPattern || this.hasDomainPattern || this.hasDomainHierarchy) {
allRoles = this.generateTempRoles(domain[0]);
} else {
allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
Expand All @@ -333,8 +345,11 @@ export class DefaultRoleManager implements RoleManager {
if (!allRoles.hasRole(name, this.matchingFunc)) {
return [];
}

return [...allRoles.values()].filter((n) => n.hasDirectRole(name)).map((n) => n.name);
const users = [];
for (const user of allRoles.values()) {
if (user.hasDirectRole(name)) users.push(user.name);
}
return users;
}

/**
Expand Down
98 changes: 98 additions & 0 deletions test/rbacHierarchicalDomain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { DefaultRoleManager, newModel, newSyncedEnforcer, RoleManager, SyncedEnforcer } from '../src';

describe('Test Hierarchical Domain RBAC', () => {
let e: SyncedEnforcer;
beforeAll(async () => {
const m = newModel();
m.addDef('r', 'r', 'sub, dom, obj, act');
m.addDef('p', 'p', 'sub, dom, obj, act');
m.addDef('g', 'g', '_, _, _');
m.addDef('g', 'g2', '_, _');
m.addDef('e', 'e', 'some(where (p.eft == allow))');
m.addDef('m', 'm', 'g(r.sub, p.sub, r.dom) && g2(r.dom, p.dom) && r.obj == p.obj && regexMatch(r.act, p.act)');

e = await newSyncedEnforcer(m);

await e.addPermissionForUser('admin', 'domain1', 'data1', '(read|write)');
await e.addPermissionForUser('admin', 'domain2', 'data2', '(read|write)');
await e.addPermissionForUser('admin', 'domain3', 'data3', '(write)');
await e.addPermissionForUser('admin', 'sibling2', 'sdata2', '(read|write)');

await e.addNamedGroupingPolicy('g', 'alice', 'admin', 'domain1');
await e.addNamedGroupingPolicy('g', 'bob', 'admin', 'domain2');
await e.addNamedGroupingPolicy('g2', 'domain1', 'domain2');
await e.addNamedGroupingPolicy('g2', 'domain1', 'sibling2');
await e.addNamedGroupingPolicy('g2', 'domain2', 'domain3');
(e.getRoleManager() as DefaultRoleManager).addDomainHierarchy(e.getNamedRoleManager('g2') as RoleManager);
});

test('Authorization to lower domain should pass', async () => {
expect(e.enforceSync('alice', 'domain1', 'data1', 'read')).toBe(true);
expect(e.enforceSync('alice', 'domain1', 'data2', 'read')).toBe(true);
expect(e.enforceSync('alice', 'domain2', 'data2', 'read')).toBe(true);
expect(e.enforceSync('alice', 'domain1', 'sdata2', 'read')).toBe(true);
expect(e.enforceSync('alice', 'sibling2', 'sdata2', 'read')).toBe(true);
expect(e.enforceSync('alice', 'domain3', 'data3', 'write')).toBe(true);
expect(e.enforceSync('alice', 'domain1', 'data3', 'write')).toBe(true);
expect(e.enforceSync('alice', 'domain2', 'data3', 'write')).toBe(true);
expect(e.enforceSync('bob', 'domain2', 'data2', 'read')).toBe(true);
expect(e.enforceSync('bob', 'domain2', 'data3', 'write')).toBe(true);
expect(e.enforceSync('bob', 'domain3', 'data3', 'write')).toBe(true);
});

test('Authorization to lower domain should faill if no role', async () => {
expect(e.enforceSync('bob', 'domain1', 'data3', 'write')).toBe(false);
expect(e.enforceSync('bob', 'domain1', 'data2', 'read')).toBe(false);
});

test('Authorization to higher domain without permissions should faill', async () => {
expect(e.enforceSync('bob', 'domain1', 'data1', 'read')).toBe(false);
expect(e.enforceSync('bob', 'domain2', 'data1', 'read')).toBe(false);
expect(e.enforceSync('bob', 'domain3', 'data1', 'write')).toBe(false);
});

test('Authorization to higher domain with permissions should faill', async () => {
// alice data1
expect(e.enforceSync('alice', 'domain2', 'data1', 'read')).toBe(false);
expect(e.enforceSync('alice', 'domain3', 'data1', 'read')).toBe(false);
expect(e.enforceSync('alice', 'sibling2', 'data1', 'read')).toBe(false);
// alice data2
expect(e.enforceSync('alice', 'domain3', 'data2', 'read')).toBe(false);
expect(e.enforceSync('alice', 'sibling2', 'data2', 'read')).toBe(false);
// alice data3
expect(e.enforceSync('alice', 'sibling2', 'data3', 'write')).toBe(false);
// alice sdata2
expect(e.enforceSync('alice', 'domain2', 'sdata2', 'read')).toBe(false);
// bob data2
expect(e.enforceSync('bob', 'domain3', 'data2', 'read')).toBe(false);
});

test('Authorization to sibling without permissions should faill', async () => {
expect(e.enforceSync('bob', 'domain1', 'sdata2', 'read')).toBe(false);
expect(e.enforceSync('bob', 'domain2', 'sdata2', 'read')).toBe(false);
expect(e.enforceSync('bob', 'sibling2', 'sdata2', 'read')).toBe(false);
expect(e.enforceSync('bob', 'domain3', 'sdata2', 'read')).toBe(false);
});

test('User should not have a permission if there is none in the lower domain', async () => {
expect(e.enforceSync('alice', 'domain3', 'data3', 'read')).toBe(false);
expect(e.enforceSync('bob', 'domain3', 'data3', 'read')).toBe(false);
});

test('test getRolesForUserInDomain', async () => {
expect(await e.getRolesForUserInDomain('alice', 'domain1')).toEqual(['admin']);
expect(await e.getRolesForUserInDomain('alice', 'domain2')).toEqual(['admin']);
expect(await e.getRolesForUserInDomain('alice', 'domain3')).toEqual(['admin']);
expect(await e.getRolesForUserInDomain('alice', 'sibling2')).toEqual(['admin']);
expect(await e.getRolesForUserInDomain('bob', 'domain1')).toEqual([]);
expect(await e.getRolesForUserInDomain('bob', 'domain2')).toEqual(['admin']);
expect(await e.getRolesForUserInDomain('bob', 'domain3')).toEqual(['admin']);
});

test('test getUsersForRoleInDomain', async () => {
expect(await e.getUsersForRoleInDomain('admin', 'domain1')).toEqual(['alice']);
expect(await e.getUsersForRoleInDomain('admin', 'domain2')).toEqual(['bob', 'alice']);
expect(await e.getUsersForRoleInDomain('admin', 'sibling2')).toEqual(['alice']);
expect(await e.getUsersForRoleInDomain('admin', 'domain3')).toEqual(['alice', 'bob']);
});
});

0 comments on commit affc436

Please sign in to comment.