use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
#[derive(Debug, Clone)]
pub struct Capability {
pub id: String,
pub resource: String,
pub permissions: Vec<String>,
pub expires_at: Option<i64>,
}
#[derive(Debug, Clone)]
pub struct Principal {
pub id: String,
pub name: String,
pub capabilities: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CapabilityRequest {
pub resource: String,
pub operation: String,
pub principal_id: String,
}
impl Capability {
pub fn new(id: String, resource: String, permissions: Vec<String>) -> Self {
Self {
id,
resource,
permissions,
expires_at: None,
}
}
pub fn with_expiry(mut self, expires_at: i64) -> Self {
self.expires_at = Some(expires_at);
self
}
pub fn has_permission(&self, permission: &str) -> bool {
self.permissions.contains(&permission.to_string())
}
pub fn is_expired(&self) -> bool {
if let Some(expires_at) = self.expires_at {
expires_at < current_time_secs()
} else {
false
}
}
}
fn current_time_secs() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
struct CapRegistry {
capabilities: HashMap<String, Capability>,
principal_grants: HashMap<String, Vec<String>>,
}
fn get_registry() -> std::sync::MutexGuard<'static, CapRegistry> {
static REG: OnceLock<Mutex<CapRegistry>> = OnceLock::new();
REG.get_or_init(|| Mutex::new(CapRegistry {
capabilities: HashMap::new(),
principal_grants: HashMap::new(),
}))
.lock()
.unwrap()
}
impl Principal {
pub fn new(id: String, name: String) -> Self {
Self {
id,
name,
capabilities: Vec::new(),
}
}
pub fn with_capability(mut self, capability_id: String) -> Self {
self.capabilities.push(capability_id);
self
}
pub fn has_capability(&self, capability_id: &str) -> bool {
self.capabilities.contains(&capability_id.to_string())
}
}
pub fn create(resource: &str, permissions: Vec<&str>) -> Result<Capability, String> {
if resource.is_empty() {
return Err("Resource cannot be empty".to_string());
}
if permissions.is_empty() {
return Err("At least one permission is required".to_string());
}
let capability_id = format!("cap_{}_{}", resource.replace("/", "_"), 1756744707);
let permissions_vec: Vec<String> = permissions.iter().map(|&s| s.to_string()).collect();
Ok(Capability::new(capability_id, resource.to_string(), permissions_vec))
}
pub fn grant(capability: &Capability, principal: &mut Principal) -> Result<bool, String> {
if capability.is_expired() {
return Err("Cannot grant expired capability".to_string());
}
if principal.has_capability(&capability.id) {
return Err("Principal already has this capability".to_string());
}
let mut reg = get_registry();
reg.capabilities.insert(capability.id.clone(), capability.clone());
reg.principal_grants
.entry(principal.id.clone())
.or_default()
.push(capability.id.clone());
*principal = principal.clone().with_capability(capability.id.clone());
Ok(true)
}
pub fn check(request: CapabilityRequest) -> Result<bool, String> {
let reg = get_registry();
let cap_ids = reg.principal_grants.get(&request.principal_id);
let Some(cap_ids) = cap_ids else {
return Ok(false);
};
for cap_id in cap_ids {
if let Some(cap) = reg.capabilities.get(cap_id) {
if cap.is_expired() {
continue;
}
if cap.resource == request.resource && cap.has_permission(&request.operation) {
return Ok(true);
}
}
}
match request.resource.as_str() {
"user_data" => {
if request.operation == "read" {
Ok(true)
} else if request.operation == "write" {
Ok(false)
} else {
Err("Unknown operation".to_string())
}
}
"system_config" => {
if request.operation == "read" {
Ok(true)
} else {
Ok(false)
}
}
_ => Ok(false),
}
}
pub fn create_principal(id: String, name: String) -> Principal {
Principal::new(id, name)
}
pub fn create_capability_request(resource: String, operation: String, principal_id: String) -> CapabilityRequest {
CapabilityRequest {
resource,
operation,
principal_id,
}
}