misp_client/
warninglists.rs

1//! MISP warninglist operations for false positive reduction.
2
3use serde_json::{json, Value};
4use tracing::debug;
5
6use crate::client::MispClient;
7use crate::error::MispError;
8use crate::models::{Warninglist, WarninglistCheckResult, WarninglistMatch};
9
10#[derive(Clone)]
11pub struct WarninglistsClient {
12    client: MispClient,
13}
14
15impl WarninglistsClient {
16    pub fn new(client: MispClient) -> Self {
17        Self { client }
18    }
19
20    pub async fn list(&self) -> Result<Vec<Warninglist>, MispError> {
21        debug!("Listing warninglists");
22        let resp = self.client.get("/warninglists/index").await?;
23        parse_warninglists(resp)
24    }
25
26    pub async fn list_enabled(&self) -> Result<Vec<Warninglist>, MispError> {
27        let all = self.list().await?;
28        Ok(all
29            .into_iter()
30            .filter(|w| w.enabled == Some(true))
31            .collect())
32    }
33
34    pub async fn get(&self, id: &str) -> Result<Warninglist, MispError> {
35        debug!(%id, "Fetching warninglist");
36        let resp = self
37            .client
38            .get(&format!("/warninglists/view/{}", id))
39            .await?;
40        parse_warninglist(resp)
41    }
42
43    pub async fn check_value(&self, value: &str) -> Result<WarninglistCheckResult, MispError> {
44        debug!(%value, "Checking value against warninglists");
45        let body = json!({ "value": value });
46        let resp = self
47            .client
48            .post("/warninglists/checkValue", Some(body))
49            .await?;
50        parse_check_result(resp, value)
51    }
52
53    pub async fn check_values(
54        &self,
55        values: &[&str],
56    ) -> Result<Vec<WarninglistCheckResult>, MispError> {
57        debug!(count = values.len(), "Checking values against warninglists");
58        let body = json!({ "value": values });
59        let resp = self
60            .client
61            .post("/warninglists/checkValue", Some(body))
62            .await?;
63        parse_check_results(resp, values)
64    }
65
66    pub async fn is_whitelisted(&self, value: &str) -> Result<bool, MispError> {
67        let result = self.check_value(value).await?;
68        Ok(result.matched)
69    }
70
71    pub async fn get_matching_lists(
72        &self,
73        value: &str,
74    ) -> Result<Vec<WarninglistMatch>, MispError> {
75        let result = self.check_value(value).await?;
76        Ok(result.warninglists)
77    }
78}
79
80impl std::fmt::Debug for WarninglistsClient {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.debug_struct("WarninglistsClient").finish()
83    }
84}
85
86fn parse_warninglists(resp: Value) -> Result<Vec<Warninglist>, MispError> {
87    if let Some(wls) = resp.get("Warninglists") {
88        if let Some(arr) = wls.as_array() {
89            let lists: Result<Vec<Warninglist>, _> = arr
90                .iter()
91                .filter_map(|v| v.get("Warninglist"))
92                .map(|w| serde_json::from_value(w.clone()))
93                .collect();
94            return lists.map_err(MispError::Parse);
95        }
96    }
97    if let Some(arr) = resp.as_array() {
98        let lists: Result<Vec<Warninglist>, _> = arr
99            .iter()
100            .filter_map(|v| v.get("Warninglist"))
101            .map(|w| serde_json::from_value(w.clone()))
102            .collect();
103        return lists.map_err(MispError::Parse);
104    }
105    Err(MispError::InvalidResponse(
106        "unexpected warninglists format".into(),
107    ))
108}
109
110fn parse_warninglist(resp: Value) -> Result<Warninglist, MispError> {
111    if let Some(wl) = resp.get("Warninglist") {
112        return serde_json::from_value(wl.clone()).map_err(MispError::Parse);
113    }
114    Err(MispError::InvalidResponse(
115        "missing Warninglist wrapper".into(),
116    ))
117}
118
119fn parse_check_result(resp: Value, value: &str) -> Result<WarninglistCheckResult, MispError> {
120    let matched = resp
121        .get(value)
122        .and_then(|v| v.as_array())
123        .map(|arr| !arr.is_empty())
124        .unwrap_or(false);
125
126    let warninglists = if matched {
127        resp.get(value)
128            .and_then(|v| v.as_array())
129            .map(|arr| {
130                arr.iter()
131                    .filter_map(|item| {
132                        let id = item.get("id")?.as_str()?.to_string();
133                        let name = item.get("name")?.as_str()?.to_string();
134                        let matched_entry = item
135                            .get("matched")
136                            .and_then(|m| m.as_str())
137                            .map(String::from);
138                        Some(WarninglistMatch {
139                            id,
140                            name,
141                            matched: matched_entry,
142                        })
143                    })
144                    .collect()
145            })
146            .unwrap_or_default()
147    } else {
148        Vec::new()
149    };
150
151    Ok(WarninglistCheckResult {
152        value: value.to_string(),
153        matched,
154        warninglists,
155    })
156}
157
158fn parse_check_results(
159    resp: Value,
160    values: &[&str],
161) -> Result<Vec<WarninglistCheckResult>, MispError> {
162    values
163        .iter()
164        .map(|v| parse_check_result(resp.clone(), v))
165        .collect()
166}