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


The following commit(s) were added to refs/heads/master by this push:
     new 711cb48  fix: keyMatch4 error on patterns without {token}
711cb48 is described below

commit 711cb48cdaeb2730304fad7cc5b41d06869111d0
Author: Yang Luo <[email protected]>
AuthorDate: Thu Aug 6 21:21:32 2026 +0800

    fix: keyMatch4 error on patterns without {token}
---
 examples/rbac_with_keymatch4_model.conf | 14 ++++++++++++++
 src/main/CoreEnforcer.lua               | 22 ++++++++++++++++++++--
 src/model/FunctionMap.lua               |  2 +-
 src/util/BuiltInFunctions.lua           | 12 ++++++++++--
 tests/main/enforcer_spec.lua            | 12 ++++++++++++
 tests/util/built_in_functions_spec.lua  | 11 +++++++++++
 6 files changed, 68 insertions(+), 5 deletions(-)

diff --git a/examples/rbac_with_keymatch4_model.conf 
b/examples/rbac_with_keymatch4_model.conf
new file mode 100644
index 0000000..da39667
--- /dev/null
+++ b/examples/rbac_with_keymatch4_model.conf
@@ -0,0 +1,14 @@
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[role_definition]
+g = _, _
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = g(r.sub, p.sub) && keyMatch4(r.obj, p.obj) && regexMatch(r.act, p.act)
diff --git a/src/main/CoreEnforcer.lua b/src/main/CoreEnforcer.lua
index 659a9b9..2cb464f 100644
--- a/src/main/CoreEnforcer.lua
+++ b/src/main/CoreEnforcer.lua
@@ -25,6 +25,24 @@ local Util = require("src.util.Util")
 
 local luaxp = require("modules.luaxp")
 
