use crate::runtime::values::Value;
use crate::stdlib::cap::{self, create_capability_request};
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use std::env;
#[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 authorize(admin_id: &str, operation: &str, resource: &str) -> bool {
let req = create_capability_request(resource.to_string(), operation.to_string(), admin_id.to_string());
if let Ok(true) = cap::check(req) {
return true;
}
if let Some((level, perms)) = get_admin_registry().admins.get(admin_id) {
return match operation {
"read" => true,
"write" => matches!(level, AdminLevel::Admin | AdminLevel::SuperAdmin) || perms.contains(&"write".to_string()),
"delete" => *level == AdminLevel::SuperAdmin || perms.contains(&"delete".to_string()),
_ => perms.contains(&operation.to_string()),
};
}
match operation {
"read" => true,
"write" => admin_id == "admin" || admin_id == "superadmin",
"delete" => admin_id == "superadmin",
_ => false,
}
}
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))
}