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)]
203#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
204pub struct ComplianceDashboardConfig {
205 pub enabled: bool,
207 pub refresh_interval_seconds: u64,
209 pub alert_thresholds: AlertThresholds,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
216pub struct AlertThresholds {
217 pub compliance_score: u8,
219 pub control_effectiveness: u8,
221}
222
223impl Default for ComplianceDashboardConfig {
224 fn default() -> Self {
225 Self {
226 enabled: true,
227 refresh_interval_seconds: 300, alert_thresholds: AlertThresholds {
229 compliance_score: 90,
230 control_effectiveness: 85,
231 },
232 }
233 }
234}
235
236pub struct ComplianceDashboardEngine {
241 config: ComplianceDashboardConfig,
242 gaps: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceGap>>>,
244 alerts: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceAlert>>>,
246 control_effectiveness:
248 std::sync::Arc<tokio::sync::RwLock<HashMap<ControlCategory, ControlEffectiveness>>>,
249}
250
251impl ComplianceDashboardEngine {
252 pub fn new(config: ComplianceDashboardConfig) -> Self {
254 Self {
255 config,
256 gaps: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
257 alerts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
258 control_effectiveness: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
259 }
260 }
261
262 pub async fn get_dashboard_data(&self) -> Result<ComplianceDashboardData, Error> {
267 let soc2_compliance = self.calculate_soc2_compliance().await?;
269 let iso27001_compliance = self.calculate_iso27001_compliance().await?;
270 let overall_compliance = (soc2_compliance + iso27001_compliance) / 2;
271
272 let control_effectiveness = self.get_control_effectiveness().await?;
274
275 let gaps = self.get_gap_summary().await?;
277
278 let alerts = self.get_alert_summary().await?;
280
281 let remediation = self.get_remediation_status().await?;
283
284 Ok(ComplianceDashboardData {
285 overall_compliance,
286 soc2_compliance,
287 iso27001_compliance,
288 control_effectiveness,
289 gaps,
290 alerts,
291 remediation,
292 last_updated: Utc::now(),
293 })
294 }
295
296 async fn calculate_soc2_compliance(&self) -> Result<u8, Error> {
298 Ok(95) }
312
313 async fn calculate_iso27001_compliance(&self) -> Result<u8, Error> {
315 Ok(92) }
319
320 async fn get_control_effectiveness(
322 &self,
323 ) -> Result<HashMap<ControlCategory, ControlEffectiveness>, Error> {
324 let mut effectiveness = HashMap::new();
325
326 effectiveness.insert(
328 ControlCategory::AccessControl,
329 ControlEffectiveness {
330 category: ControlCategory::AccessControl,
331 effectiveness: 98, last_test_date: Some(Utc::now() - chrono::Duration::days(7)),
333 test_results: Some("All access controls tested and passing".to_string()),
334 },
335 );
336
337 effectiveness.insert(
339 ControlCategory::Encryption,
340 ControlEffectiveness {
341 category: ControlCategory::Encryption,
342 effectiveness: 100, last_test_date: Some(Utc::now() - chrono::Duration::days(14)),
344 test_results: Some("Encryption controls verified".to_string()),
345 },
346 );
347
348 effectiveness.insert(
350 ControlCategory::Monitoring,
351 ControlEffectiveness {
352 category: ControlCategory::Monitoring,
353 effectiveness: 95, last_test_date: Some(Utc::now() - chrono::Duration::days(3)),
355 test_results: Some("SIEM integration operational".to_string()),
356 },
357 );
358
359 effectiveness.insert(
361 ControlCategory::ChangeManagement,
362 ControlEffectiveness {
363 category: ControlCategory::ChangeManagement,
364 effectiveness: 90, last_test_date: Some(Utc::now() - chrono::Duration::days(10)),
366 test_results: Some("Change management process followed".to_string()),
367 },
368 );
369
370 effectiveness.insert(
372 ControlCategory::IncidentResponse,
373 ControlEffectiveness {
374 category: ControlCategory::IncidentResponse,
375 effectiveness: 95, last_test_date: Some(Utc::now() - chrono::Duration::days(5)),
377 test_results: Some("Incident response procedures tested".to_string()),
378 },
379 );
380
381 Ok(effectiveness)
382 }
383
384 async fn get_gap_summary(&self) -> Result<GapSummary, Error> {
386 let gaps = self.gaps.read().await;
387
388 let mut summary = GapSummary {
389 total: gaps.len() as u32,
390 critical: 0,
391 high: 0,
392 medium: 0,
393 low: 0,
394 };
395
396 for gap in gaps.values() {
397 match gap.severity {
398 GapSeverity::Critical => summary.critical += 1,
399 GapSeverity::High => summary.high += 1,
400 GapSeverity::Medium => summary.medium += 1,
401 GapSeverity::Low => summary.low += 1,
402 }
403 }
404
405 Ok(summary)
406 }
407
408 async fn get_alert_summary(&self) -> Result<AlertSummary, Error> {
410 let alerts = self.alerts.read().await;
411
412 let mut summary = AlertSummary {
413 total: alerts.len() as u32,
414 critical: 0,
415 high: 0,
416 medium: 0,
417 low: 0,
418 };
419
420 for alert in alerts.values() {
421 if alert.resolved_at.is_none() {
422 match alert.severity {
423 GapSeverity::Critical => summary.critical += 1,
424 GapSeverity::High => summary.high += 1,
425 GapSeverity::Medium => summary.medium += 1,
426 GapSeverity::Low => summary.low += 1,
427 }
428 }
429 }
430
431 Ok(summary)
432 }
433
434 async fn get_remediation_status(&self) -> Result<RemediationStatus, Error> {
436 let gaps = self.gaps.read().await;
437 let now = Utc::now();
438 let month_start_str = format!("{}-{:02}-01T00:00:00Z", now.format("%Y"), now.format("%m"));
440 let start_of_month = DateTime::parse_from_rfc3339(&month_start_str)
441 .map(|dt| dt.with_timezone(&Utc))
442 .unwrap_or(now);
443
444 let mut status = RemediationStatus {
445 in_progress: 0,
446 completed_this_month: 0,
447 overdue: 0,
448 };
449
450 for gap in gaps.values() {
451 match gap.status {
452 GapStatus::InProgress => status.in_progress += 1,
453 GapStatus::Remediated => {
454 if let Some(remediated_at) = gap.remediated_at {
455 if remediated_at >= start_of_month {
456 status.completed_this_month += 1;
457 }
458 }
459 }
460 GapStatus::Overdue => status.overdue += 1,
461 GapStatus::Identified => {
462 if let Some(target_date) = gap.target_remediation_date {
464 if now > target_date {
465 status.overdue += 1;
466 }
467 }
468 }
469 }
470 }
471
472 Ok(status)
473 }
474
475 pub async fn add_gap(
477 &self,
478 gap_id: String,
479 description: String,
480 severity: GapSeverity,
481 standard: ComplianceStandard,
482 control_id: Option<String>,
483 target_remediation_date: Option<DateTime<Utc>>,
484 ) -> Result<(), Error> {
485 let mut gaps = self.gaps.write().await;
486 let gap = ComplianceGap {
487 gap_id: gap_id.clone(),
488 description,
489 severity,
490 standard,
491 control_id,
492 status: GapStatus::Identified,
493 created_at: Utc::now(),
494 target_remediation_date,
495 remediated_at: None,
496 };
497 gaps.insert(gap_id, gap);
498 Ok(())
499 }
500
501 pub async fn update_gap_status(&self, gap_id: &str, status: GapStatus) -> Result<(), Error> {
503 let mut gaps = self.gaps.write().await;
504 if let Some(gap) = gaps.get_mut(gap_id) {
505 gap.status = status;
506 if status == GapStatus::Remediated {
507 gap.remediated_at = Some(Utc::now());
508 }
509 } else {
510 return Err(Error::Generic("Gap not found".to_string()));
511 }
512 Ok(())
513 }
514
515 pub async fn add_alert(
517 &self,
518 alert_id: String,
519 alert_type: AlertType,
520 severity: GapSeverity,
521 message: String,
522 standard: Option<ComplianceStandard>,
523 control_id: Option<String>,
524 ) -> Result<(), Error> {
525 let mut alerts = self.alerts.write().await;
526 let alert = ComplianceAlert {
527 alert_id: alert_id.clone(),
528 alert_type,
529 severity,
530 message,
531 standard,
532 control_id,
533 created_at: Utc::now(),
534 acknowledged_at: None,
535 resolved_at: None,
536 };
537 alerts.insert(alert_id, alert);
538 Ok(())
539 }
540
541 pub async fn get_all_gaps(&self) -> Result<Vec<ComplianceGap>, Error> {
543 let gaps = self.gaps.read().await;
544 Ok(gaps.values().cloned().collect())
545 }
546
547 pub async fn get_all_alerts(&self) -> Result<Vec<ComplianceAlert>, Error> {
549 let alerts = self.alerts.read().await;
550 Ok(alerts.values().cloned().collect())
551 }
552
553 pub async fn get_gaps_by_severity(
555 &self,
556 severity: GapSeverity,
557 ) -> Result<Vec<ComplianceGap>, Error> {
558 let gaps = self.gaps.read().await;
559 Ok(gaps.values().filter(|g| g.severity == severity).cloned().collect())
560 }
561
562 pub async fn get_alerts_by_severity(
564 &self,
565 severity: GapSeverity,
566 ) -> Result<Vec<ComplianceAlert>, Error> {
567 let alerts = self.alerts.read().await;
568 Ok(alerts
569 .values()
570 .filter(|a| a.severity == severity && a.resolved_at.is_none())
571 .cloned()
572 .collect())
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[tokio::test]
581 async fn test_dashboard_data() {
582 let config = ComplianceDashboardConfig::default();
583 let engine = ComplianceDashboardEngine::new(config);
584
585 let dashboard = engine.get_dashboard_data().await.unwrap();
586 assert!(dashboard.overall_compliance <= 100);
587 assert!(dashboard.soc2_compliance <= 100);
588 assert!(dashboard.iso27001_compliance <= 100);
589 }
590}