manta_server/service/
group.rs1use manta_backend_dispatcher::error::Error;
4use manta_backend_dispatcher::interfaces::hsm::{
5 component::ComponentTrait, group::GroupTrait,
6};
7use manta_backend_dispatcher::types::Group;
8
9use crate::server::common::{app_context::InfraContext, jwt_ops};
10use crate::service::authorization::{
11 validate_group_vec_access, validate_user_group_members_access,
12};
13use crate::service::node_ops::{self, from_hosts_expression_to_xname_vec};
14pub use manta_shared::types::api::group::GetGroupParams;
15
16pub async fn resolve_target_and_available_groups(
36 infra: &InfraContext<'_>,
37 token: &str,
38 settings_group_name_opt: Option<&str>,
39) -> Result<(Vec<Group>, Vec<String>), Error> {
40 let group_available_vec = infra.backend.get_group_available(token).await?;
41
42 let target_group_vec: Vec<String> = match settings_group_name_opt {
43 Some(label) => {
44 if !jwt_ops::is_user_admin(token) {
45 let available_labels: Vec<String> = group_available_vec
46 .iter()
47 .map(|g| g.label.clone())
48 .collect();
49 validate_group_vec_access(
50 std::slice::from_ref(&label.to_string()),
51 &available_labels,
52 )?;
53 }
54 vec![label.to_string()]
55 }
56 None => group_available_vec
57 .iter()
58 .map(|g| g.label.clone())
59 .collect(),
60 };
61
62 Ok((group_available_vec, target_group_vec))
63}
64
65pub async fn get_groups(
73 infra: &InfraContext<'_>,
74 token: &str,
75 params: &GetGroupParams,
76) -> Result<Vec<Group>, Error> {
77 let (_group_available_vec, target_group_vec) =
81 resolve_target_and_available_groups(
82 infra,
83 token,
84 params.group_name.as_deref(),
85 )
86 .await?;
87
88 infra
89 .backend
90 .get_groups(token, Some(&target_group_vec))
91 .await
92}
93
94pub async fn validate_group_deletion(
103 infra: &InfraContext<'_>,
104 token: &str,
105 label: &str,
106) -> Result<(), Error> {
107 let xname_vec = infra
108 .backend
109 .get_member_vec_from_group_name_vec(token, &[label.to_string()])
110 .await?;
111
112 let xname_vec_ref: Vec<&str> = xname_vec.iter().map(String::as_str).collect();
113 let mut xname_map = infra
114 .backend
115 .get_group_map_and_filter_by_group_vec(token, &xname_vec_ref)
116 .await?;
117
118 xname_map.retain(|_xname, group_name_vec| {
119 group_name_vec.len() == 1
120 && group_name_vec.first().is_some_and(|name| name == label)
121 });
122
123 let mut members_orphan_if_group_deleted: Vec<String> =
124 xname_map.into_keys().collect();
125 members_orphan_if_group_deleted.sort();
126
127 if !members_orphan_if_group_deleted.is_empty() {
128 return Err(Error::Conflict(format!(
129 "The hosts below will become orphan if group '{}' gets deleted: {}",
130 label,
131 members_orphan_if_group_deleted.join(", ")
132 )));
133 }
134
135 Ok(())
136}
137
138pub async fn delete_group(
143 infra: &InfraContext<'_>,
144 token: &str,
145 label: &str,
146 force: bool,
147) -> Result<(), Error> {
148 if !force {
149 validate_group_deletion(infra, token, label).await?;
150 }
151 infra.backend.delete_group(token, label).await.map(|_| ())
152}
153
154pub async fn create_group(
159 infra: &InfraContext<'_>,
160 token: &str,
161 group: Group,
162) -> Result<(), Error> {
163 infra.backend.add_group(token, group).await.map(|_| ())
164}
165
166pub async fn delete_group_members(
174 infra: &InfraContext<'_>,
175 token: &str,
176 group_name: &str,
177 host_expression: &str,
178 dry_run: bool,
179) -> Result<(), Error> {
180 let node_metadata_available_vec =
181 infra.backend.get_node_metadata_available(token).await?;
182
183 let xname_vec = from_hosts_expression_to_xname_vec(
184 host_expression,
185 false,
186 &node_metadata_available_vec,
187 )?;
188
189 validate_user_group_members_access(infra, token, &xname_vec).await?;
190
191 if xname_vec.is_empty() {
192 return Err(Error::BadRequest(
193 "The list of nodes to operate is empty. Nothing to do".to_string(),
194 ));
195 }
196
197 validate_user_group_members_access(infra, token, &xname_vec).await?;
205
206 for xname in &xname_vec {
207 if dry_run {
208 tracing::info!(
209 "Dryrun enabled: no changes persisted into the system.\nGroup member '{}' removed from group '{}'",
210 xname,
211 group_name
212 );
213 } else {
214 infra
215 .backend
216 .delete_member_from_group(token, group_name, xname)
217 .await?;
218 }
219 }
220
221 Ok(())
222}
223
224pub async fn add_nodes_to_group(
232 infra: &InfraContext<'_>,
233 token: &str,
234 target_hsm_name: &str,
235 hosts_expression: &str,
236) -> Result<(Vec<String>, Vec<String>), Error> {
237 let xname_to_move_vec = node_ops::from_user_hosts_expression_to_xname_vec(
238 infra,
239 token,
240 hosts_expression,
241 false,
242 )
243 .await?;
244
245 validate_user_group_members_access(infra, token, &xname_to_move_vec).await?;
246
247 if xname_to_move_vec.is_empty() {
248 return Err(Error::BadRequest(
249 "The list of nodes to move is empty. Nothing to do".to_string(),
250 ));
251 }
252
253 if infra
254 .backend
255 .get_group(token, target_hsm_name)
256 .await
257 .is_err()
258 {
259 return Err(Error::NotFound(format!(
260 "Target HSM group '{target_hsm_name}' does not exist"
261 )));
262 }
263
264 let xnames_to_move: Vec<&str> =
265 xname_to_move_vec.iter().map(String::as_str).collect();
266
267 let mut updated_members = infra
268 .backend
269 .add_members_to_group(token, target_hsm_name, &xnames_to_move)
270 .await?;
271
272 updated_members.sort();
273
274 Ok((xname_to_move_vec, updated_members))
275}