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-casbin.js.git


The following commit(s) were added to refs/heads/master by this push:
     new 7535621  fix: support CasbinJsGetPermissionForUser output in 
setPermission
7535621 is described below

commit 7535621be93f5c57777480ed30b643813d4a5d47
Author: Yang Luo <[email protected]>
AuthorDate: Thu Aug 6 18:29:05 2026 +0800

    fix: support CasbinJsGetPermissionForUser output in setPermission
---
 README.md                       | 17 ++++++++
 src/Authorizer.ts               | 91 +++++++++++++++++++++++++++++++----------
 src/Permission.ts               |  2 +-
 src/__test__/enforcer.test.ts   | 43 ++++++++++++++++++-
 src/__test__/permission.test.ts | 12 ++++--
 5 files changed, 139 insertions(+), 26 deletions(-)

diff --git a/README.md b/README.md
index d5f3c9d..24776d1 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,23 @@ authorizer.cannot("write", "data2").then(result => {
 });
 ```
 
+In `manual` mode you can also pass the model and policies produced by the 
backend's
+`CasbinJsGetPermissionForUser` (a JSON object with the keys `m`, `p` and `g`) 
to
+`setPermission`. Casbin.js then builds a real enforcer from that data, so 
roles (`g`
+rules) are taken into account. Remember to set the user, since the request is 
evaluated
+as `(user, object, action)`.
+```javascript
+const authorizer = new casbinjs.Authorizer("manual");
+
+// The JSON string or object returned by CasbinJsGetPermissionForUser
+authorizer.setPermission(responseFromApi);
+await authorizer.setUser("alice");
+
+authorizer.can("read", "data1").then(result => {
+  console.log(result)
+});
+```
+
 You can also use the `auto` mode. In details, specify a casbin backend service 
endpoint when initializing the Casbin.js authorizer, and set the subject when 
the frontend user identity changes. Casbin.js will automatically fetch the 
permission from the endpoint. (A pre-configurated casbin service API is 
required at the backend.)
 ```javascript
 const casbinjs = require('casbin.js');
