Hacktoberfest 2026:維護者為十月標記出來的 issue,仍然開放、適合新手。 瀏覽 Hacktoberfest issue

Feature request: add the `keyMatch5` built-in matcher function

未關閉
#275 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
3/5
預估耗時
半天
新手友好度
74/100
Issue 類型
功能
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
cpp
領域
authorization

研究方向

Start at casbin/model/evaluator.cpp LoadFunctions; follow KeyMatch4 through include/casbin/model/exprtk_config.h, casbin/util/built_in_functions.cpp and include/casbin/util/built_in_functions.h. Add KeyMatch5 by stripping key1 at the first ?, then using KeyMatch3-style wildcard/placeholder matching, and register it in the factory and LoadFunctions. Add a TestKeyMatch5 beside TestKeyMatch4 in tests/built_in_functions_test.cpp; run the gtest suite, and done is the new matcher registered as keyMatch5 with the test passing.

由索引模型根據 Issue 內容生成。

描述

Location (apache/casbin-cpp @ default branch 0ad64a7):

void ExprtkEvaluator::LoadFunctions() {
    AddFunction("keyMatch", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch, 2));
    AddFunction("keyMatch2", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch2, 2));
    AddFunction("keyMatch3", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch3, 2));
    AddFunction("keyMatch4", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch4, 2));
    AddFunction("regexMatch", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::RegexMatch, 2));
    AddFunction("ipMatch", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::IpMatch, 2));
    AddFunction("keyGet", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyGet, 2));
    AddFunction("keyGet2", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyGet2, 3));
    AddFunction("keyGet3", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyGet3, 3));
}

Problem
Casbin's function reference lists keyMatch5: arg1 is "a URL path like /alice_data/123/?status=1", arg2 is "a URL path, a {} or * pattern like /alice_data/{id}/*". The query string of the request path is ignored when matching. casbin-cpp registers keyMatch … keyMatch4, regexMatch, ipMatch and keyGet* as built-ins, but not keyMatch5, so a model that uses it in other Casbin implementations, e.g.

[matchers]
m = r.sub == p.sub && keyMatch5(r.obj, p.obj) && r.act == p.act

with p, alice, /parent/child, GET, can't be used unchanged with casbin-cpp. Users have to strip the query string themselves or register their own function (AddFunction) in each application.

This is a feature request, not a bug: an unknown function is already an error, not an allow.

Reproduction
With the matcher above, the first Enforce call throws, and gtest reports:

unknown file: Failure
Unknown C++ exception thrown in the test body.

Environment: master 0ad64a7.

Already implemented in apache/casbin-node-casbin
PR: https://github.com/apache/casbin-node-casbin/pull/359 — "feat: #357 Support keyMatch5" (merged 2022-04-15T01:43:23Z, merge commit e6a6d8ab253). It adds keyMatch5 to node-casbin's built-in function map, matching after dropping the query string from the request path. node-casbin's version compares the remaining path exactly; the reference implementation (apache/casbin KeyMatch5) additionally supports {name} and /* in the pattern, which is the behaviour the docs describe.

How node-casbin implemented it:

