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