manta_server/service/
authorization.rs1use manta_backend_dispatcher::error::Error;
4use manta_backend_dispatcher::interfaces::hsm::group::GroupTrait;
5
6use crate::server::common::{app_context::InfraContext, jwt_ops};
7
8pub static PA_ADMIN: &str = "pa_admin";
11
12pub async fn validate_user_group_access(
19 infra: &InfraContext<'_>,
20 token: &str,
21 group_name: &str,
22) -> Result<(), Error> {
23 if jwt_ops::is_user_admin(token) {
24 return Ok(());
25 }
26
27 let group_available_vec =
28 infra.backend.get_group_name_available(token).await?;
29
30 validate_group_vec_access(&[group_name.to_string()], &group_available_vec)
31}
32
33pub async fn validate_user_group_vec_access(
42 infra: &InfraContext<'_>,
43 token: &str,
44 group_vec: &[String],
45) -> Result<(), Error> {
46 if jwt_ops::is_user_admin(token) {
47 return Ok(());
48 }
49
50 let group_available_vec =
51 infra.backend.get_group_name_available(token).await?;
52
53 validate_group_vec_access(group_vec, &group_available_vec)
54}
55
56pub fn validate_group_vec_access(
66 group_target_vec: &[String],
67 group_available_vec: &[String],
68) -> Result<(), Error> {
69 let mut invalid_group_vec: Vec<String> = group_target_vec
70 .iter()
71 .filter(|group| !group_available_vec.contains(group))
72 .cloned()
73 .collect();
74
75 if invalid_group_vec.is_empty() {
76 Ok(())
77 } else {
78 invalid_group_vec.sort();
79
80 Err(Error::BadRequest(format!(
81 "Invalid groups '{:?}'.\nPlease choose one from the list below:\n{}",
82 invalid_group_vec,
83 group_available_vec.join(", ")
84 )))
85 }
86}
87
88pub async fn validate_ansible_limit_membership_access(
96 infra: &InfraContext<'_>,
97 token: &str,
98 ansible_limit: &str,
99) -> Result<(), Error> {
100 if jwt_ops::is_user_admin(token) {
101 return Ok(());
102 }
103
104 let xnames: Vec<String> = ansible_limit
105 .split(',')
106 .map(|s| s.trim().to_string())
107 .collect();
108 validate_user_group_members_access(infra, token, &xnames).await
109}
110
111pub async fn validate_user_group_members_access(
119 infra: &InfraContext<'_>,
120 token: &str,
121 group_members_target_vec: &[String],
122) -> Result<(), Error> {
123 if jwt_ops::is_user_admin(token) {
124 return Ok(());
125 }
126
127 let hsm_groups_user_has_access =
128 infra.backend.get_group_name_available(token).await?;
129
130 validate_group_members_access(
131 infra,
132 token,
133 group_members_target_vec,
134 &hsm_groups_user_has_access,
135 )
136 .await
137}
138
139pub async fn validate_group_members_access(
146 infra: &InfraContext<'_>,
147 token: &str,
148 group_members_target_vec: &[String],
149 hsm_groups_user_has_access: &[String],
150) -> Result<(), Error> {
151 if jwt_ops::is_user_admin(token) {
152 return Ok(());
153 }
154
155 let all_xnames_user_has_access = infra
156 .backend
157 .get_member_vec_from_group_name_vec(token, hsm_groups_user_has_access)
158 .await?;
159
160 let accessible_set: std::collections::HashSet<&str> =
164 all_xnames_user_has_access
165 .iter()
166 .map(String::as_str)
167 .collect();
168 let invalid_xnames: Vec<String> = group_members_target_vec
169 .iter()
170 .filter(|group| !accessible_set.contains(group.as_str()))
171 .cloned()
172 .collect();
173
174 if invalid_xnames.is_empty() {
175 Ok(())
176 } else {
177 Err(Error::BadRequest(format!(
178 "Invalid group members:\n'{:?}'.\nPlease choose members from the list of groups below:\n{}",
179 invalid_xnames,
180 hsm_groups_user_has_access.join(", ")
181 )))
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 fn s(v: &[&str]) -> Vec<String> {
190 v.iter().map(|s| (*s).to_string()).collect()
191 }
192
193 #[test]
194 fn allows_when_every_target_is_in_available_set() {
195 let result = validate_group_vec_access(
196 &s(&["compute", "login"]),
197 &s(&["compute", "login", "storage"]),
198 );
199 assert!(result.is_ok(), "got {result:?}");
200 }
201
202 #[test]
203 fn allows_empty_target_set() {
204 let result = validate_group_vec_access(&[], &s(&["compute"]));
205 assert!(result.is_ok(), "got {result:?}");
206 }
207
208 #[test]
209 fn rejects_when_any_target_is_missing_from_available_set() {
210 let err =
211 validate_group_vec_access(&s(&["compute", "secret"]), &s(&["compute"]))
212 .unwrap_err();
213 let Error::BadRequest(msg) = err else {
214 panic!("expected BadRequest, got {err:?}");
215 };
216 assert!(
217 msg.contains("\"secret\""),
218 "error message should name the offending group: {msg}"
219 );
220 assert!(
221 !msg.contains("\"compute\""),
222 "error message should not name the allowed group: {msg}"
223 );
224 }
225
226 #[test]
227 fn rejects_when_available_set_is_empty() {
228 let err = validate_group_vec_access(&s(&["compute"]), &[]).unwrap_err();
229 assert!(matches!(err, Error::BadRequest(_)));
230 }
231
232 #[test]
235 fn error_message_sorts_offending_groups_alphabetically() {
236 let err =
237 validate_group_vec_access(&s(&["zeta", "alpha", "mu"]), &s(&["other"]))
238 .unwrap_err();
239 let Error::BadRequest(msg) = err else {
240 panic!("expected BadRequest, got {err:?}");
241 };
242 let alpha = msg.find("alpha").expect("alpha listed");
243 let mu = msg.find("mu").expect("mu listed");
244 let zeta = msg.find("zeta").expect("zeta listed");
245 assert!(alpha < mu && mu < zeta, "got: {msg}");
246 }
247}