#![allow(missing_docs)]
use std::sync::Arc;
use crate::quad::Tree;
pub trait AuthorizationProvider: Send + Sync {
fn authorize(&self, tree: &Tree, cycle: u64) -> Result<(), String>;
}
pub struct PermitAll;
impl AuthorizationProvider for PermitAll {
fn authorize(&self, _tree: &Tree, _cycle: u64) -> Result<(), String> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PermissionRequirement {
pub command: String,
pub min_role_key: String,
pub activity_maximum_key: String,
pub decision_tier_key: String,
pub security_floor_key: String,
}
impl PermissionRequirement {
pub fn new(
command: impl Into<String>,
min_role_key: impl Into<String>,
activity_maximum_key: impl Into<String>,
decision_tier_key: impl Into<String>,
security_floor_key: impl Into<String>,
) -> Self {
Self {
command: command.into(),
min_role_key: min_role_key.into(),
activity_maximum_key: activity_maximum_key.into(),
decision_tier_key: decision_tier_key.into(),
security_floor_key: security_floor_key.into(),
}
}
}
#[derive(Debug, Clone)]
pub struct PermissionCollision {
pub command: String,
pub existing_min_role_key: String,
pub new_min_role_key: String,
}
#[derive(Debug, Clone, Default)]
pub struct PermissionRegistry {
requirements: Vec<PermissionRequirement>,
}
impl PermissionRegistry {
pub fn new() -> Self { Self::default() }
pub fn register(&mut self, reqs: Vec<PermissionRequirement>) { self.requirements.extend(reqs); }
pub fn lookup(&self, command_type: &str) -> Option<&PermissionRequirement> {
self.requirements.iter().find(|r| r.command == command_type)
}
pub fn len(&self) -> usize { self.requirements.len() }
pub fn is_empty(&self) -> bool { self.requirements.is_empty() }
pub fn register_checked(&mut self, reqs: Vec<PermissionRequirement>) -> Vec<PermissionCollision> {
let mut collisions = Vec::new();
for req in reqs {
if let Some(existing) = self.requirements.iter().find(|r| r.command == req.command) {
collisions.push(PermissionCollision {
command: req.command.clone(),
existing_min_role_key: existing.min_role_key.clone(),
new_min_role_key: req.min_role_key.clone(),
});
} else {
self.requirements.push(req);
}
}
collisions
}
pub fn iter(&self) -> impl Iterator<Item = &PermissionRequirement> { self.requirements.iter() }
pub fn count_by_prefix(&self, prefix: &str) -> usize {
self.requirements.iter().filter(|r| r.command.starts_with(prefix)).count()
}
}
pub type SharedPermissionRegistry = Arc<std::sync::Mutex<PermissionRegistry>>;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct AuthorizationTableEntry {
pub rule_id: String,
pub command_pattern: String,
pub group_pattern: String,
pub context: String,
pub autonomy: String,
pub tier: String,
pub effect: String,
pub config_layer: String,
}
#[derive(Debug, Clone, Default)]
pub struct AuthorizationTable {
rules: Vec<AuthorizationTableEntry>,
}
impl AuthorizationTable {
pub fn new() -> Self { Self::default() }
pub fn set_rules(&mut self, rules: Vec<AuthorizationTableEntry>) { self.rules = rules; }
pub fn rules(&self) -> &[AuthorizationTableEntry] { &self.rules }
pub fn len(&self) -> usize { self.rules.len() }
pub fn is_empty(&self) -> bool { self.rules.is_empty() }
pub fn evaluate(&self, command_type: &str, user_groups: &[&str], request_context: Option<&str>) -> Option<AuthorizationTableResult> {
for rule in &self.rules {
if matches_rule(rule, command_type, user_groups, request_context) {
return Some(AuthorizationTableResult {
matched_rule_id: rule.rule_id.clone(),
autonomy: rule.autonomy.clone(),
tier: rule.tier.clone(),
effect: rule.effect.clone(),
config_layer: rule.config_layer.clone(),
});
}
}
None
}
}
#[derive(Debug, Clone)]
pub struct AuthorizationTableResult {
pub matched_rule_id: String,
pub autonomy: String,
pub tier: String,
pub effect: String,
pub config_layer: String,
}
impl AuthorizationTableResult {
pub fn is_allowed(&self) -> bool { self.effect == "allow" }
}
pub type SharedAuthorizationTable = Arc<std::sync::Mutex<AuthorizationTable>>;
fn matches_rule(rule: &AuthorizationTableEntry, command_type: &str, user_groups: &[&str], request_context: Option<&str>) -> bool {
matches_command(&rule.command_pattern, command_type)
&& matches_group(&rule.group_pattern, user_groups)
&& matches_context(&rule.context, request_context)
}
fn matches_command(pattern: &str, command_type: &str) -> bool {
if pattern == "*" { return true; }
if let Some(prefix) = pattern.strip_suffix(".*") {
return command_type.starts_with(prefix) && (command_type.len() == prefix.len() || command_type.as_bytes().get(prefix.len()) == Some(&b'.'));
}
if pattern.starts_with("*.") {
let suffix = &pattern[1..];
return command_type.ends_with(suffix);
}
pattern == command_type
}
fn matches_group(pattern: &str, user_groups: &[&str]) -> bool {
if pattern == "-" { return true; }
if let Some(prefix) = pattern.strip_suffix('*') {
return user_groups.iter().any(|g| g.starts_with(prefix));
}
user_groups.contains(&pattern)
}
fn matches_context(pattern: &str, request_context: Option<&str>) -> bool {
if pattern == "-" { return true; }
match request_context { Some(ctx) => pattern == ctx, None => false }
}