use crate::multi_tenancy::types::{MultiTenancyError, MultiTenancyResult};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
Read,
Write,
Delete,
BuildIndex,
Admin,
ViewMetrics,
ManageBilling,
Custom(u32),
}
impl Permission {
pub fn includes(&self, other: &Permission) -> bool {
match self {
Self::Admin => true, _ => self == other,
}
}
pub fn name(&self) -> &'static str {
match self {
Self::Read => "read",
Self::Write => "write",
Self::Delete => "delete",
Self::BuildIndex => "build_index",
Self::Admin => "admin",
Self::ViewMetrics => "view_metrics",
Self::ManageBilling => "manage_billing",
Self::Custom(_) => "custom",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Role {
pub name: String,
pub permissions: HashSet<Permission>,
pub description: Option<String>,
}
impl Role {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
permissions: HashSet::new(),
description: None,
}
}
pub fn readonly() -> Self {
let mut role = Self::new("readonly");
role.permissions.insert(Permission::Read);
role.permissions.insert(Permission::ViewMetrics);
role.description = Some("Read-only access to vectors and metrics".to_string());
role
}
pub fn readwrite() -> Self {
let mut role = Self::new("readwrite");
role.permissions.insert(Permission::Read);
role.permissions.insert(Permission::Write);
role.permissions.insert(Permission::ViewMetrics);
role.description = Some("Read and write access to vectors".to_string());
role
}
pub fn admin() -> Self {
let mut role = Self::new("admin");
role.permissions.insert(Permission::Admin);
role.description = Some("Full administrative access".to_string());
role
}
pub fn add_permission(&mut self, permission: Permission) {
self.permissions.insert(permission);
}
pub fn has_permission(&self, permission: Permission) -> bool {
self.permissions.iter().any(|p| p.includes(&permission))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessPolicy {
pub tenant_id: String,
pub user_roles: HashMap<String, Vec<String>>,
pub roles: HashMap<String, Role>,
pub ip_whitelist: Vec<String>,
pub ip_blacklist: Vec<String>,
}
impl AccessPolicy {
pub fn new(tenant_id: impl Into<String>) -> Self {
let mut policy = Self {
tenant_id: tenant_id.into(),
user_roles: HashMap::new(),
roles: HashMap::new(),
ip_whitelist: Vec::new(),
ip_blacklist: Vec::new(),
};
policy.add_role(Role::readonly());
policy.add_role(Role::readwrite());
policy.add_role(Role::admin());
policy
}
pub fn add_role(&mut self, role: Role) {
self.roles.insert(role.name.clone(), role);
}
pub fn assign_role(&mut self, user_id: impl Into<String>, role_name: impl Into<String>) {
self.user_roles
.entry(user_id.into())
.or_default()
.push(role_name.into());
}
pub fn has_permission(&self, user_id: &str, permission: Permission) -> bool {
if let Some(role_names) = self.user_roles.get(user_id) {
for role_name in role_names {
if let Some(role) = self.roles.get(role_name) {
if role.has_permission(permission) {
return true;
}
}
}
}
false
}
pub fn is_ip_allowed(&self, ip: &str) -> bool {
if self.ip_blacklist.contains(&ip.to_string()) {
return false;
}
if self.ip_whitelist.is_empty() {
return true;
}
self.ip_whitelist.contains(&ip.to_string())
}
}
pub struct AccessControl {
policies: Arc<RwLock<HashMap<String, AccessPolicy>>>,
}
impl AccessControl {
pub fn new() -> Self {
Self {
policies: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn set_policy(&self, policy: AccessPolicy) -> MultiTenancyResult<()> {
self.policies
.write()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?
.insert(policy.tenant_id.clone(), policy);
Ok(())
}
pub fn get_policy(&self, tenant_id: &str) -> MultiTenancyResult<AccessPolicy> {
self.policies
.read()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?
.get(tenant_id)
.cloned()
.ok_or_else(|| MultiTenancyError::TenantNotFound {
tenant_id: tenant_id.to_string(),
})
}
pub fn check_permission(
&self,
tenant_id: &str,
user_id: &str,
permission: Permission,
) -> MultiTenancyResult<bool> {
let policy = self.get_policy(tenant_id)?;
Ok(policy.has_permission(user_id, permission))
}
pub fn authorize(
&self,
tenant_id: &str,
user_id: &str,
permission: Permission,
client_ip: Option<&str>,
) -> MultiTenancyResult<()> {
let policy = self.get_policy(tenant_id)?;
if let Some(ip) = client_ip {
if !policy.is_ip_allowed(ip) {
return Err(MultiTenancyError::AccessDenied {
tenant_id: tenant_id.to_string(),
reason: format!("IP {} not allowed", ip),
});
}
}
if !policy.has_permission(user_id, permission) {
return Err(MultiTenancyError::AccessDenied {
tenant_id: tenant_id.to_string(),
reason: format!("User {} lacks permission {:?}", user_id, permission),
});
}
Ok(())
}
pub fn create_default_policy(&self, tenant_id: impl Into<String>) -> MultiTenancyResult<()> {
let policy = AccessPolicy::new(tenant_id);
self.set_policy(policy)
}
}
impl Default for AccessControl {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
use super::*;
#[test]
fn test_permissions() {
assert!(Permission::Admin.includes(&Permission::Read));
assert!(Permission::Admin.includes(&Permission::Write));
assert!(!Permission::Read.includes(&Permission::Write));
assert!(Permission::Read.includes(&Permission::Read));
}
#[test]
fn test_role_creation() {
let role = Role::readonly();
assert!(role.has_permission(Permission::Read));
assert!(!role.has_permission(Permission::Write));
assert!(!role.has_permission(Permission::Delete));
let role = Role::readwrite();
assert!(role.has_permission(Permission::Read));
assert!(role.has_permission(Permission::Write));
assert!(!role.has_permission(Permission::Delete));
let role = Role::admin();
assert!(role.has_permission(Permission::Read));
assert!(role.has_permission(Permission::Write));
assert!(role.has_permission(Permission::Delete));
assert!(role.has_permission(Permission::Admin));
}
#[test]
fn test_access_policy() {
let mut policy = AccessPolicy::new("tenant1");
policy.assign_role("user1", "readonly");
policy.assign_role("user2", "readwrite");
policy.assign_role("user3", "admin");
assert!(policy.has_permission("user1", Permission::Read));
assert!(!policy.has_permission("user1", Permission::Write));
assert!(policy.has_permission("user2", Permission::Read));
assert!(policy.has_permission("user2", Permission::Write));
assert!(!policy.has_permission("user2", Permission::Delete));
assert!(policy.has_permission("user3", Permission::Read));
assert!(policy.has_permission("user3", Permission::Write));
assert!(policy.has_permission("user3", Permission::Delete));
assert!(policy.has_permission("user3", Permission::Admin));
}
#[test]
fn test_ip_restrictions() {
let mut policy = AccessPolicy::new("tenant1");
assert!(policy.is_ip_allowed("192.168.1.1"));
assert!(policy.is_ip_allowed("10.0.0.1"));
policy.ip_blacklist.push("192.168.1.100".to_string());
assert!(!policy.is_ip_allowed("192.168.1.100"));
assert!(policy.is_ip_allowed("192.168.1.1"));
policy.ip_whitelist.push("192.168.1.1".to_string());
policy.ip_whitelist.push("192.168.1.2".to_string());
assert!(policy.is_ip_allowed("192.168.1.1"));
assert!(policy.is_ip_allowed("192.168.1.2"));
assert!(!policy.is_ip_allowed("10.0.0.1"));
assert!(!policy.is_ip_allowed("192.168.1.100")); }
#[test]
fn test_access_control_manager() -> Result<()> {
let ac = AccessControl::new();
ac.create_default_policy("tenant1")?;
let mut policy = ac.get_policy("tenant1")?;
policy.assign_role("user1", "readonly");
policy.assign_role("user2", "admin");
ac.set_policy(policy)?;
assert!(ac.check_permission("tenant1", "user1", Permission::Read)?);
assert!(!ac.check_permission("tenant1", "user1", Permission::Write)?);
assert!(ac.check_permission("tenant1", "user2", Permission::Admin)?);
assert!(ac
.authorize("tenant1", "user1", Permission::Read, None)
.is_ok());
assert!(ac
.authorize("tenant1", "user1", Permission::Write, None)
.is_err());
assert!(ac
.authorize("tenant1", "user2", Permission::Write, None)
.is_ok());
Ok(())
}
#[test]
fn test_authorize_with_ip() -> Result<()> {
let ac = AccessControl::new();
let mut policy = AccessPolicy::new("tenant1");
policy.assign_role("user1", "readonly");
policy.ip_whitelist.push("192.168.1.1".to_string());
ac.set_policy(policy)?;
assert!(ac
.authorize("tenant1", "user1", Permission::Read, Some("192.168.1.1"))
.is_ok());
assert!(ac
.authorize("tenant1", "user1", Permission::Read, Some("10.0.0.1"))
.is_err());
Ok(())
}
#[test]
fn test_custom_roles() {
let mut role = Role::new("custom");
role.add_permission(Permission::Read);
role.add_permission(Permission::ViewMetrics);
role.add_permission(Permission::Custom(100));
assert!(role.has_permission(Permission::Read));
assert!(role.has_permission(Permission::ViewMetrics));
assert!(role.has_permission(Permission::Custom(100)));
assert!(!role.has_permission(Permission::Write));
}
}