scim_server/multi_tenant/
scim_config.rs

1//! SCIM-specific tenant configuration for multi-tenant SCIM operations.
2//!
3//! This module provides SCIM-focused configuration management for multi-tenant
4//! deployments. Unlike general-purpose configuration systems, this focuses
5//! exclusively on SCIM protocol requirements and multi-tenant orchestration.
6//!
7//! # Design Principles
8//!
9//! * **SCIM Protocol Focus**: Only configuration related to SCIM 2.0 specification
10//! * **Tenant Isolation**: Configuration for SCIM-level tenant separation
11//! * **Client Management**: SCIM client connection and authentication settings
12//! * **Protocol Compliance**: Settings that affect SCIM protocol behavior
13//!
14//! # Scope Boundaries
15//!
16//! ## ✅ In Scope (SCIM-Specific Configuration)
17//! - SCIM endpoint configuration per tenant
18//! - SCIM client authentication and connection settings
19//! - SCIM protocol rate limiting and throttling
20//! - SCIM schema extensions and customizations
21//! - SCIM operation audit trails
22//! - SCIM filtering and search configuration
23//!
24//! ## ❌ Out of Scope (General Application Configuration)
25//! - UI branding and theming
26//! - General performance tuning
27//! - Business logic configuration
28//! - General session management
29//! - Infrastructure encryption settings
30//! - General compliance frameworks
31//!
32//! # Example Usage
33//!
34//! ```rust
35//! use scim_server::multi_tenant::{ScimTenantConfiguration, ScimEndpointConfig};
36//! use std::time::Duration;
37//!
38//! // Create SCIM-specific tenant configuration
39//! let config = ScimTenantConfiguration::builder("tenant-a".to_string())
40//!     .with_endpoint_path("/scim/v2")
41//!     .with_scim_rate_limit(100, Duration::from_secs(60))
42//!     .with_scim_client("client-1", "api_key_123")
43//!     .enable_scim_audit_log()
44//!     .build()
45//!     .expect("Valid SCIM configuration");
46//! ```
47
48use chrono::{DateTime, Utc};
49use serde::{Deserialize, Serialize};
50use serde_json::Value;
51use std::collections::HashMap;
52use std::time::Duration;
53use thiserror::Error;
54
55/// Errors specific to SCIM configuration management.
56#[derive(Debug, Error)]
57pub enum ScimConfigurationError {
58    /// SCIM configuration validation failed
59    #[error("SCIM configuration validation failed: {message}")]
60    ValidationError { message: String },
61    /// SCIM configuration not found for tenant
62    #[error("SCIM configuration not found for tenant: {tenant_id}")]
63    NotFound { tenant_id: String },
64    /// SCIM client configuration conflict
65    #[error("SCIM client configuration conflict: {message}")]
66    ClientConflict { message: String },
67    /// Invalid SCIM endpoint configuration
68    #[error("Invalid SCIM endpoint configuration: {message}")]
69    InvalidEndpoint { message: String },
70    /// SCIM schema extension error
71    #[error("SCIM schema extension error: {message}")]
72    SchemaExtensionError { message: String },
73}
74
75/// Complete SCIM-specific configuration for a tenant.
76///
77/// This configuration encompasses all SCIM protocol-related settings
78/// for a tenant, including endpoint configuration, client connections,
79/// schema customizations, and protocol-specific operational settings.
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
81pub struct ScimTenantConfiguration {
82    /// Unique identifier for the tenant
83    pub tenant_id: String,
84    /// When this configuration was created
85    pub created_at: DateTime<Utc>,
86    /// When this configuration was last modified
87    pub last_modified: DateTime<Utc>,
88    /// Configuration version for optimistic locking
89    pub version: u64,
90    /// SCIM endpoint configuration
91    pub endpoint: ScimEndpointConfig,
92    /// SCIM client connection configurations
93    pub clients: Vec<ScimClientConfig>,
94    /// SCIM protocol-specific rate limiting
95    pub rate_limits: ScimRateLimits,
96    /// SCIM schema extensions and customizations
97    pub schema_config: ScimSchemaConfig,
98    /// SCIM operation audit settings
99    pub audit_config: ScimAuditConfig,
100    /// SCIM filtering and search configuration
101    pub search_config: ScimSearchConfig,
102}
103
104impl ScimTenantConfiguration {
105    /// Create a new builder for SCIM tenant configuration.
106    pub fn builder(tenant_id: String) -> ScimTenantConfigurationBuilder {
107        ScimTenantConfigurationBuilder::new(tenant_id)
108    }
109
110    /// Get SCIM client configuration by client ID.
111    pub fn get_client_config(&self, client_id: &str) -> Option<&ScimClientConfig> {
112        self.clients.iter().find(|c| c.client_id == client_id)
113    }
114
115    /// Check if a SCIM operation is rate limited for this tenant.
116    pub fn is_rate_limited(&self, operation: &str, current_count: u32) -> bool {
117        match operation {
118            "create" => self.rate_limits.check_create_limit(current_count),
119            "read" => self.rate_limits.check_read_limit(current_count),
120            "update" => self.rate_limits.check_update_limit(current_count),
121            "delete" => self.rate_limits.check_delete_limit(current_count),
122            "list" => self.rate_limits.check_list_limit(current_count),
123            "search" => self.rate_limits.check_search_limit(current_count),
124            _ => false,
125        }
126    }
127
128    /// Check if a SCIM schema extension is enabled for this tenant.
129    pub fn has_schema_extension(&self, extension_uri: &str) -> bool {
130        self.schema_config
131            .extensions
132            .iter()
133            .any(|ext| ext.uri == extension_uri && ext.enabled)
134    }
135}
136
137/// SCIM endpoint configuration for a tenant.
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
139pub struct ScimEndpointConfig {
140    /// Base path for SCIM endpoints (e.g., "/scim/v2")
141    pub base_path: String,
142    /// Whether to include tenant ID in the path
143    pub include_tenant_in_path: bool,
144    /// Custom path pattern if include_tenant_in_path is true
145    pub tenant_path_pattern: Option<String>,
146    /// Maximum request payload size for SCIM operations
147    pub max_payload_size: usize,
148    /// SCIM protocol version (typically "2.0")
149    pub scim_version: String,
150    /// Supported SCIM authentication schemes
151    pub supported_auth_schemes: Vec<ScimAuthScheme>,
152}
153
154impl Default for ScimEndpointConfig {
155    fn default() -> Self {
156        Self {
157            base_path: "/scim/v2".to_string(),
158            include_tenant_in_path: false,
159            tenant_path_pattern: None,
160            max_payload_size: 1024 * 1024, // 1MB
161            scim_version: "2.0".to_string(),
162            supported_auth_schemes: vec![ScimAuthScheme::Bearer, ScimAuthScheme::ApiKey],
163        }
164    }
165}
166
167/// SCIM authentication schemes supported by the endpoint.
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
169pub enum ScimAuthScheme {
170    /// Bearer token authentication
171    Bearer,
172    /// API key authentication
173    ApiKey,
174    /// HTTP Basic authentication
175    Basic,
176    /// OAuth 2.0 authentication
177    OAuth2,
178    /// Custom authentication scheme
179    Custom(String),
180}
181
182/// SCIM client connection configuration.
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub struct ScimClientConfig {
185    /// Unique identifier for this SCIM client
186    pub client_id: String,
187    /// Human-readable name for the client
188    pub client_name: String,
189    /// Authentication credentials for this client
190    pub auth_config: ScimClientAuth,
191    /// Client-specific rate limits (overrides tenant defaults)
192    pub rate_limits: Option<ScimRateLimits>,
193    /// SCIM operations this client is allowed to perform
194    pub allowed_operations: Vec<ScimOperation>,
195    /// Resource types this client can access
196    pub allowed_resource_types: Vec<String>,
197    /// Whether audit logging is enabled for this client
198    pub audit_enabled: bool,
199    /// Client-specific configuration metadata
200    pub metadata: HashMap<String, Value>,
201}
202
203/// SCIM client authentication configuration.
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
205pub struct ScimClientAuth {
206    /// Authentication scheme for this client
207    pub scheme: ScimAuthScheme,
208    /// Authentication credentials (hashed/encrypted)
209    pub credentials: HashMap<String, String>,
210    /// Token expiration settings
211    pub token_expiration: Option<Duration>,
212    /// Whether to validate client IP restrictions
213    pub ip_restrictions: Vec<String>,
214}
215
216/// SCIM operations that can be performed.
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218pub enum ScimOperation {
219    /// Create new resources
220    Create,
221    /// Read existing resources
222    Read,
223    /// Update existing resources
224    Update,
225    /// Delete resources
226    Delete,
227    /// List resources with pagination
228    List,
229    /// Search resources with filtering
230    Search,
231    /// Bulk operations
232    Bulk,
233    /// Schema discovery
234    Schema,
235}
236
237/// SCIM protocol-specific rate limiting configuration.
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
239pub struct ScimRateLimits {
240    /// Rate limit for SCIM create operations
241    pub create_operations: Option<RateLimit>,
242    /// Rate limit for SCIM read operations
243    pub read_operations: Option<RateLimit>,
244    /// Rate limit for SCIM update operations
245    pub update_operations: Option<RateLimit>,
246    /// Rate limit for SCIM delete operations
247    pub delete_operations: Option<RateLimit>,
248    /// Rate limit for SCIM list operations
249    pub list_operations: Option<RateLimit>,
250    /// Rate limit for SCIM search operations
251    pub search_operations: Option<RateLimit>,
252    /// Rate limit for SCIM bulk operations
253    pub bulk_operations: Option<RateLimit>,
254    /// Global rate limit across all SCIM operations
255    pub global_limit: Option<RateLimit>,
256}
257
258impl ScimRateLimits {
259    pub fn check_create_limit(&self, current_count: u32) -> bool {
260        self.create_operations
261            .as_ref()
262            .map_or(false, |limit| current_count >= limit.max_requests)
263    }
264
265    pub fn check_read_limit(&self, current_count: u32) -> bool {
266        self.read_operations
267            .as_ref()
268            .map_or(false, |limit| current_count >= limit.max_requests)
269    }
270
271    pub fn check_update_limit(&self, current_count: u32) -> bool {
272        self.update_operations
273            .as_ref()
274            .map_or(false, |limit| current_count >= limit.max_requests)
275    }
276
277    pub fn check_delete_limit(&self, current_count: u32) -> bool {
278        self.delete_operations
279            .as_ref()
280            .map_or(false, |limit| current_count >= limit.max_requests)
281    }
282
283    pub fn check_list_limit(&self, current_count: u32) -> bool {
284        self.list_operations
285            .as_ref()
286            .map_or(false, |limit| current_count >= limit.max_requests)
287    }
288
289    pub fn check_search_limit(&self, current_count: u32) -> bool {
290        self.search_operations
291            .as_ref()
292            .map_or(false, |limit| current_count >= limit.max_requests)
293    }
294}
295
296impl Default for ScimRateLimits {
297    fn default() -> Self {
298        Self {
299            create_operations: Some(RateLimit::new(100, Duration::from_secs(60))),
300            read_operations: Some(RateLimit::new(1000, Duration::from_secs(60))),
301            update_operations: Some(RateLimit::new(100, Duration::from_secs(60))),
302            delete_operations: Some(RateLimit::new(50, Duration::from_secs(60))),
303            list_operations: Some(RateLimit::new(200, Duration::from_secs(60))),
304            search_operations: Some(RateLimit::new(100, Duration::from_secs(60))),
305            bulk_operations: Some(RateLimit::new(10, Duration::from_secs(60))),
306            global_limit: Some(RateLimit::new(2000, Duration::from_secs(60))),
307        }
308    }
309}
310
311/// Rate limiting configuration for specific operations.
312#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
313pub struct RateLimit {
314    /// Maximum number of requests allowed
315    pub max_requests: u32,
316    /// Time window for the rate limit
317    #[serde(with = "duration_serde")]
318    pub window: Duration,
319    /// Burst allowance for short-term spikes
320    pub burst_allowance: Option<u32>,
321}
322
323impl RateLimit {
324    pub fn new(max_requests: u32, window: Duration) -> Self {
325        Self {
326            max_requests,
327            window,
328            burst_allowance: None,
329        }
330    }
331
332    pub fn with_burst(mut self, burst: u32) -> Self {
333        self.burst_allowance = Some(burst);
334        self
335    }
336}
337
338/// SCIM schema customization configuration.
339#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
340pub struct ScimSchemaConfig {
341    /// SCIM schema extensions enabled for this tenant
342    pub extensions: Vec<ScimSchemaExtension>,
343    /// Custom attributes added to standard SCIM schemas
344    pub custom_attributes: HashMap<String, ScimCustomAttribute>,
345    /// Standard SCIM attributes disabled for this tenant
346    pub disabled_attributes: Vec<String>,
347    /// Additional required attributes for this tenant
348    pub additional_required: Vec<String>,
349}
350
351impl Default for ScimSchemaConfig {
352    fn default() -> Self {
353        Self {
354            extensions: Vec::new(),
355            custom_attributes: HashMap::new(),
356            disabled_attributes: Vec::new(),
357            additional_required: Vec::new(),
358        }
359    }
360}
361
362/// SCIM schema extension configuration.
363#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
364pub struct ScimSchemaExtension {
365    /// SCIM extension URI (e.g., "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User")
366    pub uri: String,
367    /// Whether this extension is enabled for the tenant
368    pub enabled: bool,
369    /// Whether this extension is required for resources
370    pub required: bool,
371    /// Custom attributes defined in this extension
372    pub attributes: HashMap<String, ScimCustomAttribute>,
373}
374
375/// Custom SCIM attribute definition.
376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
377pub struct ScimCustomAttribute {
378    /// Attribute name
379    pub name: String,
380    /// SCIM attribute type (string, boolean, decimal, integer, dateTime, reference, complex)
381    pub attribute_type: String,
382    /// Whether the attribute supports multiple values
383    pub multi_valued: bool,
384    /// Whether the attribute is required
385    pub required: bool,
386    /// Whether the attribute is case-sensitive
387    pub case_exact: bool,
388    /// Mutability of the attribute (readOnly, readWrite, immutable, writeOnly)
389    pub mutability: String,
390    /// When the attribute is returned (always, never, default, request)
391    pub returned: String,
392    /// Uniqueness constraint (none, server, global)
393    pub uniqueness: String,
394    /// Description of the attribute
395    pub description: Option<String>,
396    /// Canonical values for the attribute
397    pub canonical_values: Option<Vec<String>>,
398}
399
400/// SCIM operation audit configuration.
401#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
402pub struct ScimAuditConfig {
403    /// Whether SCIM operation auditing is enabled
404    pub enabled: bool,
405    /// SCIM operations to audit
406    pub audited_operations: Vec<ScimOperation>,
407    /// Whether to include request/response payloads in audit logs
408    pub include_payloads: bool,
409    /// Whether to include sensitive attributes in audit logs
410    pub include_sensitive_data: bool,
411    /// How long to retain SCIM audit logs
412    #[serde(with = "duration_serde")]
413    pub retention_period: Duration,
414    /// Additional metadata to include in audit logs
415    pub additional_metadata: HashMap<String, String>,
416}
417
418impl Default for ScimAuditConfig {
419    fn default() -> Self {
420        Self {
421            enabled: true,
422            audited_operations: vec![
423                ScimOperation::Create,
424                ScimOperation::Update,
425                ScimOperation::Delete,
426            ],
427            include_payloads: false,
428            include_sensitive_data: false,
429            retention_period: Duration::from_secs(90 * 24 * 60 * 60), // 90 days
430            additional_metadata: HashMap::new(),
431        }
432    }
433}
434
435/// SCIM search and filtering configuration.
436#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
437pub struct ScimSearchConfig {
438    /// Maximum number of resources returned in a single search
439    pub max_results: u32,
440    /// Default number of resources returned if not specified
441    pub default_count: u32,
442    /// Maximum allowed depth for complex filter expressions
443    pub max_filter_depth: u32,
444    /// Attributes that support filtering
445    pub filterable_attributes: Vec<String>,
446    /// Attributes that support sorting
447    pub sortable_attributes: Vec<String>,
448    /// Whether case-insensitive filtering is supported
449    pub case_insensitive_filtering: bool,
450    /// Custom search operators supported
451    pub custom_operators: Vec<String>,
452}
453
454impl Default for ScimSearchConfig {
455    fn default() -> Self {
456        Self {
457            max_results: 200,
458            default_count: 20,
459            max_filter_depth: 10,
460            filterable_attributes: vec![
461                "userName".to_string(),
462                "displayName".to_string(),
463                "emails.value".to_string(),
464                "active".to_string(),
465                "meta.created".to_string(),
466                "meta.lastModified".to_string(),
467            ],
468            sortable_attributes: vec![
469                "userName".to_string(),
470                "displayName".to_string(),
471                "meta.created".to_string(),
472                "meta.lastModified".to_string(),
473            ],
474            case_insensitive_filtering: true,
475            custom_operators: Vec::new(),
476        }
477    }
478}
479
480/// Builder for creating SCIM tenant configurations.
481pub struct ScimTenantConfigurationBuilder {
482    tenant_id: String,
483    endpoint: Option<ScimEndpointConfig>,
484    clients: Vec<ScimClientConfig>,
485    rate_limits: Option<ScimRateLimits>,
486    schema_config: Option<ScimSchemaConfig>,
487    audit_config: Option<ScimAuditConfig>,
488    search_config: Option<ScimSearchConfig>,
489}
490
491impl ScimTenantConfigurationBuilder {
492    pub fn new(tenant_id: String) -> Self {
493        Self {
494            tenant_id,
495            endpoint: None,
496            clients: Vec::new(),
497            rate_limits: None,
498            schema_config: None,
499            audit_config: None,
500            search_config: None,
501        }
502    }
503
504    pub fn with_endpoint_path(mut self, path: &str) -> Self {
505        let mut endpoint = self.endpoint.unwrap_or_default();
506        endpoint.base_path = path.to_string();
507        self.endpoint = Some(endpoint);
508        self
509    }
510
511    pub fn with_scim_rate_limit(mut self, max_requests: u32, window: Duration) -> Self {
512        let rate_limit = RateLimit::new(max_requests, window);
513        let mut rate_limits = self.rate_limits.unwrap_or_default();
514
515        // Set the global limit and all operation-specific limits to the same value
516        rate_limits.global_limit = Some(rate_limit.clone());
517        rate_limits.create_operations = Some(rate_limit.clone());
518        rate_limits.read_operations = Some(rate_limit.clone());
519        rate_limits.update_operations = Some(rate_limit.clone());
520        rate_limits.delete_operations = Some(rate_limit.clone());
521        rate_limits.list_operations = Some(rate_limit.clone());
522        rate_limits.search_operations = Some(rate_limit.clone());
523        rate_limits.bulk_operations = Some(rate_limit);
524
525        self.rate_limits = Some(rate_limits);
526        self
527    }
528
529    pub fn with_scim_client(mut self, client_id: &str, api_key: &str) -> Self {
530        let mut credentials = HashMap::new();
531        credentials.insert("api_key".to_string(), api_key.to_string());
532
533        let client = ScimClientConfig {
534            client_id: client_id.to_string(),
535            client_name: client_id.to_string(),
536            auth_config: ScimClientAuth {
537                scheme: ScimAuthScheme::ApiKey,
538                credentials,
539                token_expiration: None,
540                ip_restrictions: Vec::new(),
541            },
542            rate_limits: None,
543            allowed_operations: vec![
544                ScimOperation::Create,
545                ScimOperation::Read,
546                ScimOperation::Update,
547                ScimOperation::Delete,
548                ScimOperation::List,
549                ScimOperation::Search,
550            ],
551            allowed_resource_types: vec!["User".to_string(), "Group".to_string()],
552            audit_enabled: true,
553            metadata: HashMap::new(),
554        };
555
556        self.clients.push(client);
557        self
558    }
559
560    pub fn enable_scim_audit_log(mut self) -> Self {
561        let mut audit_config = self.audit_config.unwrap_or_default();
562        audit_config.enabled = true;
563        self.audit_config = Some(audit_config);
564        self
565    }
566
567    pub fn with_schema_extension(mut self, uri: &str, required: bool) -> Self {
568        let mut schema_config = self.schema_config.unwrap_or_default();
569        schema_config.extensions.push(ScimSchemaExtension {
570            uri: uri.to_string(),
571            enabled: true,
572            required,
573            attributes: HashMap::new(),
574        });
575        self.schema_config = Some(schema_config);
576        self
577    }
578
579    pub fn build(self) -> Result<ScimTenantConfiguration, ScimConfigurationError> {
580        let now = Utc::now();
581
582        Ok(ScimTenantConfiguration {
583            tenant_id: self.tenant_id,
584            created_at: now,
585            last_modified: now,
586            version: 1,
587            endpoint: self.endpoint.unwrap_or_default(),
588            clients: self.clients,
589            rate_limits: self.rate_limits.unwrap_or_default(),
590            schema_config: self.schema_config.unwrap_or_default(),
591            audit_config: self.audit_config.unwrap_or_default(),
592            search_config: self.search_config.unwrap_or_default(),
593        })
594    }
595}
596
597// Custom serialization for Duration fields
598mod duration_serde {
599    use serde::{Deserialize, Deserializer, Serializer};
600    use std::time::Duration;
601
602    pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
603    where
604        S: Serializer,
605    {
606        serializer.serialize_u64(duration.as_secs())
607    }
608
609    pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
610    where
611        D: Deserializer<'de>,
612    {
613        let secs = u64::deserialize(deserializer)?;
614        Ok(Duration::from_secs(secs))
615    }
616}
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    #[test]
623    fn test_scim_tenant_configuration_builder() {
624        let config = ScimTenantConfiguration::builder("test-tenant".to_string())
625            .with_endpoint_path("/scim/v2")
626            .with_scim_rate_limit(100, Duration::from_secs(60))
627            .with_scim_client("client-1", "api_key_123")
628            .enable_scim_audit_log()
629            .with_schema_extension(
630                "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
631                false,
632            )
633            .build()
634            .expect("Valid SCIM configuration");
635
636        assert_eq!(config.tenant_id, "test-tenant");
637        assert_eq!(config.endpoint.base_path, "/scim/v2");
638        assert_eq!(config.clients.len(), 1);
639        assert_eq!(config.clients[0].client_id, "client-1");
640        assert!(config.audit_config.enabled);
641        assert_eq!(config.schema_config.extensions.len(), 1);
642    }
643
644    #[test]
645    fn test_rate_limit_checking() {
646        let rate_limits = ScimRateLimits::default();
647
648        // Test with default create limit (100 per minute)
649        assert!(!rate_limits.check_create_limit(50));
650        assert!(rate_limits.check_create_limit(100));
651        assert!(rate_limits.check_create_limit(150));
652    }
653
654    #[test]
655    fn test_client_config_lookup() {
656        let config = ScimTenantConfiguration::builder("test-tenant".to_string())
657            .with_scim_client("client-1", "api_key_123")
658            .with_scim_client("client-2", "api_key_456")
659            .build()
660            .expect("Valid configuration");
661
662        assert!(config.get_client_config("client-1").is_some());
663        assert!(config.get_client_config("client-2").is_some());
664        assert!(config.get_client_config("client-3").is_none());
665    }
666
667    #[test]
668    fn test_schema_extension_checking() {
669        let config = ScimTenantConfiguration::builder("test-tenant".to_string())
670            .with_schema_extension(
671                "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User",
672                true,
673            )
674            .build()
675            .expect("Valid configuration");
676
677        assert!(
678            config
679                .has_schema_extension("urn:ietf:params:scim:schemas:extension:enterprise:2.0:User")
680        );
681        assert!(!config.has_schema_extension("urn:example:custom:extension"));
682    }
683
684    #[test]
685    fn test_default_configurations() {
686        let endpoint = ScimEndpointConfig::default();
687        assert_eq!(endpoint.base_path, "/scim/v2");
688        assert_eq!(endpoint.scim_version, "2.0");
689
690        let rate_limits = ScimRateLimits::default();
691        assert!(rate_limits.create_operations.is_some());
692        assert!(rate_limits.global_limit.is_some());
693
694        let audit_config = ScimAuditConfig::default();
695        assert!(audit_config.enabled);
696        assert_eq!(audit_config.audited_operations.len(), 3);
697    }
698}