diff --git a/src/coreEnforcer.ts b/src/coreEnforcer.ts index 614b088..352bdf0 100644 --- a/src/coreEnforcer.ts +++ b/src/coreEnforcer.ts @@ -18,9 +18,10 @@ import { DefaultEffector, Effect, Effector } from './effect'; import { FunctionMap, Model, newModel } from './model'; import { Adapter, Filter, FilteredAdapter, Watcher } from './persist'; import { DefaultRoleManager, RoleManager } from './rbac'; -import { generateGFunction } from './util'; +import { generateGFunction, hasEval, getEvalValue, escapeAssertion, replaceEval } from './util'; import { getLogger, logPrint } from './log'; +type Matcher = ((context: object) => Promise) | ((context: object) => any); /** * CoreEnforcer defines the core functionality of an enforcer. */ @@ -29,7 +30,7 @@ export class CoreEnforcer { protected model: Model; protected fm: FunctionMap = FunctionMap.loadFunctionMap(); protected eft: Effector = new DefaultEffector(); - private matcherMap: Map Promise) | ((context: object) => any)> = new Map(); + private matcherMap: Map = new Map(); protected adapter: FilteredAdapter | Adapter; protected watcher: Watcher | null = null; @@ -40,6 +41,17 @@ export class CoreEnforcer { protected autoBuildRoleLinks = true; protected autoNotifyWatcher = true; + private getExpression(asyncCompile: boolean, exp: string): Matcher { + const matcherKey = `${asyncCompile ? 'ASYNC[' : 'SYNC['}${exp}]`; + + let expression = this.matcherMap.get(matcherKey); + if (!expression) { + expression = asyncCompile ? compileAsync(exp) : compile(exp); + this.matcherMap.set(matcherKey, expression); + } + return expression; + } + /** * loadModel reloads the model from the model CONF file. * Because the policy is attached to a model, @@ -279,12 +291,11 @@ export class CoreEnforcer { throw new Error('Unable to find policy_effect in model'); } - const matcherKey = `${asyncCompile ? 'ASYNC[' : 'SYNC['}${expString}]`; + const HasEval: boolean = hasEval(expString); + let expression; - let expression = this.matcherMap.get(matcherKey); - if (!expression) { - expression = asyncCompile ? compileAsync(expString) : compile(expString); - this.matcherMap.set(matcherKey, expression); + if (!HasEval) { + expression = this.getExpression(asyncCompile, expString); } let policyEffects: Effect[]; @@ -315,8 +326,26 @@ export class CoreEnforcer { parameters[token] = p?.policy[i][j]; }); - const context = { ...parameters, ...functions }; - const result = asyncCompile ? await expression(context) : expression(context); + if (HasEval) { + const ruleNames: string[] = getEvalValue(expString); + let expWithRule = expString; + for (const ruleName of ruleNames) { + if (ruleName in parameters) { + const rule = escapeAssertion(parameters[ruleName]); + expWithRule = replaceEval(expWithRule, rule); + } else { + return false; + } + + expression = this.getExpression(asyncCompile, expWithRule); + } + } + + let result; + if (expression != undefined) { + const context = { ...parameters, ...functions }; + result = asyncCompile ? await expression(context) : expression(context); + } switch (typeof result) { case 'boolean': @@ -368,8 +397,12 @@ export class CoreEnforcer { parameters[token] = ''; }); - const context = { ...parameters, ...functions }; - const result = asyncCompile ? await expression(context) : expression(context); + let result = false; + + if (expression != undefined) { + const context = { ...parameters, ...functions }; + result = asyncCompile ? await expression(context) : expression(context); + } if (result) { policyEffects[0] = Effect.Allow; diff --git a/src/util/util.ts b/src/util/util.ts index 1265ccf..7cd3429 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -97,8 +97,11 @@ function replaceEval(s: string, rule: string): string { // getEvalValue returns the parameters of function eval function getEvalValue(s: string): string[] { - const subMatch: string[] = s.match(evalReg) as string[]; + const subMatch = s.match(evalReg); const rules: string[] = []; + if (!subMatch) { + return []; + } for (const rule of subMatch) { const index: number = rule.indexOf('('); rules.push(rule.slice(index + 1, -1)); diff --git a/test/enforcer.test.ts b/test/enforcer.test.ts index 2bb0f0c..0a9aa45 100644 --- a/test/enforcer.test.ts +++ b/test/enforcer.test.ts @@ -16,7 +16,7 @@ import { readFileSync } from 'fs'; import { newModel, newEnforcer, Enforcer, FileAdapter, StringAdapter, Util } from '../src'; -async function testEnforce(e: Enforcer, sub: string, obj: string, act: string, res: boolean): Promise { +async function testEnforce(e: Enforcer, sub: any, obj: string, act: string, res: boolean): Promise { await expect(e.enforce(sub, obj, act)).resolves.toBe(res); } @@ -535,3 +535,34 @@ describe('Unimplemented String Adapter methods', () => { await expect(a.removeFilteredPolicy('', '', 0, '')).rejects.toThrow('not implemented'); }); }); + +class TestSub { + Name: string; + Age: number; + + constructor(name: string, age: number) { + this.Name = name; + this.Age = age; + } +} + +test('test ABAC Scaling', async () => { + const e = await newEnforcer('examples/abac_rule_model.conf', 'examples/abac_rule_policy.csv'); + + const sub1 = new TestSub('alice', 16); + const sub2 = new TestSub('alice', 20); + const sub3 = new TestSub('alice', 65); + + await testEnforce(e, sub1, '/data1', 'read', false); + await testEnforce(e, sub1, '/data2', 'read', false); + await testEnforce(e, sub1, '/data1', 'write', false); + await testEnforce(e, sub1, '/data2', 'write', true); + await testEnforce(e, sub2, '/data1', 'read', true); + await testEnforce(e, sub2, '/data2', 'read', false); + await testEnforce(e, sub2, '/data1', 'write', false); + await testEnforce(e, sub2, '/data2', 'write', true); + await testEnforce(e, sub3, '/data1', 'read', true); + await testEnforce(e, sub3, '/data2', 'read', false); + await testEnforce(e, sub3, '/data1', 'write', false); + await testEnforce(e, sub3, '/data2', 'write', false); +});