use crate::runtime::values::Value;
use crate::stdlib::key::{self, create_capability_request};
use std::collections::HashMap;
use std::env;
use std::sync::{Mutex, OnceLock};
#[derive(Debug, Clone, PartialEq)]
pub enum AdminLevel {
SuperAdmin,
Admin,
Moderator,
User,
}
impl AdminLevel {
pub fn from_string(s: &str) -> Option<Self> {
match s {
"superadmin" => Some(AdminLevel::SuperAdmin),
"admin" => Some(AdminLevel::Admin),
"moderator" => Some(AdminLevel::Moderator),
"user" => Some(AdminLevel::User),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct AdminContext {
pub admin_id: String,
pub level: AdminLevel,
pub permissions: Vec<String>,
pub metadata: HashMap<String, Value>,
}
impl AdminContext {
pub fn new(admin_id: String, level: AdminLevel) -> Self {
Self {
admin_id,
level,
permissions: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn with_permissions(mut self, permissions: Vec<String>) -> Self {
self.permissions = permissions;
self
}
pub fn with_metadata(mut self, metadata: HashMap<String, Value>) -> Self {
self.metadata = metadata;
self
}
}
#[derive(Debug, Clone)]
pub struct AdminPolicy {
pub name: String,
pub rules: Vec<String>,
pub admin_level: AdminLevel,
}
struct AdminRegistry {
admins: HashMap<String, (AdminLevel, Vec<String>)>,
}
fn get_admin_registry() -> std::sync::MutexGuard<'static, AdminRegistry> {
static REG: OnceLock<Mutex<AdminRegistry>> = OnceLock::new();
let reg = REG.get_or_init(|| {
Mutex::new(AdminRegistry {
admins: HashMap::new(),
})
});
let mut guard = reg.lock().unwrap();
if guard.admins.is_empty() {
let mut to_insert = HashMap::new();
if let Ok(ids) = env::var("ADMIN_IDS") {
for id in ids.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) {
let level_str = env::var(format!("ADMIN_LEVEL_{}", id.replace('.', "_")))
.unwrap_or_else(|_| "user".to_string());
if let Some(level) = AdminLevel::from_string(&level_str) {
to_insert.insert(id.to_string(), (level, vec![]));
}
}
}
drop(guard);
guard = reg.lock().unwrap();
for (k, v) in to_insert {
guard.admins.insert(k, v);
}
}
guard
}
pub fn register_admin(admin_id: String, level: AdminLevel, permissions: Vec<String>) {
let mut reg = get_admin_registry();
reg.admins.insert(admin_id, (level, permissions));
}
pub fn remove_admin(admin_id: &str) -> bool {
let mut reg = get_admin_registry();
reg.admins.remove(admin_id).is_some()
}
pub fn get_admin_info(admin_id: &str) -> Option<(AdminLevel, Vec<String>)> {
let reg = get_admin_registry();
reg.admins.get(admin_id).cloned()
}
pub fn authorize(admin_id: &str, operation: &str, resource: &str) -> bool {
let req = create_capability_request(
resource.to_string(),
operation.to_string(),
admin_id.to_string(),
);
let (allowed, reason) = if let Ok(true) = key::check(req) {
(true, "key_registry_grant".to_string())
} else if let Some((level, perms)) = get_admin_registry().admins.get(admin_id) {
match operation {
"read" => (
true,
format!("admin_registry:{}:read", format!("{:?}", level)),
),
"write" => {
let result = matches!(level, AdminLevel::Admin | AdminLevel::SuperAdmin)
|| perms.contains(&"write".to_string());
(
result,
format!(
"admin_registry:{}:write:{}",
format!("{:?}", level),
if result { "granted" } else { "denied" }
),
)
}
"delete" => {
let result =
*level == AdminLevel::SuperAdmin || perms.contains(&"delete".to_string());
(
result,
format!(
"admin_registry:{}:delete:{}",
format!("{:?}", level),
if result { "granted" } else { "denied" }
),
)
}
_ => {
let result = perms.contains(&operation.to_string());
(
result,
format!(
"admin_registry:{}:{}:{}",
format!("{:?}", level),
operation,
if result { "granted" } else { "denied" }
),
)
}
}
} else {
match operation {
"read" => (true, "builtin_rule:read_allowed".to_string()),
"write" => {
let result = admin_id == "admin" || admin_id == "superadmin";
(
result,
format!(
"builtin_rule:write:{}",
if result { "granted" } else { "denied" }
),
)
}
"delete" => {
let result = admin_id == "superadmin";
(
result,
format!(
"builtin_rule:delete:{}",
if result { "granted" } else { "denied" }
),
)
}
_ => (false, "builtin_rule:default_deny".to_string()),
}
};
crate::stdlib::log::audit(
"admin_authorization",
{
let mut data = std::collections::HashMap::new();
data.insert(
"admin_id".to_string(),
crate::runtime::values::Value::String(admin_id.to_string()),
);
data.insert(
"operation".to_string(),
crate::runtime::values::Value::String(operation.to_string()),
);
data.insert(
"resource".to_string(),
crate::runtime::values::Value::String(resource.to_string()),
);
data.insert(
"result".to_string(),
crate::runtime::values::Value::String(if allowed {
"allowed".to_string()
} else {
"denied".to_string()
}),
);
data.insert(
"reason".to_string(),
crate::runtime::values::Value::String(reason.clone()),
);
data
},
Some("trust"),
);
allowed
}
fn policy_min_level(policy_name: &str) -> Option<AdminLevel> {
let key = format!(
"POLICY_{}_LEVEL",
policy_name.to_uppercase().replace('-', "_")
);
env::var(key)
.ok()
.and_then(|s| AdminLevel::from_string(s.trim()))
}
fn level_at_least(level: &AdminLevel, min_level: &AdminLevel) -> bool {
use AdminLevel::*;
matches!(
(min_level, level),
(User, _)
| (Moderator, Moderator | Admin | SuperAdmin)
| (Admin, Admin | SuperAdmin)
| (SuperAdmin, SuperAdmin)
)
}
pub fn enforce_policy(policy_name: &str, context: AdminContext) -> Result<bool, String> {
if let Some(min_level) = policy_min_level(policy_name) {
return Ok(level_at_least(&context.level, &min_level));
}
match policy_name {
"strict" => Ok(context.level == AdminLevel::SuperAdmin),
"moderate" => {
Ok(context.level == AdminLevel::Admin || context.level == AdminLevel::SuperAdmin)
}
"permissive" => Ok(true),
_ => Err(format!("Unknown policy: {}", policy_name)),
}
}
pub fn validate_hybrid_trust(admin_trust: &str, user_trust: &str) -> bool {
admin_trust == "valid" && user_trust == "valid"
}
pub fn bridge_trusts(centralized_trust: &str, decentralized_trust: &str) -> bool {
centralized_trust == "admin" && decentralized_trust == "user"
}
pub fn create_admin_context(admin_id: String, level: &str) -> Option<AdminContext> {
let admin_level = AdminLevel::from_string(level)?;
Some(AdminContext::new(admin_id, admin_level))
}