You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@casbin.apache.org by hs...@apache.org on 2026/10/20 17:49:35 UTC
(casbin-node-casbin) branch master updated: fix: support role definition params and implicit keyMatch domain matching
This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-node-casbin.git
The following commit(s) were added to refs/heads/master by this push:
new fed65ac fix: support role definition params and implicit keyMatch domain matching
fed65ac is described below
commit fed65ace4fcbd15755802d911a4a7d4e71f1b7aa
Author: Yang Luo <hs...@gmail.com>
AuthorDate: Wed Oct 21 01:49:23 2026 +0800
fix: support role definition params and implicit keyMatch domain matching
---
.github/workflows/main.yml | 8 ++++++
src/coreEnforcer.ts | 20 +++++++++++--
src/model/assertion.ts | 8 ++++--
src/model/model.ts | 4 +++
test/conformance.test.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 43ad8fe..386ae5c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -14,12 +14,20 @@ jobs:
steps:
- uses: actions/checkout@v4
+ - uses: actions/checkout@v4
+ with:
+ repository: apache/casbin
+ path: casbin-conformance
+ sparse-checkout: conformance
+
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: yarn install
- run: yarn lint
- run: yarn test
+ env:
+ CASBIN_CONFORMANCE_DIR: ${{ github.workspace }}/casbin-conformance/conformance
- run: yarn coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
diff --git a/src/coreEnforcer.ts b/src/coreEnforcer.ts
index 0fa1a24..8cfe6f4 100644
--- a/src/coreEnforcer.ts
+++ b/src/coreEnforcer.ts
@@ -25,6 +25,7 @@ import {
generateGFunction,
generateSyncedGFunction,
getEvalValue,
+ keyMatchFunc,
hasEval,
replaceEval,
generatorRunSync,
@@ -211,12 +212,27 @@ export class CoreEnforcer {
this.rmMap = new Map<string, RoleManager>();
const rm = this.model.model.get('g');
if (rm) {
- for (const ptype of rm.keys()) {
- this.rmMap.set(ptype, new DefaultRoleManager(10));
+ for (const [ptype, ast] of rm) {
+ const roleManager = new DefaultRoleManager(10);
+ this.rmMap.set(ptype, roleManager);
+ const count = (ast.value.match(/_/g) || []).length - ast.paramsTokens.length;
+ if (count > 2 && this.usesKeyMatchForDomain()) {
+ roleManager.addDomainMatchingFunc(keyMatchFunc);
+ }
}
}
}
+ private usesKeyMatchForDomain(): boolean {
+ const rDomainToken = this.model.model.get('r')?.get('r')?.tokens[1];
+ const pDomainToken = this.model.model.get('p')?.get('p')?.tokens[1];
+ if (!rDomainToken || !pDomainToken) {
+ return false;
+ }
+ const matcher = this.model.model.get('m')?.get('m')?.value ?? '';
+ return matcher.includes(`keyMatch(${rDomainToken}, ${pDomainToken})`);
+ }
+
public sortPolicies(): void {
this.model.model.get('p')?.forEach((value, key) => {
const policy = value.policy;
diff --git a/src/model/assertion.ts b/src/model/assertion.ts
index 4a65930..727bf17 100644
--- a/src/model/assertion.ts
+++ b/src/model/assertion.ts
@@ -22,6 +22,7 @@ export class Assertion {
public key: string;
public value: string;
public tokens: string[];
+ public paramsTokens: string[];
public policy: string[][];
public rm: rbac.RoleManager;
public fieldIndexMap: Map<string, number>;
@@ -33,6 +34,7 @@ export class Assertion {
this.key = '';
this.value = '';
this.tokens = [];
+ this.paramsTokens = [];
this.policy = [];
this.rm = new rbac.DefaultRoleManager(10);
this.fieldIndexMap = new Map<string, number>();
@@ -53,10 +55,10 @@ export class Assertion {
}
switch (op) {
case PolicyOp.PolicyAdd:
- await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
+ await this.rm.addLink(rule[0], rule[1], ...rule.slice(2, count - this.paramsTokens.length));
break;
case PolicyOp.PolicyRemove:
- await this.rm.deleteLink(rule[0], rule[1], ...rule.slice(2));
+ await this.rm.deleteLink(rule[0], rule[1], ...rule.slice(2, count - this.paramsTokens.length));
break;
default:
throw new Error('unsupported operation');
@@ -74,7 +76,7 @@ export class Assertion {
if (rule.length > count) {
rule = rule.slice(0, count);
}
- await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
+ await this.rm.addLink(rule[0], rule[1], ...rule.slice(2, count - this.paramsTokens.length));
}
logPrint(`Role links for: ${this.key}`);
await this.rm.printRoles();
diff --git a/src/model/model.ts b/src/model/model.ts
index d31666d..20ffda0 100644
--- a/src/model/model.ts
+++ b/src/model/model.ts
@@ -105,6 +105,10 @@ export class Model {
ast.value = value;
} else {
+ if (sec === 'g') {
+ const params = value.match(/\((.*?)\)/);
+ ast.paramsTokens = params ? params[1].split(',') : [];
+ }
ast.value = util.escapeAssertion(value);
}
diff --git a/test/conformance.test.ts b/test/conformance.test.ts
new file mode 100644
index 0000000..e5b19a3
--- /dev/null
+++ b/test/conformance.test.ts
@@ -0,0 +1,72 @@
+// Copyright 2026 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 { readdirSync, readFileSync } from 'fs';
+import * as path from 'path';
+
+import { newEnforcer } from '../src';
+
+interface ConformanceTest {
+ request: string[];
+ allow: boolean;
+ explain: string[];
+}
+
+interface ConformanceCase {
+ model: string;
+ policy: string;
+ tests: ConformanceTest[];
+}
+
+const conformanceDir = process.env.CASBIN_CONFORMANCE_DIR;
+const conformanceTest = conformanceDir ? test : test.skip;
+
+conformanceTest('TestConformance', async () => {
+ const dir = conformanceDir as string;
+ const casesDir = path.join(dir, 'cases');
+ const dataDir = path.join(dir, 'data');
+ const files = readdirSync(casesDir)
+ .filter((f) => f.endsWith('.json'))
+ .sort();
+ expect(files.length).toBeGreaterThan(0);
+
+ const mismatches: string[] = [];
+ for (const file of files) {
+ const c: ConformanceCase = JSON.parse(readFileSync(path.join(casesDir, file), 'utf-8'));
+ let e;
+ try {
+ e = await newEnforcer(path.join(dataDir, c.model), path.join(dataDir, c.policy));
+ } catch (err) {
+ mismatches.push(`${file}: newEnforcer failed: ${err}`);
+ continue;
+ }
+ for (const t of c.tests) {
+ const want = JSON.stringify([t.allow, t.explain]);
+ let got: string;
+ try {
+ const [allow, explain] = await e.enforceEx(...t.request);
+ got = JSON.stringify([allow, explain ?? []]);
+ } catch (err) {
+ got = `error: ${err}`;
+ }
+ if (got !== want) {
+ mismatches.push(`${file}: request ${JSON.stringify(t.request)}: got ${got}, want ${want}`);
+ }
+ }
+ }
+
+ if (mismatches.length > 0) {
+ throw new Error(`${mismatches.length} conformance mismatches:\n${mismatches.join('\n')}`);
+ }
+});
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@casbin.apache.org
For additional commands, e-mail: commits-help@casbin.apache.org