1use reqwest::Method;
2use serde::{Deserialize, Serialize};
3use serde_json::{json, Value};
4use tracing::{debug, info};
5
6use super::error::WazuhApiError;
7use super::wazuh_client::WazuhApiClient;
8
9#[derive(Debug, Clone, Deserialize, Serialize)]
10pub struct AgentConfiguration {
11 pub agent_id: String,
12 pub configuration: Value,
13}
14
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct ManagerConfiguration {
17 pub configuration: Value,
18}
19
20#[derive(Debug, Clone, Deserialize, Serialize)]
21pub struct GroupFilters {
22 pub os: Option<String>,
23 pub name: Option<String>,
24 pub profile: Option<String>,
25 }
27
28#[derive(Debug, Clone, Deserialize, Serialize)]
29pub struct GroupConfigContent {
30 #[serde(flatten)]
33 pub config: Value,
34}
35
36#[derive(Debug, Clone, Deserialize, Serialize)]
37pub struct GroupConfigurationItem {
38 pub filters: Option<GroupFilters>, pub config: GroupConfigContent,
40}
41
42#[derive(Debug, Clone, Deserialize, Serialize)]
43pub struct GroupConfiguration {
44 pub group_name: String,
45 pub filters: Option<GroupFilters>,
46 pub config: Value,
47}
48
49#[derive(Debug, Clone, Deserialize, Serialize)]
50pub struct ConfigurationSection {
51 pub section: String,
52 pub content: Value,
53}
54
55#[derive(Debug, Clone)]
56pub struct ConfigurationClient {
57 api_client: WazuhApiClient,
58}
59
60impl ConfigurationClient {
61 pub fn new(api_client: WazuhApiClient) -> Self {
62 Self { api_client }
63 }
64
65 pub async fn get_agent_configuration(
66 &mut self,
67 agent_id: &str,
68 section: Option<&str>,
69 field: Option<&str>,
70 ) -> Result<AgentConfiguration, WazuhApiError> {
71 debug!(%agent_id, ?section, ?field, "Getting agent configuration");
72
73 let mut query_params = Vec::new();
74
75 if let Some(section) = section {
76 query_params.push(("section", section.to_string()));
77 }
78 if let Some(field) = field {
79 query_params.push(("field", field.to_string()));
80 }
81
82 let query_params_ref: Vec<(&str, &str)> =
83 query_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
84
85 let endpoint = format!("/agents/{}/config", agent_id);
86 let response = self
87 .api_client
88 .make_request(
89 Method::GET,
90 &endpoint,
91 None,
92 if query_params_ref.is_empty() {
93 None
94 } else {
95 Some(&query_params_ref)
96 },
97 )
98 .await?;
99
100 let config_data = response
101 .get("data")
102 .and_then(|d| d.get("affected_items"))
103 .and_then(|items| items.as_array())
104 .and_then(|arr| arr.first())
105 .ok_or_else(|| {
106 WazuhApiError::ApiError(format!("Configuration for agent {} not found", agent_id))
107 })?;
108
109 let configuration = AgentConfiguration {
110 agent_id: agent_id.to_string(),
111 configuration: config_data.clone(),
112 };
113
114 info!(%agent_id, "Retrieved agent configuration");
115 Ok(configuration)
116 }
117
118 pub async fn get_manager_configuration(
119 &mut self,
120 section: Option<&str>,
121 field: Option<&str>,
122 ) -> Result<ManagerConfiguration, WazuhApiError> {
123 debug!(?section, ?field, "Getting manager configuration");
124
125 let mut query_params = Vec::new();
126
127 if let Some(section) = section {
128 query_params.push(("section", section.to_string()));
129 }
130 if let Some(field) = field {
131 query_params.push(("field", field.to_string()));
132 }
133
134 let query_params_ref: Vec<(&str, &str)> =
135 query_params.iter().map(|(k, v)| (*k, v.as_str())).collect();
136
137 let response = self
138 .api_client
139 .make_request(
140 Method::GET,
141 "/manager/configuration",
142 None,
143 if query_params_ref.is_empty() {
144 None
145 } else {
146 Some(&query_params_ref)
147 },
148 )
149 .await?;
150
151 let config_data = response
152 .get("data")
153 .and_then(|d| d.get("affected_items"))
154 .and_then(|items| items.as_array())
155 .and_then(|arr| arr.first())
156 .ok_or_else(|| {
157 WazuhApiError::ApiError("Manager configuration not found".to_string())
158 })?;
159
160 let configuration = ManagerConfiguration {
161 configuration: config_data.clone(),
162 };
163
164 info!("Retrieved manager configuration");
165 Ok(configuration)
166 }
167
168 pub async fn get_group_configuration(
169 &mut self,
170 group_name: &str,
171 ) -> Result<GroupConfiguration, WazuhApiError> {
172 debug!(%group_name, "Getting group configuration");
173
174 let endpoint = format!("/agents/groups/{}/configuration", group_name);
175 let response = self
176 .api_client
177 .make_request(Method::GET, &endpoint, None, None)
178 .await?;
179
180 let config_data = response
181 .get("data")
182 .and_then(|d| d.get("affected_items"))
183 .and_then(|items| items.as_array())
184 .and_then(|arr| arr.first())
185 .ok_or_else(|| {
186 WazuhApiError::ApiError(format!("Configuration for group {} not found", group_name))
187 })?;
188
189 let item_config: Value = config_data
190 .get("config")
191 .cloned()
192 .unwrap_or_else(|| config_data.clone());
193 let item_filters: Option<GroupFilters> = config_data
194 .get("filters")
195 .and_then(|f| serde_json::from_value(f.clone()).ok());
196
197 let configuration = GroupConfiguration {
198 group_name: group_name.to_string(), filters: item_filters,
200 config: item_config,
201 };
202
203 info!(%group_name, "Retrieved group configuration");
204 Ok(configuration)
205 }
206
207 pub async fn update_group_configuration(
208 &mut self,
209 group_name: &str,
210 xml_configuration: String, ) -> Result<Value, WazuhApiError> {
212 debug!(%group_name, "Updating group configuration with XML");
213
214 let endpoint = format!("/agents/groups/{}/configuration", group_name);
215 let body_value = json!({ "xml_content": xml_configuration });
229
230 let response = self
231 .api_client
232 .make_request(Method::PUT, &endpoint, Some(body_value), None)
233 .await?;
234 info!(%group_name, "Updated group configuration (attempted with XML-in-JSON)");
237 Ok(response)
238 }
239
240 pub async fn get_agent_config_sections(
241 &mut self,
242 agent_id: &str,
243 ) -> Result<Vec<String>, WazuhApiError> {
244 debug!(%agent_id, "Getting agent configuration sections");
245
246 let endpoint = format!("/agents/{}/config", agent_id);
247 let response = self
248 .api_client
249 .make_request(Method::GET, &endpoint, None, None)
250 .await?;
251
252 let config_data = response
253 .get("data")
254 .and_then(|d| d.get("affected_items"))
255 .and_then(|items| items.as_array())
256 .and_then(|arr| arr.first())
257 .ok_or_else(|| {
258 WazuhApiError::ApiError(format!("Configuration for agent {} not found", agent_id))
259 })?;
260
261 let sections: Vec<String> = if let Some(obj) = config_data.as_object() {
262 obj.keys().cloned().collect()
263 } else {
264 Vec::new()
265 };
266
267 info!(%agent_id, "Retrieved {} configuration sections", sections.len());
268 Ok(sections)
269 }
270
271 pub async fn get_manager_config_sections(&mut self) -> Result<Vec<String>, WazuhApiError> {
272 debug!("Getting manager configuration sections");
273
274 let response = self
275 .api_client
276 .make_request(Method::GET, "/manager/configuration", None, None)
277 .await?;
278
279 let config_data = response
280 .get("data")
281 .and_then(|d| d.get("affected_items"))
282 .and_then(|items| items.as_array())
283 .and_then(|arr| arr.first())
284 .ok_or_else(|| {
285 WazuhApiError::ApiError("Manager configuration not found".to_string())
286 })?;
287
288 let sections: Vec<String> = if let Some(obj) = config_data.as_object() {
289 obj.keys().cloned().collect()
290 } else {
291 Vec::new()
292 };
293
294 info!(
295 "Retrieved {} manager configuration sections",
296 sections.len()
297 );
298 Ok(sections)
299 }
300
301 pub async fn compare_agent_configurations(
302 &mut self,
303 agent_id1: &str,
304 agent_id2: &str,
305 section: Option<&str>,
306 ) -> Result<Value, WazuhApiError> {
307 debug!(%agent_id1, %agent_id2, ?section, "Comparing agent configurations");
308
309 let config1 = self
310 .get_agent_configuration(agent_id1, section, None)
311 .await?;
312 let config2 = self
313 .get_agent_configuration(agent_id2, section, None)
314 .await?;
315
316 let comparison = json!({
317 "agent1": {
318 "id": agent_id1,
319 "configuration": config1.configuration
320 },
321 "agent2": {
322 "id": agent_id2,
323 "configuration": config2.configuration
324 },
325 "differences": self.find_config_differences(&config1.configuration, &config2.configuration)
326 });
327
328 info!(%agent_id1, %agent_id2, "Completed configuration comparison");
329 Ok(comparison)
330 }
331
332 fn find_config_differences(&self, config1: &Value, config2: &Value) -> Value {
333 let mut differences = Vec::new();
335
336 if let (Some(obj1), Some(obj2)) = (config1.as_object(), config2.as_object()) {
337 for (key, value1) in obj1 {
338 if let Some(value2) = obj2.get(key) {
339 if value1 != value2 {
340 differences.push(json!({
341 "section": key,
342 "agent1_value": value1,
343 "agent2_value": value2
344 }));
345 }
346 } else {
347 differences.push(json!({
348 "section": key,
349 "agent1_value": value1,
350 "agent2_value": null
351 }));
352 }
353 }
354
355 for (key, value2) in obj2 {
356 if !obj1.contains_key(key) {
357 differences.push(json!({
358 "section": key,
359 "agent1_value": null,
360 "agent2_value": value2
361 }));
362 }
363 }
364 }
365
366 json!(differences)
367 }
368
369 pub async fn validate_configuration(&mut self) -> Result<Value, WazuhApiError> {
370 debug!("Validating manager configuration");
371
372 let response = self
373 .api_client
374 .make_request(Method::GET, "/manager/configuration/validation", None, None)
375 .await?;
376
377 info!("Retrieved configuration validation status");
378 Ok(response)
379 }
380}