Skip to content
Closed
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
40 changes: 35 additions & 5 deletions casbin/model/evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
#include "casbin/model/evaluator.h"

#include <functional>
#include <regex>

#include "casbin/util/util.h"
Expand Down Expand Up @@ -61,7 +62,37 @@ 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
std::function<void(const std::string&, const nlohmann::json&)> flatten;
flatten = [&](const std::string& prefix, const nlohmann::json& j) {
if (j.is_object()) {
// Recursively process nested objects
for (auto it = j.begin(); it != j.end(); ++it) {
std::string key = it.key();
flatten(prefix + "." + key, it.value());
}
} else if (j.is_string()) {
// Add string value
this->AddIdentifier(prefix, j.get<std::string>());
} else if (j.is_number_integer()) {
// Convert integer to string
this->AddIdentifier(prefix, std::to_string(j.get<int>()));
} else if (j.is_number_float()) {
// Convert float to string
this->AddIdentifier(prefix, std::to_string(j.get<double>()));
} else if (j.is_boolean()) {
// Convert boolean to string
this->AddIdentifier(prefix, j.get<bool>() ? "true" : "false");
} else if (j.is_null()) {
// Handle null as empty string
this->AddIdentifier(prefix, "");
}
// Note: JSON arrays are intentionally not handled by this implementation.
// If an array is encountered, it is silently ignored and no identifier is added.
};

flatten(identifier, var);
}

void ExprtkEvaluator::LoadFunctions() {
Expand Down Expand Up @@ -144,17 +175,16 @@ std::unordered_map<std::string, std::string> ExprtkEvaluator::requestValues() co
std::vector<std::string> var_list;
symbol_table.get_stringvar_list(var_list);
std::unordered_map<std::string, std::string> result;
for (const auto& e : var_list ) {
if (e[0] == 'r') {
for (const auto& e : var_list) {
if (e[0] == 'r') {
auto token = e.substr(2, e.size() - 2);
auto value = symbol_table.get_stringvar("r." + token)->ref().c_str();
auto value = symbol_table.get_stringvar("r." + token)->ref().c_str();
result.emplace(token, value);
}
}
return result;
}


void ExprtkEvaluator::AddIdentifier(const std::string& identifier, const std::string& var) {
if (!symbol_table.symbol_exists(identifier)) {
identifiers_[identifier] = std::make_unique<std::string>("");
Expand Down
124 changes: 124 additions & 0 deletions tests/enforcer_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -416,4 +416,128 @@ TEST(TestEnforcerEx, TestMapParams) {
// ASSERT_TRUE(!EvalAndGetTop(scope, s6));
// }

TEST(TestEnforcer, JsonData) {
using json = nlohmann::json;

// Create evaluator and initialize
auto evaluator = std::make_shared<casbin::ExprtkEvaluator>();
evaluator->InitialObject("r");

// Test simple JSON with various data types
json myJson = {{"DoubleCase", 3.141}, {"IntegerCase", 2}, {"BooleanCase", true}, {"StringCase", "Bob"}, {"x", {{"y", {{"z", 1}}}, {"x", 2}}}};

evaluator->PushObjectJson("r", "data", myJson);

// Test double value (stored as string "3.141000")
ASSERT_TRUE(evaluator->Eval("r.data.DoubleCase == '3.141000'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test integer value (stored as string "2")
ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '2'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test boolean value (stored as string "true")
ASSERT_TRUE(evaluator->Eval("r.data.BooleanCase == 'true'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test string value
ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'Bob'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test nested JSON - x.y.z (stored as string "1")
ASSERT_TRUE(evaluator->Eval("r.data.x.y.z == '1'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test nested JSON - x.x (stored as string "2")
ASSERT_TRUE(evaluator->Eval("r.data.x.x == '2'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test negative cases
ASSERT_TRUE(evaluator->Eval("r.data.DoubleCase == '3.14'"));
ASSERT_FALSE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.data.IntegerCase == '1'"));
ASSERT_FALSE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.data.BooleanCase == 'false'"));
ASSERT_FALSE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.data.StringCase == 'BoB'"));
ASSERT_FALSE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.data.x.y.z == '2'"));
ASSERT_FALSE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.data.x.x == '1'"));
ASSERT_FALSE(evaluator->GetBoolean());
}

TEST(TestEnforcer, JsonDataComplex) {
using json = nlohmann::json;

// Create evaluator and initialize
auto evaluator = std::make_shared<casbin::ExprtkEvaluator>();
evaluator->InitialObject("r");

// Test complex nested JSON similar to the issue example
json sub = {{"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"},
{"ESecurityProtocol", "HTTPS"}};

json obj = {{"SecurityLevel", "3"}, {"Source", "ISS"}, {"DistributionMethod", "C"}};

evaluator->PushObjectJson("r", "sub", sub);
evaluator->PushObjectJson("r", "obj", obj);
evaluator->PushObjectString("r", "act", "read");

// Test sub attributes
ASSERT_TRUE(evaluator->Eval("r.sub.ID == 'zk'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.sub.proxy == 'vpn'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.sub.Department == 'nlp'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.sub.OS == 'HarmonyOS'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.sub.CPU == 'XeonPlatinum8480+'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test obj attributes
ASSERT_TRUE(evaluator->Eval("r.obj.SecurityLevel == '3'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.obj.Source == 'ISS'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.obj.DistributionMethod == 'C'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test act attribute
ASSERT_TRUE(evaluator->Eval("r.act == 'read'"));
ASSERT_TRUE(evaluator->GetBoolean());

// Test combined conditions
ASSERT_TRUE(evaluator->Eval("r.sub.ID == 'zk' and r.obj.SecurityLevel == '3' and r.act == 'read'"));
ASSERT_TRUE(evaluator->GetBoolean());

ASSERT_TRUE(evaluator->Eval("r.sub.Department == 'nlp' and r.obj.Source == 'ISS'"));
ASSERT_TRUE(evaluator->GetBoolean());
}

} // namespace