use manta_backend_dispatcher::error::Error;
use manta_backend_dispatcher::interfaces::hsm::group::GroupTrait;
use crate::server::common::{app_context::InfraContext, jwt_ops};
pub static PA_ADMIN: &str = "pa_admin";
pub async fn validate_user_group_access(
infra: &InfraContext<'_>,
token: &str,
group_name: &str,
) -> Result<(), Error> {
if jwt_ops::is_user_admin(token) {
return Ok(());
}
let group_available_vec =
infra.backend.get_group_name_available(token).await?;
validate_group_vec_access(&[group_name.to_string()], &group_available_vec)
}
pub async fn validate_user_group_vec_access(
infra: &InfraContext<'_>,
token: &str,
group_vec: &[String],
) -> Result<(), Error> {
if jwt_ops::is_user_admin(token) {
return Ok(());
}
let group_available_vec =
infra.backend.get_group_name_available(token).await?;
validate_group_vec_access(group_vec, &group_available_vec)
}
pub fn validate_group_vec_access(
group_target_vec: &[String],
group_available_vec: &[String],
) -> Result<(), Error> {
let mut invalid_group_vec: Vec<String> = group_target_vec
.iter()
.filter(|group| !group_available_vec.contains(group))
.cloned()
.collect();
if invalid_group_vec.is_empty() {
Ok(())
} else {
invalid_group_vec.sort();
Err(Error::BadRequest(format!(
"Invalid groups '{:?}'.\nPlease choose one from the list below:\n{}",
invalid_group_vec,
group_available_vec.join(", ")
)))
}
}
pub async fn validate_ansible_limit_membership_access(
infra: &InfraContext<'_>,
token: &str,
ansible_limit: &str,
) -> Result<(), Error> {
if jwt_ops::is_user_admin(token) {
return Ok(());
}
let xnames: Vec<String> = ansible_limit
.split(',')
.map(|s| s.trim().to_string())
.collect();
validate_user_group_members_access(infra, token, &xnames).await
}
pub async fn validate_user_group_members_access(
infra: &InfraContext<'_>,
token: &str,
group_members_target_vec: &[String],
) -> Result<(), Error> {
if jwt_ops::is_user_admin(token) {
return Ok(());
}
let hsm_groups_user_has_access =
infra.backend.get_group_name_available(token).await?;
validate_group_members_access(
infra,
token,
group_members_target_vec,
&hsm_groups_user_has_access,
)
.await
}
pub async fn validate_group_members_access(
infra: &InfraContext<'_>,
token: &str,
group_members_target_vec: &[String],
hsm_groups_user_has_access: &[String],
) -> Result<(), Error> {
if jwt_ops::is_user_admin(token) {
return Ok(());
}
let all_xnames_user_has_access = infra
.backend
.get_member_vec_from_group_name_vec(token, hsm_groups_user_has_access)
.await?;
let accessible_set: std::collections::HashSet<&str> =
all_xnames_user_has_access
.iter()
.map(String::as_str)
.collect();
let invalid_xnames: Vec<String> = group_members_target_vec
.iter()
.filter(|group| !accessible_set.contains(group.as_str()))
.cloned()
.collect();
if invalid_xnames.is_empty() {
Ok(())
} else {
Err(Error::BadRequest(format!(
"Invalid group members:\n'{:?}'.\nPlease choose members from the list of groups below:\n{}",
invalid_xnames,
hsm_groups_user_has_access.join(", ")
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(v: &[&str]) -> Vec<String> {
v.iter().map(|s| (*s).to_string()).collect()
}
#[test]
fn allows_when_every_target_is_in_available_set() {
let result = validate_group_vec_access(
&s(&["compute", "login"]),
&s(&["compute", "login", "storage"]),
);
assert!(result.is_ok(), "got {result:?}");
}
#[test]
fn allows_empty_target_set() {
let result = validate_group_vec_access(&[], &s(&["compute"]));
assert!(result.is_ok(), "got {result:?}");
}
#[test]
fn rejects_when_any_target_is_missing_from_available_set() {
let err =
validate_group_vec_access(&s(&["compute", "secret"]), &s(&["compute"]))
.unwrap_err();
let Error::BadRequest(msg) = err else {
panic!("expected BadRequest, got {err:?}");
};
assert!(
msg.contains("\"secret\""),
"error message should name the offending group: {msg}"
);
assert!(
!msg.contains("\"compute\""),
"error message should not name the allowed group: {msg}"
);
}
#[test]
fn rejects_when_available_set_is_empty() {
let err = validate_group_vec_access(&s(&["compute"]), &[]).unwrap_err();
assert!(matches!(err, Error::BadRequest(_)));
}
#[test]
fn error_message_sorts_offending_groups_alphabetically() {
let err =
validate_group_vec_access(&s(&["zeta", "alpha", "mu"]), &s(&["other"]))
.unwrap_err();
let Error::BadRequest(msg) = err else {
panic!("expected BadRequest, got {err:?}");
};
let alpha = msg.find("alpha").expect("alpha listed");
let mu = msg.find("mu").expect("mu listed");
let zeta = msg.find("zeta").expect("zeta listed");
assert!(alpha < mu && mu < zeta, "got: {msg}");
}
}