Datensicherheit: Backup, Verschlüsselung, App-Sperre (Kapitel 13.3)
Automatisches rollierendes Backup der Datenbank beim Start mit Wiederherstellung über die Einstellungen, versionierte Schema-Migration, optionale Passwort-Verschlüsselung der LiteDB-Datei und eine App-Sperre nach Inaktivität mit eigenem Passwort. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
@@ -8,15 +8,21 @@ namespace LehrerApp.Data;
|
||||
/// </summary>
|
||||
public class LiteDbContext : IDisposable
|
||||
{
|
||||
/// Aktuelle Schema-Version. Migrationsschritte werden versioniert unter
|
||||
/// <see cref="RunVersionedMigrations"/> ergänzt, statt bei jedem Start erneut
|
||||
/// (idempotent, aber unnötig) über alle Daten zu laufen.
|
||||
private const int CurrentSchemaVersion = 1;
|
||||
|
||||
private readonly LiteDatabase _db;
|
||||
|
||||
public LiteDbContext(string databasePath)
|
||||
public LiteDbContext(string databasePath, string? password = null)
|
||||
{
|
||||
_db = new LiteDatabase(new ConnectionString(databasePath)
|
||||
{
|
||||
Connection = ConnectionType.Shared,
|
||||
Password = password,
|
||||
});
|
||||
MigrateExistingData();
|
||||
RunVersionedMigrations();
|
||||
EnsureIndexes();
|
||||
}
|
||||
|
||||
@@ -24,7 +30,7 @@ public class LiteDbContext : IDisposable
|
||||
public LiteDbContext(Stream stream)
|
||||
{
|
||||
_db = new LiteDatabase(stream);
|
||||
MigrateExistingData();
|
||||
RunVersionedMigrations();
|
||||
EnsureIndexes();
|
||||
}
|
||||
|
||||
@@ -51,6 +57,32 @@ public class LiteDbContext : IDisposable
|
||||
|
||||
public void Checkpoint() => _db.Checkpoint();
|
||||
|
||||
public int SchemaVersion => ReadSchemaVersion();
|
||||
|
||||
private void RunVersionedMigrations()
|
||||
{
|
||||
var version = ReadSchemaVersion();
|
||||
if (version < 1)
|
||||
{
|
||||
MigrateExistingData();
|
||||
version = 1;
|
||||
}
|
||||
WriteSchemaVersion(version);
|
||||
}
|
||||
|
||||
private int ReadSchemaVersion()
|
||||
{
|
||||
var meta = _db.GetCollection<BsonDocument>("meta").FindById(1);
|
||||
return meta?["SchemaVersion"].AsInt32 ?? 0;
|
||||
}
|
||||
|
||||
private void WriteSchemaVersion(int version) =>
|
||||
_db.GetCollection<BsonDocument>("meta").Upsert(new BsonDocument
|
||||
{
|
||||
["_id"] = 1,
|
||||
["SchemaVersion"] = version,
|
||||
});
|
||||
|
||||
private void MigrateExistingData()
|
||||
{
|
||||
MigrateMemberships();
|
||||
|
||||
Reference in New Issue
Block a user