1use crate::Error;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "lowercase")]
15pub enum ComplianceStandard {
16 Soc2,
18 Iso27001,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum ControlCategory {
26 AccessControl,
28 Encryption,
30 Monitoring,
32 ChangeManagement,
34 IncidentResponse,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
40#[serde(rename_all = "lowercase")]
41pub enum GapSeverity {
42 Critical,
44 High,
46 Medium,
48 Low,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ComplianceGap {
55 pub gap_id: String,
57 pub description: String,
59 pub severity: GapSeverity,
61 pub standard: ComplianceStandard,
63 pub control_id: Option<String>,
65 pub status: GapStatus,
67 pub created_at: DateTime<Utc>,
69 pub target_remediation_date: Option<DateTime<Utc>>,
71 pub remediated_at: Option<DateTime<Utc>>,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum GapStatus {
79 Identified,
81 InProgress,
83 Remediated,
85 Overdue,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ComplianceAlert {
92 pub alert_id: String,
94 pub alert_type: AlertType,
96 pub severity: GapSeverity,
98 pub message: String,
100 pub standard: Option<ComplianceStandard>,
102 pub control_id: Option<String>,
104 pub created_at: DateTime<Utc>,
106 pub acknowledged_at: Option<DateTime<Utc>>,
108 pub resolved_at: Option<DateTime<Utc>>,
110}
111
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(rename_all = "snake_case")]
115pub enum AlertType {
116 ComplianceViolation,
118 ControlFailure,
120 RemediationOverdue,
122 AuditFinding,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ControlEffectiveness {
129 pub category: ControlCategory,
131 pub effectiveness: u8,
133 pub last_test_date: Option<DateTime<Utc>>,
135 pub test_results: Option<String>,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ComplianceDashboardData {
142 pub overall_compliance: u8,
144 pub soc2_compliance: u8,
146 pub iso27001_compliance: u8,
148 pub control_effectiveness: HashMap<ControlCategory, ControlEffectiveness>,
150 pub gaps: GapSummary,
152 pub alerts: AlertSummary,
154 pub remediation: RemediationStatus,
156 pub last_updated: DateTime<Utc>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct GapSummary {
163 pub total: u32,
165 pub critical: u32,
167 pub high: u32,
169 pub medium: u32,
171 pub low: u32,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct AlertSummary {
178 pub total: u32,
180 pub critical: u32,
182 pub high: u32,
184 pub medium: u32,
186 pub low: u32,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
192pub struct RemediationStatus {
193 pub in_progress: u32,
195 pub completed_this_month: u32,
197 pub overdue: u32,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ComplianceDashboardConfig {
204 pub enabled: bool,
206 pub refresh_interval_seconds: u64,
208 pub alert_thresholds: AlertThresholds,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214pub struct AlertThresholds {
215 pub compliance_score: u8,
217 pub control_effectiveness: u8,
219}
220
221impl Default for ComplianceDashboardConfig {
222 fn default() -> Self {
223 Self {
224 enabled: true,
225 refresh_interval_seconds: 300, alert_thresholds: AlertThresholds {
227 compliance_score: 90,
228 control_effectiveness: 85,
229 },
230 }
231 }
232}
233
234pub struct ComplianceDashboardEngine {
239 config: ComplianceDashboardConfig,
240 gaps: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceGap>>>,
242 alerts: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceAlert>>>,
244 control_effectiveness: std::sync::Arc<tokio::sync::RwLock<HashMap<ControlCategory, ControlEffectiveness>>>,
246}
247
248impl ComplianceDashboardEngine {
249 pub fn new(config: ComplianceDashboardConfig) -> Self {
251 Self {
252 config,
253 gaps: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
254 alerts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
255 control_effectiveness: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
256 }
257 }
258
259 pub async fn get_dashboard_data(&self) -> Result<ComplianceDashboardData, Error> {
264 let soc2_compliance = self.calculate_soc2_compliance().await?;
266 let iso27001_compliance = self.calculate_iso27001_compliance().await?;
267 let overall_compliance = (soc2_compliance + iso27001_compliance) / 2;
268
269 let control_effectiveness = self.get_control_effectiveness().await?;
271
272 let gaps = self.get_gap_summary().await?;
274
275 let alerts = self.get_alert_summary().await?;
277
278 let remediation = self.get_remediation_status().await?;
280
281 Ok(ComplianceDashboardData {
282 overall_compliance,
283 soc2_compliance,
284 iso27001_compliance,
285 control_effectiveness,
286 gaps,
287 alerts,
288 remediation,
289 last_updated: Utc::now(),
290 })
291 }
292
293 async fn calculate_soc2_compliance(&self) -> Result<u8, Error> {
295 Ok(95) }
309
310 async fn calculate_iso27001_compliance(&self) -> Result<u8, Error> {
312 Ok(92) }
316
317 async fn get_control_effectiveness(&self) -> Result<HashMap<ControlCategory, ControlEffectiveness>, Error> {
319 let mut effectiveness = HashMap::new();
320
321 effectiveness.insert(
323 ControlCategory::AccessControl,
324 ControlEffectiveness {
325 category: ControlCategory::AccessControl,
326 effectiveness: 98, last_test_date: Some(Utc::now() - chrono::Duration::days(7)),
328 test_results: Some("All access controls tested and passing".to_string()),
329 },
330 );
331
332 effectiveness.insert(
334 ControlCategory::Encryption,
335 ControlEffectiveness {
336 category: ControlCategory::Encryption,
337 effectiveness: 100, last_test_date: Some(Utc::now() - chrono::Duration::days(14)),
339 test_results: Some("Encryption controls verified".to_string()),
340 },
341 );
342
343 effectiveness.insert(
345 ControlCategory::Monitoring,
346 ControlEffectiveness {
347 category: ControlCategory::Monitoring,
348 effectiveness: 95, last_test_date: Some(Utc::now() - chrono::Duration::days(3)),
350 test_results: Some("SIEM integration operational".to_string()),
351 },
352 );
353
354 effectiveness.insert(
356 ControlCategory::ChangeManagement,
357 ControlEffectiveness {
358 category: ControlCategory::ChangeManagement,
359 effectiveness: 90, last_test_date: Some(Utc::now() - chrono::Duration::days(10)),
361 test_results: Some("Change management process followed".to_string()),
362 },
363 );
364
365 effectiveness.insert(
367 ControlCategory::IncidentResponse,
368 ControlEffectiveness {
369 category: ControlCategory::IncidentResponse,
370 effectiveness: 95, last_test_date: Some(Utc::now() - chrono::Duration::days(5)),
372 test_results: Some("Incident response procedures tested".to_string()),
373 },
374 );
375
376 Ok(effectiveness)
377 }
378
379 async fn get_gap_summary(&self) -> Result<GapSummary, Error> {
381 let gaps = self.gaps.read().await;
382
383 let mut summary = GapSummary {
384 total: gaps.len() as u32,
385 critical: 0,
386 high: 0,
387 medium: 0,
388 low: 0,
389 };
390
391 for gap in gaps.values() {
392 match gap.severity {
393 GapSeverity::Critical => summary.critical += 1,
394 GapSeverity::High => summary.high += 1,
395 GapSeverity::Medium => summary.medium += 1,
396 GapSeverity::Low => summary.low += 1,
397 }
398 }
399
400 Ok(summary)
401 }
402
403 async fn get_alert_summary(&self) -> Result<AlertSummary, Error> {
405 let alerts = self.alerts.read().await;
406
407 let mut summary = AlertSummary {
408 total: alerts.len() as u32,
409 critical: 0,
410 high: 0,
411 medium: 0,
412 low: 0,
413 };
414
415 for alert in alerts.values() {
416 if alert.resolved_at.is_none() {
417 match alert.severity {
418 GapSeverity::Critical => summary.critical += 1,
419 GapSeverity::High => summary.high += 1,
420 GapSeverity::Medium => summary.medium += 1,
421 GapSeverity::Low => summary.low += 1,
422 }
423 }
424 }
425
426 Ok(summary)
427 }
428
429 async fn get_remediation_status(&self) -> Result<RemediationStatus, Error> {
431 let gaps = self.gaps.read().await;
432 let now = Utc::now();
433 let month_start_str = format!("{}-{:02}-01T00:00:00Z", now.format("%Y"), now.format("%m"));
435 let start_of_month = DateTime::parse_from_rfc3339(&month_start_str)
436 .map(|dt| dt.with_timezone(&Utc))
437 .unwrap_or(now);
438
439 let mut status = RemediationStatus {
440 in_progress: 0,
441 completed_this_month: 0,
442 overdue: 0,
443 };
444
445 for gap in gaps.values() {
446 match gap.status {
447 GapStatus::InProgress => status.in_progress += 1,
448 GapStatus::Remediated => {
449 if let Some(remediated_at) = gap.remediated_at {
450 if remediated_at >= start_of_month {
451 status.completed_this_month += 1;
452 }
453 }
454 }
455 GapStatus::Overdue => status.overdue += 1,
456 GapStatus::Identified => {
457 if let Some(target_date) = gap.target_remediation_date {
459 if now > target_date {
460 status.overdue += 1;
461 }
462 }
463 }
464 }
465 }
466
467 Ok(status)
468 }
469
470 pub async fn add_gap(
472 &self,
473 gap_id: String,
474 description: String,
475 severity: GapSeverity,
476 standard: ComplianceStandard,
477 control_id: Option<String>,
478 target_remediation_date: Option<DateTime<Utc>>,
479 ) -> Result<(), Error> {
480 let mut gaps = self.gaps.write().await;
481 let gap = ComplianceGap {
482 gap_id: gap_id.clone(),
483 description,
484 severity,
485 standard,
486 control_id,
487 status: GapStatus::Identified,
488 created_at: Utc::now(),
489 target_remediation_date,
490 remediated_at: None,
491 };
492 gaps.insert(gap_id, gap);
493 Ok(())
494 }
495
496 pub async fn update_gap_status(
498 &self,
499 gap_id: &str,
500 status: GapStatus,
501 ) -> Result<(), Error> {
502 let mut gaps = self.gaps.write().await;
503 if let Some(gap) = gaps.get_mut(gap_id) {
504 gap.status = status;
505 if status == GapStatus::Remediated {
506 gap.remediated_at = Some(Utc::now());
507 }
508 } else {
509 return Err(Error::Generic("Gap not found".to_string()));
510 }
511 Ok(())
512 }
513
514 pub async fn add_alert(
516 &self,
517 alert_id: String,
518 alert_type: AlertType,
519 severity: GapSeverity,
520 message: String,
521 standard: Option<ComplianceStandard>,
522 control_id: Option<String>,
523 ) -> Result<(), Error> {
524 let mut alerts = self.alerts.write().await;
525 let alert = ComplianceAlert {
526 alert_id: alert_id.clone(),
527 alert_type,
528 severity,
529 message,
530 standard,
531 control_id,
532 created_at: Utc::now(),
533 acknowledged_at: None,
534 resolved_at: None,
535 };
536 alerts.insert(alert_id, alert);
537 Ok(())
538 }
539
540 pub async fn get_all_gaps(&self) -> Result<Vec<ComplianceGap>, Error> {
542 let gaps = self.gaps.read().await;
543 Ok(gaps.values().cloned().collect())
544 }
545
546 pub async fn get_all_alerts(&self) -> Result<Vec<ComplianceAlert>, Error> {
548 let alerts = self.alerts.read().await;
549 Ok(alerts.values().cloned().collect())
550 }
551
552 pub async fn get_gaps_by_severity(&self, severity: GapSeverity) -> Result<Vec<ComplianceGap>, Error> {
554 let gaps = self.gaps.read().await;
555 Ok(gaps
556 .values()
557 .filter(|g| g.severity == severity)
558 .cloned()
559 .collect())
560 }
561
562 pub async fn get_alerts_by_severity(&self, severity: GapSeverity) -> Result<Vec<ComplianceAlert>, Error> {
564 let alerts = self.alerts.read().await;
565 Ok(alerts
566 .values()
567 .filter(|a| a.severity == severity && a.resolved_at.is_none())
568 .cloned()
569 .collect())
570 }
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576
577 #[tokio::test]
578 async fn test_dashboard_data() {
579 let config = ComplianceDashboardConfig::default();
580 let engine = ComplianceDashboardEngine::new(config);
581
582 let dashboard = engine.get_dashboard_data().await.unwrap();
583 assert!(dashboard.overall_compliance <= 100);
584 assert!(dashboard.soc2_compliance <= 100);
585 assert!(dashboard.iso27001_compliance <= 100);
586 }
587}