Skip to main content

auth_framework/analytics/
mod.rs

1//! Analytics and monitoring for RBAC systems.
2//!
3//! This module provides comprehensive analytics capabilities for monitoring
4//! and analyzing RBAC usage patterns, security compliance, and system performance.
5//!
6//! > **Status:** Analytics are event-backed today. The `compliance`, `reports`,
7//! > `metrics`, and `dashboard` modules derive results from stored analytics
8//! > events and persisted snapshots, and they expand as additional collectors
9//! > feed the pipeline.
10
11use crate::storage::AuthStorage;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15use tokio::time::{Duration, Instant};
16
17pub mod compliance;
18pub mod dashboard;
19pub mod metrics;
20pub mod reports;
21
22/// Analytics configuration
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AnalyticsConfig {
25    /// Enable real-time analytics
26    pub real_time_enabled: bool,
27
28    /// Data retention period
29    pub data_retention_days: u32,
30
31    /// Metrics collection interval
32    pub collection_interval: Duration,
33
34    /// Enable security compliance monitoring
35    pub compliance_monitoring: bool,
36
37    /// Enable performance monitoring
38    pub performance_monitoring: bool,
39
40    /// Maximum number of events to buffer
41    pub max_event_buffer: usize,
42}
43
44impl Default for AnalyticsConfig {
45    fn default() -> Self {
46        Self {
47            real_time_enabled: true,
48            data_retention_days: 90,
49            collection_interval: Duration::from_secs(60),
50            compliance_monitoring: true,
51            performance_monitoring: true,
52            max_event_buffer: 10000,
53        }
54    }
55}
56
57/// RBAC analytics event types
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59pub enum RbacEventType {
60    /// Role assignment/revocation
61    RoleAssignment,
62    /// Permission check
63    PermissionCheck,
64    /// Role creation/modification
65    RoleManagement,
66    /// User authentication
67    Authentication,
68    /// Authorization decision
69    Authorization,
70    /// Policy violation
71    PolicyViolation,
72    /// Privilege escalation attempt
73    PrivilegeEscalation,
74    /// Access pattern anomaly
75    AccessAnomaly,
76}
77
78/// Analytics event data
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct AnalyticsEvent {
81    /// Unique event ID
82    pub id: String,
83
84    /// Event type
85    pub event_type: RbacEventType,
86
87    /// Timestamp
88    pub timestamp: chrono::DateTime<chrono::Utc>,
89
90    /// User ID (if applicable)
91    pub user_id: Option<String>,
92
93    /// Role ID (if applicable)
94    pub role_id: Option<String>,
95
96    /// Permission/resource (if applicable)
97    pub resource: Option<String>,
98
99    /// Action performed
100    pub action: Option<String>,
101
102    /// Result of the operation
103    pub result: EventResult,
104
105    /// Additional metadata
106    pub metadata: HashMap<String, String>,
107
108    /// Processing duration (milliseconds)
109    pub duration_ms: Option<u64>,
110
111    /// Source IP address
112    pub source_ip: Option<String>,
113
114    /// User agent
115    pub user_agent: Option<String>,
116}
117
118/// Event result types
119#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
120pub enum EventResult {
121    Success,
122    Failure,
123    Denied,
124    Error,
125}
126
127/// Role usage statistics
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct RoleUsageStats {
130    /// Role ID
131    pub role_id: String,
132
133    /// Role name
134    pub role_name: String,
135
136    /// Number of users assigned to this role
137    pub user_count: u32,
138
139    /// Number of permission checks for this role
140    pub permission_checks: u64,
141
142    /// Number of successful access attempts
143    pub successful_access: u64,
144
145    /// Number of denied access attempts
146    pub denied_access: u64,
147
148    /// Last time this role was used
149    pub last_used: Option<chrono::DateTime<chrono::Utc>>,
150
151    /// Average response time for permission checks (ms)
152    pub avg_response_time_ms: f64,
153
154    /// Most frequently accessed resources
155    pub top_resources: Vec<ResourceAccess>,
156}
157
158/// Resource access statistics
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ResourceAccess {
161    /// Resource identifier
162    pub resource: String,
163
164    /// Number of access attempts
165    pub access_count: u64,
166
167    /// Success rate (0.0 to 1.0)
168    pub success_rate: f64,
169}
170
171/// Permission usage analytics
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct PermissionUsageStats {
174    /// Permission identifier
175    pub permission_id: String,
176
177    /// Number of checks for this permission
178    pub check_count: u64,
179
180    /// Success rate
181    pub success_rate: f64,
182
183    /// Roles that use this permission
184    pub used_by_roles: crate::types::Roles,
185
186    /// Most active users for this permission
187    pub top_users: Vec<UserActivity>,
188
189    /// Peak usage times
190    pub peak_hours: Vec<u8>, // Hours of day (0-23)
191}
192
193/// User activity statistics
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct UserActivity {
196    /// User ID
197    pub user_id: String,
198
199    /// Number of actions
200    pub activity_count: u64,
201
202    /// Last activity timestamp
203    pub last_activity: chrono::DateTime<chrono::Utc>,
204}
205
206/// Security compliance metrics
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct ComplianceMetrics {
209    /// Percentage of users with appropriate role assignments
210    pub role_assignment_compliance: f64,
211
212    /// Percentage of permissions properly scoped
213    pub permission_scoping_compliance: f64,
214
215    /// Number of orphaned permissions
216    pub orphaned_permissions: u32,
217
218    /// Number of over-privileged users
219    pub over_privileged_users: u32,
220
221    /// Number of unused roles
222    pub unused_roles: u32,
223
224    /// Average time to revoke access (hours)
225    pub avg_access_revocation_time_hours: f64,
226
227    /// Policy violations in the last period
228    pub policy_violations: u32,
229
230    /// Security incidents related to RBAC
231    pub security_incidents: u32,
232}
233
234/// Performance metrics for RBAC operations
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct PerformanceMetrics {
237    /// Average permission check latency (ms)
238    pub avg_permission_check_latency_ms: f64,
239
240    /// 95th percentile permission check latency (ms)
241    pub p95_permission_check_latency_ms: f64,
242
243    /// 99th percentile permission check latency (ms)
244    pub p99_permission_check_latency_ms: f64,
245
246    /// Total number of permission checks per second
247    pub permission_checks_per_second: f64,
248
249    /// Cache hit rate for permission checks
250    pub permission_cache_hit_rate: f64,
251
252    /// Error rate for RBAC operations
253    pub error_rate: f64,
254
255    /// System resource utilization
256    pub cpu_usage_percent: f64,
257
258    /// Memory usage (MB)
259    pub memory_usage_mb: u64,
260}
261
262/// Time-based analytics data
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct TimeSeriesData {
265    /// Timestamp
266    pub timestamp: chrono::DateTime<chrono::Utc>,
267
268    /// Metric value
269    pub value: f64,
270
271    /// Additional tags/dimensions
272    pub tags: HashMap<String, String>,
273}
274
275/// Analytics trend information
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct TrendAnalysis {
278    /// Metric name
279    pub metric_name: String,
280
281    /// Current value
282    pub current_value: f64,
283
284    /// Value from previous period
285    pub previous_value: f64,
286
287    /// Percentage change
288    pub change_percent: f64,
289
290    /// Trend direction
291    pub trend: TrendDirection,
292
293    /// Time series data points
294    pub data_points: Vec<TimeSeriesData>,
295}
296
297/// Trend direction
298#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
299pub enum TrendDirection {
300    Increasing,
301    Decreasing,
302    Stable,
303    Volatile,
304}
305
306/// Main analytics manager
307pub struct AnalyticsManager {
308    config: AnalyticsConfig,
309    storage: Arc<dyn AuthStorage>,
310    event_buffer: Vec<AnalyticsEvent>,
311    last_collection: Instant,
312}
313
314impl AnalyticsManager {
315    /// Create new analytics manager
316    pub fn new(config: AnalyticsConfig, storage: Arc<dyn AuthStorage>) -> Self {
317        Self {
318            config,
319            storage,
320            event_buffer: Vec::new(),
321            last_collection: Instant::now(),
322        }
323    }
324
325    /// Record an analytics event
326    pub async fn record_event(&mut self, event: AnalyticsEvent) -> Result<(), AnalyticsError> {
327        if self.event_buffer.len() >= self.config.max_event_buffer {
328            self.flush_events().await?;
329        }
330
331        self.event_buffer.push(event);
332
333        if self.config.real_time_enabled {
334            // Process event immediately for real-time analytics
335            self.process_real_time_event(
336                self.event_buffer
337                    .last()
338                    .expect("buffer non-empty after push"),
339            )
340            .await?;
341        }
342
343        Ok(())
344    }
345
346    /// Get role usage statistics
347    pub async fn get_role_usage_stats(
348        &self,
349        _role_id: Option<&str>,
350        _time_range: Option<TimeRange>,
351    ) -> Result<Vec<RoleUsageStats>, AnalyticsError> {
352        let keys = self
353            .storage
354            .list_kv_keys("analytics_event_")
355            .await
356            .unwrap_or_default();
357        let mut stats: HashMap<String, RoleUsageStats> = HashMap::new();
358        for key in keys {
359            if let Ok(Some(data)) = self.storage.get_kv(&key).await {
360                if let Ok(event) = serde_json::from_slice::<AnalyticsEvent>(&data) {
361                    if let Some(ref role) = event.role_id {
362                        let entry = stats.entry(role.clone()).or_insert_with(|| RoleUsageStats {
363                            role_id: role.clone(),
364                            role_name: role.clone(),
365                            user_count: 1,
366                            permission_checks: 0,
367                            successful_access: 0,
368                            denied_access: 0,
369                            last_used: None,
370                            avg_response_time_ms: 0.0,
371                            top_resources: Vec::new(),
372                        });
373                        if event.event_type == RbacEventType::PermissionCheck {
374                            entry.permission_checks += 1;
375                            if let Some(action) = &event.action {
376                                if action == "Granted" {
377                                    entry.successful_access += 1;
378                                } else {
379                                    entry.denied_access += 1;
380                                }
381                            }
382                            entry.last_used = Some(event.timestamp);
383                        }
384                    }
385                }
386            }
387        }
388        Ok(stats.into_values().collect())
389    }
390
391    /// Get permission usage statistics
392    pub async fn get_permission_usage_stats(
393        &self,
394        _permission_id: Option<&str>,
395        _time_range: Option<TimeRange>,
396    ) -> Result<Vec<PermissionUsageStats>, AnalyticsError> {
397        let keys = self
398            .storage
399            .list_kv_keys("analytics_event_")
400            .await
401            .unwrap_or_default();
402        let mut stats: HashMap<String, PermissionUsageStats> = HashMap::new();
403        for key in keys {
404            if let Ok(Some(data)) = self.storage.get_kv(&key).await {
405                if let Ok(event) = serde_json::from_slice::<AnalyticsEvent>(&data) {
406                    if let Some(ref perm) = event.resource {
407                        let entry =
408                            stats
409                                .entry(perm.clone())
410                                .or_insert_with(|| PermissionUsageStats {
411                                    permission_id: perm.clone(),
412                                    check_count: 0,
413                                    success_rate: 1.0,
414                                    used_by_roles: crate::types::Roles::empty(),
415                                    top_users: Vec::new(),
416                                    peak_hours: Vec::new(),
417                                });
418                        if event.event_type == RbacEventType::PermissionCheck {
419                            entry.check_count += 1;
420                        }
421                    }
422                }
423            }
424        }
425        Ok(stats.into_values().collect())
426    }
427
428    /// Get compliance metrics
429    pub async fn get_compliance_metrics(
430        &self,
431        _time_range: Option<TimeRange>,
432    ) -> Result<ComplianceMetrics, AnalyticsError> {
433        let keys = self
434            .storage
435            .list_kv_keys("analytics_event_")
436            .await
437            .unwrap_or_default();
438        let mut total_events = 0;
439        let mut policy_violations = 0;
440        let mut orphaned_permissions = 0;
441        let mut security_incidents = 0;
442        // Track revocation timing from events that contain revocation metadata
443        let mut revocation_durations = Vec::new();
444        // Track users with escalation/anomaly events as potentially over-privileged
445        let mut escalation_users = std::collections::HashSet::new();
446
447        for key in keys {
448            if let Ok(Some(data)) = self.storage.get_kv(&key).await {
449                if let Ok(event) = serde_json::from_slice::<AnalyticsEvent>(&data) {
450                    total_events += 1;
451                    if let Some(action) = &event.action {
452                        if action.contains("Violation") || action.contains("Denied") {
453                            policy_violations += 1;
454                        }
455                    }
456                    if event.event_type == RbacEventType::PermissionCheck
457                        && event.action.as_deref() == Some("Orphaned")
458                    {
459                        orphaned_permissions += 1;
460                    }
461                    if matches!(
462                        event.event_type,
463                        RbacEventType::PolicyViolation
464                            | RbacEventType::PrivilegeEscalation
465                            | RbacEventType::AccessAnomaly
466                    ) || event
467                        .action
468                        .as_deref()
469                        .is_some_and(|action| action.contains("Incident"))
470                    {
471                        security_incidents += 1;
472                    }
473                    // Track over-privileged users from escalation events
474                    if event.event_type == RbacEventType::PrivilegeEscalation {
475                        if let Some(ref user) = event.user_id {
476                            escalation_users.insert(user.clone());
477                        }
478                    }
479                    // Track access revocation timing from metadata
480                    if let Some(ref action) = event.action {
481                        if action.contains("Revoked") || action.contains("Revocation") {
482                            if let Some(hours_str) = event.metadata.get("revocation_hours") {
483                                if let Ok(hours) = hours_str.parse::<f64>() {
484                                    revocation_durations.push(hours);
485                                }
486                            }
487                        }
488                    }
489                }
490            }
491        }
492
493        let compliance_score = if total_events > 0 {
494            100.0 - ((policy_violations as f64 / total_events as f64) * 100.0)
495        } else {
496            100.0
497        };
498
499        let avg_access_revocation_time_hours = if !revocation_durations.is_empty() {
500            revocation_durations.iter().sum::<f64>() / revocation_durations.len() as f64
501        } else {
502            0.0 // No revocation data available
503        };
504
505        // Count unused roles by comparing defined roles against actual assignments
506        let unused_roles = {
507            let defined_roles = self
508                .storage
509                .list_kv_keys("rbac:role:")
510                .await
511                .unwrap_or_default();
512            let assigned: std::collections::HashSet<String> = {
513                let user_role_keys = self
514                    .storage
515                    .list_kv_keys("rbac:user_roles:")
516                    .await
517                    .unwrap_or_default();
518                let mut set = std::collections::HashSet::new();
519                for key in &user_role_keys {
520                    if let Ok(Some(data)) = self.storage.get_kv(key).await {
521                        if let Ok(roles) = serde_json::from_slice::<Vec<String>>(&data) {
522                            set.extend(roles);
523                        }
524                    }
525                }
526                set
527            };
528            defined_roles
529                .iter()
530                .filter(|k: &&String| {
531                    let role_name = k.strip_prefix("rbac:role:").unwrap_or(k);
532                    !assigned.contains(role_name)
533                })
534                .count() as u32
535        };
536
537        Ok(ComplianceMetrics {
538            role_assignment_compliance: compliance_score,
539            permission_scoping_compliance: compliance_score,
540            orphaned_permissions,
541            over_privileged_users: escalation_users.len() as u32,
542            unused_roles,
543            avg_access_revocation_time_hours,
544            policy_violations,
545            security_incidents,
546        })
547    }
548
549    /// Get performance metrics
550    pub async fn get_performance_metrics(
551        &self,
552        _time_range: Option<TimeRange>,
553    ) -> Result<PerformanceMetrics, AnalyticsError> {
554        let keys = self
555            .storage
556            .list_kv_keys("analytics_event_")
557            .await
558            .unwrap_or_default();
559        let mut total_events = 0;
560        let mut total_duration_ms = 0.0;
561        let mut duration_samples = Vec::new();
562        let mut errors = 0;
563        let mut permission_check_timestamps = Vec::new();
564
565        for key in keys {
566            if let Ok(Some(data)) = self.storage.get_kv(&key).await {
567                if let Ok(event) = serde_json::from_slice::<AnalyticsEvent>(&data) {
568                    total_events += 1;
569                    if let Some(duration_ms) = event.duration_ms {
570                        total_duration_ms += duration_ms as f64;
571                        duration_samples.push(duration_ms);
572                    }
573                    if event.event_type == RbacEventType::PermissionCheck {
574                        permission_check_timestamps.push(event.timestamp);
575                    }
576                    if matches!(event.result, EventResult::Failure | EventResult::Error) {
577                        errors += 1;
578                    }
579                }
580            }
581        }
582
583        duration_samples.sort_unstable();
584        let p95 = if duration_samples.is_empty() {
585            0.0
586        } else {
587            let index = ((duration_samples.len() as f64 * 0.95).floor() as usize)
588                .min(duration_samples.len() - 1);
589            duration_samples[index] as f64
590        };
591        let p99 = if duration_samples.is_empty() {
592            0.0
593        } else {
594            let index = ((duration_samples.len() as f64 * 0.99).floor() as usize)
595                .min(duration_samples.len() - 1);
596            duration_samples[index] as f64
597        };
598        let avg = if duration_samples.is_empty() {
599            0.0
600        } else {
601            total_duration_ms / duration_samples.len() as f64
602        };
603        let error_rate = if total_events > 0 {
604            errors as f64 / total_events as f64
605        } else {
606            0.0
607        };
608        permission_check_timestamps.sort_unstable();
609        let permission_checks_per_second = match (
610            permission_check_timestamps.first(),
611            permission_check_timestamps.last(),
612        ) {
613            (Some(first), Some(last)) if permission_check_timestamps.len() > 1 => {
614                let span_seconds = (*last - *first).num_seconds().max(1) as f64;
615                permission_check_timestamps.len() as f64 / span_seconds
616            }
617            _ => 0.0,
618        };
619
620        Ok(PerformanceMetrics {
621            avg_permission_check_latency_ms: avg,
622            p95_permission_check_latency_ms: p95,
623            p99_permission_check_latency_ms: p99,
624            permission_checks_per_second,
625            // Cache hit rate calculation: count events with metadata key
626            // "cache_hit" set to "true" vs total permission check events.
627            permission_cache_hit_rate: {
628                let cache_hits = self
629                    .event_buffer
630                    .iter()
631                    .filter(|e| e.event_type == RbacEventType::PermissionCheck)
632                    .filter(|e| e.metadata.get("cache_hit").is_some_and(|v| v == "true"))
633                    .count();
634                let cache_total = self
635                    .event_buffer
636                    .iter()
637                    .filter(|e| e.event_type == RbacEventType::PermissionCheck)
638                    .count();
639                if cache_total > 0 {
640                    cache_hits as f64 / cache_total as f64
641                } else {
642                    0.0
643                }
644            },
645            error_rate,
646            // Best-effort process CPU usage via sysinfo crate
647            cpu_usage_percent: {
648                let mut sys = sysinfo::System::new();
649                let pid = sysinfo::get_current_pid().ok();
650                if let Some(pid) = pid {
651                    sys.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[pid]), true);
652                    sys.process(pid)
653                        .map(|p| p.cpu_usage() as f64)
654                        .unwrap_or(0.0)
655                } else {
656                    0.0
657                }
658            },
659            memory_usage_mb: {
660                // Use Rust's allocator stats when available, or a rough
661                // approximation via /proc/self/statm on Linux.
662                #[cfg(target_os = "linux")]
663                {
664                    std::fs::read_to_string("/proc/self/statm")
665                        .ok()
666                        .and_then(|s| s.split_whitespace().nth(1)?.parse::<u64>().ok())
667                        .map(|pages| pages * 4096 / (1024 * 1024))
668                        .unwrap_or(0)
669                }
670                #[cfg(not(target_os = "linux"))]
671                {
672                    0
673                }
674            },
675        })
676    }
677
678    /// Get trend analysis for a specific metric
679    pub async fn get_trend_analysis(
680        &self,
681        metric_name: &str,
682        _time_range: Option<TimeRange>,
683    ) -> Result<TrendAnalysis, AnalyticsError> {
684        let keys = self
685            .storage
686            .list_kv_keys("analytics_event_")
687            .await
688            .unwrap_or_default();
689        let mut data_points = Vec::new();
690
691        for key in keys {
692            if let Ok(Some(data)) = self.storage.get_kv(&key).await {
693                if let Ok(event) = serde_json::from_slice::<AnalyticsEvent>(&data) {
694                    data_points.push(TimeSeriesData {
695                        timestamp: event.timestamp,
696                        value: 1.0,
697                        tags: event.metadata.clone(),
698                    });
699                }
700            }
701        }
702
703        data_points.sort_by_key(|point| point.timestamp);
704        let midpoint = data_points.len() / 2;
705        let previous_value = midpoint as f64;
706        let current_value = (data_points.len() - midpoint) as f64;
707        let change_percent = if previous_value == 0.0 {
708            if current_value == 0.0 { 0.0 } else { 100.0 }
709        } else {
710            ((current_value - previous_value) / previous_value) * 100.0
711        };
712        let direction = if change_percent.abs() < 5.0 {
713            TrendDirection::Stable
714        } else if change_percent > 0.0 {
715            TrendDirection::Increasing
716        } else {
717            TrendDirection::Decreasing
718        };
719
720        Ok(TrendAnalysis {
721            metric_name: metric_name.to_string(),
722            current_value,
723            previous_value,
724            change_percent,
725            trend: direction,
726            data_points,
727        })
728    }
729
730    /// Generate comprehensive analytics report
731    pub async fn generate_report(
732        &self,
733        report_type: ReportType,
734        time_range: TimeRange,
735    ) -> Result<AnalyticsReport, AnalyticsError> {
736        let role_stats = self
737            .get_role_usage_stats(None, Some(time_range.clone()))
738            .await?;
739        let permission_stats = self
740            .get_permission_usage_stats(None, Some(time_range.clone()))
741            .await?;
742        let compliance_metrics = self
743            .get_compliance_metrics(Some(time_range.clone()))
744            .await?;
745        let performance_metrics = self
746            .get_performance_metrics(Some(time_range.clone()))
747            .await?;
748
749        Ok(AnalyticsReport {
750            report_type,
751            time_range,
752            generated_at: chrono::Utc::now(),
753            role_stats,
754            permission_stats,
755            compliance_metrics: compliance_metrics.clone(),
756            performance_metrics: performance_metrics.clone(),
757            summary: self.generate_report_summary(&compliance_metrics, &performance_metrics),
758        })
759    }
760
761    /// Flush buffered events
762    async fn flush_events(&mut self) -> Result<(), AnalyticsError> {
763        if self.event_buffer.is_empty() {
764            return Ok(());
765        }
766
767        // Persist events to storage
768        for event in &self.event_buffer {
769            if let Ok(json_data) = serde_json::to_vec(event) {
770                let key = format!("analytics_event_{}", event.id);
771                // We use an arbitrary TTL of 90 days if data retention is enabled
772                let ttl =
773                    std::time::Duration::from_secs(self.config.data_retention_days as u64 * 86400);
774                let _ = self.storage.store_kv(&key, &json_data, Some(ttl)).await;
775            }
776        }
777
778        // Clear the buffer
779        self.event_buffer.clear();
780        self.last_collection = Instant::now();
781
782        Ok(())
783    }
784
785    /// Process event for real-time analytics by persisting it to storage
786    /// immediately so that dashboards and alerts can query the latest data
787    /// without waiting for the periodic buffer flush.
788    async fn process_real_time_event(&self, event: &AnalyticsEvent) -> Result<(), AnalyticsError> {
789        let json_data = serde_json::to_vec(event)?;
790        let key = format!("analytics_event_{}", event.id);
791        let ttl = std::time::Duration::from_secs(self.config.data_retention_days as u64 * 86400);
792        self.storage
793            .store_kv(&key, &json_data, Some(ttl))
794            .await
795            .map_err(|e| AnalyticsError::StorageError(e.to_string()))?;
796        Ok(())
797    }
798
799    /// Generate report summary
800    fn generate_report_summary(
801        &self,
802        compliance: &ComplianceMetrics,
803        performance: &PerformanceMetrics,
804    ) -> String {
805        format!(
806            "RBAC Analytics Summary: {}% compliance, {:.1}ms avg latency, {:.1}% error rate",
807            compliance.role_assignment_compliance,
808            performance.avg_permission_check_latency_ms,
809            performance.error_rate * 100.0
810        )
811    }
812}
813
814/// Time range for analytics queries
815#[derive(Debug, Clone, Serialize, Deserialize)]
816pub struct TimeRange {
817    pub start: chrono::DateTime<chrono::Utc>,
818    pub end: chrono::DateTime<chrono::Utc>,
819}
820
821impl TimeRange {
822    /// Create time range for the last N hours
823    pub fn last_hours(hours: u32) -> Self {
824        let end = chrono::Utc::now();
825        let start = end - chrono::Duration::hours(hours as i64);
826        Self { start, end }
827    }
828
829    /// Create time range for the last N days
830    pub fn last_days(days: u32) -> Self {
831        let end = chrono::Utc::now();
832        let start = end - chrono::Duration::days(days as i64);
833        Self { start, end }
834    }
835
836    /// Create time range for today
837    pub fn today() -> Self {
838        let now = chrono::Utc::now();
839        let start = now
840            .date_naive()
841            .and_hms_opt(0, 0, 0)
842            .expect("0,0,0 are valid h/m/s values")
843            .and_utc();
844        let end = now;
845        Self { start, end }
846    }
847}
848
849/// Report types
850#[derive(Debug, Clone, Serialize, Deserialize)]
851pub enum ReportType {
852    /// Daily summary report
853    Daily,
854    /// Weekly summary report
855    Weekly,
856    /// Monthly summary report
857    Monthly,
858    /// Security compliance report
859    Compliance,
860    /// Performance analysis report
861    Performance,
862    /// Custom report with specific criteria
863    Custom(String),
864}
865
866/// Comprehensive analytics report
867#[derive(Debug, Clone, Serialize, Deserialize)]
868pub struct AnalyticsReport {
869    pub report_type: ReportType,
870    pub time_range: TimeRange,
871    pub generated_at: chrono::DateTime<chrono::Utc>,
872    pub role_stats: Vec<RoleUsageStats>,
873    pub permission_stats: Vec<PermissionUsageStats>,
874    pub compliance_metrics: ComplianceMetrics,
875    pub performance_metrics: PerformanceMetrics,
876    pub summary: String,
877}
878
879/// Analytics-related errors
880#[derive(Debug, thiserror::Error)]
881pub enum AnalyticsError {
882    #[error("Data processing error: {0}")]
883    ProcessingError(String),
884
885    #[error("Storage error: {0}")]
886    StorageError(String),
887
888    #[error("Query error: {0}")]
889    QueryError(String),
890
891    #[error("Configuration error: {0}")]
892    ConfigError(String),
893
894    #[error("IO error: {0}")]
895    IoError(#[from] std::io::Error),
896
897    #[error("Serialization error: {0}")]
898    SerializationError(#[from] serde_json::Error),
899}
900
901#[cfg(test)]
902mod tests {
903    use super::*;
904
905    #[test]
906    fn test_analytics_config_default() {
907        let config = AnalyticsConfig::default();
908        assert!(config.real_time_enabled);
909        assert_eq!(config.data_retention_days, 90);
910        assert!(config.compliance_monitoring);
911    }
912
913    #[test]
914    fn test_time_range_creation() {
915        let range = TimeRange::last_hours(24);
916        assert!(range.end > range.start);
917
918        let today = TimeRange::today();
919        assert!(today.end > today.start);
920    }
921
922    #[tokio::test]
923    async fn test_analytics_manager_creation() {
924        let config = AnalyticsConfig::default();
925        let manager = AnalyticsManager::new(
926            config,
927            std::sync::Arc::new(crate::storage::MemoryStorage::new()),
928        );
929        assert_eq!(manager.event_buffer.len(), 0);
930    }
931
932    #[tokio::test]
933    async fn test_record_event() {
934        let config = AnalyticsConfig::default();
935        let mut manager = AnalyticsManager::new(
936            config,
937            std::sync::Arc::new(crate::storage::MemoryStorage::new()),
938        );
939
940        let event = AnalyticsEvent {
941            id: "test_event_1".to_string(),
942            event_type: RbacEventType::PermissionCheck,
943            timestamp: chrono::Utc::now(),
944            user_id: Some("user123".to_string()),
945            role_id: Some("admin".to_string()),
946            resource: Some("user_data".to_string()),
947            action: Some("read".to_string()),
948            result: EventResult::Success,
949            metadata: HashMap::new(),
950            duration_ms: Some(15),
951            source_ip: Some("192.168.1.1".to_string()),
952            user_agent: Some("TestAgent/1.0".to_string()),
953        };
954
955        let result = manager.record_event(event).await;
956        assert!(result.is_ok());
957        assert_eq!(manager.event_buffer.len(), 1);
958    }
959}