fix(storage): ST-02 — aggregate errors in recursive node deletion without swallowing
This commit is contained in:
+63
-2
@@ -6,7 +6,7 @@ use anyhow::{bail, Result};
|
|||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rand::RngCore;
|
use rand::RngCore;
|
||||||
use rusqlite::{params, Connection, OptionalExtension};
|
use rusqlite::{params, Connection, OptionalExtension};
|
||||||
use tracing::warn;
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::crypto::{
|
use crate::crypto::{
|
||||||
compute_metadata_mac, decrypt_node_name, derive_kek, derive_metadata_mac_key,
|
compute_metadata_mac, decrypt_node_name, derive_kek, derive_metadata_mac_key,
|
||||||
@@ -1706,8 +1706,18 @@ impl Database {
|
|||||||
ids
|
ids
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut errors = Vec::new();
|
||||||
for child_id in child_ids {
|
for child_id in child_ids {
|
||||||
let _ = self.delete_node(child_id);
|
if let Err(e) = self.delete_node(child_id) {
|
||||||
|
error!("Konnte Kindknoten {} nicht löschen: {e}", child_id);
|
||||||
|
errors.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !errors.is_empty() {
|
||||||
|
bail!(
|
||||||
|
"Fehler beim rekursiven Löschen: {} Kindknoten konnten nicht gelöscht werden (ST-02)",
|
||||||
|
errors.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn = self.conn();
|
let conn = self.conn();
|
||||||
@@ -3111,4 +3121,55 @@ mod tests {
|
|||||||
assert!(valid_by_id.is_some());
|
assert!(valid_by_id.is_some());
|
||||||
assert_eq!(valid_by_id.unwrap().name, "secret_valid.txt");
|
assert_eq!(valid_by_id.unwrap().name, "secret_valid.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_st02_recursive_delete_error_propagation_not_swallowed() {
|
||||||
|
let db = Database::open_in_memory().unwrap();
|
||||||
|
let (salt, kdf, wrapped_dek, nonce, tag) = (
|
||||||
|
[1u8; 16],
|
||||||
|
KdfParams::default(),
|
||||||
|
vec![2u8; 40],
|
||||||
|
[3u8; 12],
|
||||||
|
[4u8; 16],
|
||||||
|
);
|
||||||
|
db.init_schema(&salt, &kdf, &wrapped_dek, &nonce, &tag)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Verzeichnisstruktur erstellen:
|
||||||
|
// /parent (id=3)
|
||||||
|
// /parent/normal.txt (id=4)
|
||||||
|
// /parent/protected.txt (id=5)
|
||||||
|
let parent = db.create_node(1, "parent", true).unwrap();
|
||||||
|
let _child_normal = db.create_node(parent.id, "normal.txt", false).unwrap();
|
||||||
|
let _child_protected = db.create_node(parent.id, "protected.txt", false).unwrap();
|
||||||
|
|
||||||
|
// SQLite-Trigger einrichten, der das Löschen von 'protected.txt' blockiert
|
||||||
|
{
|
||||||
|
let conn = db.conn();
|
||||||
|
conn.execute(
|
||||||
|
"CREATE TRIGGER block_delete_protected BEFORE DELETE ON nodes
|
||||||
|
WHEN OLD.name = 'protected.txt'
|
||||||
|
BEGIN
|
||||||
|
SELECT RAISE(FAIL, 'Löschen von protected.txt blockiert');
|
||||||
|
END;",
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ST-02: delete_node(parent.id) darf den Fehler nicht mehr mit `let _ =` verschlucken!
|
||||||
|
let res = db.delete_node(parent.id);
|
||||||
|
assert!(
|
||||||
|
res.is_err(),
|
||||||
|
"Löschen muss fehlschlagen, wenn ein Kindknoten nicht gelöscht werden kann"
|
||||||
|
);
|
||||||
|
let err_msg = res.unwrap_err().to_string();
|
||||||
|
assert!(
|
||||||
|
err_msg.contains(
|
||||||
|
"Fehler beim rekursiven Löschen: 1 Kindknoten konnten nicht gelöscht werden"
|
||||||
|
),
|
||||||
|
"Fehlermeldung muss die Anzahl fehlgeschlagener Kindknoten exakt ausweisen: {}",
|
||||||
|
err_msg
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user