- 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>
92 lines
3.2 KiB
C#
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);
|
|
}
|
|
}
|