Skip to main content

fraiseql_core/security/
profiles.rs

1//! Security Profiles - v1.9.6 enforcement levels
2//!
3//! This module implements the v1.9.6 security profile system:
4//! - **STANDARD**: Basic security (rate limiting, audit logging)
5//! - **REGULATED**: Full compliance (HIPAA/SOC2 level with field masking, error redaction)
6//!
7//! ## Profile Levels
8//!
9//! ### STANDARD Profile
10//! - Rate limiting enabled
11//! - Audit logging of queries
12//! - Basic error messages visible
13//! - No field masking
14//! - Large responses allowed
15//!
16//! ### REGULATED Profile
17//! - All STANDARD features +
18//! - Detailed field-level audit logging
19//! - Sensitive field masking (PII, secrets)
20//! - Error detail reduction (no internal details)
21//! - Query logging for compliance audit trails
22//! - Response size limits (prevent data exfiltration)
23//! - Strict field filtering (only requested fields)
24//!
25//! ## Usage
26//!
27//! ```no_run
28//! use fraiseql_core::security::profiles::SecurityProfile;
29//!
30//! // Create a profile
31//! let profile = SecurityProfile::standard();
32//! assert!(profile.is_standard());
33//!
34//! let regulated = SecurityProfile::regulated();
35//! assert!(regulated.is_regulated());
36//! ```
37
38use std::fmt;
39
40use serde::{Deserialize, Serialize};
41
42/// Security profile configuration
43#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
44#[non_exhaustive]
45pub enum SecurityProfile {
46    /// STANDARD: Basic security (rate limit + audit)
47    #[default]
48    Standard,
49
50    /// REGULATED: Full security with compliance features (HIPAA/SOC2)
51    Regulated,
52}
53
54impl SecurityProfile {
55    /// Create STANDARD profile
56    #[must_use]
57    pub const fn standard() -> Self {
58        Self::Standard
59    }
60
61    /// Create REGULATED profile
62    #[must_use]
63    pub const fn regulated() -> Self {
64        Self::Regulated
65    }
66
67    /// Check if this is STANDARD profile
68    #[must_use]
69    pub const fn is_standard(&self) -> bool {
70        matches!(self, Self::Standard)
71    }
72
73    /// Check if this is REGULATED profile
74    #[must_use]
75    pub const fn is_regulated(&self) -> bool {
76        matches!(self, Self::Regulated)
77    }
78
79    /// Get profile name
80    #[must_use]
81    pub const fn name(&self) -> &'static str {
82        match self {
83            Self::Standard => "STANDARD",
84            Self::Regulated => "REGULATED",
85        }
86    }
87
88    /// Check if rate limiting is enabled for this profile
89    #[must_use]
90    pub const fn rate_limit_enabled(&self) -> bool {
91        true
92    }
93
94    /// Check if audit logging is enabled for this profile
95    #[must_use]
96    pub const fn audit_logging_enabled(&self) -> bool {
97        true
98    }
99
100    /// Check if field-level audit is enabled (REGULATED only)
101    #[must_use]
102    pub const fn audit_field_access(&self) -> bool {
103        matches!(self, Self::Regulated)
104    }
105
106    /// Check if sensitive field masking is enabled (REGULATED only)
107    #[must_use]
108    pub const fn sensitive_field_masking(&self) -> bool {
109        matches!(self, Self::Regulated)
110    }
111
112    /// Check if error detail reduction is enabled (REGULATED only)
113    #[must_use]
114    pub const fn error_detail_reduction(&self) -> bool {
115        matches!(self, Self::Regulated)
116    }
117
118    /// Check if query logging for compliance is enabled (REGULATED only)
119    #[must_use]
120    pub const fn query_logging_for_compliance(&self) -> bool {
121        matches!(self, Self::Regulated)
122    }
123
124    /// Check if response size limits are enforced (REGULATED only)
125    #[must_use]
126    pub const fn response_size_limits(&self) -> bool {
127        matches!(self, Self::Regulated)
128    }
129
130    /// Check if strict field filtering is enabled (REGULATED only)
131    #[must_use]
132    pub const fn field_filtering_strict(&self) -> bool {
133        matches!(self, Self::Regulated)
134    }
135
136    /// Get maximum response size for this profile (bytes)
137    #[must_use]
138    pub const fn max_response_size_bytes(&self) -> usize {
139        match self {
140            Self::Standard => usize::MAX, // No limit
141            Self::Regulated => 1_000_000, // 1MB for REGULATED
142        }
143    }
144
145    /// Get maximum query complexity for this profile
146    #[must_use]
147    pub const fn max_query_complexity(&self) -> usize {
148        match self {
149            Self::Standard => 100_000,
150            Self::Regulated => 50_000, // Stricter for REGULATED
151        }
152    }
153
154    /// Get maximum query depth for this profile
155    #[must_use]
156    pub const fn max_query_depth(&self) -> usize {
157        match self {
158            Self::Standard => 20,
159            Self::Regulated => 10, // Stricter for REGULATED
160        }
161    }
162
163    /// Get rate limit - requests per second per user
164    #[must_use]
165    pub const fn rate_limit_rps(&self) -> u32 {
166        match self {
167            Self::Standard => 100,
168            Self::Regulated => 10, // Stricter for REGULATED
169        }
170    }
171
172    /// Get enforcement level description
173    #[must_use]
174    pub const fn description(&self) -> &'static str {
175        match self {
176            Self::Standard => "Basic security with rate limiting and audit logging",
177            Self::Regulated => {
178                "Full compliance with field masking, error redaction, and strict limits"
179            },
180        }
181    }
182
183    /// Get all enforced features for this profile
184    #[must_use]
185    pub fn enforced_features(&self) -> Vec<&'static str> {
186        let mut features = vec!["Rate Limiting", "Audit Logging"];
187
188        if self.is_regulated() {
189            features.extend(vec![
190                "Field-Level Audit",
191                "Sensitive Field Masking",
192                "Error Detail Reduction",
193                "Query Logging for Compliance",
194                "Response Size Limits",
195                "Strict Field Filtering",
196            ]);
197        }
198
199        features
200    }
201}
202
203impl fmt::Display for SecurityProfile {
204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205        write!(f, "{}", self.name())
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    // ========================================================================
214    // Test Suite 1: Profile Creation and Basic Properties
215    // ========================================================================
216
217    #[test]
218    fn test_create_standard_profile() {
219        let profile = SecurityProfile::standard();
220        assert!(profile.is_standard());
221        assert!(!profile.is_regulated());
222        assert_eq!(profile.name(), "STANDARD");
223    }
224
225    #[test]
226    fn test_create_regulated_profile() {
227        let profile = SecurityProfile::regulated();
228        assert!(!profile.is_standard());
229        assert!(profile.is_regulated());
230        assert_eq!(profile.name(), "REGULATED");
231    }
232
233    #[test]
234    fn test_default_profile_is_standard() {
235        let profile = SecurityProfile::default();
236        assert!(profile.is_standard());
237    }
238
239    #[test]
240    fn test_profile_display() {
241        assert_eq!(SecurityProfile::Standard.to_string(), "STANDARD");
242        assert_eq!(SecurityProfile::Regulated.to_string(), "REGULATED");
243    }
244
245    // ========================================================================
246    // Test Suite 2: Common Features (Both Profiles)
247    // ========================================================================
248
249    #[test]
250    fn test_standard_has_rate_limiting() {
251        let profile = SecurityProfile::standard();
252        assert!(profile.rate_limit_enabled());
253    }
254
255    #[test]
256    fn test_regulated_has_rate_limiting() {
257        let profile = SecurityProfile::regulated();
258        assert!(profile.rate_limit_enabled());
259    }
260
261    #[test]
262    fn test_standard_has_audit_logging() {
263        let profile = SecurityProfile::standard();
264        assert!(profile.audit_logging_enabled());
265    }
266
267    #[test]
268    fn test_regulated_has_audit_logging() {
269        let profile = SecurityProfile::regulated();
270        assert!(profile.audit_logging_enabled());
271    }
272
273    // ========================================================================
274    // Test Suite 3: STANDARD Profile Features
275    // ========================================================================
276
277    #[test]
278    fn test_standard_no_field_audit() {
279        let profile = SecurityProfile::standard();
280        assert!(!profile.audit_field_access());
281    }
282
283    #[test]
284    fn test_standard_no_field_masking() {
285        let profile = SecurityProfile::standard();
286        assert!(!profile.sensitive_field_masking());
287    }
288
289    #[test]
290    fn test_standard_no_error_redaction() {
291        let profile = SecurityProfile::standard();
292        assert!(!profile.error_detail_reduction());
293    }
294
295    #[test]
296    fn test_standard_no_query_logging_compliance() {
297        let profile = SecurityProfile::standard();
298        assert!(!profile.query_logging_for_compliance());
299    }
300
301    #[test]
302    fn test_standard_no_response_limits() {
303        let profile = SecurityProfile::standard();
304        assert!(!profile.response_size_limits());
305    }
306
307    #[test]
308    fn test_standard_no_strict_filtering() {
309        let profile = SecurityProfile::standard();
310        assert!(!profile.field_filtering_strict());
311    }
312
313    #[test]
314    fn test_standard_unlimited_response_size() {
315        let profile = SecurityProfile::standard();
316        assert_eq!(profile.max_response_size_bytes(), usize::MAX);
317    }
318
319    #[test]
320    fn test_standard_rate_limit_rps() {
321        let profile = SecurityProfile::standard();
322        assert_eq!(profile.rate_limit_rps(), 100);
323    }
324
325    // ========================================================================
326    // Test Suite 4: REGULATED Profile Features
327    // ========================================================================
328
329    #[test]
330    fn test_regulated_has_field_audit() {
331        let profile = SecurityProfile::regulated();
332        assert!(profile.audit_field_access());
333    }
334
335    #[test]
336    fn test_regulated_has_field_masking() {
337        let profile = SecurityProfile::regulated();
338        assert!(profile.sensitive_field_masking());
339    }
340
341    #[test]
342    fn test_regulated_has_error_redaction() {
343        let profile = SecurityProfile::regulated();
344        assert!(profile.error_detail_reduction());
345    }
346
347    #[test]
348    fn test_regulated_has_query_logging_compliance() {
349        let profile = SecurityProfile::regulated();
350        assert!(profile.query_logging_for_compliance());
351    }
352
353    #[test]
354    fn test_regulated_has_response_limits() {
355        let profile = SecurityProfile::regulated();
356        assert!(profile.response_size_limits());
357    }
358
359    #[test]
360    fn test_regulated_has_strict_filtering() {
361        let profile = SecurityProfile::regulated();
362        assert!(profile.field_filtering_strict());
363    }
364
365    #[test]
366    fn test_regulated_response_size_limit() {
367        let profile = SecurityProfile::regulated();
368        assert_eq!(profile.max_response_size_bytes(), 1_000_000);
369    }
370
371    #[test]
372    fn test_regulated_rate_limit_stricter() {
373        let standard = SecurityProfile::standard();
374        let regulated = SecurityProfile::regulated();
375        assert!(regulated.rate_limit_rps() < standard.rate_limit_rps());
376    }
377
378    #[test]
379    fn test_regulated_query_complexity_stricter() {
380        let standard = SecurityProfile::standard();
381        let regulated = SecurityProfile::regulated();
382        assert!(regulated.max_query_complexity() < standard.max_query_complexity());
383    }
384
385    #[test]
386    fn test_regulated_query_depth_stricter() {
387        let standard = SecurityProfile::standard();
388        let regulated = SecurityProfile::regulated();
389        assert!(regulated.max_query_depth() < standard.max_query_depth());
390    }
391
392    // ========================================================================
393    // Test Suite 5: Limits and Thresholds
394    // ========================================================================
395
396    #[test]
397    fn test_standard_query_limits() {
398        let profile = SecurityProfile::standard();
399        assert!(profile.max_query_complexity() > 0);
400        assert!(profile.max_query_depth() > 0);
401    }
402
403    #[test]
404    fn test_regulated_query_limits() {
405        let profile = SecurityProfile::regulated();
406        assert!(profile.max_query_complexity() > 0);
407        assert!(profile.max_query_depth() > 0);
408    }
409
410    #[test]
411    fn test_response_size_reasonable() {
412        let profile = SecurityProfile::regulated();
413        assert!(profile.max_response_size_bytes() > 100_000); // At least 100KB
414        assert!(profile.max_response_size_bytes() < 100_000_000); // Less than 100MB
415    }
416
417    // ========================================================================
418    // Test Suite 6: Feature Descriptions
419    // ========================================================================
420
421    #[test]
422    fn test_standard_description() {
423        let profile = SecurityProfile::standard();
424        let desc = profile.description();
425        assert!(!desc.is_empty());
426        assert!(desc.contains("rate limiting"));
427    }
428
429    #[test]
430    fn test_regulated_description() {
431        let profile = SecurityProfile::regulated();
432        let desc = profile.description();
433        assert!(!desc.is_empty());
434        assert!(desc.contains("compliance"));
435    }
436
437    #[test]
438    fn test_standard_enforced_features() {
439        let profile = SecurityProfile::standard();
440        let features = profile.enforced_features();
441        assert!(features.contains(&"Rate Limiting"));
442        assert!(features.contains(&"Audit Logging"));
443        assert_eq!(features.len(), 2);
444    }
445
446    #[test]
447    fn test_regulated_enforced_features() {
448        let profile = SecurityProfile::regulated();
449        let features = profile.enforced_features();
450        assert!(features.len() > 2);
451        assert!(features.contains(&"Rate Limiting"));
452        assert!(features.contains(&"Audit Logging"));
453        assert!(features.contains(&"Sensitive Field Masking"));
454        assert!(features.contains(&"Error Detail Reduction"));
455    }
456
457    // ========================================================================
458    // Test Suite 7: Profile Comparison
459    // ========================================================================
460
461    #[test]
462    fn test_profile_equality() {
463        let standard1 = SecurityProfile::standard();
464        let standard2 = SecurityProfile::standard();
465        let regulated = SecurityProfile::regulated();
466
467        assert_eq!(standard1, standard2);
468        assert_ne!(standard1, regulated);
469    }
470
471    #[test]
472    fn test_profile_clone() {
473        let original = SecurityProfile::regulated();
474        let cloned = original;
475        assert_eq!(original, cloned);
476    }
477
478    // ========================================================================
479    // Test Suite 8: Edge Cases
480    // ========================================================================
481
482    #[test]
483    fn test_profile_features_are_superset() {
484        // REGULATED should have everything STANDARD has, plus more
485        let standard = SecurityProfile::standard();
486        let regulated = SecurityProfile::regulated();
487
488        assert_eq!(standard.rate_limit_enabled(), regulated.rate_limit_enabled());
489        assert_eq!(standard.audit_logging_enabled(), regulated.audit_logging_enabled());
490        // But REGULATED should have additional features
491        assert!(regulated.audit_field_access() || !standard.audit_field_access());
492    }
493
494    #[test]
495    fn test_all_features_documented() {
496        let profile = SecurityProfile::standard();
497        assert!(!profile.name().is_empty());
498        assert!(!profile.description().is_empty());
499
500        let profile = SecurityProfile::regulated();
501        assert!(!profile.name().is_empty());
502        assert!(!profile.description().is_empty());
503    }
504
505    #[test]
506    fn test_rate_limits_are_positive() {
507        for profile in &[SecurityProfile::Standard, SecurityProfile::Regulated] {
508            assert!(
509                profile.rate_limit_rps() > 0,
510                "Profile {profile} should have positive rate limit"
511            );
512        }
513    }
514}