1use serde_json::{json, Value};
4use tracing::debug;
5
6use crate::client::MispClient;
7use crate::error::MispError;
8use crate::models::Attribute;
9
10#[derive(Clone)]
11pub struct AttributesClient {
12 client: MispClient,
13}
14
15impl AttributesClient {
16 pub fn new(client: MispClient) -> Self {
17 Self { client }
18 }
19
20 pub async fn get(&self, id: &str) -> Result<Attribute, MispError> {
21 debug!(%id, "Fetching attribute");
22 let resp = self.client.get(&format!("/attributes/view/{}", id)).await?;
23 parse_attribute_response(resp)
24 }
25
26 pub async fn search(&self, query: AttributeSearchQuery) -> Result<Vec<Attribute>, MispError> {
27 debug!("Searching attributes");
28 let resp = self
29 .client
30 .post("/attributes/restSearch", Some(query.to_json()))
31 .await?;
32 parse_rest_search_attributes(resp)
33 }
34
35 pub async fn search_by_value(&self, value: &str) -> Result<Vec<Attribute>, MispError> {
36 self.search(AttributeSearchQuery::new().value(value)).await
37 }
38
39 pub async fn search_by_type(&self, attr_type: &str) -> Result<Vec<Attribute>, MispError> {
40 self.search(AttributeSearchQuery::new().attr_type(attr_type))
41 .await
42 }
43
44 pub async fn describe_types(&self) -> Result<AttributeTypes, MispError> {
45 debug!("Fetching attribute types");
46 let resp = self.client.get("/attributes/describeTypes").await?;
47 parse_describe_types(resp)
48 }
49
50 pub async fn correlations(&self, value: &str) -> Result<Vec<AttributeCorrelation>, MispError> {
51 debug!(%value, "Searching correlations");
52 let query = AttributeSearchQuery::new()
53 .value(value)
54 .include_correlations(true);
55 let attrs = self.search(query).await?;
56
57 let mut correlations = Vec::new();
58 for attr in attrs {
59 correlations.push(AttributeCorrelation {
60 attribute_id: attr.id.clone(),
61 attribute_uuid: attr.uuid.clone(),
62 event_id: attr.event_id.clone(),
63 value: attr.value.clone(),
64 attr_type: attr.attr_type.clone(),
65 category: attr.category.clone(),
66 });
67 }
68 Ok(correlations)
69 }
70}
71
72impl std::fmt::Debug for AttributesClient {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct("AttributesClient").finish()
75 }
76}
77
78#[derive(Debug, Default, Clone)]
79pub struct AttributeSearchQuery {
80 pub value: Option<String>,
81 pub attr_type: Option<String>,
82 pub category: Option<String>,
83 pub tags: Option<Vec<String>>,
84 pub not_tags: Option<Vec<String>>,
85 pub org: Option<String>,
86 pub event_id: Option<String>,
87 pub from: Option<String>,
88 pub to: Option<String>,
89 pub last: Option<String>,
90 pub to_ids: Option<bool>,
91 pub published: Option<bool>,
92 pub deleted: Option<bool>,
93 pub include_event_uuid: Option<bool>,
94 pub include_correlations: Option<bool>,
95 pub include_sightings: Option<bool>,
96 pub include_galaxy: Option<bool>,
97 pub limit: Option<u32>,
98 pub page: Option<u32>,
99 pub timestamp: Option<String>,
100}
101
102impl AttributeSearchQuery {
103 pub fn new() -> Self {
104 Self::default()
105 }
106
107 pub fn value(mut self, v: impl Into<String>) -> Self {
108 self.value = Some(v.into());
109 self
110 }
111
112 pub fn attr_type(mut self, t: impl Into<String>) -> Self {
113 self.attr_type = Some(t.into());
114 self
115 }
116
117 pub fn category(mut self, c: impl Into<String>) -> Self {
118 self.category = Some(c.into());
119 self
120 }
121
122 pub fn tags(mut self, tags: Vec<String>) -> Self {
123 self.tags = Some(tags);
124 self
125 }
126
127 pub fn exclude_tags(mut self, tags: Vec<String>) -> Self {
128 self.not_tags = Some(tags);
129 self
130 }
131
132 pub fn org(mut self, org: impl Into<String>) -> Self {
133 self.org = Some(org.into());
134 self
135 }
136
137 pub fn event_id(mut self, id: impl Into<String>) -> Self {
138 self.event_id = Some(id.into());
139 self
140 }
141
142 pub fn from_date(mut self, from: impl Into<String>) -> Self {
143 self.from = Some(from.into());
144 self
145 }
146
147 pub fn to_date(mut self, to: impl Into<String>) -> Self {
148 self.to = Some(to.into());
149 self
150 }
151
152 pub fn last(mut self, duration: impl Into<String>) -> Self {
153 self.last = Some(duration.into());
154 self
155 }
156
157 pub fn to_ids(mut self, to_ids: bool) -> Self {
158 self.to_ids = Some(to_ids);
159 self
160 }
161
162 pub fn published(mut self, published: bool) -> Self {
163 self.published = Some(published);
164 self
165 }
166
167 pub fn include_deleted(mut self) -> Self {
168 self.deleted = Some(true);
169 self
170 }
171
172 pub fn include_event_uuid(mut self) -> Self {
173 self.include_event_uuid = Some(true);
174 self
175 }
176
177 pub fn include_correlations(mut self, include: bool) -> Self {
178 self.include_correlations = Some(include);
179 self
180 }
181
182 pub fn include_sightings(mut self) -> Self {
183 self.include_sightings = Some(true);
184 self
185 }
186
187 pub fn include_galaxy(mut self) -> Self {
188 self.include_galaxy = Some(true);
189 self
190 }
191
192 pub fn limit(mut self, limit: u32) -> Self {
193 self.limit = Some(limit);
194 self
195 }
196
197 pub fn page(mut self, page: u32) -> Self {
198 self.page = Some(page);
199 self
200 }
201
202 pub fn timestamp(mut self, ts: impl Into<String>) -> Self {
203 self.timestamp = Some(ts.into());
204 self
205 }
206
207 fn to_json(&self) -> Value {
208 let mut obj = serde_json::Map::new();
209
210 if let Some(ref v) = self.value {
211 obj.insert("value".into(), json!(v));
212 }
213 if let Some(ref v) = self.attr_type {
214 obj.insert("type".into(), json!(v));
215 }
216 if let Some(ref v) = self.category {
217 obj.insert("category".into(), json!(v));
218 }
219 if let Some(ref v) = self.tags {
220 obj.insert("tags".into(), json!(v));
221 }
222 if let Some(ref v) = self.not_tags {
223 obj.insert("not_tags".into(), json!(v));
224 }
225 if let Some(ref v) = self.org {
226 obj.insert("org".into(), json!(v));
227 }
228 if let Some(ref v) = self.event_id {
229 obj.insert("eventid".into(), json!(v));
230 }
231 if let Some(ref v) = self.from {
232 obj.insert("from".into(), json!(v));
233 }
234 if let Some(ref v) = self.to {
235 obj.insert("to".into(), json!(v));
236 }
237 if let Some(ref v) = self.last {
238 obj.insert("last".into(), json!(v));
239 }
240 if let Some(v) = self.to_ids {
241 obj.insert("to_ids".into(), json!(v));
242 }
243 if let Some(v) = self.published {
244 obj.insert("published".into(), json!(v));
245 }
246 if let Some(v) = self.deleted {
247 obj.insert("deleted".into(), json!(v));
248 }
249 if let Some(v) = self.include_event_uuid {
250 obj.insert("includeEventUuid".into(), json!(v));
251 }
252 if let Some(v) = self.include_correlations {
253 obj.insert("includeCorrelations".into(), json!(v));
254 }
255 if let Some(v) = self.include_sightings {
256 obj.insert("includeSightings".into(), json!(v));
257 }
258 if let Some(v) = self.include_galaxy {
259 obj.insert("includeGalaxy".into(), json!(v));
260 }
261 if let Some(v) = self.limit {
262 obj.insert("limit".into(), json!(v));
263 }
264 if let Some(v) = self.page {
265 obj.insert("page".into(), json!(v));
266 }
267 if let Some(ref v) = self.timestamp {
268 obj.insert("timestamp".into(), json!(v));
269 }
270
271 Value::Object(obj)
272 }
273}
274
275#[derive(Debug, Clone)]
276pub struct AttributeTypes {
277 pub types: Vec<String>,
278 pub categories: Vec<String>,
279 pub category_type_mappings: std::collections::HashMap<String, Vec<String>>,
280}
281
282#[derive(Debug, Clone)]
283pub struct AttributeCorrelation {
284 pub attribute_id: String,
285 pub attribute_uuid: String,
286 pub event_id: String,
287 pub value: String,
288 pub attr_type: String,
289 pub category: String,
290}
291
292fn parse_attribute_response(resp: Value) -> Result<Attribute, MispError> {
293 if let Some(attr) = resp.get("Attribute") {
294 return serde_json::from_value(attr.clone()).map_err(MispError::Parse);
295 }
296 Err(MispError::InvalidResponse(
297 "missing Attribute wrapper".into(),
298 ))
299}
300
301fn parse_rest_search_attributes(resp: Value) -> Result<Vec<Attribute>, MispError> {
302 if let Some(response) = resp.get("response") {
303 if let Some(attr_wrapper) = response.get("Attribute") {
304 if let Some(arr) = attr_wrapper.as_array() {
305 return arr
306 .iter()
307 .map(|a| serde_json::from_value(a.clone()))
308 .collect::<Result<Vec<_>, _>>()
309 .map_err(MispError::Parse);
310 }
311 }
312 }
313
314 if let Some(response) = resp.get("response") {
315 if let Some(arr) = response.as_array() {
316 let attrs: Result<Vec<Attribute>, _> = arr
317 .iter()
318 .filter_map(|v| v.get("Attribute"))
319 .map(|a| serde_json::from_value(a.clone()))
320 .collect();
321 return attrs.map_err(MispError::Parse);
322 }
323 }
324
325 Err(MispError::InvalidResponse(
326 "unexpected attribute response format".into(),
327 ))
328}
329
330fn parse_describe_types(resp: Value) -> Result<AttributeTypes, MispError> {
331 let result = resp
332 .get("result")
333 .ok_or_else(|| MispError::InvalidResponse("missing result in describeTypes".into()))?;
334
335 let types = result
336 .get("types")
337 .and_then(|v| v.as_array())
338 .map(|arr| {
339 arr.iter()
340 .filter_map(|v| v.as_str())
341 .map(String::from)
342 .collect()
343 })
344 .unwrap_or_default();
345
346 let categories = result
347 .get("categories")
348 .and_then(|v| v.as_array())
349 .map(|arr| {
350 arr.iter()
351 .filter_map(|v| v.as_str())
352 .map(String::from)
353 .collect()
354 })
355 .unwrap_or_default();
356
357 let mut category_type_mappings = std::collections::HashMap::new();
358 if let Some(mappings) = result
359 .get("category_type_mappings")
360 .and_then(|v| v.as_object())
361 {
362 for (cat, types_val) in mappings {
363 if let Some(arr) = types_val.as_array() {
364 let type_list: Vec<String> = arr
365 .iter()
366 .filter_map(|v| v.as_str())
367 .map(String::from)
368 .collect();
369 category_type_mappings.insert(cat.clone(), type_list);
370 }
371 }
372 }
373
374 Ok(AttributeTypes {
375 types,
376 categories,
377 category_type_mappings,
378 })
379}