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