#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <unordered_set>
#include <utility>
#include <variant>
#include <vector>
#include "action_mapper.hpp"
#include "attribute_collector.hpp"
#include "clock.hpp"
#include "condition/base.hpp"
#include "memory_resource.hpp"
#include "obfuscator.hpp"
#include "object.hpp"
#include "pointer.hpp"
#include "rule.hpp"
#include "serializer.hpp"
#include "uuid.hpp"
namespace ddwaf {
namespace {
owned_object serialize_match(condition_match &match, nonnull_ptr<memory::memory_resource> alloc)
{
auto match_map = owned_object::make_map(3, alloc);
match_map.emplace("operator", match.operator_name);
match_map.emplace("operator_value", match.operator_value);
auto parameters = match_map.emplace("parameters", owned_object::make_array(1, alloc));
if (match.args.size() == 1 && match.args[0].name == "input") {
auto param = parameters.emplace_back(owned_object::make_map(4, alloc));
auto &arg = match.args[0];
param.emplace("address", arg.address);
param.emplace("value", arg.resolved.to_object(alloc));
auto key_path =
param.emplace("key_path", owned_object::make_array(arg.key_path.size(), alloc));
for (const auto &key : arg.key_path) {
if (std::holds_alternative<std::string_view>(key)) {
key_path.emplace_back(std::get<std::string_view>(key));
} else {
key_path.emplace_back(std::get<int64_t>(key));
}
}
auto highlight_arr =
param.emplace("highlight", owned_object::make_array(match.highlights.size(), alloc));
for (auto &highlight : match.highlights) {
highlight_arr.emplace_back(highlight.to_object(alloc));
}
} else {
auto param = parameters.emplace_back(owned_object::make_map(match.args.size() + 1, alloc));
for (auto &arg : match.args) {
auto argument = param.emplace(arg.name, owned_object::make_map(3, alloc));
argument.emplace("address", arg.address);
argument.emplace("value", arg.resolved.to_object(alloc));
auto key_path =
argument.emplace("key_path", owned_object::make_array(arg.key_path.size(), alloc));
for (const auto &key : arg.key_path) {
if (std::holds_alternative<std::string_view>(key)) {
key_path.emplace_back(std::get<std::string_view>(key));
} else {
key_path.emplace_back(std::get<int64_t>(key));
}
}
}
auto highlight_arr =
param.emplace("highlight", owned_object::make_array(match.highlights.size(), alloc));
for (auto &highlight : match.highlights) {
highlight_arr.emplace_back(highlight.to_object(alloc));
}
}
return match_map;
}
struct action_tracker {
std::string_view blocking_action;
action_type blocking_action_type{action_type::none};
std::string stack_id;
std::string block_id;
std::unordered_set<std::string_view> non_blocking_actions;
const action_mapper &mapper;
};
action_type get_action_type(const action_mapper &mapper, std::string_view id)
{
if (id.empty()) {
return action_type::none;
}
auto it = mapper.find(id);
if (it == mapper.end()) {
return action_type::none;
}
return it->second.type;
}
void add_action_to_tracker(action_tracker &actions, std::string_view id, action_type type)
{
if (is_blocking_action(type)) {
if (type > actions.blocking_action_type) {
actions.blocking_action_type = type;
actions.blocking_action = id;
}
if (actions.block_id.empty()) {
actions.block_id = uuidv4_generate_pseudo();
}
} else {
if (type == action_type::generate_stack && actions.stack_id.empty()) {
actions.stack_id = uuidv4_generate_pseudo();
}
actions.non_blocking_actions.emplace(id);
}
}
void consolidate_actions(std::string_view action_override,
const std::vector<std::string> &rule_actions, action_tracker &actions)
{
if (auto type = get_action_type(actions.mapper, action_override); is_modal_action(type)) {
add_action_to_tracker(actions, action_override, type);
} else {
action_override = {};
}
for (const auto &action_id : rule_actions) {
auto type = get_action_type(actions.mapper, action_id);
if (type == action_type::none || (!action_override.empty() && is_modal_action(type))) {
continue;
}
add_action_to_tracker(actions, action_id, type);
}
}
struct generated_action {
owned_object actions_array;
bool required_stack_id;
bool required_block_id;
};
generated_action serialize_event_actions(std::string_view action_override,
const std::vector<std::string> &rule_actions, action_tracker &actions,
nonnull_ptr<memory::memory_resource> alloc)
{
auto actions_array = owned_object::make_array(rule_actions.size(), alloc);
bool has_block_id = false;
if (auto type = get_action_type(actions.mapper, action_override); is_modal_action(type)) {
actions_array.emplace_back(action_override);
has_block_id = is_blocking_action(type);
} else {
action_override = {};
}
bool has_stack_id = false;
for (const auto &action_id : rule_actions) {
auto type = get_action_type(actions.mapper, action_id);
if (!action_override.empty() && is_modal_action(type)) {
continue;
}
has_stack_id = has_stack_id || type == action_type::generate_stack;
has_block_id = has_block_id || is_blocking_action(type);
actions_array.emplace_back(action_id);
}
return {.actions_array = std::move(actions_array),
.required_stack_id = has_stack_id,
.required_block_id = has_block_id};
}
void serialize_event(rule_event &event, const match_obfuscator *obfuscator,
std::string_view action_override, const std::vector<std::string> &rule_actions,
action_tracker &actions, borrowed_object &event_array)
{
auto alloc = event_array.alloc();
auto [actions_array, requires_stack_id, requires_block_id] =
serialize_event_actions(action_override, rule_actions, actions, alloc);
const std::size_t map_size = 2 + (requires_block_id ? 1 : 0) + (requires_stack_id ? 1 : 0);
auto root_map = event_array.emplace_back(owned_object::make_map(map_size, alloc));
auto rule_map = root_map.emplace("rule", owned_object::make_map(4, alloc));
rule_map.emplace("id", event.rule.id);
rule_map.emplace("name", event.rule.name);
rule_map.emplace("on_match", std::move(actions_array));
const auto &rule_tags = event.rule.tags.get();
auto tags_map = rule_map.emplace("tags", owned_object::make_map(rule_tags.size(), alloc));
for (const auto &[key, value] : rule_tags) { tags_map.emplace(key, value); }
auto match_array =
root_map.emplace("rule_matches", owned_object::make_array(event.matches.size(), alloc));
for (auto &match : event.matches) {
if (obfuscator != nullptr) {
obfuscator->obfuscate_match(match);
}
match_array.emplace_back(serialize_match(match, alloc));
}
if (requires_stack_id) {
root_map.emplace("stack_id", actions.stack_id);
}
if (requires_block_id) {
root_map.emplace("security_response_id", actions.block_id);
}
}
void serialize_action(
std::string_view id, const action_tracker &actions, borrowed_object action_map)
{
auto action_it = actions.mapper.find(id);
if (action_it == actions.mapper.end()) {
return;
}
const auto &[type, type_str, parameters] = action_it->second;
if (type == action_type::monitor) {
return;
}
if (type != action_type::generate_stack) {
auto param_map = action_map.emplace(
type_str, owned_object::make_map(parameters.size(), action_map.alloc()));
for (const auto &[k, v] : parameters) {
if (std::holds_alternative<std::string>(v)) {
param_map.emplace(k, std::get<std::string>(v));
} else if (std::holds_alternative<bool>(v)) {
param_map.emplace(k, std::get<bool>(v));
} else if (std::holds_alternative<int64_t>(v)) {
param_map.emplace(k, std::get<int64_t>(v));
} else if (std::holds_alternative<uint64_t>(v)) {
param_map.emplace(k, std::get<uint64_t>(v));
} else if (std::holds_alternative<double>(v)) {
param_map.emplace(k, std::get<double>(v));
}
}
if (is_blocking_action(type)) {
param_map.emplace("security_response_id", actions.block_id);
}
} else {
auto param_map =
action_map.emplace(type_str, owned_object::make_map(1, action_map.alloc()));
param_map.emplace("stack_id", actions.stack_id);
}
}
void serialize_actions(const action_tracker &actions, borrowed_object action_map)
{
if (actions.blocking_action_type != action_type::none) {
serialize_action(actions.blocking_action, actions, action_map);
}
for (const auto &id : actions.non_blocking_actions) {
serialize_action(id, actions, action_map);
}
}
}
void result_serializer::serialize(std::vector<rule_result> &results, attribute_collector &collector,
const timer &deadline, result_components output)
{
action_tracker actions{.blocking_action = {},
.stack_id = {},
.block_id = {},
.non_blocking_actions = {},
.mapper = actions_};
bool final_keep = false;
for (auto &result : results) {
final_keep |= result.keep;
consolidate_actions(result.action_override, result.actions, actions);
if (result.event) {
serialize_event(result.event.value(), obfuscator_, result.action_override,
result.actions, actions, output.events);
}
}
output.duration = owned_object::make_unsigned(deadline.elapsed().count());
output.timeout = owned_object::make_boolean(deadline.expired_before());
output.keep = owned_object::make_boolean(final_keep);
output.attributes = collector.get_available_attributes_and_reset();
serialize_actions(actions, output.actions);
}
std::pair<owned_object, result_components> result_serializer::initialise_result_object()
{
auto object = object_builder::map({{"events", object_builder::array({}, alloc_)},
{"actions", object_builder::map({}, alloc_)},
{"duration", owned_object::make_unsigned(0)},
{"timeout", owned_object::make_boolean(false)},
{"attributes", object_builder::map({}, alloc_)},
{"keep", owned_object::make_boolean(false)},
{"evaluated", owned_object::make_unsigned(0)}},
alloc_);
const result_components res{.events = object.at(0),
.actions = object.at(1),
.duration = object.at(2),
.timeout = object.at(3),
.attributes = object.at(4),
.keep = object.at(5),
.evaluated = object.at(6)};
return {std::move(object), res};
}
}