Skip to content

Commit 0ace1a6

Browse files
committed
feat: implementation cachedEnforcer
1 parent ab93a92 commit 0ace1a6

File tree

5 files changed

+115
-19
lines changed

5 files changed

+115
-19
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "casbin",
3-
"version": "3.0.9",
3+
"version": "0.0.0-development",
44
"description": "An authorization library that supports access control models like ACL, RBAC, ABAC in Node.JS",
55
"main": "lib/index.js",
66
"typings": "lib/index.d.ts",

src/cachedEnforcer.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Enforcer, newEnforcerWithClass } from './enforcer';
2+
3+
// CachedEnforcer wraps Enforcer and provides decision cache
4+
export class CachedEnforcer extends Enforcer {
5+
private enableCache = true;
6+
private m = new Map<string, boolean>();
7+
8+
// invalidateCache deletes all the existing cached decisions.
9+
public invalidateCache(): void {
10+
this.m = new Map<string, boolean>();
11+
}
12+
13+
// setEnableCache determines whether to enable cache on e nforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.
14+
public setEnableCache(enableCache: boolean): void {
15+
this.enableCache = enableCache;
16+
}
17+
18+
private static canCache(...rvals: any[]): boolean {
19+
return rvals.every(n => typeof n === 'string');
20+
}
21+
22+
private static getCacheKey(...rvals: string[]): string {
23+
return rvals.join('$$');
24+
}
25+
26+
private getCache(key: string): boolean | undefined {
27+
return this.m.get(key);
28+
}
29+
30+
private setCache(key: string, value: boolean): void {
31+
this.m.set(key, value);
32+
}
33+
34+
// enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
35+
// if rvals is not string , ingore the cache
36+
public async enforce(...rvals: any[]): Promise<boolean> {
37+
if (!this.enableCache) {
38+
return super.enforce(...rvals);
39+
}
40+
41+
let key = '';
42+
const cache = CachedEnforcer.canCache(...rvals);
43+
44+
if (cache) {
45+
key = CachedEnforcer.getCacheKey(...rvals);
46+
const res = this.getCache(key);
47+
48+
if (res != undefined) {
49+
return res;
50+
}
51+
}
52+
53+
const res = await super.enforce(...rvals);
54+
55+
if (cache) {
56+
this.setCache(key, res);
57+
}
58+
59+
return res;
60+
}
61+
}
62+
63+
// newCachedEnforcer creates a cached enforcer via file or DB.
64+
export async function newCachedEnforcer(...params: any[]): Promise<CachedEnforcer> {
65+
return newEnforcerWithClass(CachedEnforcer, ...params);
66+
}

src/enforcer.ts

+22-18
Original file line numberDiff line numberDiff line change
@@ -317,24 +317,8 @@ export class Enforcer extends ManagementEnforcer {
317317
}
318318
}
319319

320-
/**
321-
* newEnforcer creates an enforcer via file or DB.
322-
*
323-
* File:
324-
* ```js
325-
* const e = new Enforcer('path/to/basic_model.conf', 'path/to/basic_policy.csv');
326-
* ```
327-
*
328-
* MySQL DB:
329-
* ```js
330-
* const a = new MySQLAdapter('mysql', 'mysql_username:mysql_password@tcp(127.0.0.1:3306)/');
331-
* const e = new Enforcer('path/to/basic_model.conf', a);
332-
* ```
333-
*
334-
* @param params
335-
*/
336-
export async function newEnforcer(...params: any[]): Promise<Enforcer> {
337-
const e = new Enforcer();
320+
export async function newEnforcerWithClass<T extends Enforcer>(enforcer: new () => T, ...params: any[]): Promise<T> {
321+
const e = new enforcer();
338322

339323
let parsedParamLen = 0;
340324
if (params.length >= 1) {
@@ -373,3 +357,23 @@ export async function newEnforcer(...params: any[]): Promise<Enforcer> {
373357

374358
return e;
375359
}
360+
361+
/**
362+
* newEnforcer creates an enforcer via file or DB.
363+
*
364+
* File:
365+
* ```js
366+
* const e = new Enforcer('path/to/basic_model.conf', 'path/to/basic_policy.csv');
367+
* ```
368+
*
369+
* MySQL DB:
370+
* ```js
371+
* const a = new MySQLAdapter('mysql', 'mysql_username:mysql_password@tcp(127.0.0.1:3306)/');
372+
* const e = new Enforcer('path/to/basic_model.conf', a);
373+
* ```
374+
*
375+
* @param params
376+
*/
377+
export async function newEnforcer(...params: any[]): Promise<Enforcer> {
378+
return newEnforcerWithClass(Enforcer, ...params);
379+
}

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import * as Util from './util';
1616

1717
export * from './config';
1818
export * from './enforcer';
19+
export * from './cachedEnforcer';
1920
export * from './effect';
2021
export * from './model';
2122
export * from './persist';

test/cachedEnforcer.test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2020 The Casbin Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Enforcer, newCachedEnforcer } from '../src';
16+
17+
async function testEnforce(e: Enforcer, sub: string, obj: string, act: string, res: boolean): Promise<void> {
18+
await expect(e.enforce(sub, obj, act)).resolves.toBe(res);
19+
}
20+
21+
test('TestRBACModel', async () => {
22+
const e = await newCachedEnforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');
23+
24+
await testEnforce(e, 'alice', 'data1', 'read', true);
25+
});

0 commit comments

Comments
 (0)