scim_server/multi_tenant/
provider.rs

1//! Multi-tenant provider utilities and validation helpers.
2//!
3//! This module provides utilities for working with the unified ResourceProvider
4//! in multi-tenant scenarios. The unified ResourceProvider supports both single
5//! and multi-tenant operations through the RequestContext.
6
7use crate::resource::RequestContext;
8#[cfg(test)]
9use crate::resource::TenantContext;
10
11/// Helper trait for validating tenant context in provider operations.
12///
13/// This trait provides common validation logic that can be reused across
14/// different multi-tenant provider implementations.
15pub trait TenantValidator {
16    /// Validate that the context has the expected tenant.
17    fn validate_tenant_context(
18        &self,
19        expected_tenant_id: &str,
20        context: &RequestContext,
21    ) -> Result<(), String> {
22        match context.tenant_id() {
23            Some(actual_tenant_id) if actual_tenant_id == expected_tenant_id => Ok(()),
24            Some(actual_tenant_id) => Err(format!(
25                "Tenant mismatch: context has '{}', operation requested '{}'",
26                actual_tenant_id, expected_tenant_id
27            )),
28            None => Err(format!(
29                "Multi-tenant operation requested '{}' but context has no tenant",
30                expected_tenant_id
31            )),
32        }
33    }
34
35    /// Validate that the context is for single-tenant operation.
36    fn validate_single_tenant_context(&self, context: &RequestContext) -> Result<(), String> {
37        match context.tenant_id() {
38            None => Ok(()),
39            Some(tenant_id) => Err(format!(
40                "Single-tenant operation but context has tenant '{}'",
41                tenant_id
42            )),
43        }
44    }
45
46    /// Extract tenant context or return error for multi-tenant operations.
47    fn require_tenant_context(&self, context: &RequestContext) -> Result<(), String> {
48        match context.tenant_context {
49            Some(_) => Ok(()),
50            None => Err("Multi-tenant operation requires tenant context".to_string()),
51        }
52    }
53}
54
55/// Default implementation of TenantValidator for any type.
56impl<T> TenantValidator for T {}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    struct MockValidator;
63
64    #[test]
65    fn test_tenant_validator_success() {
66        let validator = MockValidator;
67        let tenant_context = TenantContext {
68            tenant_id: "test-tenant".to_string(),
69            client_id: "client".to_string(),
70            permissions: Default::default(),
71            isolation_level: Default::default(),
72        };
73        let context = RequestContext::with_tenant_generated_id(tenant_context);
74
75        // Should succeed with matching tenant
76        assert!(
77            validator
78                .validate_tenant_context("test-tenant", &context)
79                .is_ok()
80        );
81
82        // Should succeed with tenant context requirement
83        assert!(validator.require_tenant_context(&context).is_ok());
84    }
85
86    #[test]
87    fn test_tenant_validator_failure() {
88        let validator = MockValidator;
89        let tenant_context = TenantContext {
90            tenant_id: "test-tenant".to_string(),
91            client_id: "client".to_string(),
92            permissions: Default::default(),
93            isolation_level: Default::default(),
94        };
95        let context = RequestContext::with_tenant_generated_id(tenant_context);
96
97        // Should fail with mismatched tenant
98        let result = validator.validate_tenant_context("different-tenant", &context);
99        assert!(result.is_err());
100        assert!(result.unwrap_err().contains("Tenant mismatch"));
101    }
102
103    #[test]
104    fn test_single_tenant_validation() {
105        let validator = MockValidator;
106
107        // Single-tenant context should pass single-tenant validation
108        let context = RequestContext::with_generated_id();
109        assert!(validator.validate_single_tenant_context(&context).is_ok());
110
111        // Multi-tenant context should fail single-tenant validation
112        let tenant_context = TenantContext {
113            tenant_id: "test-tenant".to_string(),
114            client_id: "client".to_string(),
115            permissions: Default::default(),
116            isolation_level: Default::default(),
117        };
118        let multi_context = RequestContext::with_tenant_generated_id(tenant_context);
119        assert!(
120            validator
121                .validate_single_tenant_context(&multi_context)
122                .is_err()
123        );
124    }
125
126    #[test]
127    fn test_require_tenant_context() {
128        let validator = MockValidator;
129
130        // Should fail for single-tenant context
131        let single_context = RequestContext::with_generated_id();
132        assert!(validator.require_tenant_context(&single_context).is_err());
133
134        // Should succeed for multi-tenant context
135        let tenant_context = TenantContext {
136            tenant_id: "test-tenant".to_string(),
137            client_id: "client".to_string(),
138            permissions: Default::default(),
139            isolation_level: Default::default(),
140        };
141        let multi_context = RequestContext::with_tenant_generated_id(tenant_context);
142        assert!(validator.require_tenant_context(&multi_context).is_ok());
143    }
144}