// Copyright (C) 2024 Bellande Architecture Mechanism Research Innovation Center, Ronaldson Bellande // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . use std::collections::HashMap; use std::fmt; use std::hash::Hash; use std::str::FromStr; use std::time::{Duration, SystemTime}; use anyhow::Result; use log::error; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::audit::audit::log_audit_event; use crate::config::config::Config; use crate::user_privilege::user::User; use chrono::Timelike; #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone, Copy)] pub enum PrivilegeLevel { User, // Basic user privileges Group, // Group-based privileges Administrator, // Administrative privileges Root, // Root-level access Bell, // Highest level - system owner } #[derive(Error, Debug)] pub enum PrivilegeLevelError { #[error("Invalid privilege level: {0}")] InvalidPrivilegeLevel(String), #[error("Insufficient privileges")] InsufficientPrivileges, #[error("Expired privileges")] ExpiredPrivileges, #[error("Group not found: {0}")] GroupNotFound(String), #[error("Permission not found: {0}")] PermissionNotFound(String), } #[derive(Debug, Serialize, Deserialize)] pub struct PrivilegeConfig { pub elevation_timeout: Duration, pub require_mfa: bool, pub allowed_elevation_hours: Vec, pub max_concurrent_elevations: usize, pub restricted_commands: HashMap>, } impl Default for PrivilegeConfig { fn default() -> Self { Self { elevation_timeout: Duration::from_secs(3600), require_mfa: true, allowed_elevation_hours: (0..24).collect(), max_concurrent_elevations: 3, restricted_commands: HashMap::new(), } } } #[derive(Debug)] pub struct PrivilegeManager { config: PrivilegeConfig, active_elevations: HashMap>, } #[derive(Debug)] struct PrivilegeElevation { level: PrivilegeLevel, granted_at: SystemTime, expires_at: SystemTime, reason: String, } impl FromStr for PrivilegeLevel { type Err = PrivilegeLevelError; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "user" => Ok(PrivilegeLevel::User), "group" => Ok(PrivilegeLevel::Group), "admin" | "administrator" => Ok(PrivilegeLevel::Administrator), "root" => Ok(PrivilegeLevel::Root), "bell" => Ok(PrivilegeLevel::Bell), _ => Err(PrivilegeLevelError::InvalidPrivilegeLevel(s.to_string())), } } } impl fmt::Display for PrivilegeLevel { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PrivilegeLevel::User => write!(f, "user"), PrivilegeLevel::Group => write!(f, "group"), PrivilegeLevel::Administrator => write!(f, "administrator"), PrivilegeLevel::Root => write!(f, "root"), PrivilegeLevel::Bell => write!(f, "bell"), } } } impl PrivilegeManager { pub fn new(config: PrivilegeConfig) -> Self { Self { config, active_elevations: HashMap::new(), } } pub async fn check_permission( &self, user: &User, required_privilege: PrivilegeLevel, config: &Config, ) -> Result { // Direct privilege level check if user.privilege >= required_privilege { log_audit_event( "PRIVILEGE_CHECK", &user.username, &format!("Direct privilege granted: {:?}", required_privilege), ) .await?; return Ok(true); } // Check active elevations if let Some(elevations) = self.active_elevations.get(&user.username) { for elevation in elevations { if elevation.level >= required_privilege && SystemTime::now() < elevation.expires_at { log_audit_event( "PRIVILEGE_CHECK", &user.username, &format!("Elevation privilege granted: {:?}", required_privilege), ) .await?; return Ok(true); } } } // Check group permissions for group_name in &user.groups { if let Some(group) = config.groups.iter().find(|g| g.name == *group_name) { if group.permissions.contains(&required_privilege.to_string()) { log_audit_event( "PRIVILEGE_CHECK", &user.username, &format!( "Group privilege granted: {:?} from {}", required_privilege, group_name ), ) .await?; return Ok(true); } } } log_audit_event( "PRIVILEGE_CHECK", &user.username, &format!("Permission denied for: {:?}", required_privilege), ) .await?; Ok(false) } pub async fn elevate_privilege( &mut self, user: &User, requested_level: PrivilegeLevel, reason: &str, mfa_token: Option<&str>, ) -> Result<()> { // Check if elevation is allowed at current hour let current_hour = chrono::Local::now().hour() as u8; if !self.config.allowed_elevation_hours.contains(¤t_hour) { return Err(PrivilegeLevelError::InsufficientPrivileges.into()); } // Check MFA requirement if self.config.require_mfa && mfa_token.is_none() { return Err(anyhow::anyhow!( "MFA token required for privilege elevation" )); } // Check concurrent elevations let user_elevations = self .active_elevations .entry(user.username.clone()) .or_default(); if user_elevations.len() >= self.config.max_concurrent_elevations { return Err(anyhow::anyhow!("Maximum concurrent elevations reached")); } // Create new elevation let elevation = PrivilegeElevation { level: requested_level, granted_at: SystemTime::now(), expires_at: SystemTime::now() + self.config.elevation_timeout, reason: reason.to_string(), }; user_elevations.push(elevation); log_audit_event( "PRIVILEGE_ELEVATION", &user.username, &format!("Elevated to {:?} for reason: {}", requested_level, reason), ) .await?; Ok(()) } pub async fn revoke_elevation(&mut self, user: &str, level: PrivilegeLevel) -> Result<()> { if let Some(elevations) = self.active_elevations.get_mut(user) { elevations.retain(|e| e.level != level); log_audit_event( "PRIVILEGE_REVOCATION", user, &format!("Revoked elevation: {:?}", level), ) .await?; } Ok(()) } pub fn cleanup_expired_elevations(&mut self) { let now = SystemTime::now(); for elevations in self.active_elevations.values_mut() { elevations.retain(|e| e.expires_at > now); } } } // OS-specific privilege checks pub async fn check_os_specific_privileges( user: &User, required_privilege: PrivilegeLevel, ) -> Result { match std::env::consts::OS { "macos" => check_macos_privileges(user, required_privilege).await, "linux" => check_linux_privileges(user, required_privilege).await, "bellandeos" => check_bellande_privileges(user, required_privilege).await, _ => Ok(false), } } async fn check_macos_privileges(user: &User, required_privilege: PrivilegeLevel) -> Result { // Check admin group membership if required_privilege >= PrivilegeLevel::Administrator { let output = std::process::Command::new("dseditgroup") .args(&["-o", "checkmember", "-m", &user.username, "admin"]) .output()?; if !output.status.success() { return Ok(false); } } Ok(true) } async fn check_linux_privileges(user: &User, required_privilege: PrivilegeLevel) -> Result { // Check sudo group membership if required_privilege >= PrivilegeLevel::Administrator { let output = std::process::Command::new("groups") .arg(&user.username) .output()?; let groups = String::from_utf8_lossy(&output.stdout); if !groups.contains("sudo") && !groups.contains("wheel") { return Ok(false); } } Ok(true) } async fn check_bellande_privileges( user: &User, required_privilege: PrivilegeLevel, ) -> Result { // Check BellandeOS specific privileges let output = std::process::Command::new("bellctl") .args(&[ "user", "check-privilege", &user.username, &required_privilege.to_string(), ]) .output()?; Ok(output.status.success()) }