Skip to content

Commit

Permalink
feat: add addMatchingFunc to DefaultRoleManager
Browse files Browse the repository at this point in the history
  • Loading branch information
nodece committed Apr 25, 2020
1 parent bf0b66e commit cc04e65
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 7 deletions.
15 changes: 15 additions & 0 deletions examples/rbac_with_pattern_model.conf
@@ -0,0 +1,15 @@
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _
g2 = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && g2(r.obj, p.obj) && regexMatch(r.act, p.act)
17 changes: 17 additions & 0 deletions examples/rbac_with_pattern_policy.csv
@@ -0,0 +1,17 @@
p, alice, /pen/1, GET
p, alice, /pen2/1, GET
p, book_admin, book_group, GET
p, pen_admin, pen_group, GET

g, alice, book_admin
g, bob, pen_admin

g, /book/*, book_group
g, cathy, /book/1/2/3/4/5
g, cathy, pen_admin

g2, /book/:id, book_group
g2, /pen/:id, pen_group

g2, /book2/{id}, book_group
g2, /pen2/{id}, pen_group
7 changes: 7 additions & 0 deletions src/coreEnforcer.ts
Expand Up @@ -120,6 +120,13 @@ export class CoreEnforcer {
this.rm = rm;
}

/**
* getRoleManager gets the current role manager.
*/
public getRoleManager(): RoleManager {
return this.rm;
}

/**
* setEffector sets the current effector.
*
Expand Down
54 changes: 48 additions & 6 deletions src/rbac/defaultRoleManager.ts
Expand Up @@ -67,10 +67,14 @@ class Role {
}
}

type MatchingFunc = (arg1: string, arg2: string) => boolean;

// RoleManager provides a default implementation for the RoleManager interface
export class DefaultRoleManager implements RoleManager {
private allRoles: Map<string, Role>;
private maxHierarchyLevel: number;
private hasPattern = false;
private matchingFunc: MatchingFunc;

/**
* DefaultRoleManager is the constructor for creating an instance of the
Expand All @@ -83,6 +87,19 @@ export class DefaultRoleManager implements RoleManager {
this.maxHierarchyLevel = maxHierarchyLevel;
}

/**
* e.buildRoleLinks must be called after addMatchingFunc().
* @param name
* @param fn
* @example ```javascript
* await e.GetRoleManager().addMatchingFunc('matcher', util.keyMatch); await e.buildRoleLinks();
* ```
*/
public async addMatchingFunc(name: string, fn: MatchingFunc): Promise<void> {
this.hasPattern = true;
this.matchingFunc = fn;
}

/**
* addLink adds the inheritance link between role: name1 and role: name2.
* aka role: name1 inherits role: name2.
Expand Down Expand Up @@ -210,17 +227,42 @@ export class DefaultRoleManager implements RoleManager {
}

private createRole(name: string): Role {
const role = this.allRoles.get(name);
if (role) {
return role;
} else {
let role = this.allRoles.get(name);
if (!role) {
const newRole = new Role(name);
role = newRole;
this.allRoles.set(name, newRole);
return newRole;
}

if (!this.hasPattern) {
return role;
}

for (const roleName of this.allRoles.keys()) {
if (!(this.matchingFunc(name, roleName) && name !== roleName)) {
continue;
}

const inherit = this.allRoles.get(roleName);
if (inherit) {
role.addRole(inherit);
}
}

return role;
}

private hasRole(name: string): boolean {
return this.allRoles.has(name);
if (!this.hasPattern) {
return this.allRoles.has(name);
} else {
for (const role of this.allRoles.keys()) {
if (this.matchingFunc(name, role)) {
return true;
}
}
}

return false;
}
}
37 changes: 36 additions & 1 deletion test/model.test.ts
Expand Up @@ -13,7 +13,8 @@
// limitations under the License.

import * as _ from 'lodash';
import { newEnforcer, Enforcer, newModel } from '../src';
import { DefaultRoleManager, Enforcer, newEnforcer, newModel } from '../src';
import { keyMatch2Func, keyMatch3Func } from '../src/util';

async function testEnforce(e: Enforcer, sub: string, obj: any, act: string, res: boolean): Promise<void> {
await expect(e.enforce(sub, obj, act)).resolves.toBe(res);
Expand Down Expand Up @@ -300,3 +301,37 @@ test('TestMatcher', async () => {

expect(m.model.get('m')?.get('m')?.value).toEqual(`keyMatch(r_obj, ".*get$") || regexMatch(r_act, ".user.")`);
});

test('TestRBACModelWithPattern', async () => {
const e = await newEnforcer('examples/rbac_with_pattern_model.conf', 'examples/rbac_with_pattern_policy.csv');

// Here's a little confusing: the matching function here is not the custom function used in matcher.
// It is the matching function used by "g" (and "g2", "g3" if any..)
// You can see in policy that: "g2, /book/:id, book_group", so in "g2()" function in the matcher, instead
// of checking whether "/book/:id" equals the obj: "/book/1", it checks whether the pattern matches.
// You can see it as normal RBAC: "/book/:id" == "/book/1" becomes KeyMatch2("/book/:id", "/book/1")
const rm = e.getRoleManager() as DefaultRoleManager;
await rm.addMatchingFunc('KeyMatch2', keyMatch2Func);
await e.buildRoleLinks();
await testEnforce(e, 'alice', '/book/1', 'GET', true);
await testEnforce(e, 'alice', '/book/2', 'GET', true);
await testEnforce(e, 'alice', '/pen/1', 'GET', true);
await testEnforce(e, 'alice', '/pen/2', 'GET', false);
await testEnforce(e, 'bob', '/book/1', 'GET', false);
await testEnforce(e, 'bob', '/book/2', 'GET', false);
await testEnforce(e, 'bob', '/pen/1', 'GET', true);
await testEnforce(e, 'bob', '/pen/2', 'GET', true);

// AddMatchingFunc() is actually setting a function because only one function is allowed,
// so when we set "KeyMatch3", we are actually replacing "KeyMatch2" with "KeyMatch3".
await rm.addMatchingFunc('KeyMatch3', keyMatch3Func);
await e.buildRoleLinks();
await testEnforce(e, 'alice', '/book2/1', 'GET', true);
await testEnforce(e, 'alice', '/book2/2', 'GET', true);
await testEnforce(e, 'alice', '/pen2/1', 'GET', true);
await testEnforce(e, 'alice', '/pen2/2', 'GET', false);
await testEnforce(e, 'bob', '/book2/1', 'GET', false);
await testEnforce(e, 'bob', '/book2/2', 'GET', false);
await testEnforce(e, 'bob', '/pen2/1', 'GET', true);
await testEnforce(e, 'bob', '/pen2/2', 'GET', true);
});

0 comments on commit cc04e65

Please sign in to comment.