use ankurah_proto::CollectionId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyConfig {
pub roles: HashMap<String, Vec<String>>,
pub collections: HashMap<String, CollectionRules>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScopeRule {
pub filter: String,
pub unless_privilege: Option<String>,
#[serde(default)]
pub applies_to: ScopeRuleOp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ScopeRuleOp {
#[default]
ReadWrite,
Read,
Write,
}
impl ScopeRuleOp {
pub fn applies_to_reads(self) -> bool { matches!(self, Self::ReadWrite | Self::Read) }
pub fn applies_to_writes(self) -> bool { matches!(self, Self::ReadWrite | Self::Write) }
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CollectionRules {
pub read: Option<String>,
pub write: Option<String>,
#[serde(default)]
pub scope: Vec<ScopeRule>,
}
impl PolicyConfig {
pub fn can_access_collection(&self, roles: &[String], collection: &CollectionId) -> bool {
for role in roles {
if self.role_has_wildcard(role) {
return true;
}
}
let collection_name = collection.as_str();
if let Some(rules) = self.collections.get(collection_name) {
for role in roles {
let privileges = self.privileges_for_role(role);
if let Some(ref read_priv) = rules.read {
if privileges.contains(&read_priv.as_str()) {
return true;
}
}
if let Some(ref write_priv) = rules.write {
if privileges.contains(&write_priv.as_str()) {
return true;
}
}
}
}
false
}
pub fn can_write_collection(&self, roles: &[String], collection: &CollectionId) -> bool {
for role in roles {
if self.role_has_wildcard(role) {
return true;
}
}
let collection_name = collection.as_str();
if let Some(rules) = self.collections.get(collection_name) {
for role in roles {
let privileges = self.privileges_for_role(role);
if let Some(ref write_priv) = rules.write {
if privileges.contains(&write_priv.as_str()) {
return true;
}
}
}
}
false
}
pub fn scope_rules_for_collection(&self, collection: &str) -> &[ScopeRule] {
self.collections.get(collection).map_or(&[], |r| &r.scope)
}
pub fn roles_have_privilege(&self, roles: &[String], privilege: &str) -> bool {
for role in roles {
if self.role_has_wildcard(role) {
return true;
}
let privs = self.privileges_for_role(role);
if privs.contains(&privilege) {
return true;
}
}
false
}
fn role_has_wildcard(&self, role: &str) -> bool { self.roles.get(role).map_or(false, |privs| privs.iter().any(|p| p == "*")) }
fn privileges_for_role(&self, role: &str) -> Vec<&str> {
self.roles.get(role).map(|privs| privs.iter().map(|s| s.as_str()).collect()).unwrap_or_default()
}
}
impl Default for PolicyConfig {
fn default() -> Self { Self { roles: HashMap::new(), collections: HashMap::new() } }
}