From c252c4ad33230df22dbc9f135b8ee16bd5535b38 Mon Sep 17 00:00:00 2001 From: Shivansh Yadav Date: Sat, 16 Apr 2022 10:56:23 +0530 Subject: [PATCH] feat: added support for wildcard match --- examples/custom_rbac_policy.csv | 10 ++++++++ src/model/model.ts | 30 +++++++++++++++++++--- test/managementAPI.test.ts | 45 +++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 examples/custom_rbac_policy.csv diff --git a/examples/custom_rbac_policy.csv b/examples/custom_rbac_policy.csv new file mode 100644 index 00000000..c577fa90 --- /dev/null +++ b/examples/custom_rbac_policy.csv @@ -0,0 +1,10 @@ +p, alice, data1, read +p, bob, data2, write +p, data2_admin, data2, read +p, data2_admin, data2, write +p, admin:create:3, user, create +p, admin:create:2, user, create +p, admin/data/2, user, write +p, admin/data/4, user, read +g, alice, data2_admin +g, bob, data3_admin \ No newline at end of file diff --git a/src/model/model.ts b/src/model/model.ts index 60d03922..d8569ddc 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -18,6 +18,7 @@ import { Config, ConfigInterface } from '../config'; import { Assertion } from './assertion'; import { getLogger, logPrint } from '../log'; import { DefaultRoleManager } from '../rbac'; +import { isMatch } from 'picomatch'; export const sectionNameMap: { [index: string]: string } = { r: 'request_definition', @@ -71,6 +72,25 @@ export class Model { } } + private matchWithPattern(policy: string | string[], pattern: string | string[]): boolean { + if (policy === '' || (!policy && pattern)) { + return false; + } + if (typeof policy === 'string' && typeof pattern === 'string') { + if (pattern === '*') { + return true; + } + return isMatch(policy, pattern); + } + + for (let i = 0; i < pattern.length; i++) { + if (!this.matchWithPattern(policy[i], pattern[i])) { + return false; + } + } + return true; + } + // addDef adds an assertion to the model. public addDef(sec: string, key: string, value: string): boolean { if (value === '') { @@ -217,7 +237,7 @@ export class Model { if (!ast) { return false; } - return ast.policy.some((n: string[]) => util.arrayEquals(n, rule)); + return ast.policy.some((n: string[]) => this.matchWithPattern(n, rule)); } // addPolicy adds a policy rule to the model. @@ -358,9 +378,11 @@ export class Model { let matched = true; for (let i = 0; i < fieldValues.length; i++) { const fieldValue = fieldValues[i]; - if (fieldValue !== '' && rule[fieldIndex + i] !== fieldValue) { - matched = false; - break; + if (fieldValue !== '') { + matched = this.matchWithPattern(rule[fieldIndex + i], fieldValue); + if (!matched) { + break; + } } } diff --git a/test/managementAPI.test.ts b/test/managementAPI.test.ts index fde73c65..78ce5307 100644 --- a/test/managementAPI.test.ts +++ b/test/managementAPI.test.ts @@ -86,10 +86,32 @@ test('getPolicy', async () => { }); test('getFilteredPolicy', async () => { + e = await newEnforcer('examples/rbac_model.conf', 'examples/custom_rbac_policy.csv'); let filteredPolicy = await e.getFilteredPolicy(0, 'alice'); testArray2DEquals(filteredPolicy, [['alice', 'data1', 'read']]); filteredPolicy = await e.getFilteredPolicy(0, 'bob'); testArray2DEquals(filteredPolicy, [['bob', 'data2', 'write']]); + filteredPolicy = await e.getFilteredPolicy(0, '*'); + testArray2DEquals(filteredPolicy, [ + ['alice', 'data1', 'read'], + ['bob', 'data2', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write'], + ['admin:create:3', 'user', 'create'], + ['admin:create:2', 'user', 'create'], + ['admin/data/2', 'user', 'write'], + ['admin/data/4', 'user', 'read'], + ]); + filteredPolicy = await e.getFilteredPolicy(0, 'admin:create:*', 'user', 'create'); + testArray2DEquals(filteredPolicy, [ + ['admin:create:3', 'user', 'create'], + ['admin:create:2', 'user', 'create'], + ]); + filteredPolicy = await e.getFilteredPolicy(0, 'admin/data/*', 'user'); + testArray2DEquals(filteredPolicy, [ + ['admin/data/2', 'user', 'write'], + ['admin/data/4', 'user', 'read'], + ]); }); test('getNamedPolicy', async () => { @@ -115,8 +137,16 @@ test('getGroupingPolicy', async () => { }); test('getFilteredGroupingPolicy', async () => { - const filteredGroupingPolicy = await e.getFilteredGroupingPolicy(0, 'alice'); + e = await newEnforcer('examples/rbac_model.conf', 'examples/custom_rbac_policy.csv'); + let filteredGroupingPolicy = await e.getFilteredGroupingPolicy(0, 'alice'); testArray2DEquals(filteredGroupingPolicy, [['alice', 'data2_admin']]); + + filteredGroupingPolicy = await e.getFilteredGroupingPolicy(0, '*'); + console.log(filteredGroupingPolicy); + testArray2DEquals(filteredGroupingPolicy, [ + ['alice', 'data2_admin'], + ['bob', 'data3_admin'], + ]); }); test('getNamedGroupingPolicy', async () => { @@ -130,8 +160,19 @@ test('getFilteredNamedGroupingPolicy', async () => { }); test('hasPolicy', async () => { - const hasPolicy = await e.hasPolicy('data2_admin', 'data2', 'read'); + e = await newEnforcer('examples/rbac_model.conf', 'examples/custom_rbac_policy.csv'); + let hasPolicy = await e.hasPolicy('data2_admin', 'data2', 'read'); + expect(hasPolicy).toBe(true); + hasPolicy = await e.hasPolicy('*', 'user', 'read'); + expect(hasPolicy).toBe(true); + hasPolicy = await e.hasPolicy('*', 'user', 'delete'); + expect(hasPolicy).toBe(false); + hasPolicy = await e.hasPolicy('admin/data/*', 'user', 'read'); + expect(hasPolicy).toBe(true); + hasPolicy = await e.hasPolicy('admin:create:*', '*', '*'); expect(hasPolicy).toBe(true); + hasPolicy = await e.hasPolicy('admin:delete:*', '*', '*'); + expect(hasPolicy).toBe(false); }); test('hasNamedPolicy', async () => {