1use serde_json::{json, Value};
4use tracing::debug;
5
6use crate::client::MispClient;
7use crate::error::MispError;
8use crate::models::{Galaxy, GalaxyCluster};
9
10#[derive(Clone)]
11pub struct GalaxiesClient {
12 client: MispClient,
13}
14
15impl GalaxiesClient {
16 pub fn new(client: MispClient) -> Self {
17 Self { client }
18 }
19
20 pub async fn list(&self) -> Result<Vec<Galaxy>, MispError> {
21 debug!("Listing galaxies");
22 let resp = self.client.get("/galaxies").await?;
23 parse_galaxies_list(resp)
24 }
25
26 pub async fn get(&self, id: &str) -> Result<Galaxy, MispError> {
27 debug!(%id, "Fetching galaxy");
28 let resp = self.client.get(&format!("/galaxies/view/{}", id)).await?;
29 parse_galaxy_response(resp)
30 }
31
32 pub async fn get_cluster(&self, cluster_id: &str) -> Result<GalaxyCluster, MispError> {
33 debug!(%cluster_id, "Fetching galaxy cluster");
34 let resp = self
35 .client
36 .get(&format!("/galaxy_clusters/view/{}", cluster_id))
37 .await?;
38 parse_cluster_response(resp)
39 }
40
41 pub async fn search_clusters(
42 &self,
43 query: ClusterSearchQuery,
44 ) -> Result<Vec<GalaxyCluster>, MispError> {
45 debug!("Searching galaxy clusters");
46 let resp = self
47 .client
48 .post("/galaxy_clusters/restSearch", Some(query.to_json()))
49 .await?;
50 parse_clusters_search(resp)
51 }
52
53 pub async fn search_clusters_by_value(
54 &self,
55 value: &str,
56 ) -> Result<Vec<GalaxyCluster>, MispError> {
57 self.search_clusters(ClusterSearchQuery::new().value(value))
58 .await
59 }
60
61 pub async fn get_by_type(&self, galaxy_type: &str) -> Result<Option<Galaxy>, MispError> {
62 let galaxies = self.list().await?;
63 Ok(galaxies.into_iter().find(|g| g.galaxy_type == galaxy_type))
64 }
65
66 pub async fn get_mitre_attack(&self) -> Result<Option<Galaxy>, MispError> {
67 self.get_by_type("mitre-attack-pattern").await
68 }
69
70 pub async fn get_threat_actors(&self) -> Result<Option<Galaxy>, MispError> {
71 self.get_by_type("threat-actor").await
72 }
73
74 pub async fn get_malware(&self) -> Result<Option<Galaxy>, MispError> {
75 self.get_by_type("malpedia").await
76 }
77
78 pub async fn search_threat_actor(&self, name: &str) -> Result<Vec<GalaxyCluster>, MispError> {
79 self.search_clusters(
80 ClusterSearchQuery::new()
81 .value(name)
82 .galaxy_type("threat-actor"),
83 )
84 .await
85 }
86
87 pub async fn search_attack_pattern(&self, name: &str) -> Result<Vec<GalaxyCluster>, MispError> {
88 self.search_clusters(
89 ClusterSearchQuery::new()
90 .value(name)
91 .galaxy_type("mitre-attack-pattern"),
92 )
93 .await
94 }
95}
96
97impl std::fmt::Debug for GalaxiesClient {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.debug_struct("GalaxiesClient").finish()
100 }
101}
102
103#[derive(Debug, Default, Clone)]
104pub struct ClusterSearchQuery {
105 pub value: Option<String>,
106 pub galaxy_type: Option<String>,
107 pub tag_name: Option<String>,
108 pub uuid: Option<String>,
109 pub source: Option<String>,
110}
111
112impl ClusterSearchQuery {
113 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn value(mut self, v: impl Into<String>) -> Self {
118 self.value = Some(v.into());
119 self
120 }
121
122 pub fn galaxy_type(mut self, t: impl Into<String>) -> Self {
123 self.galaxy_type = Some(t.into());
124 self
125 }
126
127 pub fn tag_name(mut self, t: impl Into<String>) -> Self {
128 self.tag_name = Some(t.into());
129 self
130 }
131
132 pub fn uuid(mut self, u: impl Into<String>) -> Self {
133 self.uuid = Some(u.into());
134 self
135 }
136
137 pub fn source(mut self, s: impl Into<String>) -> Self {
138 self.source = Some(s.into());
139 self
140 }
141
142 fn to_json(&self) -> Value {
143 let mut obj = serde_json::Map::new();
144 if let Some(ref v) = self.value {
145 obj.insert("value".into(), json!(v));
146 }
147 if let Some(ref v) = self.galaxy_type {
148 obj.insert("type".into(), json!(v));
149 }
150 if let Some(ref v) = self.tag_name {
151 obj.insert("tag_name".into(), json!(v));
152 }
153 if let Some(ref v) = self.uuid {
154 obj.insert("uuid".into(), json!(v));
155 }
156 if let Some(ref v) = self.source {
157 obj.insert("source".into(), json!(v));
158 }
159 Value::Object(obj)
160 }
161}
162
163fn parse_galaxies_list(resp: Value) -> Result<Vec<Galaxy>, MispError> {
164 if let Some(arr) = resp.as_array() {
165 let galaxies: Result<Vec<Galaxy>, _> = arr
166 .iter()
167 .filter_map(|v| v.get("Galaxy"))
168 .map(|g| serde_json::from_value(g.clone()))
169 .collect();
170 return galaxies.map_err(MispError::Parse);
171 }
172 Err(MispError::InvalidResponse(
173 "expected array of galaxies".into(),
174 ))
175}
176
177fn parse_galaxy_response(resp: Value) -> Result<Galaxy, MispError> {
178 if let Some(galaxy) = resp.get("Galaxy") {
179 let mut g: Galaxy = serde_json::from_value(galaxy.clone()).map_err(MispError::Parse)?;
180 if let Some(clusters) = resp.get("GalaxyCluster").and_then(|v| v.as_array()) {
181 g.clusters = clusters
182 .iter()
183 .filter_map(|c| serde_json::from_value(c.clone()).ok())
184 .collect();
185 }
186 return Ok(g);
187 }
188 Err(MispError::InvalidResponse("missing Galaxy wrapper".into()))
189}
190
191fn parse_cluster_response(resp: Value) -> Result<GalaxyCluster, MispError> {
192 if let Some(cluster) = resp.get("GalaxyCluster") {
193 return serde_json::from_value(cluster.clone()).map_err(MispError::Parse);
194 }
195 Err(MispError::InvalidResponse(
196 "missing GalaxyCluster wrapper".into(),
197 ))
198}
199
200fn parse_clusters_search(resp: Value) -> Result<Vec<GalaxyCluster>, MispError> {
201 if let Some(response) = resp.get("response") {
202 if let Some(arr) = response.as_array() {
203 let clusters: Result<Vec<GalaxyCluster>, _> = arr
204 .iter()
205 .filter_map(|v| v.get("GalaxyCluster"))
206 .map(|c| serde_json::from_value(c.clone()))
207 .collect();
208 return clusters.map_err(MispError::Parse);
209 }
210 }
211 if let Some(arr) = resp.as_array() {
212 let clusters: Result<Vec<GalaxyCluster>, _> = arr
213 .iter()
214 .filter_map(|v| v.get("GalaxyCluster"))
215 .map(|c| serde_json::from_value(c.clone()))
216 .collect();
217 return clusters.map_err(MispError::Parse);
218 }
219 Err(MispError::InvalidResponse(
220 "unexpected cluster search format".into(),
221 ))
222}