scim_server/multi_tenant/
mod.rs

1//! Multi-tenant SCIM server capabilities.
2//!
3//! This module provides the core infrastructure for multi-tenant SCIM operations,
4//! including tenant resolution, multi-tenant resource providers, and tenant-aware
5//! error handling.
6//!
7//! # Architecture
8//!
9//! The multi-tenant system is built around several key concepts:
10//!
11//! * **Tenant Resolution**: Mapping authentication credentials to tenant contexts
12//! * **Multi-Tenant Providers**: Resource providers that understand tenant isolation
13//! * **Enhanced Context**: Request contexts that carry tenant information
14//! * **Isolation Levels**: Different levels of tenant data separation
15//!
16//! # Example Usage
17//!
18//! ```rust,no_run
19//! use scim_server::multi_tenant::{
20//!     TenantValidator, TenantResolver, StaticTenantResolver
21//! };
22//! use scim_server::{TenantContext, RequestContext, Resource, ResourceProvider};
23//! use serde_json::json;
24//!
25//! #[tokio::main]
26//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
27//!     // Set up tenant resolver
28//!     let resolver = StaticTenantResolver::new();
29//!     resolver.add_tenant("api-key-123", TenantContext::new(
30//!         "tenant-a".to_string(),
31//!         "client-a".to_string()
32//!     )).await;
33//!
34//!     // Use in multi-tenant operations
35//!     let tenant_context = resolver.resolve_tenant("api-key-123").await?;
36//!     let context = RequestContext::with_tenant_generated_id(tenant_context);
37//!
38//!     let user_data = json!({
39//!         "userName": "john.doe",
40//!         "displayName": "John Doe"
41//!     });
42//!
43//!     Ok(())
44//! }
45//! ```
46
47pub mod adapter;
48
49pub mod provider;
50pub mod resolver;
51pub mod scim_config;
52
53// Re-export key types for convenience
54pub use adapter::{SingleTenantAdapter, ToSingleTenant};
55
56// SCIM-focused configuration (recommended)
57pub use scim_config::{
58    RateLimit, ScimAuditConfig, ScimAuthScheme, ScimClientAuth, ScimClientConfig,
59    ScimConfigurationError, ScimCustomAttribute, ScimEndpointConfig, ScimOperation, ScimRateLimits,
60    ScimSchemaConfig, ScimSchemaExtension, ScimSearchConfig, ScimTenantConfiguration,
61};
62
63pub use provider::TenantValidator;
64pub use resolver::{StaticTenantResolver, StaticTenantResolverBuilder, TenantResolver};
65
66// Re-export core types from resource module
67pub use crate::resource::{IsolationLevel, TenantContext, TenantPermissions};
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::resource::RequestContext;
73
74    #[test]
75    fn test_module_exports() {
76        // Test that all expected types are accessible
77        let _resolver = StaticTenantResolver::new();
78
79        // Test core type re-exports
80        let tenant_context = TenantContext::new("test".to_string(), "client".to_string());
81        let _context = RequestContext::with_tenant_generated_id(tenant_context);
82    }
83
84    #[test]
85    fn test_isolation_levels() {
86        assert_eq!(IsolationLevel::default(), IsolationLevel::Standard);
87
88        let strict = IsolationLevel::Strict;
89        let standard = IsolationLevel::Standard;
90        let shared = IsolationLevel::Shared;
91
92        assert_ne!(strict, standard);
93        assert_ne!(standard, shared);
94        assert_ne!(strict, shared);
95    }
96
97    #[test]
98    fn test_tenant_permissions() {
99        let default_perms = TenantPermissions::default();
100        assert!(default_perms.can_create);
101        assert!(default_perms.can_read);
102        assert!(default_perms.can_update);
103        assert!(default_perms.can_delete);
104        assert!(default_perms.can_list);
105        assert!(default_perms.max_users.is_none());
106        assert!(default_perms.max_groups.is_none());
107    }
108
109    #[test]
110    fn test_tenant_context_operations() {
111        let context = TenantContext::new("test-tenant".to_string(), "test-client".to_string())
112            .with_isolation_level(IsolationLevel::Strict);
113
114        assert_eq!(context.tenant_id, "test-tenant");
115        assert_eq!(context.client_id, "test-client");
116        assert_eq!(context.isolation_level, IsolationLevel::Strict);
117
118        assert!(context.can_perform_operation("create"));
119        assert!(context.can_perform_operation("read"));
120        assert!(!context.can_perform_operation("invalid"));
121
122        assert!(context.check_user_limit(100));
123        assert!(context.check_group_limit(50));
124    }
125
126    #[test]
127    fn test_tenant_context_with_limits() {
128        let mut permissions = TenantPermissions::default();
129        permissions.max_users = Some(10);
130        permissions.max_groups = Some(5);
131
132        let context = TenantContext::new("test".to_string(), "client".to_string())
133            .with_permissions(permissions);
134
135        assert!(context.check_user_limit(5));
136        assert!(!context.check_user_limit(10));
137        assert!(context.check_group_limit(3));
138        assert!(!context.check_group_limit(5));
139    }
140
141    #[test]
142    fn test_request_context_usage() {
143        let tenant_context = TenantContext::new("test".to_string(), "client".to_string());
144        let context = RequestContext::with_tenant_generated_id(tenant_context.clone());
145
146        assert_eq!(context.tenant_id(), Some("test"));
147        assert_eq!(context.client_id(), Some("client"));
148        assert!(context.is_multi_tenant());
149        assert!(context.can_perform_operation("read"));
150        assert!(context.validate_operation("create").is_ok());
151    }
152
153    #[test]
154    fn test_single_tenant_context() {
155        let context = RequestContext::new("req-123".to_string());
156        assert_eq!(context.request_id, "req-123");
157        assert!(!context.is_multi_tenant());
158        assert_eq!(context.tenant_id(), None);
159    }
160}