Skip to main content

bulwark_security/security/
decision.rs

1use crate::logging::simple::SimpleLogger;
2use crate::request::context::RequestContext;
3use crate::security::FindingSeverity;
4use crate::{BulwarkError, BulwarkResult, Decision};
5
6use super::inspector::{Inspector, InspectorFinding};
7
8/// DecisionEngine
9///
10/// Satu-satunya komponen yang BERHAK
11/// menentukan keputusan security.
12///
13/// Alur:
14/// RequestContext -> Inspectors -> Decision -> Logging -> Server
15pub struct DecisionEngine {
16    inspectors: Vec<Box<dyn Inspector>>,
17}
18
19impl DecisionEngine {
20    /// Membuat decision engine kosong tanpa inspector.
21    pub fn new() -> Self {
22        Self {
23            inspectors: Vec::new(),
24        }
25    }
26
27    /// Menambahkan inspector ke pipeline decision.
28    pub fn add<I>(&mut self, inspector: I)
29    where
30        I: Inspector + 'static,
31    {
32        self.inspectors.push(Box::new(inspector));
33    }
34
35    /// Menjalankan seluruh inspector dan menghasilkan keputusan final.
36    ///
37    /// Prioritas keputusan:
38    /// BLOCK > LOG > ALLOW
39    ///
40    /// ⚠️ Catatan API:
41    /// - Jika keputusan akhir adalah `Decision::Block`,
42    ///   fungsi ini akan mengembalikan `Err(BulwarkError::Blocked)`.
43    /// - Isi pesan error **TIDAK dijamin stabil** dan bukan bagian dari API.
44    pub fn decide(&self, ctx: &RequestContext) -> BulwarkResult<Decision> {
45        let mut findings: Vec<InspectorFinding> = Vec::new();
46
47        // 1. Jalankan seluruh inspector
48        for inspector in &self.inspectors {
49            match inspector.inspect(ctx) {
50                Ok(Some(finding)) => findings.push(finding),
51                Ok(None) => {}
52                Err(err) => {
53                    // Error dari inspector dianggap fatal
54                    SimpleLogger::log(ctx, &Decision::Block, "inspector error");
55                    return Err(err);
56                }
57            }
58        }
59
60        // 2. Evaluasi semua findings
61        let decision = Self::evaluate_findings(&findings);
62
63        // 3. Logging terpusat
64        for finding in &findings {
65            SimpleLogger::log(ctx, &decision, &finding.reason);
66        }
67
68        // 4. Kembalikan keputusan
69        match decision {
70            Decision::Block => Err(BulwarkError::blocked(
71                // ⚠️ Message is NOT part of the public API
72                "request blocked by security decision",
73            )),
74            _ => Ok(decision),
75        }
76    }
77
78    /// Mengevaluasi semua temuan inspector menjadi satu keputusan final.
79    fn evaluate_findings(findings: &[InspectorFinding]) -> Decision {
80        let mut decision = Decision::Allow;
81
82        for finding in findings {
83            match finding.severity {
84                FindingSeverity::High => return Decision::Block,
85                FindingSeverity::Medium => decision = Decision::Log,
86                FindingSeverity::Low => {}
87            }
88        }
89
90        decision
91    }
92}
93
94/// Tingkat keparahan temuan inspector.
95///
96/// ## Kontrak API (STABLE sejak v0.3.0)
97///
98/// - `High`
99///   Temuan berbahaya yang **WAJIB memblokir request**.
100///
101/// - `Medium`
102///   Temuan mencurigakan namun tidak fatal.
103///   Request **tidak diblokir**, tetapi **WAJIB dilog**.
104///
105/// - `Low`
106///   Sinyal ringan atau informasi.
107///   Tidak mempengaruhi keputusan akhir.
108impl Default for DecisionEngine {
109    fn default() -> Self {
110        Self::new()
111    }
112}