mockforge_http/handlers/
compliance_dashboard.rs

1//! HTTP handlers for compliance dashboard
2//!
3//! This module provides REST API endpoints for accessing compliance
4//! dashboard data, gaps, alerts, and control effectiveness metrics.
5
6use axum::{
7    extract::{Path, Query, State},
8    http::StatusCode,
9    response::Json,
10};
11use mockforge_core::security::compliance_dashboard::{
12    AlertType, ComplianceDashboardEngine, ComplianceStandard, GapSeverity,
13};
14use serde::Deserialize;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::sync::RwLock;
18use tracing::{error, info};
19
20/// State for compliance dashboard handlers
21#[derive(Clone)]
22pub struct ComplianceDashboardState {
23    /// Compliance dashboard engine
24    pub engine: Arc<RwLock<ComplianceDashboardEngine>>,
25}
26
27/// Request to add a compliance gap
28#[derive(Debug, Deserialize)]
29pub struct AddGapRequest {
30    /// Gap description
31    pub description: String,
32    /// Severity
33    pub severity: GapSeverity,
34    /// Standard
35    pub standard: ComplianceStandard,
36    /// Control ID (optional)
37    pub control_id: Option<String>,
38    /// Target remediation date (optional)
39    pub target_remediation_date: Option<chrono::DateTime<chrono::Utc>>,
40}
41
42/// Request to add a compliance alert
43#[derive(Debug, Deserialize)]
44pub struct AddAlertRequest {
45    /// Alert type
46    pub alert_type: AlertType,
47    /// Severity
48    pub severity: GapSeverity,
49    /// Message
50    pub message: String,
51    /// Standard (optional)
52    pub standard: Option<ComplianceStandard>,
53    /// Control ID (optional)
54    pub control_id: Option<String>,
55}
56
57/// Request to update gap status
58#[derive(Debug, Deserialize)]
59pub struct UpdateGapStatusRequest {
60    /// New status
61    pub status: mockforge_core::security::compliance_dashboard::GapStatus,
62}
63
64/// Get dashboard data
65///
66/// GET /api/v1/compliance/dashboard
67pub async fn get_dashboard(
68    State(state): State<ComplianceDashboardState>,
69) -> Result<Json<serde_json::Value>, StatusCode> {
70    let engine = state.engine.read().await;
71    let dashboard = engine.get_dashboard_data().await.map_err(|e| {
72        error!("Failed to get dashboard data: {}", e);
73        StatusCode::INTERNAL_SERVER_ERROR
74    })?;
75
76    Ok(Json(serde_json::to_value(&dashboard).unwrap()))
77}
78
79/// Get all compliance gaps
80///
81/// GET /api/v1/compliance/gaps
82pub async fn get_gaps(
83    State(state): State<ComplianceDashboardState>,
84    Query(params): Query<HashMap<String, String>>,
85) -> Result<Json<serde_json::Value>, StatusCode> {
86    let engine = state.engine.read().await;
87
88    let gaps = if let Some(severity_str) = params.get("severity") {
89        let severity = match severity_str.as_str() {
90            "critical" => GapSeverity::Critical,
91            "high" => GapSeverity::High,
92            "medium" => GapSeverity::Medium,
93            "low" => GapSeverity::Low,
94            _ => return Err(StatusCode::BAD_REQUEST),
95        };
96        engine.get_gaps_by_severity(severity).await.map_err(|e| {
97            error!("Failed to get gaps by severity: {}", e);
98            StatusCode::INTERNAL_SERVER_ERROR
99        })?
100    } else {
101        engine.get_all_gaps().await.map_err(|e| {
102            error!("Failed to get all gaps: {}", e);
103            StatusCode::INTERNAL_SERVER_ERROR
104        })?
105    };
106
107    Ok(Json(serde_json::to_value(&gaps).unwrap()))
108}
109
110/// Add a compliance gap
111///
112/// POST /api/v1/compliance/gaps
113pub async fn add_gap(
114    State(state): State<ComplianceDashboardState>,
115    Json(request): Json<AddGapRequest>,
116) -> Result<Json<serde_json::Value>, StatusCode> {
117    let gap_id = format!("GAP-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
118
119    let engine = state.engine.write().await;
120    engine
121        .add_gap(
122            gap_id.clone(),
123            request.description,
124            request.severity,
125            request.standard,
126            request.control_id,
127            request.target_remediation_date,
128        )
129        .await
130        .map_err(|e| {
131            error!("Failed to add gap: {}", e);
132            StatusCode::INTERNAL_SERVER_ERROR
133        })?;
134
135    info!("Compliance gap added: {}", gap_id);
136
137    Ok(Json(serde_json::json!({
138        "gap_id": gap_id,
139        "status": "created"
140    })))
141}
142
143/// Update gap status
144///
145/// PATCH /api/v1/compliance/gaps/{gap_id}/status
146pub async fn update_gap_status(
147    State(state): State<ComplianceDashboardState>,
148    Path(gap_id): Path<String>,
149    Json(request): Json<UpdateGapStatusRequest>,
150) -> Result<Json<serde_json::Value>, StatusCode> {
151    let engine = state.engine.write().await;
152    engine.update_gap_status(&gap_id, request.status).await.map_err(|e| {
153        error!("Failed to update gap status: {}", e);
154        StatusCode::BAD_REQUEST
155    })?;
156
157    info!("Gap status updated: {}", gap_id);
158
159    Ok(Json(serde_json::json!({
160        "gap_id": gap_id,
161        "status": "updated"
162    })))
163}
164
165/// Get all compliance alerts
166///
167/// GET /api/v1/compliance/alerts
168pub async fn get_alerts(
169    State(state): State<ComplianceDashboardState>,
170    Query(params): Query<HashMap<String, String>>,
171) -> Result<Json<serde_json::Value>, StatusCode> {
172    let engine = state.engine.read().await;
173
174    let alerts = if let Some(severity_str) = params.get("severity") {
175        let severity = match severity_str.as_str() {
176            "critical" => GapSeverity::Critical,
177            "high" => GapSeverity::High,
178            "medium" => GapSeverity::Medium,
179            "low" => GapSeverity::Low,
180            _ => return Err(StatusCode::BAD_REQUEST),
181        };
182        engine.get_alerts_by_severity(severity).await.map_err(|e| {
183            error!("Failed to get alerts by severity: {}", e);
184            StatusCode::INTERNAL_SERVER_ERROR
185        })?
186    } else {
187        engine.get_all_alerts().await.map_err(|e| {
188            error!("Failed to get all alerts: {}", e);
189            StatusCode::INTERNAL_SERVER_ERROR
190        })?
191    };
192
193    Ok(Json(serde_json::to_value(&alerts).unwrap()))
194}
195
196/// Add a compliance alert
197///
198/// POST /api/v1/compliance/alerts
199pub async fn add_alert(
200    State(state): State<ComplianceDashboardState>,
201    Json(request): Json<AddAlertRequest>,
202) -> Result<Json<serde_json::Value>, StatusCode> {
203    let alert_id = format!("ALERT-{}", uuid::Uuid::new_v4().to_string().split('-').next().unwrap());
204
205    let engine = state.engine.write().await;
206    engine
207        .add_alert(
208            alert_id.clone(),
209            request.alert_type,
210            request.severity,
211            request.message,
212            request.standard,
213            request.control_id,
214        )
215        .await
216        .map_err(|e| {
217            error!("Failed to add alert: {}", e);
218            StatusCode::INTERNAL_SERVER_ERROR
219        })?;
220
221    info!("Compliance alert added: {}", alert_id);
222
223    Ok(Json(serde_json::json!({
224        "alert_id": alert_id,
225        "status": "created"
226    })))
227}
228
229/// Get compliance status
230///
231/// GET /api/v1/compliance/status
232pub async fn get_compliance_status(
233    State(state): State<ComplianceDashboardState>,
234) -> Result<Json<serde_json::Value>, StatusCode> {
235    let engine = state.engine.read().await;
236    let dashboard = engine.get_dashboard_data().await.map_err(|e| {
237        error!("Failed to get dashboard data: {}", e);
238        StatusCode::INTERNAL_SERVER_ERROR
239    })?;
240
241    // Extract control effectiveness by area
242    let mut by_area = serde_json::Map::new();
243    for (category, effectiveness) in &dashboard.control_effectiveness {
244        let category_name = match category {
245            mockforge_core::security::compliance_dashboard::ControlCategory::AccessControl => "access_control",
246            mockforge_core::security::compliance_dashboard::ControlCategory::Encryption => "encryption",
247            mockforge_core::security::compliance_dashboard::ControlCategory::Monitoring => "monitoring",
248            mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement => "change_management",
249            mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse => "incident_response",
250        };
251        by_area.insert(category_name.to_string(), serde_json::Value::Number(effectiveness.effectiveness.into()));
252    }
253
254    Ok(Json(serde_json::json!({
255        "overall_compliance": dashboard.overall_compliance,
256        "soc2_compliance": dashboard.soc2_compliance,
257        "iso27001_compliance": dashboard.iso27001_compliance,
258        "by_area": by_area,
259        "gaps": dashboard.gaps.total,
260        "remediation_in_progress": dashboard.remediation.in_progress
261    })))
262}
263
264/// Get compliance report
265///
266/// GET /api/v1/compliance/reports/{period}
267pub async fn get_compliance_report(
268    State(state): State<ComplianceDashboardState>,
269    Path(period): Path<String>,
270    Query(params): Query<HashMap<String, String>>,
271) -> Result<Json<serde_json::Value>, StatusCode> {
272    let engine = state.engine.read().await;
273    let dashboard = engine.get_dashboard_data().await.map_err(|e| {
274        error!("Failed to get dashboard data: {}", e);
275        StatusCode::INTERNAL_SERVER_ERROR
276    })?;
277
278    // Extract report period from query or use provided period
279    let report_period = params.get("month")
280        .or_else(|| params.get("period"))
281        .cloned()
282        .unwrap_or_else(|| {
283            // Default to current month
284            let now = chrono::Utc::now();
285            now.format("%Y-%m").to_string()
286        });
287
288    // Get all gaps for recommendations
289    let all_gaps = engine.get_all_gaps().await.map_err(|e| {
290        error!("Failed to get gaps: {}", e);
291        StatusCode::INTERNAL_SERVER_ERROR
292    })?;
293
294    // Generate recommendations based on gaps
295    let mut recommendations = Vec::new();
296    for gap in &all_gaps {
297        match gap.severity {
298            mockforge_core::security::compliance_dashboard::GapSeverity::Critical => {
299                recommendations.push(format!("Urgent: {}", gap.description));
300            }
301            mockforge_core::security::compliance_dashboard::GapSeverity::High => {
302                recommendations.push(format!("High priority: {}", gap.description));
303            }
304            _ => {}
305        }
306    }
307
308    // Add generic recommendations if no gaps
309    if recommendations.is_empty() {
310        if dashboard.control_effectiveness.get(&mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement)
311            .map(|e| e.effectiveness < 95)
312            .unwrap_or(false) {
313            recommendations.push("Enhance change management procedures".to_string());
314        }
315        if dashboard.control_effectiveness.get(&mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse)
316            .map(|e| e.effectiveness < 95)
317            .unwrap_or(false) {
318            recommendations.push("Improve incident response time".to_string());
319        }
320    }
321
322    // Format gaps for report
323    let gaps_summary: Vec<serde_json::Value> = all_gaps.iter().take(10).map(|gap| {
324        serde_json::json!({
325            "id": gap.gap_id,
326            "severity": format!("{:?}", gap.severity).to_lowercase(),
327            "remediation_status": format!("{:?}", gap.status).to_lowercase()
328        })
329    }).collect();
330
331    // Format control effectiveness
332    let mut control_effectiveness = serde_json::Map::new();
333    for (category, effectiveness) in &dashboard.control_effectiveness {
334        let category_name = match category {
335            mockforge_core::security::compliance_dashboard::ControlCategory::AccessControl => "access_control",
336            mockforge_core::security::compliance_dashboard::ControlCategory::Encryption => "encryption",
337            mockforge_core::security::compliance_dashboard::ControlCategory::Monitoring => "monitoring",
338            mockforge_core::security::compliance_dashboard::ControlCategory::ChangeManagement => "change_management",
339            mockforge_core::security::compliance_dashboard::ControlCategory::IncidentResponse => "incident_response",
340        };
341        control_effectiveness.insert(category_name.to_string(), serde_json::Value::Number(effectiveness.effectiveness.into()));
342    }
343
344    Ok(Json(serde_json::json!({
345        "report_period": report_period,
346        "overall_compliance": dashboard.overall_compliance,
347        "control_effectiveness": control_effectiveness,
348        "gaps": gaps_summary,
349        "recommendations": recommendations
350    })))
351}
352
353/// Create compliance dashboard router
354pub fn compliance_dashboard_router(state: ComplianceDashboardState) -> axum::Router {
355    use axum::routing::{get, patch, post};
356
357    axum::Router::new()
358        .route("/dashboard", get(get_dashboard))
359        .route("/status", get(get_compliance_status))
360        .route("/reports/:period", get(get_compliance_report))
361        .route("/gaps", get(get_gaps))
362        .route("/gaps", post(add_gap))
363        .route("/gaps/{gap_id}/status", patch(update_gap_status))
364        .route("/alerts", get(get_alerts))
365        .route("/alerts", post(add_alert))
366        .with_state(state)
367}