use crate::Error;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ComplianceStandard {
Soc2,
Iso27001,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ControlCategory {
AccessControl,
Encryption,
Monitoring,
ChangeManagement,
IncidentResponse,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum GapSeverity {
Critical,
High,
Medium,
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceGap {
pub gap_id: String,
pub description: String,
pub severity: GapSeverity,
pub standard: ComplianceStandard,
pub control_id: Option<String>,
pub status: GapStatus,
pub created_at: DateTime<Utc>,
pub target_remediation_date: Option<DateTime<Utc>>,
pub remediated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GapStatus {
Identified,
InProgress,
Remediated,
Overdue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceAlert {
pub alert_id: String,
pub alert_type: AlertType,
pub severity: GapSeverity,
pub message: String,
pub standard: Option<ComplianceStandard>,
pub control_id: Option<String>,
pub created_at: DateTime<Utc>,
pub acknowledged_at: Option<DateTime<Utc>>,
pub resolved_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AlertType {
ComplianceViolation,
ControlFailure,
RemediationOverdue,
AuditFinding,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ControlEffectiveness {
pub category: ControlCategory,
pub effectiveness: u8,
pub last_test_date: Option<DateTime<Utc>>,
pub test_results: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplianceDashboardData {
pub overall_compliance: u8,
pub soc2_compliance: u8,
pub iso27001_compliance: u8,
pub control_effectiveness: HashMap<ControlCategory, ControlEffectiveness>,
pub gaps: GapSummary,
pub alerts: AlertSummary,
pub remediation: RemediationStatus,
pub last_updated: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GapSummary {
pub total: u32,
pub critical: u32,
pub high: u32,
pub medium: u32,
pub low: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlertSummary {
pub total: u32,
pub critical: u32,
pub high: u32,
pub medium: u32,
pub low: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RemediationStatus {
pub in_progress: u32,
pub completed_this_month: u32,
pub overdue: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct ComplianceDashboardConfig {
pub enabled: bool,
pub refresh_interval_seconds: u64,
pub alert_thresholds: AlertThresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct AlertThresholds {
pub compliance_score: u8,
pub control_effectiveness: u8,
}
impl Default for ComplianceDashboardConfig {
fn default() -> Self {
Self {
enabled: true,
refresh_interval_seconds: 300, alert_thresholds: AlertThresholds {
compliance_score: 90,
control_effectiveness: 85,
},
}
}
}
pub struct ComplianceDashboardEngine {
config: ComplianceDashboardConfig,
gaps: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceGap>>>,
alerts: std::sync::Arc<tokio::sync::RwLock<HashMap<String, ComplianceAlert>>>,
control_effectiveness:
std::sync::Arc<tokio::sync::RwLock<HashMap<ControlCategory, ControlEffectiveness>>>,
}
impl ComplianceDashboardEngine {
pub fn new(config: ComplianceDashboardConfig) -> Self {
Self {
config,
gaps: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
alerts: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
control_effectiveness: std::sync::Arc::new(tokio::sync::RwLock::new(HashMap::new())),
}
}
pub fn config(&self) -> &ComplianceDashboardConfig {
&self.config
}
pub async fn get_dashboard_data(&self) -> Result<ComplianceDashboardData, Error> {
if !self.config.enabled {
return Err(Error::feature_disabled("Compliance dashboard"));
}
let soc2_compliance = self.calculate_soc2_compliance().await?;
let iso27001_compliance = self.calculate_iso27001_compliance().await?;
let overall_compliance = (soc2_compliance + iso27001_compliance) / 2;
let control_effectiveness = self.get_control_effectiveness().await?;
let gaps = self.get_gap_summary().await?;
let alerts = self.get_alert_summary().await?;
let remediation = self.get_remediation_status().await?;
Ok(ComplianceDashboardData {
overall_compliance,
soc2_compliance,
iso27001_compliance,
control_effectiveness,
gaps,
alerts,
remediation,
last_updated: Utc::now(),
})
}
async fn calculate_soc2_compliance(&self) -> Result<u8, Error> {
use crate::security::{
is_access_review_service_initialized, is_change_management_engine_initialized,
is_privileged_access_manager_initialized, is_siem_emitter_initialized,
};
let mut score = 0u8;
if is_access_review_service_initialized().await {
score += 20;
}
if is_privileged_access_manager_initialized().await {
score += 20;
}
if is_change_management_engine_initialized().await {
score += 20;
}
if is_siem_emitter_initialized().await {
score += 20;
}
if is_siem_emitter_initialized().await && is_privileged_access_manager_initialized().await {
score += 20;
}
Ok(score)
}
async fn calculate_iso27001_compliance(&self) -> Result<u8, Error> {
use crate::security::{
is_access_review_service_initialized, is_change_management_engine_initialized,
is_privileged_access_manager_initialized, is_siem_emitter_initialized,
};
let mut score = 0u8;
if is_access_review_service_initialized().await {
score += 18;
}
if is_privileged_access_manager_initialized().await {
score += 18;
}
if is_change_management_engine_initialized().await {
score += 18;
}
if is_siem_emitter_initialized().await {
score += 23;
}
if is_siem_emitter_initialized().await && is_privileged_access_manager_initialized().await {
score += 23;
}
Ok(score)
}
async fn get_control_effectiveness(
&self,
) -> Result<HashMap<ControlCategory, ControlEffectiveness>, Error> {
{
let cached = self.control_effectiveness.read().await;
if !cached.is_empty() {
let cache_valid = cached.values().any(|ce| {
ce.last_test_date
.map(|d| Utc::now().signed_duration_since(d).num_seconds() < 60)
.unwrap_or(false)
});
if cache_valid {
return Ok(cached.clone());
}
}
}
use crate::security::{
get_global_access_review_service, get_global_change_management_engine,
is_siem_emitter_initialized,
};
let mut effectiveness = HashMap::new();
let access_control_effectiveness = if get_global_access_review_service().await.is_some() {
100
} else {
0
};
effectiveness.insert(
ControlCategory::AccessControl,
ControlEffectiveness {
category: ControlCategory::AccessControl,
effectiveness: access_control_effectiveness,
last_test_date: Some(Utc::now() - chrono::Duration::days(7)),
test_results: Some(if access_control_effectiveness > 0 {
"Access review service operational".to_string()
} else {
"Access review service not initialized".to_string()
}),
},
);
effectiveness.insert(
ControlCategory::Encryption,
ControlEffectiveness {
category: ControlCategory::Encryption,
effectiveness: 100, last_test_date: Some(Utc::now() - chrono::Duration::days(14)),
test_results: Some("Encryption controls verified".to_string()),
},
);
let monitoring_effectiveness = if is_siem_emitter_initialized().await {
95
} else {
0
};
effectiveness.insert(
ControlCategory::Monitoring,
ControlEffectiveness {
category: ControlCategory::Monitoring,
effectiveness: monitoring_effectiveness,
last_test_date: Some(Utc::now() - chrono::Duration::days(3)),
test_results: Some(if monitoring_effectiveness > 0 {
"SIEM integration operational".to_string()
} else {
"SIEM not initialized".to_string()
}),
},
);
let change_mgmt_effectiveness = if get_global_change_management_engine().await.is_some() {
100
} else {
0
};
effectiveness.insert(
ControlCategory::ChangeManagement,
ControlEffectiveness {
category: ControlCategory::ChangeManagement,
effectiveness: change_mgmt_effectiveness,
last_test_date: Some(Utc::now() - chrono::Duration::days(10)),
test_results: Some(if change_mgmt_effectiveness > 0 {
"Change management process operational".to_string()
} else {
"Change management engine not initialized".to_string()
}),
},
);
use crate::security::is_privileged_access_manager_initialized;
let incident_response_effectiveness = if is_privileged_access_manager_initialized().await
&& is_siem_emitter_initialized().await
{
95
} else if is_siem_emitter_initialized().await {
70
} else {
0
};
effectiveness.insert(
ControlCategory::IncidentResponse,
ControlEffectiveness {
category: ControlCategory::IncidentResponse,
effectiveness: incident_response_effectiveness,
last_test_date: Some(Utc::now() - chrono::Duration::days(5)),
test_results: Some(if incident_response_effectiveness > 0 {
"Incident response systems operational".to_string()
} else {
"Incident response systems not fully initialized".to_string()
}),
},
);
{
let mut cache = self.control_effectiveness.write().await;
*cache = effectiveness.clone();
}
Ok(effectiveness)
}
async fn get_gap_summary(&self) -> Result<GapSummary, Error> {
let gaps = self.gaps.read().await;
let mut summary = GapSummary {
total: gaps.len() as u32,
critical: 0,
high: 0,
medium: 0,
low: 0,
};
for gap in gaps.values() {
match gap.severity {
GapSeverity::Critical => summary.critical += 1,
GapSeverity::High => summary.high += 1,
GapSeverity::Medium => summary.medium += 1,
GapSeverity::Low => summary.low += 1,
}
}
Ok(summary)
}
async fn get_alert_summary(&self) -> Result<AlertSummary, Error> {
let alerts = self.alerts.read().await;
let mut summary = AlertSummary {
total: alerts.len() as u32,
critical: 0,
high: 0,
medium: 0,
low: 0,
};
for alert in alerts.values() {
if alert.resolved_at.is_none() {
match alert.severity {
GapSeverity::Critical => summary.critical += 1,
GapSeverity::High => summary.high += 1,
GapSeverity::Medium => summary.medium += 1,
GapSeverity::Low => summary.low += 1,
}
}
}
Ok(summary)
}
async fn get_remediation_status(&self) -> Result<RemediationStatus, Error> {
let gaps = self.gaps.read().await;
let now = Utc::now();
let month_start_str = format!("{}-{:02}-01T00:00:00Z", now.format("%Y"), now.format("%m"));
let start_of_month = DateTime::parse_from_rfc3339(&month_start_str)
.map(|dt| dt.with_timezone(&Utc))
.unwrap_or(now);
let mut status = RemediationStatus {
in_progress: 0,
completed_this_month: 0,
overdue: 0,
};
for gap in gaps.values() {
match gap.status {
GapStatus::InProgress => status.in_progress += 1,
GapStatus::Remediated => {
if let Some(remediated_at) = gap.remediated_at {
if remediated_at >= start_of_month {
status.completed_this_month += 1;
}
}
}
GapStatus::Overdue => status.overdue += 1,
GapStatus::Identified => {
if let Some(target_date) = gap.target_remediation_date {
if now > target_date {
status.overdue += 1;
}
}
}
}
}
Ok(status)
}
pub async fn add_gap(
&self,
gap_id: String,
description: String,
severity: GapSeverity,
standard: ComplianceStandard,
control_id: Option<String>,
target_remediation_date: Option<DateTime<Utc>>,
) -> Result<(), Error> {
let mut gaps = self.gaps.write().await;
let gap = ComplianceGap {
gap_id: gap_id.clone(),
description,
severity,
standard,
control_id,
status: GapStatus::Identified,
created_at: Utc::now(),
target_remediation_date,
remediated_at: None,
};
gaps.insert(gap_id, gap);
Ok(())
}
pub async fn update_gap_status(&self, gap_id: &str, status: GapStatus) -> Result<(), Error> {
let mut gaps = self.gaps.write().await;
if let Some(gap) = gaps.get_mut(gap_id) {
gap.status = status;
if status == GapStatus::Remediated {
gap.remediated_at = Some(Utc::now());
}
} else {
return Err(Error::not_found("ComplianceGap", gap_id));
}
Ok(())
}
pub async fn add_alert(
&self,
alert_id: String,
alert_type: AlertType,
severity: GapSeverity,
message: String,
standard: Option<ComplianceStandard>,
control_id: Option<String>,
) -> Result<(), Error> {
let mut alerts = self.alerts.write().await;
let alert = ComplianceAlert {
alert_id: alert_id.clone(),
alert_type,
severity,
message,
standard,
control_id,
created_at: Utc::now(),
acknowledged_at: None,
resolved_at: None,
};
alerts.insert(alert_id, alert);
Ok(())
}
pub async fn get_all_gaps(&self) -> Result<Vec<ComplianceGap>, Error> {
let gaps = self.gaps.read().await;
Ok(gaps.values().cloned().collect())
}
pub async fn get_all_alerts(&self) -> Result<Vec<ComplianceAlert>, Error> {
let alerts = self.alerts.read().await;
Ok(alerts.values().cloned().collect())
}
pub async fn get_gaps_by_severity(
&self,
severity: GapSeverity,
) -> Result<Vec<ComplianceGap>, Error> {
let gaps = self.gaps.read().await;
Ok(gaps.values().filter(|g| g.severity == severity).cloned().collect())
}
pub async fn get_alerts_by_severity(
&self,
severity: GapSeverity,
) -> Result<Vec<ComplianceAlert>, Error> {
let alerts = self.alerts.read().await;
Ok(alerts
.values()
.filter(|a| a.severity == severity && a.resolved_at.is_none())
.cloned()
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_dashboard_data() {
let config = ComplianceDashboardConfig::default();
let engine = ComplianceDashboardEngine::new(config);
let dashboard = engine.get_dashboard_data().await.unwrap();
assert!(dashboard.overall_compliance <= 100);
assert!(dashboard.soc2_compliance <= 100);
assert!(dashboard.iso27001_compliance <= 100);
}
}