oxirs_vec/multi_tenancy/
isolation.rs

1//! Data isolation strategies for multi-tenancy
2
3use crate::multi_tenancy::types::{MultiTenancyError, MultiTenancyResult};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8/// Level of tenant isolation
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum IsolationLevel {
11    /// Shared index with namespace prefixes (lowest isolation, highest efficiency)
12    Namespace,
13
14    /// Separate indices per tenant (medium isolation)
15    SeparateIndex,
16
17    /// Separate databases/instances per tenant (highest isolation, highest cost)
18    Dedicated,
19}
20
21impl IsolationLevel {
22    /// Get isolation strength (0-10, higher = stronger)
23    pub fn strength(&self) -> u8 {
24        match self {
25            Self::Namespace => 3,
26            Self::SeparateIndex => 7,
27            Self::Dedicated => 10,
28        }
29    }
30
31    /// Get performance efficiency (0-10, higher = more efficient)
32    pub fn efficiency(&self) -> u8 {
33        match self {
34            Self::Namespace => 10,
35            Self::SeparateIndex => 6,
36            Self::Dedicated => 3,
37        }
38    }
39
40    /// Recommended for tenant tier
41    pub fn for_tier(tier: &str) -> Self {
42        match tier {
43            "free" | "trial" => Self::Namespace,
44            "pro" | "business" => Self::SeparateIndex,
45            "enterprise" | "dedicated" => Self::Dedicated,
46            _ => Self::Namespace,
47        }
48    }
49}
50
51/// Isolation strategy configuration
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct IsolationStrategy {
54    /// Base isolation level
55    pub level: IsolationLevel,
56
57    /// Whether to encrypt tenant data at rest
58    pub encryption_at_rest: bool,
59
60    /// Whether to encrypt tenant data in transit
61    pub encryption_in_transit: bool,
62
63    /// Custom namespace separator
64    pub namespace_separator: String,
65
66    /// Maximum namespace depth
67    pub max_namespace_depth: usize,
68}
69
70impl IsolationStrategy {
71    /// Create new isolation strategy
72    pub fn new(level: IsolationLevel) -> Self {
73        Self {
74            level,
75            encryption_at_rest: false,
76            encryption_in_transit: true,
77            namespace_separator: ":".to_string(),
78            max_namespace_depth: 5,
79        }
80    }
81
82    /// Create strategy for free tier
83    pub fn free_tier() -> Self {
84        Self::new(IsolationLevel::Namespace)
85    }
86
87    /// Create strategy for pro tier
88    pub fn pro_tier() -> Self {
89        let mut strategy = Self::new(IsolationLevel::SeparateIndex);
90        strategy.encryption_at_rest = true;
91        strategy
92    }
93
94    /// Create strategy for enterprise tier
95    pub fn enterprise_tier() -> Self {
96        let mut strategy = Self::new(IsolationLevel::Dedicated);
97        strategy.encryption_at_rest = true;
98        strategy
99    }
100
101    /// Enable encryption
102    pub fn with_encryption(mut self, at_rest: bool, in_transit: bool) -> Self {
103        self.encryption_at_rest = at_rest;
104        self.encryption_in_transit = in_transit;
105        self
106    }
107
108    /// Set custom namespace separator
109    pub fn with_separator(mut self, separator: impl Into<String>) -> Self {
110        self.namespace_separator = separator.into();
111        self
112    }
113}
114
115/// Namespace manager for tenant data isolation
116pub struct NamespaceManager {
117    /// Tenant namespaces
118    namespaces: Arc<RwLock<HashMap<String, Namespace>>>,
119
120    /// Isolation strategy
121    strategy: IsolationStrategy,
122}
123
124/// Namespace for tenant data
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct Namespace {
127    /// Tenant identifier
128    pub tenant_id: String,
129
130    /// Namespace prefix
131    pub prefix: String,
132
133    /// Sub-namespaces
134    pub sub_namespaces: Vec<String>,
135
136    /// Metadata
137    pub metadata: HashMap<String, String>,
138}
139
140impl Namespace {
141    /// Create new namespace
142    pub fn new(tenant_id: impl Into<String>, prefix: impl Into<String>) -> Self {
143        Self {
144            tenant_id: tenant_id.into(),
145            prefix: prefix.into(),
146            sub_namespaces: Vec::new(),
147            metadata: HashMap::new(),
148        }
149    }
150
151    /// Create sub-namespace
152    pub fn create_sub_namespace(&mut self, name: impl Into<String>, separator: &str) -> String {
153        let sub = format!("{}{}{}", self.prefix, separator, name.into());
154        self.sub_namespaces.push(sub.clone());
155        sub
156    }
157
158    /// Get fully qualified key
159    pub fn qualify_key(&self, key: &str, separator: &str) -> String {
160        format!("{}{}{}", self.prefix, separator, key)
161    }
162
163    /// Check if key belongs to this namespace
164    pub fn owns_key(&self, key: &str) -> bool {
165        key.starts_with(&self.prefix)
166    }
167}
168
169impl NamespaceManager {
170    /// Create new namespace manager
171    pub fn new(strategy: IsolationStrategy) -> Self {
172        Self {
173            namespaces: Arc::new(RwLock::new(HashMap::new())),
174            strategy,
175        }
176    }
177
178    /// Register namespace for tenant
179    pub fn register_tenant(&self, tenant_id: impl Into<String>) -> MultiTenancyResult<String> {
180        let tenant_id = tenant_id.into();
181        let prefix = self.generate_namespace_prefix(&tenant_id);
182
183        let namespace = Namespace::new(tenant_id.clone(), prefix.clone());
184
185        let mut namespaces =
186            self.namespaces
187                .write()
188                .map_err(|e| MultiTenancyError::InternalError {
189                    message: format!("Lock error: {}", e),
190                })?;
191
192        if namespaces.contains_key(&tenant_id) {
193            return Err(MultiTenancyError::TenantAlreadyExists {
194                tenant_id: tenant_id.clone(),
195            });
196        }
197
198        namespaces.insert(tenant_id, namespace);
199
200        Ok(prefix)
201    }
202
203    /// Unregister tenant namespace
204    pub fn unregister_tenant(&self, tenant_id: &str) -> MultiTenancyResult<()> {
205        let mut namespaces =
206            self.namespaces
207                .write()
208                .map_err(|e| MultiTenancyError::InternalError {
209                    message: format!("Lock error: {}", e),
210                })?;
211
212        namespaces
213            .remove(tenant_id)
214            .ok_or_else(|| MultiTenancyError::TenantNotFound {
215                tenant_id: tenant_id.to_string(),
216            })?;
217
218        Ok(())
219    }
220
221    /// Get namespace prefix for tenant
222    pub fn get_prefix(&self, tenant_id: &str) -> MultiTenancyResult<String> {
223        let namespaces = self
224            .namespaces
225            .read()
226            .map_err(|e| MultiTenancyError::InternalError {
227                message: format!("Lock error: {}", e),
228            })?;
229
230        namespaces
231            .get(tenant_id)
232            .map(|ns| ns.prefix.clone())
233            .ok_or_else(|| MultiTenancyError::TenantNotFound {
234                tenant_id: tenant_id.to_string(),
235            })
236    }
237
238    /// Qualify key with tenant namespace
239    pub fn qualify_key(&self, tenant_id: &str, key: &str) -> MultiTenancyResult<String> {
240        let namespaces = self
241            .namespaces
242            .read()
243            .map_err(|e| MultiTenancyError::InternalError {
244                message: format!("Lock error: {}", e),
245            })?;
246
247        let namespace =
248            namespaces
249                .get(tenant_id)
250                .ok_or_else(|| MultiTenancyError::TenantNotFound {
251                    tenant_id: tenant_id.to_string(),
252                })?;
253
254        Ok(namespace.qualify_key(key, &self.strategy.namespace_separator))
255    }
256
257    /// Extract tenant ID from namespaced key
258    pub fn extract_tenant_id(&self, namespaced_key: &str) -> MultiTenancyResult<String> {
259        let namespaces = self
260            .namespaces
261            .read()
262            .map_err(|e| MultiTenancyError::InternalError {
263                message: format!("Lock error: {}", e),
264            })?;
265
266        for (tenant_id, namespace) in namespaces.iter() {
267            if namespace.owns_key(namespaced_key) {
268                return Ok(tenant_id.clone());
269            }
270        }
271
272        Err(MultiTenancyError::IsolationViolation {
273            message: format!("No tenant owns key: {}", namespaced_key),
274        })
275    }
276
277    /// Validate that a key belongs to a tenant
278    pub fn validate_access(&self, tenant_id: &str, key: &str) -> MultiTenancyResult<bool> {
279        let namespaces = self
280            .namespaces
281            .read()
282            .map_err(|e| MultiTenancyError::InternalError {
283                message: format!("Lock error: {}", e),
284            })?;
285
286        let namespace =
287            namespaces
288                .get(tenant_id)
289                .ok_or_else(|| MultiTenancyError::TenantNotFound {
290                    tenant_id: tenant_id.to_string(),
291                })?;
292
293        Ok(namespace.owns_key(key))
294    }
295
296    /// Create sub-namespace for tenant
297    pub fn create_sub_namespace(
298        &self,
299        tenant_id: &str,
300        name: impl Into<String>,
301    ) -> MultiTenancyResult<String> {
302        let mut namespaces =
303            self.namespaces
304                .write()
305                .map_err(|e| MultiTenancyError::InternalError {
306                    message: format!("Lock error: {}", e),
307                })?;
308
309        let namespace =
310            namespaces
311                .get_mut(tenant_id)
312                .ok_or_else(|| MultiTenancyError::TenantNotFound {
313                    tenant_id: tenant_id.to_string(),
314                })?;
315
316        if namespace.sub_namespaces.len() >= self.strategy.max_namespace_depth {
317            return Err(MultiTenancyError::InvalidConfiguration {
318                message: "Maximum namespace depth exceeded".to_string(),
319            });
320        }
321
322        Ok(namespace.create_sub_namespace(name, &self.strategy.namespace_separator))
323    }
324
325    /// Get all namespaces
326    pub fn list_namespaces(&self) -> MultiTenancyResult<Vec<String>> {
327        let namespaces = self
328            .namespaces
329            .read()
330            .map_err(|e| MultiTenancyError::InternalError {
331                message: format!("Lock error: {}", e),
332            })?;
333
334        Ok(namespaces.keys().cloned().collect())
335    }
336
337    /// Generate namespace prefix from tenant ID
338    fn generate_namespace_prefix(&self, tenant_id: &str) -> String {
339        // Sanitize tenant ID for use as namespace
340        let sanitized: String = tenant_id
341            .chars()
342            .map(|c| match c {
343                '-' | '.' | '/' => '_',
344                c => c,
345            })
346            .collect();
347
348        format!("tenant_{}", sanitized)
349    }
350
351    /// Get isolation strategy
352    pub fn strategy(&self) -> &IsolationStrategy {
353        &self.strategy
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    #[test]
362    fn test_isolation_levels() {
363        assert_eq!(IsolationLevel::Namespace.strength(), 3);
364        assert_eq!(IsolationLevel::SeparateIndex.strength(), 7);
365        assert_eq!(IsolationLevel::Dedicated.strength(), 10);
366
367        assert_eq!(IsolationLevel::Namespace.efficiency(), 10);
368        assert_eq!(IsolationLevel::Dedicated.efficiency(), 3);
369    }
370
371    #[test]
372    fn test_isolation_level_for_tier() {
373        assert_eq!(IsolationLevel::for_tier("free"), IsolationLevel::Namespace);
374        assert_eq!(
375            IsolationLevel::for_tier("pro"),
376            IsolationLevel::SeparateIndex
377        );
378        assert_eq!(
379            IsolationLevel::for_tier("enterprise"),
380            IsolationLevel::Dedicated
381        );
382    }
383
384    #[test]
385    fn test_isolation_strategy() {
386        let strategy = IsolationStrategy::free_tier();
387        assert_eq!(strategy.level, IsolationLevel::Namespace);
388        assert!(!strategy.encryption_at_rest);
389
390        let strategy = IsolationStrategy::pro_tier();
391        assert_eq!(strategy.level, IsolationLevel::SeparateIndex);
392        assert!(strategy.encryption_at_rest);
393
394        let strategy = IsolationStrategy::enterprise_tier();
395        assert_eq!(strategy.level, IsolationLevel::Dedicated);
396        assert!(strategy.encryption_at_rest);
397    }
398
399    #[test]
400    fn test_namespace_creation() {
401        let ns = Namespace::new("tenant1", "tenant_tenant1");
402        assert_eq!(ns.tenant_id, "tenant1");
403        assert_eq!(ns.prefix, "tenant_tenant1");
404        assert!(ns.sub_namespaces.is_empty());
405    }
406
407    #[test]
408    fn test_namespace_qualification() {
409        let ns = Namespace::new("tenant1", "tenant_tenant1");
410        let qualified = ns.qualify_key("vector123", ":");
411        assert_eq!(qualified, "tenant_tenant1:vector123");
412        assert!(ns.owns_key(&qualified));
413        assert!(!ns.owns_key("other_tenant:vector123"));
414    }
415
416    #[test]
417    fn test_namespace_manager() {
418        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
419        let manager = NamespaceManager::new(strategy);
420
421        // Register tenant
422        let prefix = manager.register_tenant("tenant1").unwrap();
423        assert!(prefix.contains("tenant_tenant1"));
424
425        // Get prefix
426        let retrieved_prefix = manager.get_prefix("tenant1").unwrap();
427        assert_eq!(prefix, retrieved_prefix);
428
429        // Qualify key
430        let qualified = manager.qualify_key("tenant1", "vector123").unwrap();
431        assert!(qualified.starts_with(&prefix));
432        assert!(qualified.contains("vector123"));
433
434        // Validate access
435        assert!(manager.validate_access("tenant1", &qualified).unwrap());
436        assert!(!manager
437            .validate_access("tenant1", "other_tenant:key")
438            .unwrap());
439
440        // Extract tenant ID
441        let extracted = manager.extract_tenant_id(&qualified).unwrap();
442        assert_eq!(extracted, "tenant1");
443
444        // Unregister tenant
445        manager.unregister_tenant("tenant1").unwrap();
446        assert!(manager.get_prefix("tenant1").is_err());
447    }
448
449    #[test]
450    fn test_sub_namespaces() {
451        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
452        let manager = NamespaceManager::new(strategy);
453
454        manager.register_tenant("tenant1").unwrap();
455
456        // Create sub-namespace
457        let sub = manager.create_sub_namespace("tenant1", "vectors").unwrap();
458        assert!(sub.contains("tenant_tenant1"));
459        assert!(sub.contains("vectors"));
460
461        // Create another sub-namespace
462        let sub2 = manager
463            .create_sub_namespace("tenant1", "embeddings")
464            .unwrap();
465        assert!(sub2.contains("embeddings"));
466        assert_ne!(sub, sub2);
467    }
468
469    #[test]
470    fn test_namespace_manager_errors() {
471        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
472        let manager = NamespaceManager::new(strategy);
473
474        // Getting prefix for non-existent tenant should fail
475        assert!(manager.get_prefix("nonexistent").is_err());
476
477        // Qualifying key for non-existent tenant should fail
478        assert!(manager.qualify_key("nonexistent", "key").is_err());
479
480        // Registering same tenant twice should fail
481        manager.register_tenant("tenant1").unwrap();
482        assert!(manager.register_tenant("tenant1").is_err());
483
484        // Unregistering non-existent tenant should fail
485        assert!(manager.unregister_tenant("nonexistent").is_err());
486    }
487
488    #[test]
489    fn test_list_namespaces() {
490        let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
491        let manager = NamespaceManager::new(strategy);
492
493        assert_eq!(manager.list_namespaces().unwrap().len(), 0);
494
495        manager.register_tenant("tenant1").unwrap();
496        manager.register_tenant("tenant2").unwrap();
497
498        let namespaces = manager.list_namespaces().unwrap();
499        assert_eq!(namespaces.len(), 2);
500        assert!(namespaces.contains(&"tenant1".to_string()));
501        assert!(namespaces.contains(&"tenant2".to_string()));
502    }
503}