misp_client/
attributes.rs

1//! MISP attribute operations and search queries.
2
3use 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}