Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -367,4 +367,4 @@ build_support/
*.egg-info

# clang-format
build_support/clang_format_exclusions.txt
build_support/clang_format_exclusions.txt
25 changes: 24 additions & 1 deletion casbin/model/evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string&>(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<std::string>());
} else if (var.is_number()) {
// Get as double first to preserve precision, then cast to numerical_type
this->AddNumericIdentifier(identifier, static_cast<numerical_type>(var.get<double>()));
} else if (var.is_boolean()) {
this->AddNumericIdentifier(identifier, static_cast<numerical_type>(var.get<bool>() ? 1 : 0));
}
// For other types (arrays, null, etc.), we skip them as they're not supported in the expression evaluator
}

void ExprtkEvaluator::LoadFunctions() {
Expand Down Expand Up @@ -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<exprtk_func_t> func) {
Expand Down Expand Up @@ -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<numerical_type>(0);
this->symbol_table.add_variable(identifier, *numeric_identifiers_[identifier]);
}
*numeric_identifiers_[identifier] = value;
}

} // namespace casbin
3 changes: 3 additions & 0 deletions include/casbin/model/evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class ExprtkEvaluator : public IEvaluator {
parser_t parser;
std::vector<std::shared_ptr<exprtk_func_t>> Functions;
std::unordered_map<std::string, std::unique_ptr<std::string>> identifiers_;
std::unordered_map<std::string, std::unique_ptr<numerical_type>> numeric_identifiers_;

public:
ExprtkEvaluator() {
Expand Down Expand Up @@ -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<std::string, std::string> requestValues() const override;
};
} // namespace casbin
Expand Down
119 changes: 119 additions & 0 deletions tests/enforcer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>(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>(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>(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>(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>(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>(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 <typename T>
void TestEnforceEx(casbin::Enforcer& e, T&& params, const bool expect_result, const std::vector<std::string>& expect_explain) {
std::vector<std::string> actual_explain;
Expand Down