diff --git a/examples/mulitple_policy.csv b/examples/mulitple_policy.csv new file mode 100644 index 0000000..f896c69 --- /dev/null +++ b/examples/mulitple_policy.csv @@ -0,0 +1,2 @@ +p2, alice, data1, read +p2, bob, data2, write \ No newline at end of file diff --git a/src/coreEnforcer.ts b/src/coreEnforcer.ts index cc6a8b2..dbeb165 100644 --- a/src/coreEnforcer.ts +++ b/src/coreEnforcer.ts @@ -18,6 +18,8 @@ import { DefaultEffector, Effect, Effector } from './effect'; import { FunctionMap, Model, newModel, PolicyOp } from './model'; import { Adapter, FilteredAdapter, Watcher, BatchAdapter, UpdatableAdapter } from './persist'; import { DefaultRoleManager, RoleManager } from './rbac'; +import { EnforceContext } from './enforceContext'; + import { escapeAssertion, generateGFunction, @@ -45,6 +47,7 @@ export class CoreEnforcer { protected fm: FunctionMap = FunctionMap.loadFunctionMap(); protected eft: Effector = new DefaultEffector(); private matcherMap: Map = new Map(); + private defaultEnforceContext: EnforceContext = new EnforceContext('r', 'p', 'e', 'm'); protected adapter: UpdatableAdapter | FilteredAdapter | Adapter | BatchAdapter; protected watcher: Watcher | null = null; @@ -368,7 +371,12 @@ export class CoreEnforcer { } } - private *privateEnforce(asyncCompile = true, explain = false, ...rvals: any[]): EnforceResult { + private *privateEnforce( + asyncCompile = true, + explain = false, + enforceContext: EnforceContext = new EnforceContext('r', 'p', 'e', 'm'), + ...rvals: any[] + ): EnforceResult { if (!this.enabled) { return true; } @@ -387,12 +395,12 @@ export class CoreEnforcer { functions[key] = generateGFunction(rm); }); - const expString = this.model.model.get('m')?.get('m')?.value; + const expString = this.model.model.get('m')?.get(enforceContext.mType)?.value; if (!expString) { throw new Error('Unable to find matchers in model'); } - const effectExpr = this.model.model.get('e')?.get('e')?.value; + const effectExpr = this.model.model.get('e')?.get(enforceContext.eType)?.value; if (!effectExpr) { throw new Error('Unable to find policy_effect in model'); } @@ -400,10 +408,10 @@ export class CoreEnforcer { const HasEval: boolean = hasEval(expString); let expression: Matcher | undefined = undefined; - const p = this.model.model.get('p')?.get('p'); + const p = this.model.model.get('p')?.get(enforceContext.pType); const policyLen = p?.policy?.length; - const rTokens = this.model.model.get('r')?.get('r')?.tokens; + const rTokens = this.model.model.get('r')?.get(enforceContext.rType)?.tokens; const rTokensLen = rTokens?.length; const effectStream = this.eft.newStream(effectExpr); @@ -551,7 +559,11 @@ export class CoreEnforcer { * @return whether to allow the request. */ public enforceSync(...rvals: any[]): boolean { - return generatorRunSync(this.privateEnforce(false, false, ...rvals)); + if (rvals[0] instanceof EnforceContext) { + const enforceContext: EnforceContext = rvals.shift(); + return generatorRunSync(this.privateEnforce(false, false, enforceContext, ...rvals)); + } + return generatorRunSync(this.privateEnforce(false, false, this.defaultEnforceContext, ...rvals)); } /** @@ -565,7 +577,11 @@ export class CoreEnforcer { * @return whether to allow the request and the reason rule. */ public enforceExSync(...rvals: any[]): [boolean, string[]] { - return generatorRunSync(this.privateEnforce(false, true, ...rvals)); + if (rvals[0] instanceof EnforceContext) { + const enforceContext: EnforceContext = rvals.shift(); + return generatorRunSync(this.privateEnforce(false, true, enforceContext, ...rvals)); + } + return generatorRunSync(this.privateEnforce(false, true, this.defaultEnforceContext, ...rvals)); } /** @@ -584,7 +600,11 @@ export class CoreEnforcer { * @return whether to allow the request. */ public async enforce(...rvals: any[]): Promise { - return generatorRunAsync(this.privateEnforce(true, false, ...rvals)); + if (rvals[0] instanceof EnforceContext) { + const enforceContext: EnforceContext = rvals.shift(); + return generatorRunAsync(this.privateEnforce(true, false, enforceContext, ...rvals)); + } + return generatorRunAsync(this.privateEnforce(true, false, this.defaultEnforceContext, ...rvals)); } /** @@ -596,7 +616,11 @@ export class CoreEnforcer { * @return whether to allow the request and the reason rule. */ public async enforceEx(...rvals: any[]): Promise<[boolean, string[]]> { - return generatorRunAsync(this.privateEnforce(true, true, ...rvals)); + if (rvals[0] instanceof EnforceContext) { + const enforceContext: EnforceContext = rvals.shift(); + return generatorRunAsync(this.privateEnforce(true, true, enforceContext, ...rvals)); + } + return generatorRunAsync(this.privateEnforce(true, true, this.defaultEnforceContext, ...rvals)); } /** diff --git a/src/enforceContext.ts b/src/enforceContext.ts new file mode 100644 index 0000000..d235676 --- /dev/null +++ b/src/enforceContext.ts @@ -0,0 +1,20 @@ +import { newEnforcer } from './enforcer'; + +export class EnforceContext { + public pType: string; + public rType: string; + public eType: string; + public mType: string; + + constructor(rType: string, pType: string, eType: string, mType: string) { + this.pType = pType; + this.eType = eType; + this.mType = mType; + this.rType = rType; + } +} +export class NewEnforceContext { + constructor(index: string) { + return new EnforceContext('r' + index, 'p' + index, 'e' + index, 'm' + index); + } +} diff --git a/src/enforcer.ts b/src/enforcer.ts index 99307b0..85bfc70 100644 --- a/src/enforcer.ts +++ b/src/enforcer.ts @@ -14,7 +14,7 @@ import { ManagementEnforcer } from './managementEnforcer'; import { Model, newModel } from './model'; -import { Adapter, FileAdapter, StringAdapter } from './persist'; +import { Adapter, FileAdapter, MemoryAdapter } from './persist'; import { getLogger } from './log'; import { arrayRemoveDuplicates } from './util'; @@ -40,7 +40,7 @@ export class Enforcer extends ManagementEnforcer { * @param lazyLoad whether to load policy at initial time */ public async initWithString(modelPath: string, policyString: string, lazyLoad = false): Promise { - const a = new StringAdapter(policyString); + const a = new MemoryAdapter(policyString); await this.initWithAdapter(modelPath, a, lazyLoad); } diff --git a/src/index.ts b/src/index.ts index 12ffde9..74113a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,5 +23,6 @@ export * from './model'; export * from './persist'; export * from './rbac'; export * from './log'; +export { EnforceContext } from './enforceContext'; export * from './frontend'; export { Util }; diff --git a/src/model/model.ts b/src/model/model.ts index 60d0392..fbb3631 100644 --- a/src/model/model.ts +++ b/src/model/model.ts @@ -42,8 +42,11 @@ export class Model { /** * constructor is the constructor for Model. */ - constructor() { + constructor(text?: string) { this.model = new Map>(); + if (text) { + this.loadModelFromText(text); + } } private loadAssertion(cfg: ConfigInterface, sec: string, key: string): boolean { diff --git a/src/persist/index.ts b/src/persist/index.ts index 59d0b4d..0c54146 100644 --- a/src/persist/index.ts +++ b/src/persist/index.ts @@ -1,6 +1,6 @@ export * from './adapter'; export * from './fileAdapter'; -export * from './stringAdapter'; +export * from './memoryAdapter'; export * from './helper'; export * from './watcher'; export * from './filteredAdapter'; diff --git a/src/persist/memoryAdapter.ts b/src/persist/memoryAdapter.ts new file mode 100644 index 0000000..bf33135 --- /dev/null +++ b/src/persist/memoryAdapter.ts @@ -0,0 +1,105 @@ +import { Adapter } from './adapter'; +import { Model } from '../model'; +import { Helper } from './helper'; +import { BatchAdapter } from './batchAdapter'; +import { arrayEquals, policyArrayToString, policyStringToArray } from '../util'; + +/** + * MemoryAdapter is the memory adapter for Casbin. + * It can load policy from a string. + */ +export class MemoryAdapter implements Adapter, BatchAdapter { + protected policies: string[][] = []; + + /** + * MemoryAdapter is the constructor for MemoryAdapter. + * @param policy - policy formatted as a CSV string, or policy array. + */ + constructor(policy: string | string[][]) { + if (!policy) { + return; + } + if (typeof policy === 'string') { + this.policies = policyStringToArray(policy); + } else { + this.policies = policy; + } + } + + /** + * hasPolicy checks if specific policy exists in storage. + */ + public hasPolicy(policy: string[]): boolean { + return this.policies.some((prePolicy) => { + return arrayEquals(prePolicy, policy); + }); + } + + /** + * loadPolicy loads data in adapter to model. + * @param model + */ + public async loadPolicy(model: Model): Promise { + this.policies.forEach((n: string[]) => { + if (!n) { + return; + } + Helper.loadPolicyLine(policyArrayToString(n), model); + }); + } + + /** + * savePolicy saves all policy rules to the storage. + */ + public async savePolicy(model: Model): Promise { + throw new Error('not implemented'); + } + + /** + * addPolicy adds a policy rule to the storage. + */ + public async addPolicy(sec: string, ptype: string, rule: string[]): Promise { + const policy = rule.slice(); + policy.unshift(ptype); + if (!this.hasPolicy(rule)) { + this.policies.push(policy); + } + } + + /** + * removePolicy removes a policy rule from the storage. + */ + public async removePolicy(sec: string, ptype: string, rule: string[]): Promise { + const ruleClone = rule.slice(); + ruleClone.unshift(ptype); + this.policies = this.policies.filter((r) => !arrayEquals(ruleClone, r)); + } + + /** + * removeFilteredPolicy removes policy rules that match the filter from the storage. + */ + public async removeFilteredPolicy(sec: string, ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise { + throw new Error('not implemented'); + } + + /** + * addPolicies adds policy rules to the storage. + */ + public async addPolicies(sec: string, ptype: string, rules: string[][]): Promise { + for (const rule of rules) { + if (!this.hasPolicy(rule)) { + await this.addPolicy(sec, ptype, rule); + } + } + } + + /** + * removePolicies removes policy rules from the storage. + * This is part of the Auto-Save feature. + */ + public async removePolicies(sec: string, ptype: string, rules: string[][]): Promise { + this.policies = this.policies.filter((rule) => { + return !rules.some((deleteRule) => arrayEquals(deleteRule, rule)); + }); + } +} diff --git a/src/persist/stringAdapter.ts b/src/persist/stringAdapter.ts deleted file mode 100644 index 8cf43a8..0000000 --- a/src/persist/stringAdapter.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Adapter } from './adapter'; -import { Model } from '../model'; -import { Helper } from './helper'; - -/** - * StringAdapter is the string adapter for Casbin. - * It can load policy from a string. - */ -export class StringAdapter implements Adapter { - public readonly policy: string; - - /** - * StringAdapter is the constructor for StringAdapter. - * @param {string} policy policy formatted as a CSV string. - */ - - constructor(policy: string) { - this.policy = policy; - } - - public async loadPolicy(model: Model): Promise { - if (!this.policy) { - throw new Error('Invalid policy, policy document cannot be false-y'); - } - await this.loadRules(model, Helper.loadPolicyLine); - } - - private async loadRules(model: Model, handler: (line: string, model: Model) => void): Promise { - const rules = this.policy.split('\n'); - rules.forEach((n: string, index: number) => { - if (!n) { - return; - } - handler(n, model); - }); - } - - /** - * savePolicy saves all policy rules to the storage. - */ - public async savePolicy(model: Model): Promise { - throw new Error('not implemented'); - } - - /** - * addPolicy adds a policy rule to the storage. - */ - public async addPolicy(sec: string, ptype: string, rule: string[]): Promise { - throw new Error('not implemented'); - } - - /** - * removePolicy removes a policy rule from the storage. - */ - public async removePolicy(sec: string, ptype: string, rule: string[]): Promise { - throw new Error('not implemented'); - } - - /** - * removeFilteredPolicy removes policy rules that match the filter from the storage. - */ - public async removeFilteredPolicy(sec: string, ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise { - throw new Error('not implemented'); - } -} diff --git a/src/util/util.ts b/src/util/util.ts index af680cd..89e3718 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -17,9 +17,12 @@ import * as fs from 'fs'; // escapeAssertion escapes the dots in the assertion, // because the expression evaluation doesn't support such variable names. function escapeAssertion(s: string): string { - s = s.replace(/r\./g, 'r_'); - s = s.replace(/p\./g, 'p_'); - return s; + if (s.startsWith('r') || s.startsWith('p')) { + s = s.replace('.', '_'); + } + return s.replace(/([| =)(&<>,+\-!*\/])([rp][0-9]*)\./g, (match) => { + return match.replace('.', '_'); + }); } // removeComments removes the comments starting with # in the text. @@ -171,6 +174,68 @@ function deepCopy(obj: Array | any): any { return newObj; } +function policyArrayToString(policy: string[]): string { + return policy + .map((n) => { + return `"${(n === null ? '' : n.toString()).replace(/"/g, '""')}"`; + }) + .join(','); +} + +function policyStringToArray(policy: string): string[][] { + const endCommaRe = /,$/; + const quotaWrapperRe = /^".*"$/; + + const lines: string[] = policy.split(/\r?\n/); + const arrays: string[][] = []; + + for (let line of lines) { + const commentLabel = line.indexOf('#'); + if (commentLabel !== -1) { + line = line.substr(0, commentLabel); + } + + line = line.trim(); + + if (endCommaRe.test(line)) { + throw new Error('The csv standard does not allow a comma at the end of a sentence'); + } + + const slices = line.split(','); + let tokens: string[] = []; + + for (let slice of slices) { + slice = slice.trim(); + + // Remove parcel quotes + if (quotaWrapperRe.test(slice)) { + slice = slice.substr(1, slice.length - 2); + } + + if (slice.includes('""')) { + // "" Escape processing + for (let i = 0; i < slice.length; ) { + if (slice[i] === '"') { + if (slice[i + 1] !== '"') { + throw new Error(`Unescaped " at ${line}`); + } + i += 2; + } + i += 1; + } + + slice = slice.replace(/""/g, '"'); + } + + tokens.push(slice); + } + + arrays.push(deepCopy(tokens)); + tokens = []; + } + return arrays; +} + function customIn(a: number | string, b: number | string): number { if ((b as any) instanceof Array) { return (((b as any) as Array).includes(a) as unknown) as number; @@ -216,6 +281,8 @@ export { generatorRunSync, generatorRunAsync, deepCopy, + policyArrayToString, + policyStringToArray, customIn, bracketCompatible, }; diff --git a/test/enforcer.test.ts b/test/enforcer.test.ts index e3565c1..79d8edf 100644 --- a/test/enforcer.test.ts +++ b/test/enforcer.test.ts @@ -14,7 +14,9 @@ import { readFileSync } from 'fs'; -import { newModel, newEnforcer, Enforcer, FileAdapter, StringAdapter, Util } from '../src'; +import { newModel, newEnforcer, Enforcer, FileAdapter, MemoryAdapter, Util } from '../src'; +import { NewEnforceContext, EnforceContext } from '../src/enforceContext'; +import { getEnforcerWithPath, getStringAdapter } from './utils'; async function testEnforce(e: Enforcer, sub: any, obj: string, act: string, res: boolean): Promise { await expect(e.enforce(sub, obj, act)).resolves.toBe(res); @@ -391,7 +393,7 @@ test('TestInitWithAdapter', async () => { test('TestInitWithStringAdapter', async () => { const policy = readFileSync('examples/basic_policy.csv').toString(); - const adapter = new StringAdapter(policy); + const adapter = new MemoryAdapter(policy); const e = await newEnforcer('examples/basic_model.conf', adapter); await testEnforce(e, 'alice', 'data1', 'read', true); @@ -455,7 +457,7 @@ test('TestSetAdapterFromString', async () => { const policy = readFileSync('examples/basic_policy.csv').toString(); - const a = new StringAdapter(policy); + const a = new MemoryAdapter(policy); e.setAdapter(a); await e.loadPolicy(); @@ -490,7 +492,7 @@ test('TestInitEmpty with String Adapter', async () => { m.addDef('m', 'm', 'r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)'); const policy = readFileSync('examples/keymatch_policy.csv').toString(); - const a = new StringAdapter(policy); + const a = new MemoryAdapter(policy); e.setModel(m); e.setAdapter(a); @@ -523,11 +525,11 @@ describe('Unimplemented File Adapter methods', () => { describe('Unimplemented String Adapter methods', () => { let e = {} as Enforcer; - let a = {} as StringAdapter; + let a = {} as MemoryAdapter; beforeEach(async () => { const policy = readFileSync('examples/basic_policy.csv').toString(); - a = new StringAdapter(policy); + a = new MemoryAdapter(policy); e = await newEnforcer('examples/basic_model.conf', a); }); @@ -535,14 +537,6 @@ describe('Unimplemented String Adapter methods', () => { await expect(a.savePolicy(e.getModel())).rejects.toThrow('not implemented'); }); - test('addPolicy', async () => { - await expect(a.addPolicy('', '', [''])).rejects.toThrow('not implemented'); - }); - - test('removePolicy', async () => { - await expect(a.removePolicy('', '', [''])).rejects.toThrow('not implemented'); - }); - test('removeFilteredPolicy', async () => { await expect(a.removeFilteredPolicy('', '', 0, '')).rejects.toThrow('not implemented'); }); @@ -586,7 +580,7 @@ test('test ABAC multiple eval()', async () => { m.addDef('e', 'e', 'some(where (p.eft == allow))'); m.addDef('m', 'm', 'eval(p.sub_rule_1) && eval(p.sub_rule_2) && r.act == p.act'); - const policy = new StringAdapter( + const policy = new MemoryAdapter( ` p, r.sub > 50, r.obj > 50, read ` @@ -697,3 +691,71 @@ test('TestEnforceExWithPriorityModel', async () => { testEnforceEx(e, 'bob', 'data2', 'read', [true, ['data2_allow_group', 'data2', 'read', 'allow']]); testEnforceEx(e, 'alice', 'data2', 'read', [false, []]); }); + +test('TestEnforce Multiple policies config', async () => { + const m = newModel(); + m.addDef('r', 'r2', 'sub, obj, act'); + m.addDef('p', 'p2', 'sub, obj, act'); + m.addDef('g', 'g', '_, _'); + m.addDef('e', 'e2', 'some(where (p.eft == allow))'); + m.addDef('m', 'm2', 'g(r2.sub, p2.sub) && r2.obj == p2.obj && r2.act == p2.act'); + const a = getStringAdapter('examples/mulitple_policy.csv'); + + const e = await newEnforcer(m, a); + + //const e = await getEnforcerWithPath(m); + const enforceContext = new EnforceContext('r2', 'p2', 'e2', 'm2'); + await expect(e.enforce(enforceContext, 'alice', 'data1', 'read')).resolves.toStrictEqual(true); + await expect(e.enforce(enforceContext, 'bob', 'data2', 'write')).resolves.toStrictEqual(true); +}); + +test('new EnforceContext config', async () => { + const m = newModel(); + m.addDef('r', 'r2', 'sub, obj, act'); + m.addDef('p', 'p2', 'sub, obj, act'); + m.addDef('g', 'g', '_, _'); + m.addDef('e', 'e2', 'some(where (p.eft == allow))'); + m.addDef('m', 'm2', 'g(r2.sub, p2.sub) && r2.obj == p2.obj && r2.act == p2.act'); + const a = getStringAdapter('examples/mulitple_policy.csv'); + + const e = await newEnforcer(m, a); + + //const e = await getEnforcerWithPath(m); + const enforceContext = new NewEnforceContext('2'); + await expect(e.enforce(enforceContext, 'alice', 'data1', 'read')).resolves.toStrictEqual(true); + await expect(e.enforce(enforceContext, 'bob', 'data2', 'write')).resolves.toStrictEqual(true); +}); + +test('TestEnforceEX Multiple policies config', async () => { + const m = newModel(); + m.addDef('r', 'r2', 'sub, obj, act'); + m.addDef('p', 'p2', 'sub, obj, act'); + m.addDef('g', 'g', '_, _'); + m.addDef('e', 'e2', 'some(where (p.eft == allow))'); + m.addDef('m', 'm2', 'g(r2.sub, p2.sub) && r2.obj == p2.obj && r2.act == p2.act'); + const a = getStringAdapter('examples/mulitple_policy.csv'); + + const e = await newEnforcer(m, a); + + //const e = await getEnforcerWithPath(m); + const enforceContext = new EnforceContext('r2', 'p2', 'e2', 'm2'); + await expect(e.enforceEx(enforceContext, 'alice', 'data1', 'read')).resolves.toStrictEqual([true, ['alice', 'data1', 'read']]); + await expect(e.enforceEx(enforceContext, 'bob', 'data2', 'write')).resolves.toStrictEqual([true, ['bob', 'data2', 'write']]); +}); + +test('new EnforceContextEX config', async () => { + const m = newModel(); + m.addDef('r', 'r2', 'sub, obj, act'); + m.addDef('p', 'p2', 'sub, obj, act'); + m.addDef('g', 'g', '_, _'); + m.addDef('e', 'e2', 'some(where (p.eft == allow))'); + m.addDef('m', 'm2', 'g(r2.sub, p2.sub) && r2.obj == p2.obj && r2.act == p2.act'); + const a = getStringAdapter('examples/mulitple_policy.csv'); + + const e = await newEnforcer(m, a); + + //const e = await getEnforcerWithPath(m); + const enforceContext = new NewEnforceContext('2'); + await expect(e.enforceEx(enforceContext, 'alice', 'data1', 'read')).resolves.toStrictEqual([true, ['alice', 'data1', 'read']]); + await expect(e.enforceEx(enforceContext, 'bob', 'data2', 'write')).resolves.toStrictEqual([true, ['bob', 'data2', 'write']]); +}); diff --git a/test/util.test.ts b/test/util.test.ts index fe03076..227f350 100644 --- a/test/util.test.ts +++ b/test/util.test.ts @@ -193,3 +193,49 @@ test('bracketCompatible', () => { ) ).toEqual("g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ['data2', 'data3'] || r.obj in ['data4', 'data5']"); }); + +test('test policyStringToArray', () => { + expect(util.policyStringToArray('p,ali""ce,data1,read')).toEqual([['p', 'ali"ce', 'data1', 'read']]); + expect(util.policyStringToArray(`"p","alice","data1","read"`)).toEqual([['p', 'alice', 'data1', 'read']]); + expect(util.policyStringToArray(`"p"," alice","data1 ","read"`)).toEqual([['p', ' alice', 'data1 ', 'read']]); + expect(util.policyStringToArray(`p,alice,data1,read\np,bob,data1,write`)).toEqual([ + ['p', 'alice', 'data1', 'read'], + ['p', 'bob', 'data1', 'write'], + ]); +}); + +test('test policyArrayToString', () => { + expect(util.policyArrayToString(['p', 'alice', 'data1', 'read'])).toEqual(`"p","alice","data1","read"`); + expect(util.policyArrayToString(['p', 'alice ', ' data1', 'read'])).toEqual(`"p","alice "," data1","read"`); +}); + +test('test keyGetFunc', () => { + expect(util.keyGetFunc('/foo/bar', '/foo/*')).toEqual('bar'); + expect(util.keyGetFunc('/bar/foo', '/foo/*')).toEqual(''); +}); + +test('test keyGet2Func', () => { + expect(util.keyGet2Func('/foo/bar', '/foo/*', 'bar')).toEqual(''); + expect(util.keyGet2Func('/foo/baz', '/foo/:bar', 'bar')).toEqual('baz'); + expect(util.keyGet2Func('/foo/baz/foo', '/foo/:bar/foo', 'bar')).toEqual('baz'); + expect(util.keyGet2Func('/baz', '/foo', 'bar')).toEqual(''); + expect(util.keyGet2Func('/foo/baz', '/foo', 'bar')).toEqual(''); +}); + +test('test escapeAssertion', () => { + expect(util.escapeAssertion('r.attr.value == p.attr')).toEqual('r_attr.value == p_attr'); + expect(util.escapeAssertion('r.attp.value || p.attr')).toEqual('r_attp.value || p_attr'); + expect(util.escapeAssertion('r.attp.value && p.attr')).toEqual('r_attp.value && p_attr'); + expect(util.escapeAssertion('r.attp.value >p.attr')).toEqual('r_attp.value >p_attr'); + expect(util.escapeAssertion('r.attp.value { + if (!modelPath) { + return await newEnforcer(); + } + + let m: Model; + if (typeof modelPath === 'string') { + m = new Model(path2Content(modelPath)); + } else { + m = modelPath; + } + + let a: Adapter | undefined; + if (typeof policyPath === 'string') { + a = new MemoryAdapter(path2Content(policyPath)); + } else { + a = policyPath; + } + + return await newEnforcer(m, a, logOption); +} + +export function getStringAdapter(path: string): MemoryAdapter { + return new MemoryAdapter(path2Content(path)); +}