Skip to content

Commit 7155fb5

Browse files
authored
feat: add JSON support for Enforce() by enabling PushObjectJson (#264)
1 parent 1cba76e commit 7155fb5

File tree

4 files changed

+147
-2
lines changed

4 files changed

+147
-2
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,4 +367,4 @@ build_support/
367367
*.egg-info
368368

369369
# clang-format
370-
build_support/clang_format_exclusions.txt
370+
build_support/clang_format_exclusions.txt

casbin/model/evaluator.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,21 @@ void ExprtkEvaluator::PushObjectString(const std::string& target, const std::str
6161

6262
void ExprtkEvaluator::PushObjectJson(const std::string& target, const std::string& proprity, const nlohmann::json& var) {
6363
auto identifier = target + "." + proprity;
64-
// this->symbol_table.add_stringvar(identifier, const_cast<std::string&>(var));
64+
65+
// Recursively flatten JSON object into dot-notation identifiers
66+
if (var.is_object()) {
67+
for (auto& [key, value] : var.items()) {
68+
PushObjectJson(identifier, key, value);
69+
}
70+
} else if (var.is_string()) {
71+
this->AddIdentifier(identifier, var.get<std::string>());
72+
} else if (var.is_number()) {
73+
// Get as double first to preserve precision, then cast to numerical_type
74+
this->AddNumericIdentifier(identifier, static_cast<numerical_type>(var.get<double>()));
75+
} else if (var.is_boolean()) {
76+
this->AddNumericIdentifier(identifier, static_cast<numerical_type>(var.get<bool>() ? 1 : 0));
77+
}
78+
// For other types (arrays, null, etc.), we skip them as they're not supported in the expression evaluator
6579
}
6680

6781
void ExprtkEvaluator::LoadFunctions() {
@@ -118,6 +132,7 @@ void ExprtkEvaluator::Clean(AssertionMap& section, bool after_enforce) {
118132
this->expression_string_ = "";
119133
this->Functions.clear();
120134
this->identifiers_.clear();
135+
this->numeric_identifiers_.clear();
121136
}
122137

123138
void ExprtkEvaluator::AddFunction(const std::string& func_name, std::shared_ptr<exprtk_func_t> func) {
@@ -163,4 +178,12 @@ void ExprtkEvaluator::AddIdentifier(const std::string& identifier, const std::st
163178
symbol_table.get_stringvar(identifier)->ref() = var;
164179
}
165180

181+
void ExprtkEvaluator::AddNumericIdentifier(const std::string& identifier, numerical_type value) {
182+
if (!symbol_table.symbol_exists(identifier)) {
183+
numeric_identifiers_[identifier] = std::make_unique<numerical_type>(0);
184+
this->symbol_table.add_variable(identifier, *numeric_identifiers_[identifier]);
185+
}
186+
*numeric_identifiers_[identifier] = value;
187+
}
188+
166189
} // namespace casbin

include/casbin/model/evaluator.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class ExprtkEvaluator : public IEvaluator {
7171
parser_t parser;
7272
std::vector<std::shared_ptr<exprtk_func_t>> Functions;
7373
std::unordered_map<std::string, std::unique_ptr<std::string>> identifiers_;
74+
std::unordered_map<std::string, std::unique_ptr<numerical_type>> numeric_identifiers_;
7475

7576
public:
7677
ExprtkEvaluator() {
@@ -109,6 +110,8 @@ class ExprtkEvaluator : public IEvaluator {
109110

110111
void AddIdentifier(const std::string& identifier, const std::string& var);
111112

113+
void AddNumericIdentifier(const std::string& identifier, numerical_type value);
114+
112115
std::unordered_map<std::string, std::string> requestValues() const override;
113116
};
114117
} // namespace casbin

tests/enforcer_test.cpp

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,125 @@ TEST(TestEnforcer, TestMapParams) {
155155
ASSERT_EQ(e.Enforce(params), true);
156156
}
157157

158+
TEST(TestEnforcer, TestJsonData) {
159+
using json = nlohmann::json;
160+
casbin::Enforcer e(abac_rule_model_path, abac_rule_policy_path);
161+
162+
// Test with JSON object containing Age property
163+
// Policy: p, r.sub.Age > 18, /data1, read
164+
auto sub_json_adult = std::make_shared<json>(json{
165+
{"Name", "alice"},
166+
{"Age", 30}
167+
});
168+
169+
casbin::DataMap params = {
170+
{"sub", sub_json_adult},
171+
{"obj", "/data1"},
172+
{"act", "read"}
173+
};
174+
175+
// Should be allowed: Age 30 > 18
176+
ASSERT_EQ(e.Enforce(params), true);
177+
178+
// Test with minor (Age < 18)
179+
auto sub_json_minor = std::make_shared<json>(json{
180+
{"Name", "bob"},
181+
{"Age", 16}
182+
});
183+
184+
params = {
185+
{"sub", sub_json_minor},
186+
{"obj", "/data1"},
187+
{"act", "read"}
188+
};
189+
190+
// Should be denied: Age 16 < 18
191+
ASSERT_EQ(e.Enforce(params), false);
192+
193+
// Test policy: p, r.sub.Age < 60, /data2, write
194+
auto sub_json_young = std::make_shared<json>(json{
195+
{"Name", "charlie"},
196+
{"Age", 45}
197+
});
198+
199+
params = {
200+
{"sub", sub_json_young},
201+
{"obj", "/data2"},
202+
{"act", "write"}
203+
};
204+
205+
// Should be allowed: Age 45 < 60
206+
ASSERT_EQ(e.Enforce(params), true);
207+
208+
// Test with senior (Age >= 60)
209+
auto sub_json_senior = std::make_shared<json>(json{
210+
{"Name", "dave"},
211+
{"Age", 65}
212+
});
213+
214+
params = {
215+
{"sub", sub_json_senior},
216+
{"obj", "/data2"},
217+
{"act", "write"}
218+
};
219+
220+
// Should be denied: Age 65 >= 60
221+
ASSERT_EQ(e.Enforce(params), false);
222+
}
223+
224+
TEST(TestEnforcer, TestComplexJsonData) {
225+
using json = nlohmann::json;
226+
227+
// Create a custom model and policy for testing complex JSON structures
228+
// similar to the issue example
229+
casbin::Enforcer e(abac_rule_model_path, abac_rule_policy_path);
230+
231+
// Test with complex nested JSON object like in the issue
232+
auto complex_request = std::make_shared<json>(json{
233+
{"ID", "zk"},
234+
{"proxy", "vpn"},
235+
{"Department", "nlp"},
236+
{"month", "Jan"},
237+
{"week", "Mon"},
238+
{"time", "morning"},
239+
{"Longitude", 123},
240+
{"Latitude", 456},
241+
{"Altitude", 789},
242+
{"OS", "HarmonyOS"},
243+
{"CPU", "XeonPlatinum8480+"},
244+
{"NetworkType", "WLan"},
245+
{"ProtocolType", "Bluetooth"},
246+
{"EncryptionType", "3DES"},
247+
{"SecurityProtocol", "HTTPS"},
248+
{"Age", 25} // For testing with existing policy
249+
});
250+
251+
casbin::DataMap params = {
252+
{"sub", complex_request},
253+
{"obj", "/data1"},
254+
{"act", "read"}
255+
};
256+
257+
// Should work with complex JSON - Age 25 > 18
258+
ASSERT_EQ(e.Enforce(params), true);
259+
260+
// Test with object that also contains nested objects
261+
auto obj_json = std::make_shared<json>(json{
262+
{"SecurityLevel", 3},
263+
{"Source", "ISS"},
264+
{"DistributionMethod", "C"}
265+
});
266+
267+
params = {
268+
{"sub", complex_request},
269+
{"obj", obj_json},
270+
{"act", "read"}
271+
};
272+
273+
// This should not crash even with JSON in multiple parameters
274+
ASSERT_NO_THROW(e.Enforce(params));
275+
}
276+
158277
template <typename T>
159278
void TestEnforceEx(casbin::Enforcer& e, T&& params, const bool expect_result, const std::vector<std::string>& expect_explain) {
160279
std::vector<std::string> actual_explain;

0 commit comments

Comments
 (0)