use crate::database::DbPool;
#[cfg(not(any(feature = "permission", feature = "data-protection")))]
use crate::foundation::DbError;
use crate::foundation::DbResult;
#[cfg(feature = "data-protection")]
use crate::access::data_protection::{DataProtection, MaskStrategy, MaskingEngine, RlsEngine};
#[cfg(feature = "permission")]
use crate::access::permission::{PermissionConfig, RolePolicy};
#[derive(Debug, Default)]
pub struct PermissionFacadeConfig {
#[cfg(feature = "permission")]
rbac: Option<PermissionConfig>,
#[cfg(feature = "data-protection")]
masking: Option<MaskingEngine>,
#[cfg(feature = "data-protection")]
rls: Option<RlsEngine>,
}
impl PermissionFacadeConfig {
pub fn new() -> Self {
Self::default()
}
#[cfg(feature = "permission")]
pub fn with_roles(mut self, roles: std::collections::HashMap<String, RolePolicy>) -> Self {
self.rbac = Some(PermissionConfig { roles });
self
}
#[cfg(feature = "data-protection")]
pub fn with_masking_rule(mut self, column: &str, strategy: MaskStrategy) -> Self {
let engine = self.masking.take().unwrap_or_default();
self.masking = Some(engine.rule(column, strategy));
self
}
#[cfg(feature = "data-protection")]
pub fn with_rls_policy(mut self, table: &str, column: &str, value: &str) -> Self {
let engine = self.rls.take().unwrap_or_default();
self.rls = Some(engine.policy(table, column, value));
self
}
}
pub struct PermissionFacade<'a> {
pool: &'a DbPool,
}
impl<'a> PermissionFacade<'a> {
pub fn new(pool: &'a DbPool) -> Self {
Self { pool }
}
pub async fn apply(&self, config: PermissionFacadeConfig) -> DbResult<()> {
#[cfg(feature = "permission")]
if let Some(rbac) = config.rbac {
self.pool.set_permission_config(rbac).await?;
}
#[cfg(feature = "data-protection")]
self.pool
.set_data_protection(DataProtection {
masking: config.masking.map(std::sync::Arc::new),
rls: config.rls.map(std::sync::Arc::new),
})
.await;
#[cfg(not(any(feature = "permission", feature = "data-protection")))]
{
let _ = &self.pool;
return Err(DbError::Config(
"PermissionFacade requires 'permission' and/or 'data-protection' features"
.to_string(),
));
}
#[allow(unreachable_code)]
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(all(feature = "permission", feature = "data-protection"))]
#[test]
fn test_config_builder_accumulates_rules() {
let config = PermissionFacadeConfig::new()
.with_masking_rule("email", MaskStrategy::Hash)
.with_masking_rule("phone", MaskStrategy::Mask { keep: 3 })
.with_rls_policy("orders", "tenant_id", "t-100")
.with_rls_policy("audit", "tenant_id", "t-100");
assert!(config.masking.is_some());
assert!(config.rls.is_some());
assert!(config.rbac.is_none());
}
}