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

Quoted fields in CSV policy files keep their quote characters, so quoted policy rules never match

已關閉 適合新手
#274 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

評估

難度
2/5
預估耗時
1-3 小時
新手友好度
85/100
Issue 類型
缺陷
描述清晰度
描述清楚
活躍度
活躍
技術堆疊
cpp
領域
authorization

研究方向

從 casbin/persist/adapter.cpp 中的 LoadPolicyLine 開始,將其以逗號分割的行為與文件中記載的 CSV 引號處理方式進行比對。用一條包含帶引號的 admin 值的策略行,以及另一條包含帶引號的逗號的策略行重現問題,然後執行專案的 GoogleTest 測試套件,必要時新增一個針對性的測試案例。當帶引號的欄位載入後不含引號,而不帶引號的行仍維持原本的行為時,即告完成。

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

描述

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

void LoadPolicyLine(const std::string& line, const std::shared_ptr<Model>& model) {
    if (line.empty() || line.find('#') == 0)
        return;

    std::vector<std::string> tokens = Split(line, ",", -1);
    for (int i = 0; i < tokens.size(); i++)
        tokens[i] = Trim(tokens[i]);

    std::string key = tokens[0];
    std::string sec = key.substr(0, 1);
    std::vector<std::string> new_tokens(tokens.begin() + 1, tokens.end());

Problem
The Casbin Policy Storage docs describe quoted CSV fields:

If a field contains commas, wrap it in double quotes … If a field contains both commas and double quotes, wrap it in double quotes and escape internal quotes by doubling them

LoadPolicyLine does Split(line, ",") and then Trim on each token. Trim only removes whitespace, so "admin" keeps its quotes and the quote characters are stored as part of the value. A request for admin therefore doesn't match a policy line written as "admin", and Enforce returns false. For the same reason a quoted comma ("data1,data2") is split into two fields (from reading the code; not run).

Expected: a quoted field is unquoted when loading: "admin" → admin, "data1,data2" → one field data1,data2, "" inside a quoted field → ". Spaces inside the quotes stay part of the value.

Reproduction

quoted_policy.csv:

p, admin, /, GET
p,    "admin",  /,  POST
#include <iostream>
#include <casbin/casbin.h>

int main() {
    casbin::Enforcer e("examples/basic_model.conf", "quoted_policy.csv");
    std::cout << std::boolalpha
              << e.Enforce({"admin", "/", "GET"}) << "\n"    // true
              << e.Enforce({"admin", "/", "POST"}) << "\n";  // false, expected true
}

Observed with a gtest:

Expected equality of these values:
  e.Enforce({"admin", "/", "POST"})
    Which is: false
  true

The unquoted GET line in the same file is enforced as expected.

Environment:

  • Commit: 0ad64a7 (master)
  • Toolchain: GCC 11.5.0, Linux x86_64, default CMake build

Already fixed in apache/casbin-node-casbin
PR: https://github.com/apache/casbin-node-casbin/pull/494 — "feat: add BracketAwareCsvParser to parse CSV file better" (merged 2024-11-22T10:12:57Z, merge commit 4c738831ebb). node-casbin's loadPolicyLine now strips the outer double quotes from the section key and from every policy value and turns "" into ", so a quoted rule loads as the plain value. The PR also adds bracket-aware token joining for policy expressions; the unquoting in PolicyLoader.loadPolicyLine is the part that addresses this behaviour. (node-casbin also trims whitespace inside the quotes; the scope proposed below keeps it.)

How node-casbin fixed it:

--- a/src/persist/helper.ts
+++ b/src/persist/helper.ts
@@ -1,23 +1,83 @@
...
+  loadPolicyLine(line: string, model: Model): void {
+    const tokens = this.parser.parse(line);
     if (!tokens || !tokens[0]) {
       return;
     }
 
-    const key = tokens[0][0];
+    let key = tokens[0][0].trim();
+    if (key.startsWith('"') && key.endsWith('"')) {
+      key = key.slice(1, -1);
+    }
+
...
@@ -28,6 +88,22 @@ export class Helper {
     if (!policy) {
       return;
     }
-    policy.policy.push(tokens[0].slice(1));
+
+    const values = tokens[0].slice(1).map((v) => {
+      if (v.startsWith('"') && v.endsWith('"')) {
+        v = v.slice(1, -1);
+      }
+      return v.replace(/""/g, '"').trim();
+    });
+
+    policy.policy.push(values);
+  }
...

Suggested fix
Replace the Split + Trim tokenizer in LoadPolicyLine with a small CSV-aware splitter: a field wrapped in double quotes may contain commas, "" inside it becomes ", whitespace inside the quotes is kept, and unquoted fields are trimmed as before:

diff --git a/casbin/persist/adapter.cpp b/casbin/persist/adapter.cpp
--- a/casbin/persist/adapter.cpp
+++ b/casbin/persist/adapter.cpp
@@ -24,14 +24,44 @@
 
 namespace casbin {
 
+namespace {
+    // SplitPolicyLine splits a CSV policy line into fields. Unquoted fields are trimmed. A field wrapped in double
+    // quotes may contain commas, "" inside it stands for one double quote, and whitespace inside the quotes is kept.
+    std::vector<std::string> SplitPolicyLine(const std::string& line) {
+        std::vector<std::string> tokens;
+        std::string token;
+        bool in_quotes = false;
+        bool quoted = false;
+        for (size_t i = 0; i < line.size(); i++) {
+            char c = line[i];
+            if (in_quotes) {
+                if (c != '"')
+                    token += c;
+                else if (i + 1 < line.size() && line[i + 1] == '"')
+                    token += line[++i];
+                else
+                    in_quotes = false;
+            } else if (c == ',') {
+                tokens.push_back(quoted ? token : Trim(token));
+                token.clear();
+                quoted = false;
+            } else if (c == '"' && !quoted && Trim(token).empty()) {
+                in_quotes = quoted = true;
+            } else if (!quoted || !std::isspace(static_cast<unsigned char>(c))) {
+                token += c;
+            }
+        }
+        tokens.push_back(quoted ? token : Trim(token));
+        return tokens;
+    }
+} // namespace
+
 // LoadPolicyLine loads a text line as a policy rule to model.
 void LoadPolicyLine(const std::string& line, const std::shared_ptr<Model>& model) {
     if (line.empty() || line.find('#') == 0)
         return;
 
-    std::vector<std::string> tokens = Split(line, ",", -1);
-    for (int i = 0; i < tokens.size(); i++)
-        tokens[i] = Trim(tokens[i]);
+    std::vector<std::string> tokens = SplitPolicyLine(line);
 
     std::string key = tokens[0];
     std::string sec = key.substr(0, 1);

Verified on 0ad64a7 (GCC 11.4.0, GoogleTest 1.14.0): the test above, extended with a quoted-comma line (p, "admin", "/a,b", PUT) and a doubled-quote line, fails without this patch and passes with it; no other test results change.

I can submit a PR for this.

Related: searched issues/PRs for LoadPolicyLine, quotes/quoted CSV, double quotes; the only hit, #132, is about RBAC role links after LoadPolicyLine, not quoting.

主要語言
C++
星號
257
分支
67
平均合併
10 小時 23 分鐘
30 天內合併 PR
1

環境準備

  • 沒有 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 摘要。