diff --git a/src/Authorizer.ts b/src/Authorizer.ts
index a533a49..86b8a85 100644
--- a/src/Authorizer.ts
+++ b/src/Authorizer.ts
@@ -10,6 +10,14 @@ interface BaseResponse {
 
 type Mode = "auto" | "cookies" | "manual"
 
+/**
+ * Whether the object is the model + policies produced by casbin's
+ * "CasbinJsGetPermissionForUser", rather than an action-to-objects map.
+ */
+function isEnforcerData(obj: unknown): obj is Record<string, unknown> {
+    return typeof obj === 'object' && obj !== null && typeof (obj as 
Record<string, unknown>)['m'] === 'string';
+}
+
 export class Authorizer {
     public mode!: Mode;
     public endpoint: string | undefined = undefined;
@@ -19,6 +27,7 @@ export class Authorizer {
     public cacheExpiredTime  = 60; // Seconds
     public user : string | undefined;
     public enforcer:casbin.Enforcer | undefined;
+    private enforcerPromise: Promise<void> | undefined;
 
     /**
      *
@@ -75,30 +84,63 @@ export class Authorizer {
         }
     }
 
+    /**
+     * Load the permission data.
+     *
+     * Two shapes are accepted:
+     * - A map of actions to objects, e.g. {"read": ["data1", "data2"], 
"write": ["data1"]}
+     * - The model and policies returned by casbin's 
"CasbinJsGetPermissionForUser"
+     *   (a JSON object with the keys "m", "p" and "g"). In this case an 
enforcer is
+     *   built from that data, and "can" evaluates the model instead of a 
static map.
+     */
     public setPermission(permission : Record<string, unknown> | string) : void{
+        const obj = typeof permission === 'string' ? JSON.parse(permission) : 
permission;
+        if (isEnforcerData(obj)) {
+            this.enforcerPromise = this.initEnforcer(obj);
+            // The rejection is surfaced when "can" awaits the promise, this 
only
+            // keeps it from being reported as an unhandled rejection 
meanwhile.
+            this.enforcerPromise.catch(() => undefined);
+            return;
+        }
         if (this.permission === undefined) {
             this.permission = new Permission();
         }
-        this.permission.load(permission);
+        this.permission.load(obj);
     }
 
-    public async initEnforcer(s: string): Promise<void> {
-        const obj = JSON.parse(s);
+    public async initEnforcer(s: string | Record<string, unknown>): 
Promise<void> {
+        const obj = typeof s === 'string' ? JSON.parse(s) : s;
         if (!('m' in obj)) {
             throw Error("No model when init enforcer.");
         }
         const m = new casbin.Model(obj['m']);
         this.enforcer = await casbin.newEnforcer(m);
-        if ('p' in obj) {
-            for (const sArray of obj['p']) {
-                let arr = sArray as string[];
-                arr = arr.map(v => v.trim())
-                const pType = arr.shift()
-                if (pType == 'p'){
-                    await this.enforcer.addPolicy(...arr);
-                } else if (pType == 'g'){
-                    await this.enforcer.addGroupingPolicy(...arr);
-                }
+        // "CasbinJsGetPermissionForUser" puts the policies under "p" and the 
grouping
+        // policies under "g", each rule prefixed with its own ptype.
+        for (const section of ['p', 'g']) {
+            if (section in obj) {
+                await this.loadRules(obj[section]);
+            }
+        }
+    }
+
+    private async loadRules(rules: unknown): Promise<void> {
+        if (!Array.isArray(rules)) {
+            return;
+        }
+        for (const rule of rules) {
+            if (!Array.isArray(rule)) {
+                continue;
+            }
+            const arr = (rule as string[]).map(v => v.trim());
+            const pType = arr.shift();
+            if (!pType) {
+                continue;
+            }
+            if (pType.startsWith('g')) {
+                await (this.enforcer as 
casbin.Enforcer).addNamedGroupingPolicy(pType, ...arr);
+            } else {
+                await (this.enforcer as casbin.Enforcer).addNamedPolicy(pType, 
...arr);
             }
         }
     }
@@ -125,8 +167,11 @@ export class Authorizer {
      * @param user The current user
      */
     public async setUser(user : string) : Promise<void> {
-        if (this.mode == 'auto' && user !== this.user) {
-            this.user = user;
+        if (user === this.user) {
+            return;
+        }
+        this.user = user;
+        if (this.mode == 'auto') {
             let config = Cache.loadFromLocalStorage(user);
             if (config === null) {
                 config = await this.getEnforcerDataFromSvr();
@@ -137,16 +182,20 @@ export class Authorizer {
     }
 
     public async can(action: string, object: string, domain?: string): 
Promise<boolean> {
-        if (this.mode == "manual") {
-            return this.permission !== undefined && 
this.permission.check(action, object);
-        } else if (this.mode == "auto") {
-            if (this.enforcer === undefined) {
-                throw Error("Enforcer not initialized");
-            } else if (domain == undefined) {
+        if (this.enforcerPromise !== undefined) {
+            await this.enforcerPromise;
+        }
+        if (this.enforcer !== undefined) {
+            if (domain == undefined) {
                 return await this.enforcer.enforce(this.user, object, action);
             } else {
                 return await this.enforcer.enforce(this.user, domain, object, 
action);
             }
+        }
+        if (this.mode == "manual") {
+            return this.permission !== undefined && 
this.permission.check(action, object);
+        } else if (this.mode == "auto") {
+            throw Error("Enforcer not initialized");
         } else {
             throw Error(`Mode ${this.mode} not recognized.`);
         }
diff --git a/src/Permission.ts b/src/Permission.ts
index ba2cd42..4e3b947 100644
--- a/src/Permission.ts
+++ b/src/Permission.ts
@@ -25,7 +25,7 @@ export default class Permission {
         // Generate data: {key:Objects, value: Array of actions}
         const tmp : StringKV = {};
         for (const act in p) {
-            for (const obj in p[act]) {
+            for (const obj of p[act]) {
                 if (!(obj in tmp)) {
                     tmp[obj] = [];
                 }
diff --git a/src/__test__/enforcer.test.ts b/src/__test__/enforcer.test.ts
index 4dbf36d..3703d23 100644
--- a/src/__test__/enforcer.test.ts
+++ b/src/__test__/enforcer.test.ts
@@ -1,6 +1,6 @@
 import * as casbin from "casbin-core"
 import { Authorizer } from "../Authorizer";
-import { basicModelStr, rbacWithDomainsModelStr} from './models';
+import { basicModelStr, rbacModelStr, rbacWithDomainsModelStr} from './models';
 
 const respData = JSON.stringify({
     m: basicModelStr,
@@ -56,6 +56,47 @@ test('Authorizer enforcer with domain API', async() => {
     expect(await authorizer.canAll("write", ["data1", "data2"], 
"domain1")).toBe(false);
 })
 
+// The shape returned by casbin's "CasbinJsGetPermissionForUser": the grouping
+// policies live under their own "g" key, not inside "p".
+const respDataWithSeparateGroupingRules = {
+    m: rbacModelStr,
+    p: [
+        ["p", "admin", "data1", "read"],
+        ["p", "admin", "data1", "write"],
+        ["p", "user", "data1", "read"],
+    ],
+    g: [
+        ["g", "alice", "admin"],
+        ["g", "bob", "user"],
+    ],
+};
+
+test('Authorizer enforcer with grouping policies in the "g" section', async() 
=> {
+    const authorizer = new Authorizer("auto", {endpoint: "whatever"});
+    await 
authorizer.initEnforcer(JSON.stringify(respDataWithSeparateGroupingRules));
+    authorizer.user = "alice";
+    expect(await authorizer.can("read", "data1")).toBe(true);
+    expect(await authorizer.can("write", "data1")).toBe(true);
+    authorizer.user = "bob";
+    expect(await authorizer.can("read", "data1")).toBe(true);
+    expect(await authorizer.can("write", "data1")).toBe(false);
+})
+
+test('Manual mode accepts the model and policies of 
CasbinJsGetPermissionForUser', async() => {
+    const authorizer = new Authorizer("manual");
+    authorizer.setPermission(respDataWithSeparateGroupingRules);
+    await authorizer.setUser("alice");
+    expect(await authorizer.can("read", "data1")).toBe(true);
+    expect(await authorizer.can("write", "data1")).toBe(true);
+    expect(await authorizer.can("read", "data2")).toBe(false);
+
+    const fromString = new Authorizer("manual");
+    
fromString.setPermission(JSON.stringify(respDataWithSeparateGroupingRules));
+    await fromString.setUser("bob");
+    expect(await fromString.can("read", "data1")).toBe(true);
+    expect(await fromString.cannot("write", "data1")).toBe(true);
+})
+
 const s = `[request_definition]
 r = sub, obj, act
 
diff --git a/src/__test__/permission.test.ts b/src/__test__/permission.test.ts
index 2639fb5..b5cd33e 100644
--- a/src/__test__/permission.test.ts
+++ b/src/__test__/permission.test.ts
@@ -35,11 +35,17 @@ describe('Permission unit test', () => {
     })
 
     test('Get action-object mapping', () => {
-        // TODO
+        permission.load(permExample);
+        const actual = permission.getActionsObjects();
+        expect(actual.get('read')).toEqual(['data1', 'data2']);
+        expect(actual.get('write')).toEqual(['data2']);
     });
-    
+
     test('Get object-action mapping', () => {
-        // TODO
+        permission.load(permExample);
+        const actual = permission.getObjectsActions();
+        expect(actual.get('data1')).toEqual(['read']);
+        expect(actual.get('data2')).toEqual(['read', 'write']);
     })
 
 })


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to