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.NET.git

commit 6b61099498b7dbd36d852eeb518d077810d8c99b
Author: Yang Luo <[email protected]>
AuthorDate: Tue Jun 16 21:08:26 2026 +0800

    feat: correct KeyMatch5 to support wildcards and path parameters
---
 Casbin.UnitTests/UtilTests/BuiltInFunctionTest.cs | 29 ++++++++++++++++++++++-
 Casbin/Functions/BuiltInFunctions.cs              | 23 +++++++++++-------
 2 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/Casbin.UnitTests/UtilTests/BuiltInFunctionTest.cs 
b/Casbin.UnitTests/UtilTests/BuiltInFunctionTest.cs
index 7619ec2..a311a31 100644
--- a/Casbin.UnitTests/UtilTests/BuiltInFunctionTest.cs
+++ b/Casbin.UnitTests/UtilTests/BuiltInFunctionTest.cs
@@ -159,7 +159,34 @@ public class BuiltInFunctionTest
         ["/parent?status=1&type=2", "/parent/child", false],
         ["/parent/child/?status=1&type=2", "/parent/child/", true],
         ["/parent/child/?status=1&type=2", "/parent/child", false],
-        ["/parent/child?status=1&type=2", "/parent/child/", false]
+        ["/parent/child?status=1&type=2", "/parent/child/", false],
+        // wildcard *
+        ["/foo", "/foo", true],
+        ["/foo", "/foo*", true],
+        ["/foo", "/foo/*", false],
+        ["/foo/bar", "/foo", false],
+        ["/foo/bar", "/foo*", false],
+        ["/foo/bar", "/foo/*", true],
+        ["/foobar", "/foo", false],
+        ["/foobar", "/foo*", false],
+        ["/foobar", "/foo/*", false],
+        // {param} placeholders
+        ["/", "/{resource}", false],
+        ["/resource1", "/{resource}", true],
+        ["/myid", "/{id}/using/{resId}", false],
+        ["/myid/using/myresid", "/{id}/using/{resId}", true],
+        // {param} + wildcard
+        ["/proxy/myid", "/proxy/{id}/*", false],
+        ["/proxy/myid/", "/proxy/{id}/*", true],
+        ["/proxy/myid/res", "/proxy/{id}/*", true],
+        ["/proxy/myid/res/res2", "/proxy/{id}/*", true],
+        ["/proxy/myid/res/res2/res3", "/proxy/{id}/*", true],
+        ["/proxy/", "/proxy/{id}/*", false],
+        // query strings with {param} + wildcard
+        ["/proxy/myid?status=1&type=2", "/proxy/{id}/*", false],
+        ["/proxy/myid/res?status=1&type=2", "/proxy/{id}/*", true],
+        ["/proxy/myid/res/res2?status=1&type=2", "/proxy/{id}/*", true],
+        ["/proxy/myid/res/res2/res3?status=1&type=2", "/proxy/{id}/*", true]
     ];
 
     public static IEnumerable<object[]> GlobMatchTestData =
diff --git a/Casbin/Functions/BuiltInFunctions.cs 
b/Casbin/Functions/BuiltInFunctions.cs
index 69f3637..6a8720b 100644
--- a/Casbin/Functions/BuiltInFunctions.cs
+++ b/Casbin/Functions/BuiltInFunctions.cs
@@ -13,6 +13,7 @@ namespace Casbin.Functions
         private static readonly Regex s_keyMatch2Regex = new(@":[^/]+");
         private static readonly Regex s_keyMatch3Regex = new(@"\{[^/]+\}");
         private static readonly Regex s_keyMatch4Regex = new(@"\{([^/]+)\}");
+        private static readonly Regex s_keyMatch5Regex = new(@"\{[^/]+\}");
         private static readonly Regex s_keyGet2Regex = new(@":[^/]+");
         private static readonly Regex s_keyGet3Regex = new(@"\{[^/]+?\}");
 
@@ -223,20 +224,26 @@ namespace Casbin.Functions
         }
 
         /// <summary>
-        /// KeyMatch determines whether key1 matches the pattern of key2 and 
ignores the parameters in key2.
-        /// For example, "/foo/bar?status=1&amp;type=2" matches "/foo/bar"
+        /// KeyMatch5 determines whether key1 matches the pattern of key2 
(similar to RESTful path),
+        /// key2 can contain a * or {param}. Query strings in key1 are ignored.
+        /// For example, "/foo/bar?status=1&amp;type=2" matches "/foo/bar", 
"/foo/bar" matches "/foo/*",
+        /// "/proxy/myid/res" matches "/proxy/{id}/*".
         /// </summary>
         /// <param name="key1"> The first argument. </param>
         /// <param name="key2"> The second argument. </param>
         /// <returns></returns>
         public static bool KeyMatch5(string key1, string key2)
         {
-            var key1Span = key1.AsSpan();
-            var key2Span = key2.AsSpan();
-            int index = key1Span.IndexOf('?');
-            return index is -1
-                ? key1Span.Equals(key2Span, StringComparison.Ordinal)
-                : key1Span.Slice(0, index).Equals(key2Span, 
StringComparison.Ordinal);
+            int queryIndex = key1.IndexOf('?');
+            if (queryIndex != -1)
+            {
+                key1 = key1.Substring(0, queryIndex);
+            }
+
+            key2 = key2.Replace("/*", "/.*");
+            key2 = s_keyMatch5Regex.Replace(key2, "([^/]+)");
+
+            return RegexMatch(key1, $"^{key2}$");
         }
 
         /// <summary>


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

Reply via email to