Skip to content

Commit

Permalink
feat: improve effector for improve performance
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
- provides a new interface for Effector
  • Loading branch information
nodece committed May 17, 2020
1 parent 85f11ef commit 57de7b2
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 65 deletions.
51 changes: 21 additions & 30 deletions src/coreEnforcer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { compileAsync, compile } from 'expression-eval';
import { compile, compileAsync } from 'expression-eval';

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, hasEval, getEvalValue, escapeAssertion, replaceEval } from './util';
import { escapeAssertion, generateGFunction, getEvalValue, hasEval, replaceEval } from './util';
import { getLogger, logPrint } from './log';

type Matcher = ((context: object) => Promise<any>) | ((context: object) => any);

/**
* CoreEnforcer defines the core functionality of an enforcer.
*/
Expand Down Expand Up @@ -286,8 +287,8 @@ export class CoreEnforcer {
throw new Error('Unable to find matchers in model');
}

const effect = this.model.model.get('e')?.get('e')?.value;
if (!effect) {
const effectExpr = this.model.model.get('e')?.get('e')?.value;
if (!effectExpr) {
throw new Error('Unable to find policy_effect in model');
}

Expand All @@ -298,19 +299,15 @@ export class CoreEnforcer {
expression = this.getExpression(asyncCompile, expString);
}

let policyEffects: Effect[];
let matcherResults: number[];

const p = this.model.model.get('p')?.get('p');
const policyLen = p?.policy?.length;

const rTokens = this.model.model.get('r')?.get('r')?.tokens;
const rTokensLen = rTokens?.length;

if (policyLen && policyLen !== 0) {
policyEffects = new Array(policyLen);
matcherResults = new Array(policyLen);
const effectStream = this.eft.newStream(effectExpr);

if (policyLen && policyLen !== 0) {
for (let i = 0; i < policyLen; i++) {
const parameters: { [key: string]: any } = {};

Expand Down Expand Up @@ -347,46 +344,40 @@ export class CoreEnforcer {
result = asyncCompile ? await expression(context) : expression(context);
}

let eftRes: Effect;
switch (typeof result) {
case 'boolean':
if (!result) {
policyEffects[i] = Effect.Indeterminate;
continue;
}
eftRes = result ? Effect.Allow : Effect.Indeterminate;
break;
case 'number':
if (result === 0) {
policyEffects[i] = Effect.Indeterminate;
continue;
eftRes = Effect.Indeterminate;
} else {
matcherResults[i] = result;
eftRes = result;
}
break;
default:
throw new Error('matcher result should be boolean or number');
}

const eft = parameters['p_eft'];
if (eft) {
if (eft && eftRes === Effect.Allow) {
if (eft === 'allow') {
policyEffects[i] = Effect.Allow;
eftRes = Effect.Allow;
} else if (eft === 'deny') {
policyEffects[i] = Effect.Deny;
eftRes = Effect.Deny;
} else {
policyEffects[i] = Effect.Indeterminate;
eftRes = Effect.Indeterminate;
}
} else {
policyEffects[i] = Effect.Allow;
}

if (effect === 'priority(p_eft) || deny') {
const [res, done] = effectStream.pushEffect(eftRes);

if (done) {
break;
}
}
} else {
policyEffects = new Array(1);
matcherResults = new Array(1);

const parameters: { [key: string]: any } = {};

rTokens?.forEach((token, j): void => {
Expand All @@ -405,13 +396,13 @@ export class CoreEnforcer {
}

if (result) {
policyEffects[0] = Effect.Allow;
effectStream.pushEffect(Effect.Allow);
} else {
policyEffects[0] = Effect.Indeterminate;
effectStream.pushEffect(Effect.Indeterminate);
}
}

const res = this.eft.mergeEffects(effect, policyEffects, matcherResults);
const res = effectStream.current();

// only generate the request --> result string if the message
// is going to be logged.
Expand Down
37 changes: 5 additions & 32 deletions src/effect/defaultEffector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { Effect, Effector } from './effector';
import { Effector } from './effector';
import { EffectorStream } from './effectorStream';
import { DefaultEffectorStream } from './defaultEffectorStream';

/**
* DefaultEffector is default effector for Casbin.
*/
export class DefaultEffector implements Effector {
/**
* mergeEffects merges all matching results collected by the enforcer into a single decision.
* @param {string} expr
* @param {Effect[]} effects
* @param {number[]} results
* @returns {boolean}
*/
public mergeEffects(expr: string, effects: Effect[], results: number[]): boolean {
let result = false;

if (expr === 'some(where (p_eft == allow))') {
result = effects.some(n => n === Effect.Allow);
} else if (expr === '!some(where (p_eft == deny))') {
result = !effects.some(n => n === Effect.Deny);
} else if (expr === 'some(where (p_eft == allow)) && !some(where (p_eft == deny))') {
result = false;
for (const eft of effects) {
if (eft === Effect.Allow) {
result = true;
} else if (eft === Effect.Deny) {
result = false;
break;
}
}
} else if (expr === 'priority(p_eft) || deny') {
result = effects.some(n => n !== Effect.Indeterminate && n === Effect.Allow);
} else {
throw new Error('unsupported effect');
}

return result;
newStream(expr: string): EffectorStream {
return new DefaultEffectorStream(expr);
}
}
68 changes: 68 additions & 0 deletions src/effect/defaultEffectorStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright 2020 The Casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { EffectorStream } from './effectorStream';
import { Effect } from './effector';

/**
* DefaultEffectorStream is the default implementation of EffectorStream.
*/
export class DefaultEffectorStream implements EffectorStream {
private done = false;
private res = false;
private readonly expr: string;

constructor(expr: string) {
this.expr = expr;
}

current(): boolean {
return this.res;
}

public pushEffect(eft: Effect): [boolean, boolean] {
switch (this.expr) {
case 'some(where (p_eft == allow))':
if (eft == Effect.Allow) {
this.res = true;
this.done = true;
}
break;
case '!some(where (p_eft == deny))':
this.res = true;
if (eft == Effect.Deny) {
this.res = false;
this.done = true;
}
break;
case 'some(where (p_eft == allow)) && !some(where (p_eft == deny))':
if (eft == Effect.Allow) {
this.res = true;
} else if (eft == Effect.Deny) {
this.res = false;
this.done = true;
}
break;
case 'priority(p_eft) || deny':
if (eft !== Effect.Indeterminate) {
this.res = eft === Effect.Allow;
this.done = true;
}
break;
default:
throw new Error('unsupported effect');
}
return [this.res, this.done];
}
}
5 changes: 3 additions & 2 deletions src/effect/effector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

// Effect is the result for a policy rule.
// Values for policy effect.
import { EffectorStream } from './effectorStream';

export enum Effect {
Allow = 1,
Indeterminate,
Expand All @@ -22,6 +24,5 @@ export enum Effect {

// Effector is the interface for Casbin effectors.
export interface Effector {
// mergeEffects merges all matching results collected by the enforcer into a single decision.
mergeEffects(expr: string, effects: Effect[], results: number[]): boolean;
newStream(expr: string): EffectorStream;
}
22 changes: 22 additions & 0 deletions src/effect/effectorStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2020 The Casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { Effect } from './effector';

// EffectorStream provides a stream interface
export interface EffectorStream {
current(): boolean;

pushEffect(eft: Effect): [boolean, boolean];
}
4 changes: 3 additions & 1 deletion src/effect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

export * from './defaultEffector';
export * from './effector';
export * from './effectorStream';
export * from './defaultEffector';
export * from './defaultEffectorStream';

0 comments on commit 57de7b2

Please sign in to comment.