From 665dc512a8ee63c00ac97172c6a48755eb243f24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:21:48 +0000 Subject: [PATCH 1/5] Initial plan From 57a098187acee614ba05284d2e0b0681dd8f5a76 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:39:43 +0000 Subject: [PATCH 2/5] Implement JSON support in PushObjectJson method Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- casbin/model/evaluator.cpp | 26 ++++++- include/casbin/model/evaluator.h | 3 + tests/enforcer_test.cpp | 119 +++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp index acf3211f..46cd2b5b 100644 --- a/casbin/model/evaluator.cpp +++ b/casbin/model/evaluator.cpp @@ -61,7 +61,22 @@ void ExprtkEvaluator::PushObjectString(const std::string& target, const std::str void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::string& proprity, const nlohmann::json& var) { auto identifier = target + "." + proprity; - // this->symbol_table.add_stringvar(identifier, const_cast(var)); + + // Recursively flatten JSON object into dot-notation identifiers + if (var.is_object()) { + for (auto& [key, value] : var.items()) { + PushObjectJson(identifier, key, value); + } + } else if (var.is_string()) { + this->AddIdentifier(identifier, var.get()); + } else if (var.is_number_integer()) { + this->AddNumericIdentifier(identifier, static_cast(var.get())); + } else if (var.is_number_float()) { + this->AddNumericIdentifier(identifier, static_cast(var.get())); + } else if (var.is_boolean()) { + this->AddNumericIdentifier(identifier, static_cast(var.get() ? 1 : 0)); + } + // For other types (arrays, null, etc.), we skip them as they're not supported in the expression evaluator } void ExprtkEvaluator::LoadFunctions() { @@ -118,6 +133,7 @@ void ExprtkEvaluator::Clean(AssertionMap& section, bool after_enforce) { this->expression_string_ = ""; this->Functions.clear(); this->identifiers_.clear(); + this->numeric_identifiers_.clear(); } void ExprtkEvaluator::AddFunction(const std::string& func_name, std::shared_ptr func) { @@ -163,4 +179,12 @@ void ExprtkEvaluator::AddIdentifier(const std::string& identifier, const std::st symbol_table.get_stringvar(identifier)->ref() = var; } +void ExprtkEvaluator::AddNumericIdentifier(const std::string& identifier, numerical_type value) { + if (!symbol_table.symbol_exists(identifier)) { + numeric_identifiers_[identifier] = std::make_unique(0); + this->symbol_table.add_variable(identifier, *numeric_identifiers_[identifier]); + } + *numeric_identifiers_[identifier] = value; +} + } // namespace casbin diff --git a/include/casbin/model/evaluator.h b/include/casbin/model/evaluator.h index cb143ac8..8db1146b 100644 --- a/include/casbin/model/evaluator.h +++ b/include/casbin/model/evaluator.h @@ -71,6 +71,7 @@ class ExprtkEvaluator : public IEvaluator { parser_t parser; std::vector> Functions; std::unordered_map> identifiers_; + std::unordered_map> numeric_identifiers_; public: ExprtkEvaluator() { @@ -109,6 +110,8 @@ class ExprtkEvaluator : public IEvaluator { void AddIdentifier(const std::string& identifier, const std::string& var); + void AddNumericIdentifier(const std::string& identifier, numerical_type value); + std::unordered_map requestValues() const override; }; } // namespace casbin diff --git a/tests/enforcer_test.cpp b/tests/enforcer_test.cpp index dc0c1b88..8a4e0a5f 100644 --- a/tests/enforcer_test.cpp +++ b/tests/enforcer_test.cpp @@ -155,6 +155,125 @@ TEST(TestEnforcer, TestMapParams) { ASSERT_EQ(e.Enforce(params), true); } +TEST(TestEnforcer, TestJsonData) { + using json = nlohmann::json; + casbin::Enforcer e(abac_rule_model_path, abac_rule_policy_path); + + // Test with JSON object containing Age property + // Policy: p, r.sub.Age > 18, /data1, read + auto sub_json_adult = std::make_shared(json{ + {"Name", "alice"}, + {"Age", 30} + }); + + casbin::DataMap params = { + {"sub", sub_json_adult}, + {"obj", "/data1"}, + {"act", "read"} + }; + + // Should be allowed: Age 30 > 18 + ASSERT_EQ(e.Enforce(params), true); + + // Test with minor (Age < 18) + auto sub_json_minor = std::make_shared(json{ + {"Name", "bob"}, + {"Age", 16} + }); + + params = { + {"sub", sub_json_minor}, + {"obj", "/data1"}, + {"act", "read"} + }; + + // Should be denied: Age 16 < 18 + ASSERT_EQ(e.Enforce(params), false); + + // Test policy: p, r.sub.Age < 60, /data2, write + auto sub_json_young = std::make_shared(json{ + {"Name", "charlie"}, + {"Age", 45} + }); + + params = { + {"sub", sub_json_young}, + {"obj", "/data2"}, + {"act", "write"} + }; + + // Should be allowed: Age 45 < 60 + ASSERT_EQ(e.Enforce(params), true); + + // Test with senior (Age >= 60) + auto sub_json_senior = std::make_shared(json{ + {"Name", "dave"}, + {"Age", 65} + }); + + params = { + {"sub", sub_json_senior}, + {"obj", "/data2"}, + {"act", "write"} + }; + + // Should be denied: Age 65 >= 60 + ASSERT_EQ(e.Enforce(params), false); +} + +TEST(TestEnforcer, TestComplexJsonData) { + using json = nlohmann::json; + + // Create a custom model and policy for testing complex JSON structures + // similar to the issue example + casbin::Enforcer e(abac_rule_model_path, abac_rule_policy_path); + + // Test with complex nested JSON object like in the issue + auto complex_request = std::make_shared(json{ + {"ID", "zk"}, + {"proxy", "vpn"}, + {"Department", "nlp"}, + {"month", "Jan"}, + {"week", "Mon"}, + {"time", "morning"}, + {"Longitude", 123}, + {"Latitude", 456}, + {"Altitude", 789}, + {"OS", "HarmonyOS"}, + {"CPU", "XeonPlatinum8480+"}, + {"NetworkType", "WLan"}, + {"ProtocolType", "Bluetooth"}, + {"EncryptionType", "3DES"}, + {"SecurityProtocol", "HTTPS"}, + {"Age", 25} // For testing with existing policy + }); + + casbin::DataMap params = { + {"sub", complex_request}, + {"obj", "/data1"}, + {"act", "read"} + }; + + // Should work with complex JSON - Age 25 > 18 + ASSERT_EQ(e.Enforce(params), true); + + // Test with object that also contains nested objects + auto obj_json = std::make_shared(json{ + {"SecurityLevel", 3}, + {"Source", "ISS"}, + {"DistributionMethod", "C"} + }); + + params = { + {"sub", complex_request}, + {"obj", obj_json}, + {"act", "read"} + }; + + // This should not crash even with JSON in multiple parameters + ASSERT_NO_THROW(e.Enforce(params)); +} + template void TestEnforceEx(casbin::Enforcer& e, T&& params, const bool expect_result, const std::vector& expect_explain) { std::vector actual_explain; From bd418fee81bc5c1895800fb0898c0e154a107c0d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:42:35 +0000 Subject: [PATCH 3/5] Address code review: improve numeric type handling in PushObjectJson Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- casbin/model/evaluator.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp index 46cd2b5b..2052ecd9 100644 --- a/casbin/model/evaluator.cpp +++ b/casbin/model/evaluator.cpp @@ -69,9 +69,8 @@ void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::strin } } else if (var.is_string()) { this->AddIdentifier(identifier, var.get()); - } else if (var.is_number_integer()) { - this->AddNumericIdentifier(identifier, static_cast(var.get())); - } else if (var.is_number_float()) { + } else if (var.is_number()) { + // Get as double first to preserve precision, then cast to numerical_type this->AddNumericIdentifier(identifier, static_cast(var.get())); } else if (var.is_boolean()) { this->AddNumericIdentifier(identifier, static_cast(var.get() ? 1 : 0)); From 319b3d487767084bf315ee22ed76d05acdccaeb1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:50:30 +0000 Subject: [PATCH 4/5] Add _codeql_build_dir to .gitignore Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6d205842..626f0d9c 100644 --- a/.gitignore +++ b/.gitignore @@ -367,4 +367,4 @@ build_support/ *.egg-info # clang-format -build_support/clang_format_exclusions.txt \ No newline at end of file +build_support/clang_format_exclusions.txt_codeql_build_dir/ From 30cde2c330a37cfdf718a6569bbb39f276f20591 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Wed, 5 Nov 2025 23:00:38 +0800 Subject: [PATCH 5/5] Update .gitignore to include clang_format_exclusions.txt --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 626f0d9c..4ad8ebf0 100644 --- a/.gitignore +++ b/.gitignore @@ -367,4 +367,4 @@ build_support/ *.egg-info # clang-format -build_support/clang_format_exclusions.txt_codeql_build_dir/ +build_support/clang_format_exclusions.txt