Skip to main content

ploit_malper/db/
mod.rs

1pub mod models;
2pub mod pocketbase;
3pub mod schema;
4pub mod sqlite;
5
6use crate::config::DatabaseConfig;
7use crate::error::Result;
8use models::{Asset, Finding, Observation, Relationship, Report, ScanRun, Service};
9
10/// Collection names used by every backend.
11pub mod collections {
12    pub const ASSETS: &str = "Assets";
13    pub const SERVICES: &str = "Services";
14    pub const FINDINGS: &str = "Findings";
15    pub const OBSERVATIONS: &str = "Observations";
16    pub const RELATIONSHIPS: &str = "Relationships";
17    pub const SCAN_RUNS: &str = "ScanRuns";
18    pub const REPORTS: &str = "Reports";
19
20    pub const ALL: [&str; 7] = [
21        ASSETS,
22        SERVICES,
23        FINDINGS,
24        OBSERVATIONS,
25        RELATIONSHIPS,
26        SCAN_RUNS,
27        REPORTS,
28    ];
29}
30
31/// Backend-agnostic persistence interface used by the importer.
32///
33/// All write operations are upserts keyed by a stable, content-derived id so
34/// that re-running an import over the same scan folder is idempotent. History
35/// is preserved through the `Observations` collection rather than by
36/// overwriting prior rows.
37pub trait Storage: Send {
38    fn kind(&self) -> &str;
39
40    /// Verify connectivity (PocketBase health / SQLite open + query).
41    fn ping(&mut self) -> Result<bool>;
42
43    /// Create all required collections/tables if they do not exist.
44    fn ensure_schema(&mut self) -> Result<()>;
45
46    // --- Scan runs ------------------------------------------------------
47
48    fn upsert_scan_run(&mut self, run: &ScanRun) -> Result<()>;
49    fn get_scan_run(&mut self, run_id: &str) -> Result<Option<ScanRun>>;
50    fn list_scan_runs(&mut self) -> Result<Vec<ScanRun>>;
51
52    // --- Assets ---------------------------------------------------------
53
54    fn upsert_asset(&mut self, asset: &Asset) -> Result<()>;
55    fn get_asset(&mut self, asset_id: &str) -> Result<Option<Asset>>;
56    fn list_assets(&mut self) -> Result<Vec<Asset>>;
57
58    // --- Services -------------------------------------------------------
59
60    fn upsert_service(&mut self, service: &Service) -> Result<()>;
61    fn get_service(&mut self, service_id: &str) -> Result<Option<Service>>;
62    fn list_services_for_asset(&mut self, asset_id: &str) -> Result<Vec<Service>>;
63    fn list_all_services(&mut self) -> Result<Vec<Service>>;
64
65    // --- Findings -------------------------------------------------------
66
67    fn upsert_finding(&mut self, finding: &Finding) -> Result<()>;
68    fn get_finding(&mut self, finding_id: &str) -> Result<Option<Finding>>;
69    fn list_findings_for_asset(&mut self, asset_id: &str) -> Result<Vec<Finding>>;
70    fn list_all_findings(&mut self) -> Result<Vec<Finding>>;
71
72    // --- Observations ---------------------------------------------------
73
74    fn add_observation(&mut self, observation: &Observation) -> Result<()>;
75    fn get_observation(&mut self, obs_id: &str) -> Result<Option<Observation>>;
76    fn list_observations_for(
77        &mut self,
78        subject_type: &str,
79        subject_id: &str,
80    ) -> Result<Vec<Observation>>;
81    fn list_all_observations(&mut self) -> Result<Vec<Observation>>;
82
83    // --- Relationships --------------------------------------------------
84
85    fn upsert_relationship(&mut self, relationship: &Relationship) -> Result<()>;
86    fn get_relationship(&mut self, rel_id: &str) -> Result<Option<Relationship>>;
87    fn list_relationships_for(
88        &mut self,
89        subject_type: &str,
90        subject_id: &str,
91    ) -> Result<Vec<Relationship>>;
92
93    // --- Reports --------------------------------------------------------
94
95    fn upsert_report(&mut self, report: &Report) -> Result<()>;
96    fn get_report(&mut self, report_id: &str) -> Result<Option<Report>>;
97    fn list_reports_for_run(&mut self, run_id: &str) -> Result<Vec<Report>>;
98}
99
100/// Open a storage backend based on the persisted configuration.
101pub fn open_storage(config: &DatabaseConfig) -> Result<Box<dyn Storage>> {
102    if config.backend == "sqlite" {
103        Ok(Box::new(sqlite::SqliteStorage::open(&config.sqlite_path)?))
104    } else {
105        Ok(Box::new(pocketbase::PocketBaseStorage::new(config)?))
106    }
107}
108
109/// Open a storage backend, overriding the configured backend.
110pub fn open_storage_with(
111    config: &DatabaseConfig,
112    backend: &str,
113    pocketbase_url: Option<&str>,
114    sqlite_path: Option<&str>,
115) -> Result<Box<dyn Storage>> {
116    if backend == "sqlite" {
117        let path = sqlite_path
118            .map(str::to_string)
119            .unwrap_or_else(|| config.sqlite_path.clone());
120        Ok(Box::new(sqlite::SqliteStorage::open(&path)?))
121    } else {
122        let mut override_config = config.clone();
123        if let Some(url) = pocketbase_url {
124            override_config.pocketbase_url = url.to_string();
125        }
126        Ok(Box::new(pocketbase::PocketBaseStorage::new(
127            &override_config,
128        )?))
129    }
130}