diff --git a/.gitignore b/.gitignore index 6d205842..4ad8ebf0 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 diff --git a/casbin/model/evaluator.cpp b/casbin/model/evaluator.cpp index acf3211f..2052ecd9 100644 --- a/casbin/model/evaluator.cpp +++ b/casbin/model/evaluator.cpp @@ -61,7 +61,21 @@ 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()) { + // 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)); + } + // For other types (arrays, null, etc.), we skip them as they're not supported in the expression evaluator } void ExprtkEvaluator::LoadFunctions() { @@ -118,6 +132,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 +178,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;