oxirs_vec/multi_tenancy/
access_control.rs

1//! Access control and RBAC for multi-tenancy
2
3use crate::multi_tenancy::types::{MultiTenancyError, MultiTenancyResult};
4use serde::{Deserialize, Serialize};
5use std::collections::{HashMap, HashSet};
6use std::sync::{Arc, RwLock};
7
8/// Permission for an operation
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum Permission {
11    /// Read vectors
12    Read,
13    /// Write/insert vectors
14    Write,
15    /// Delete vectors
16    Delete,
17    /// Build indices
18    BuildIndex,
19    /// Administer tenant
20    Admin,
21    /// View metrics and analytics
22    ViewMetrics,
23    /// Manage billing
24    ManageBilling,
25    /// Custom permission
26    Custom(u32),
27}
28
29impl Permission {
30    /// Check if this permission includes another
31    pub fn includes(&self, other: &Permission) -> bool {
32        match self {
33            Self::Admin => true, // Admin has all permissions
34            _ => self == other,
35        }
36    }
37
38    /// Get permission name
39    pub fn name(&self) -> &'static str {
40        match self {
41            Self::Read => "read",
42            Self::Write => "write",
43            Self::Delete => "delete",
44            Self::BuildIndex => "build_index",
45            Self::Admin => "admin",
46            Self::ViewMetrics => "view_metrics",
47            Self::ManageBilling => "manage_billing",
48            Self::Custom(_) => "custom",
49        }
50    }
51}
52
53/// Role with a set of permissions
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct Role {
56    /// Role name
57    pub name: String,
58    /// Permissions granted by this role
59    pub permissions: HashSet<Permission>,
60    /// Role description
61    pub description: Option<String>,
62}
63
64impl Role {
65    /// Create new role
66    pub fn new(name: impl Into<String>) -> Self {
67        Self {
68            name: name.into(),
69            permissions: HashSet::new(),
70            description: None,
71        }
72    }
73
74    /// Create read-only role
75    pub fn readonly() -> Self {
76        let mut role = Self::new("readonly");
77        role.permissions.insert(Permission::Read);
78        role.permissions.insert(Permission::ViewMetrics);
79        role.description = Some("Read-only access to vectors and metrics".to_string());
80        role
81    }
82
83    /// Create read-write role
84    pub fn readwrite() -> Self {
85        let mut role = Self::new("readwrite");
86        role.permissions.insert(Permission::Read);
87        role.permissions.insert(Permission::Write);
88        role.permissions.insert(Permission::ViewMetrics);
89        role.description = Some("Read and write access to vectors".to_string());
90        role
91    }
92
93    /// Create admin role
94    pub fn admin() -> Self {
95        let mut role = Self::new("admin");
96        role.permissions.insert(Permission::Admin);
97        role.description = Some("Full administrative access".to_string());
98        role
99    }
100
101    /// Add permission to role
102    pub fn add_permission(&mut self, permission: Permission) {
103        self.permissions.insert(permission);
104    }
105
106    /// Check if role has permission
107    pub fn has_permission(&self, permission: Permission) -> bool {
108        self.permissions.iter().any(|p| p.includes(&permission))
109    }
110}
111
112/// Access policy for a tenant
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct AccessPolicy {
115    /// Tenant ID
116    pub tenant_id: String,
117    /// User/API key to role mapping
118    pub user_roles: HashMap<String, Vec<String>>,
119    /// Available roles
120    pub roles: HashMap<String, Role>,
121    /// IP whitelist (empty = no restriction)
122    pub ip_whitelist: Vec<String>,
123    /// IP blacklist
124    pub ip_blacklist: Vec<String>,
125}
126
127impl AccessPolicy {
128    /// Create new access policy
129    pub fn new(tenant_id: impl Into<String>) -> Self {
130        let mut policy = Self {
131            tenant_id: tenant_id.into(),
132            user_roles: HashMap::new(),
133            roles: HashMap::new(),
134            ip_whitelist: Vec::new(),
135            ip_blacklist: Vec::new(),
136        };
137
138        // Add default roles
139        policy.add_role(Role::readonly());
140        policy.add_role(Role::readwrite());
141        policy.add_role(Role::admin());
142
143        policy
144    }
145
146    /// Add a role definition
147    pub fn add_role(&mut self, role: Role) {
148        self.roles.insert(role.name.clone(), role);
149    }
150
151    /// Assign role to user
152    pub fn assign_role(&mut self, user_id: impl Into<String>, role_name: impl Into<String>) {
153        self.user_roles
154            .entry(user_id.into())
155            .or_default()
156            .push(role_name.into());
157    }
158
159    /// Check if user has permission
160    pub fn has_permission(&self, user_id: &str, permission: Permission) -> bool {
161        if let Some(role_names) = self.user_roles.get(user_id) {
162            for role_name in role_names {
163                if let Some(role) = self.roles.get(role_name) {
164                    if role.has_permission(permission) {
165                        return true;
166                    }
167                }
168            }
169        }
170        false
171    }
172
173    /// Check if IP is allowed
174    pub fn is_ip_allowed(&self, ip: &str) -> bool {
175        // Check blacklist first
176        if self.ip_blacklist.contains(&ip.to_string()) {
177            return false;
178        }
179
180        // If whitelist is empty, allow all (except blacklisted)
181        if self.ip_whitelist.is_empty() {
182            return true;
183        }
184
185        // Check whitelist
186        self.ip_whitelist.contains(&ip.to_string())
187    }
188}
189
190/// Access control manager
191pub struct AccessControl {
192    /// Policies by tenant
193    policies: Arc<RwLock<HashMap<String, AccessPolicy>>>,
194}
195
196impl AccessControl {
197    /// Create new access control manager
198    pub fn new() -> Self {
199        Self {
200            policies: Arc::new(RwLock::new(HashMap::new())),
201        }
202    }
203
204    /// Set policy for tenant
205    pub fn set_policy(&self, policy: AccessPolicy) -> MultiTenancyResult<()> {
206        self.policies
207            .write()
208            .map_err(|e| MultiTenancyError::InternalError {
209                message: format!("Lock error: {}", e),
210            })?
211            .insert(policy.tenant_id.clone(), policy);
212        Ok(())
213    }
214
215    /// Get policy for tenant
216    pub fn get_policy(&self, tenant_id: &str) -> MultiTenancyResult<AccessPolicy> {
217        self.policies
218            .read()
219            .map_err(|e| MultiTenancyError::InternalError {
220                message: format!("Lock error: {}", e),
221            })?
222            .get(tenant_id)
223            .cloned()
224            .ok_or_else(|| MultiTenancyError::TenantNotFound {
225                tenant_id: tenant_id.to_string(),
226            })
227    }
228
229    /// Check if user has permission
230    pub fn check_permission(
231        &self,
232        tenant_id: &str,
233        user_id: &str,
234        permission: Permission,
235    ) -> MultiTenancyResult<bool> {
236        let policy = self.get_policy(tenant_id)?;
237        Ok(policy.has_permission(user_id, permission))
238    }
239
240    /// Authorize operation
241    pub fn authorize(
242        &self,
243        tenant_id: &str,
244        user_id: &str,
245        permission: Permission,
246        client_ip: Option<&str>,
247    ) -> MultiTenancyResult<()> {
248        let policy = self.get_policy(tenant_id)?;
249
250        // Check IP restrictions
251        if let Some(ip) = client_ip {
252            if !policy.is_ip_allowed(ip) {
253                return Err(MultiTenancyError::AccessDenied {
254                    tenant_id: tenant_id.to_string(),
255                    reason: format!("IP {} not allowed", ip),
256                });
257            }
258        }
259
260        // Check permissions
261        if !policy.has_permission(user_id, permission) {
262            return Err(MultiTenancyError::AccessDenied {
263                tenant_id: tenant_id.to_string(),
264                reason: format!("User {} lacks permission {:?}", user_id, permission),
265            });
266        }
267
268        Ok(())
269    }
270
271    /// Create default policy for tenant
272    pub fn create_default_policy(&self, tenant_id: impl Into<String>) -> MultiTenancyResult<()> {
273        let policy = AccessPolicy::new(tenant_id);
274        self.set_policy(policy)
275    }
276}
277
278impl Default for AccessControl {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn test_permissions() {
290        assert!(Permission::Admin.includes(&Permission::Read));
291        assert!(Permission::Admin.includes(&Permission::Write));
292        assert!(!Permission::Read.includes(&Permission::Write));
293        assert!(Permission::Read.includes(&Permission::Read));
294    }
295
296    #[test]
297    fn test_role_creation() {
298        let role = Role::readonly();
299        assert!(role.has_permission(Permission::Read));
300        assert!(!role.has_permission(Permission::Write));
301        assert!(!role.has_permission(Permission::Delete));
302
303        let role = Role::readwrite();
304        assert!(role.has_permission(Permission::Read));
305        assert!(role.has_permission(Permission::Write));
306        assert!(!role.has_permission(Permission::Delete));
307
308        let role = Role::admin();
309        assert!(role.has_permission(Permission::Read));
310        assert!(role.has_permission(Permission::Write));
311        assert!(role.has_permission(Permission::Delete));
312        assert!(role.has_permission(Permission::Admin));
313    }
314
315    #[test]
316    fn test_access_policy() {
317        let mut policy = AccessPolicy::new("tenant1");
318
319        // Assign roles
320        policy.assign_role("user1", "readonly");
321        policy.assign_role("user2", "readwrite");
322        policy.assign_role("user3", "admin");
323
324        // Check permissions
325        assert!(policy.has_permission("user1", Permission::Read));
326        assert!(!policy.has_permission("user1", Permission::Write));
327
328        assert!(policy.has_permission("user2", Permission::Read));
329        assert!(policy.has_permission("user2", Permission::Write));
330        assert!(!policy.has_permission("user2", Permission::Delete));
331
332        assert!(policy.has_permission("user3", Permission::Read));
333        assert!(policy.has_permission("user3", Permission::Write));
334        assert!(policy.has_permission("user3", Permission::Delete));
335        assert!(policy.has_permission("user3", Permission::Admin));
336    }
337
338    #[test]
339    fn test_ip_restrictions() {
340        let mut policy = AccessPolicy::new("tenant1");
341
342        // No restrictions by default
343        assert!(policy.is_ip_allowed("192.168.1.1"));
344        assert!(policy.is_ip_allowed("10.0.0.1"));
345
346        // Add to blacklist
347        policy.ip_blacklist.push("192.168.1.100".to_string());
348        assert!(!policy.is_ip_allowed("192.168.1.100"));
349        assert!(policy.is_ip_allowed("192.168.1.1"));
350
351        // Add whitelist (restricts to only those IPs)
352        policy.ip_whitelist.push("192.168.1.1".to_string());
353        policy.ip_whitelist.push("192.168.1.2".to_string());
354        assert!(policy.is_ip_allowed("192.168.1.1"));
355        assert!(policy.is_ip_allowed("192.168.1.2"));
356        assert!(!policy.is_ip_allowed("10.0.0.1"));
357        assert!(!policy.is_ip_allowed("192.168.1.100")); // Blacklist takes precedence
358    }
359
360    #[test]
361    fn test_access_control_manager() {
362        let ac = AccessControl::new();
363
364        // Create default policy
365        ac.create_default_policy("tenant1").unwrap();
366
367        // Get policy and modify
368        let mut policy = ac.get_policy("tenant1").unwrap();
369        policy.assign_role("user1", "readonly");
370        policy.assign_role("user2", "admin");
371        ac.set_policy(policy).unwrap();
372
373        // Check permissions
374        assert!(ac
375            .check_permission("tenant1", "user1", Permission::Read)
376            .unwrap());
377        assert!(!ac
378            .check_permission("tenant1", "user1", Permission::Write)
379            .unwrap());
380
381        assert!(ac
382            .check_permission("tenant1", "user2", Permission::Admin)
383            .unwrap());
384
385        // Authorize operations
386        assert!(ac
387            .authorize("tenant1", "user1", Permission::Read, None)
388            .is_ok());
389        assert!(ac
390            .authorize("tenant1", "user1", Permission::Write, None)
391            .is_err());
392        assert!(ac
393            .authorize("tenant1", "user2", Permission::Write, None)
394            .is_ok());
395    }
396
397    #[test]
398    fn test_authorize_with_ip() {
399        let ac = AccessControl::new();
400        let mut policy = AccessPolicy::new("tenant1");
401        policy.assign_role("user1", "readonly");
402        policy.ip_whitelist.push("192.168.1.1".to_string());
403        ac.set_policy(policy).unwrap();
404
405        // Should succeed with allowed IP
406        assert!(ac
407            .authorize("tenant1", "user1", Permission::Read, Some("192.168.1.1"))
408            .is_ok());
409
410        // Should fail with disallowed IP
411        assert!(ac
412            .authorize("tenant1", "user1", Permission::Read, Some("10.0.0.1"))
413            .is_err());
414    }
415
416    #[test]
417    fn test_custom_roles() {
418        let mut role = Role::new("custom");
419        role.add_permission(Permission::Read);
420        role.add_permission(Permission::ViewMetrics);
421        role.add_permission(Permission::Custom(100));
422
423        assert!(role.has_permission(Permission::Read));
424        assert!(role.has_permission(Permission::ViewMetrics));
425        assert!(role.has_permission(Permission::Custom(100)));
426        assert!(!role.has_permission(Permission::Write));
427    }
428}