use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WallixMenuEntry {
pub id: String,
pub target: String,
pub group: String,
}
pub fn build_expected_target(server: &ResolvedServer) -> String {
format!(
"{}@{}@{}:{}",
server.user, server.wallix_account, server.host, server.wallix_protocol
)
}
fn normalize_target_segment(value: &str) -> String {
let mut normalized = String::new();
let mut previous_was_dash = false;
for character in value.trim().chars() {
if character.is_ascii_alphanumeric() {
normalized.push(character.to_ascii_uppercase());
previous_was_dash = false;
} else if !previous_was_dash {
normalized.push('-');
previous_was_dash = true;
}
}
normalized.trim_matches('-').to_string()
}
fn infer_wallix_role(server: &ResolvedServer) -> Option<String> {
let first_host_label = server
.host
.split('.')
.next()
.unwrap_or(server.host.as_str());
let host_last_token = first_host_label
.rsplit('-')
.next()
.unwrap_or(first_host_label);
let source = if !server.name.trim().is_empty() {
server.name.as_str()
} else {
host_last_token
};
let role_key: String = source
.chars()
.take_while(|character| !character.is_ascii_digit())
.collect::<String>()
.to_ascii_lowercase();
let role = match role_key.as_str() {
"bdd" | "bd" | "db" => "BD",
"apps" | "app" | "appli" => "APPLI",
"adm" | "admin" => "ADMIN",
"web" => "WEB",
"kafka" => "KAFKA",
"els" => "ELS",
"mig" => "MIG",
"idp" => "IDP",
"frt" | "frtrac" => "FRTRAC",
other if !other.is_empty() => return Some(normalize_target_segment(other)),
_ => return None,
};
Some(role.to_string())
}
pub fn build_expected_targets(server: &ResolvedServer) -> Vec<String> {
let mut candidates = vec![build_expected_target(server)];
let first_host_label = server
.host
.split('.')
.next()
.unwrap_or(server.host.as_str());
let short_host = normalize_target_segment(first_host_label);
if !short_host.is_empty() {
candidates.push(format!(
"{}@{}@{}:{}",
server.user, server.wallix_account, short_host, server.wallix_protocol
));
}
let env = if !server.env_name.trim().is_empty() {
normalize_target_segment(&server.env_name)
} else {
normalize_target_segment(first_host_label.split('-').next().unwrap_or_default())
};
let project_from_domain = server.host.split('.').nth(1).map(normalize_target_segment);
let project_from_group = if server.group_name.trim().is_empty() {
String::new()
} else {
normalize_target_segment(&server.group_name)
};
let role = infer_wallix_role(server).unwrap_or_default();
for project in [project_from_domain.unwrap_or_default(), project_from_group] {
if !env.is_empty() && !project.is_empty() && !role.is_empty() {
candidates.push(format!(
"{}@{}@{}-{}-{}:{}",
server.user, server.wallix_account, env, project, role, server.wallix_protocol
));
}
}
candidates.dedup();
candidates
}
fn normalize_authorization_segment(value: &str) -> String {
let mut normalized = String::new();
let mut previous_was_dash = false;
for character in value.trim().chars() {
if character.is_ascii_alphanumeric() {
normalized.push(character.to_ascii_uppercase());
previous_was_dash = false;
} else if !previous_was_dash {
normalized.push('-');
previous_was_dash = true;
}
}
normalized.trim_matches('-').to_string()
}
pub fn configured_authorizations(server: &ResolvedServer) -> Vec<String> {
let mut list: Vec<String> = Vec::new();
let explicit = server
.wallix_authorization
.as_deref()
.map(str::trim)
.filter(|a| !a.is_empty());
for auth in explicit
.into_iter()
.chain(server.wallix_authorizations.iter().map(String::as_str))
{
if !list.iter().any(|a| a == auth) {
list.push(auth.to_string());
}
}
list
}
pub fn direct_authorization(server: &ResolvedServer) -> Option<String> {
let mut auths = configured_authorizations(server);
if auths.len() == 1 { auths.pop() } else { None }
}
pub fn build_direct_login(server: &ResolvedServer, authorization: Option<&str>) -> String {
let target_host = server
.host
.split_once(':')
.map_or(server.host.as_str(), |(h, _)| h);
let bastion_user = server.bastion_user.as_deref().unwrap_or("root");
let mut login = format!("{}@{}+{}", server.user, target_host, server.wallix_protocol);
if let Some(auth) = authorization.map(str::trim).filter(|a| !a.is_empty()) {
login.push('+');
login.push_str(auth);
}
login.push('+');
login.push_str(bastion_user);
login
}
pub fn build_expected_groups(server: &ResolvedServer) -> Result<Vec<String>> {
let mut candidates = configured_authorizations(server);
let configured_group = match server
.wallix_group
.as_deref()
.map(str::trim)
.filter(|g| !g.is_empty())
{
Some(g) => g,
None => return Ok(candidates),
};
candidates.push(configured_group.to_string());
if !configured_group.contains('_') {
let mut prefix_parts = Vec::new();
if !server.env_name.trim().is_empty() {
let env = normalize_authorization_segment(&server.env_name);
if !env.is_empty() {
prefix_parts.push(env);
}
}
if !server.group_name.trim().is_empty() {
let group = normalize_authorization_segment(&server.group_name);
if !group.is_empty() {
prefix_parts.push(group);
}
}
if !prefix_parts.is_empty() {
candidates.push(format!("{}_{}", prefix_parts.join("-"), configured_group));
}
}
let mut unique: Vec<String> = Vec::with_capacity(candidates.len());
for c in candidates {
if !unique.contains(&c) {
unique.push(c);
}
}
Ok(unique)
}
pub(crate) fn group_suffix_matches(entry_group: &str, configured_group: &str) -> bool {
let entry_lower = entry_group.to_ascii_lowercase();
let conf_lower = configured_group.to_ascii_lowercase();
entry_lower == conf_lower || entry_lower.ends_with(&format!("_{conf_lower}"))
}