+--[[
+     * describeEvalError turns a luaxp error into a readable string.
+     * luaxp reports its own compile/evaluation errors as a table with a
+     * "message" field, but an error raised inside a matcher function (a
+     * built-in like keyMatch4, or a user-registered one) is passed through
+     * as a plain string. Reading .message unconditionally hid the real
+     * cause behind "attempt to concatenate field 'message' (a nil value)".
+     *
+     * @param err the second value returned by luaxp.run/luaxp.evaluate.
+     * @return a string describing the error.
+]]
+local function describeEvalError(err)
+    if type(err) == "table" and err.message then
+        return tostring(err.message)
+    end
+    return tostring(err)
+end
+
 --[[
      * checkAdapterResult raises a Lua error when an adapter reports a failure
      * through the "return nil/false, err" convention instead of raising it.
@@ -463,7 +481,7 @@ function CoreEnforcer:enforceEx(...)
                 res, err = luaxp.evaluate(tExpString, context)
             end
             if err then
-                error("evaluation error: " .. err.message)
+                error("evaluation error: " .. describeEvalError(err))
             end
 
             local c = true
@@ -508,7 +526,7 @@ function CoreEnforcer:enforceEx(...)
             
         local res, err = luaxp.run(compiledExpression, context)
         if err then
-            error("evaluation error: " .. err.message)
+            error("evaluation error: " .. describeEvalError(err))
         end
 
         if res then
diff --git a/src/model/FunctionMap.lua b/src/model/FunctionMap.lua
index 5939124..9984a41 100644
--- a/src/model/FunctionMap.lua
+++ b/src/model/FunctionMap.lua
@@ -21,7 +21,7 @@ local FunctionMap = {
     ["keyGet2"] = BuiltInFunctions.keyGet2Func,
     ["keyMatch3"] = BuiltInFunctions.keyMatch3Func,
     ["keyMatch4"] = BuiltInFunctions.keyMatch4Func,
-    ["keyMatch5"] = BuiltInFunctions.keyMatch4Func,
+    ["keyMatch5"] = BuiltInFunctions.keyMatch5Func,
     ["regexMatch"] = BuiltInFunctions.regexMatchFunc,
     ["IPMatch"] = BuiltInFunctions.IPMatchFunc,
     ["globMatch"] = BuiltInFunctions.globMatchFunc,
diff --git a/src/util/BuiltInFunctions.lua b/src/util/BuiltInFunctions.lua
index 16b0f4a..c39d4d0 100644
--- a/src/util/BuiltInFunctions.lua
+++ b/src/util/BuiltInFunctions.lua
@@ -144,10 +144,18 @@ function BuiltInFunctions.keyMatch4(key1, key2)
         return "([^/]+)"
     end
     key2=string.gsub(key2,"{([^/]+)}",repl)
-    if string.match(key1, key2)==nil then
-        return false
+    key2="^" .. key2 .. "$"
+
+    -- Without any {token}, the pattern has no captures, so string.match would
+    -- return the whole match instead of a (possibly empty) list of values.
+    if #tokens==0 then
+        return string.match(key1, key2)~=nil
     end
+
     local matches={string.match(key1, key2)}
+    if matches[1]==nil then
+        return false
+    end
     if #tokens~= #matches then
         error("KeyMatch4: number of tokens is not equal to number of values")
     end
diff --git a/tests/main/enforcer_spec.lua b/tests/main/enforcer_spec.lua
index 3337ab3..54977a9 100644
--- a/tests/main/enforcer_spec.lua
+++ b/tests/main/enforcer_spec.lua
@@ -94,6 +94,18 @@ describe("Enforcer tests", function ()
         assert.is.True(e:enforce("alice", "/alice_data2/123/using/456", "GET"))
     end)
 
+    it("rbac with keyMatch4 test", function ()
+        local model  = path .. "/examples/rbac_with_keymatch4_model.conf"
+        local policy  = path .. "/examples/rbac_policy.csv"
+
+        local e = Enforcer:new(model, policy)
+        assert.is.True(e:enforce("alice", "data1", "read"))
+        assert.is.False(e:enforce("alice", "data1", "write"))
+        assert.is.True(e:enforce("alice", "data2", "write"))
+        assert.is.False(e:enforce("bob", "data1", "read"))
+        assert.is.True(e:enforce("bob", "data2", "write"))
+    end)
+
     it("priority test", function ()
         local model  = path .. "/examples/priority_model.conf"
         local policy  = path .. "/examples/priority_policy.csv"
diff --git a/tests/util/built_in_functions_spec.lua 
b/tests/util/built_in_functions_spec.lua
index f7ef8aa..44f2e30 100644
--- a/tests/util/built_in_functions_spec.lua
+++ b/tests/util/built_in_functions_spec.lua
@@ -170,6 +170,17 @@ describe("BuiltInFunctions tests", function ()
         assert.is.False(BuiltInFunctions.keyMatch4("/parent/123/child/456", 
"/parent/{id}/child/{id}/book/{id}"))
 
         assert.is.False(BuiltInFunctions.keyMatch4("/parent/123/child/123", 
"/parent/{i/d}/child/{i/d}"))
+
+        -- Patterns without any {token} must still work instead of raising
+        -- "number of tokens is not equal to number of values".
+        assert.is.True(BuiltInFunctions.keyMatch4("data1", "data1"))
+        assert.is.False(BuiltInFunctions.keyMatch4("data1", "data2"))
+        assert.is.True(BuiltInFunctions.keyMatch4("/foo/bar", "/foo/*"))
+        assert.is.False(BuiltInFunctions.keyMatch4("/bar/foo", "/foo/*"))
+
+        -- The pattern is anchored, so it must match the whole key.
+        
assert.is.False(BuiltInFunctions.keyMatch4("/parent/123/child/123/extra", 
"/parent/{id}/child/{id}"))
+        assert.is.False(BuiltInFunctions.keyMatch4("data11", "data1"))
     end)
 
     it("regexMatch tests", function ()


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

Reply via email to