Files
LehrerApp/LehrerApp.Data/DatabaseEncryptionService.cs
T
adminandClaude Sonnet 5 2b299fc940 Dashboard-Performance, CI-Workflow, Settings-Aufteilung, Backup-Härtung
- Dashboard: Fehlzeiten-Warnung lädt Mitarbeitssitzungen einmal vorab
  statt pro Schüler/Eintrag einzeln nachzuschlagen (N+1 vermieden);
  neues IParticipationSessionRepository.GetAll() dafür.
- CI: .gitea/workflows/ci.yml baut und testet bei jedem Push/PR.
  Dabei fehlende Release|Any CPU-Konfiguration für 6 Projekte in der
  .sln behoben (LehrerApp.Data.Tests wurde bei Release-Builds der
  Solution bislang stillschweigend übersprungen). TreatWarningsAsErrors
  jetzt aktiv.
- SettingsViewModel (1986 Zeilen) als partial class auf 20 Themen-
  dateien aufgeteilt, Verhalten unverändert.
- Backup: optionaler zweiter Sicherungsordner (USB-Stick/Netzlaufwerk,
  best-effort) und Integritätsprüfung nach jedem Backup
  (DatabaseEncryptionService.CanOpenAndRead).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 00:52:44 +02:00

92 lines
3.2 KiB
C#

using LiteDB;
namespace LehrerApp.Data;
/// <summary>
/// Prüft und ändert den Passwortschutz einer LiteDB-Datei.
/// Passwort-Änderungen laufen über eine Kopie (neue Datei mit Zielpasswort, alle
/// Collections umkopiert, dann Austausch) statt über <c>LiteDatabase.Rebuild</c> mit
/// Passwort, das in LiteDB 5.0.21 nachweislich fehlschlägt (per Skript verifiziert).
/// </summary>
public class DatabaseEncryptionService
{
public bool IsEncrypted(string dbPath)
{
if (!File.Exists(dbPath)) return false;
try
{
using var db = new LiteDatabase(dbPath);
return false;
}
catch (LiteException)
{
return true;
}
}
public bool VerifyPassword(string dbPath, string password)
{
try
{
using var db = new LiteDatabase(new ConnectionString(dbPath) { Password = password });
return true;
}
catch (LiteException)
{
return false;
}
}
/// Öffnet die Datei probeweise und liest die Sammlungsliste, um eine strukturell gültige
/// (nicht nur vorhandene) LiteDB-Datei zu bestätigen — z.B. direkt nach dem Anlegen eines
/// Backups, das durch einen vollen Datenträger oder einen vorzeitig entfernten USB-Stick
/// beschädigt worden sein könnte. Bewusst breites Catch: jede Art von Fehlschlag beim Öffnen
/// oder Lesen bedeutet hier "Backup nicht verifizierbar", nicht nur ein falsches Passwort.
public bool CanOpenAndRead(string dbPath, string? password)
{
if (!File.Exists(dbPath)) return false;
try
{
var connection = password is null
? new ConnectionString(dbPath)
: new ConnectionString(dbPath) { Password = password };
using var db = new LiteDatabase(connection);
_ = db.GetCollectionNames().ToList();
return true;
}
catch
{
return false;
}
}
/// Setzt (newPassword != null), ändert oder entfernt (newPassword == null) das
/// Passwort der Datenbank. Der Aufrufer muss sicherstellen, dass keine andere
/// Verbindung (z.B. der laufende <see cref="LiteDbContext"/>) die Datei offen hält.
public void SetPassword(string dbPath, string? currentPassword, string? newPassword)
{
var tempPath = dbPath + ".reencrypt.tmp";
if (File.Exists(tempPath)) File.Delete(tempPath);
var srcConnection = currentPassword is null
? new ConnectionString(dbPath)
: new ConnectionString(dbPath) { Password = currentPassword };
var dstConnection = newPassword is null
? new ConnectionString(tempPath)
: new ConnectionString(tempPath) { Password = newPassword };
using (var src = new LiteDatabase(srcConnection))
using (var dst = new LiteDatabase(dstConnection))
{
foreach (var name in src.GetCollectionNames())
{
var docs = src.GetCollection<BsonDocument>(name).FindAll().ToList();
if (docs.Count > 0) dst.GetCollection<BsonDocument>(name).InsertBulk(docs);
}
}
File.Copy(tempPath, dbPath, overwrite: true);
File.Delete(tempPath);
}
}