--- a/src/model/functionMap.ts
+++ b/src/model/functionMap.ts
@@ -37,6 +37,7 @@ export class FunctionMap {
     fm.addFunction('keyGet2', util.keyGet2Func);
     fm.addFunction('keyMatch3', util.keyMatch3Func);
     fm.addFunction('keyMatch4', util.keyMatch4Func);
+    fm.addFunction('keyMatch5', util.keyMatch5Func);
     fm.addFunction('regexMatch', util.regexMatchFunc);
     fm.addFunction('ipMatch', util.ipMatchFunc);
     fm.addFunction('globMatch', util.globMatch);
--- a/src/util/builtinOperators.ts
+++ b/src/util/builtinOperators.ts
@@ -233,6 +233,26 @@ function keyMatch4Func(...args: any[]): boolean {
   return keyMatch4(name1, name2);
 }
 
+// KeyMatch determines whether key1 matches the pattern of key2 and ignores the parameters in key2.
+// For example, "/foo/bar?status=1&type=2" matches "/foo/bar"
+function KeyMatch5(key1: string, key2: string): boolean {
+  const i: number = key1.indexOf('?');
+  if (i === -1) {
+    return key1 === key2;
+  }
+
+  return key1.slice(0, i) === key2;
+}
+
+// keyMatch5Func is the wrapper for KeyMatch5.
+function keyMatch5Func(...args: any[]): boolean {
+  const [arg0, arg1] = args;
+  const name1: string = (arg0 || '').toString();
+  const name2: string = (arg1 || '').toString();
+
+  return KeyMatch5(name1, name2);
+}
+
 // regexMatchFunc is the wrapper for regexMatch.
 function regexMatchFunc(...args: any[]): boolean {
   const [arg0, arg1] = args;

Suggested fix
Add KeyMatch5 next to KeyMatch4 (drop everything from the first ? in key1, then match like keyMatch3: /* is a wildcard and {name} matches one path segment), add it to the exprtk function factory, and register it as keyMatch5 in LoadFunctions:

diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp
--- a/casbin/model/evaluator.cpp
+++ b/casbin/model/evaluator.cpp
@@ -83,6 +83,7 @@ void ExprtkEvaluator::LoadFunctions() {
     AddFunction("keyMatch2", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch2, 2));
     AddFunction("keyMatch3", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch3, 2));
     AddFunction("keyMatch4", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch4, 2));
+    AddFunction("keyMatch5", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyMatch5, 2));
     AddFunction("regexMatch", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::RegexMatch, 2));
     AddFunction("ipMatch", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::IpMatch, 2));
     AddFunction("keyGet", ExprtkFunctionFactory::GetExprtkFunction(ExprtkFunctionType::KeyGet, 2));
diff --git a/casbin/util/built_in_functions.cpp b/casbin/util/built_in_functions.cpp
--- a/casbin/util/built_in_functions.cpp
+++ b/casbin/util/built_in_functions.cpp
@@ -204,6 +204,18 @@ bool KeyMatch4(const std::string& key1, const std::string& key2) {
     return true;
 }
 
+// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
+// The query string of key1 (everything from the first "?") is ignored.
+// For example, "/foo/bar?status=1&type=2" matches "/foo/bar", "/parent/child1" matches "/parent/*" and "/parent/{id}"
+bool KeyMatch5(const std::string& key1, const std::string& key2) {
+    std::string k1 = key1.substr(0, key1.find('?'));
+    std::string k2 = PrepareWildCardMatching(key2);
+    k2 = std::regex_replace(k2, enclosedPlaceHolderRegex, "$1[^/]+$2");
+    k2 = EscapeCurlyBraces(k2);
+
+    return RegexMatch(k1, "^" + k2 + "$");
+}
+
 // RegexMatch determines whether key1 matches the pattern of key2 in regular expression.
 bool RegexMatch(const std::string& key1, const std::string& key2) {
     std::regex regex_s(key2);
diff --git a/include/casbin/model/exprtk_config.h b/include/casbin/model/exprtk_config.h
--- a/include/casbin/model/exprtk_config.h
+++ b/include/casbin/model/exprtk_config.h
@@ -234,6 +234,7 @@ enum class ExprtkFunctionType {
     KeyMatch2,
     KeyMatch3,
     KeyMatch4,
+    KeyMatch5,
     RegexMatch,
     IpMatch,
     KeyGet,
@@ -276,6 +277,9 @@ public:
             case ExprtkFunctionType::KeyMatch4:
                 func = std::make_shared<ExprtkMatchFunction>(idenfier, KeyMatch4);
                 break;
+            case ExprtkFunctionType::KeyMatch5:
+                func = std::make_shared<ExprtkMatchFunction>(idenfier, KeyMatch5);
+                break;
             case ExprtkFunctionType::IpMatch:
                 func = std::make_shared<ExprtkMatchFunction>(idenfier, IPMatch);
                 break;
diff --git a/include/casbin/util/built_in_functions.h b/include/casbin/util/built_in_functions.h
--- a/include/casbin/util/built_in_functions.h
+++ b/include/casbin/util/built_in_functions.h
@@ -53,6 +53,11 @@ std::string KeyGet3(const std::string& key1, const std::string& key2, const std:
 // But KeyMatch3 will match both.
 bool KeyMatch4(const std::string& key1, const std::string& key2);
 
+// KeyMatch5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
+// The query string of key1 (everything from the first "?") is ignored.
+// For example, "/foo/bar?status=1&type=2" matches "/foo/bar", "/parent/child1" matches "/parent/*" and "/parent/{id}"
+bool KeyMatch5(const std::string& key1, const std::string& key2);
+
 // RegexMatch determines whether key1 matches the pattern of key2 in regular expression.
 bool RegexMatch(const std::string& key1, const std::string& key2);
 

Verified on 0ad64a7 (GCC 11.4.0, GoogleTest 1.14.0): with the example above added as a TestKeyMatch5 unit test in tests/built_in_functions_test.cpp (via the exprtk evaluator, like TestKeyMatch4), the test does not compile without this patch and passes with it; no other test results change.

I can submit a PR for this.

Related: no issue/PR mentions keyMatch5; #209 / #212 added the then-missing keyGet* / keyMatch4 built-ins in the same way.

主要語言
C++
星號
257
分支
67
PR 合併指標
30 天內沒有已合併 PR

環境準備

  • 沒有 Dockerfile 或 Docker Compose 檔案
  • 沒有 Pull Request 範本
  • 閱讀貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

apache/casbin-cpp 的其他 Issue

查看 apache/casbin-cpp 的全部 Issue

相似的 Issue

更多 C++ Issue

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。