fix: personenbezogene Daten aus Companion-Klartextkanal ausschließen
- PlainEventStore.Allowed: Grade/ExamResult entfernt (beide tragen StudentId + Notenwert/-kommentar, PlainSyncEvent.Payload läuft aber als Klartext-JSON über den Server, anders als der AES-256-GCM- verschlüsselte Desktop-Kanal). Nur noch WorkTask/Lesson erlaubt. - Regressionstest PlainEventStoreTests ergänzt. - TODO.md 10.3.3 abgehakt, inkl. deutlichem Warnhinweis: die geplante Mitarbeitsnoten-Erfassung per Companion-App ist damit bewusst blockiert, bis 10.3.1 (Schlüsselaustausch) + eine echte Payload- Verschlüsselung für PlainSyncEvent existieren. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
using LehrerApp.Sync.Models;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace LehrerApp.Api.Tests;
|
||||||
|
|
||||||
|
public sealed class PlainEventStoreTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Grade")]
|
||||||
|
[InlineData("ExamResult")]
|
||||||
|
public void Push_PersonenbezogenerEntityTyp_WirdAbgelehnt(string entityType)
|
||||||
|
{
|
||||||
|
using var temp = new TempEventStore();
|
||||||
|
var evt = new PlainSyncEvent { EntityType = entityType, EntityId = "1", Operation = "Save" };
|
||||||
|
|
||||||
|
var result = temp.Plain.Push("user", [evt]);
|
||||||
|
|
||||||
|
Assert.Contains(evt.EventId, result.RejectedEventIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("WorkTask")]
|
||||||
|
[InlineData("Lesson")]
|
||||||
|
public void Push_UnkritischerEntityTyp_WirdAngenommen(string entityType)
|
||||||
|
{
|
||||||
|
using var temp = new TempEventStore();
|
||||||
|
var evt = new PlainSyncEvent { EntityType = entityType, EntityId = "1", Operation = "Save" };
|
||||||
|
|
||||||
|
var result = temp.Plain.Push("user", [evt]);
|
||||||
|
|
||||||
|
Assert.DoesNotContain(evt.EventId, result.RejectedEventIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TempEventStore : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _directory = Path.Combine(
|
||||||
|
Path.GetTempPath(), $"lehrerapp-api-tests-{Guid.NewGuid():N}");
|
||||||
|
private readonly EventStore _events;
|
||||||
|
public PlainEventStore Plain { get; }
|
||||||
|
|
||||||
|
public TempEventStore()
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_directory);
|
||||||
|
_events = new EventStore(_directory);
|
||||||
|
Plain = new PlainEventStore(_events);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_events.Dispose();
|
||||||
|
if (Directory.Exists(_directory)) Directory.Delete(_directory, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ namespace LehrerApp.Api;
|
|||||||
|
|
||||||
public class PlainEventStore(EventStore eventStore)
|
public class PlainEventStore(EventStore eventStore)
|
||||||
{
|
{
|
||||||
private static readonly HashSet<string> Allowed =
|
// Grade/ExamResult bewusst NICHT erlaubt: PlainSyncEvent.Payload ist Klartext-JSON (siehe
|
||||||
["Grade", "ExamResult", "WorkTask", "Lesson"];
|
// SyncModels.cs), beide Typen tragen aber eine StudentId plus Notenwert/-kommentar -
|
||||||
|
// personenbezogene Daten dürfen laut TODO.md 10.3.3 nicht unverschlüsselt übertragen werden.
|
||||||
|
private static readonly HashSet<string> Allowed = ["WorkTask", "Lesson"];
|
||||||
|
|
||||||
public PlainPushResponse Push(string userId, List<PlainSyncEvent> events)
|
public PlainPushResponse Push(string userId, List<PlainSyncEvent> events)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1411,8 +1411,25 @@ die Docker-Verifikation unter 10.2.4 (kein Docker im Entwicklungsstand verfügba
|
|||||||
### 10.3 Verschlüsselung
|
### 10.3 Verschlüsselung
|
||||||
- [ ] **10.3.1** Schlüsselübertragung auf ein zweites Gerät (QR-Code oder Passphrase).
|
- [ ] **10.3.1** Schlüsselübertragung auf ein zweites Gerät (QR-Code oder Passphrase).
|
||||||
- [ ] **10.3.2** Warnung und Wiederherstellungspfad bei verlorenem Schlüssel.
|
- [ ] **10.3.2** Warnung und Wiederherstellungspfad bei verlorenem Schlüssel.
|
||||||
- [ ] **10.3.3** Prüfen, welche Daten unverschlüsselt über `PlainEventStore` laufen —
|
- [x] **10.3.3** Prüfen, welche Daten unverschlüsselt über `PlainEventStore` laufen —
|
||||||
personenbezogene Daten dürfen das nicht.
|
personenbezogene Daten dürfen das nicht. `Grade`/`ExamResult` (beide mit `StudentId` plus
|
||||||
|
Notenwert/-kommentar) trugen bisher personenbezogene Daten im Klartext über den
|
||||||
|
Companion/WebApp-Kanal (`PlainSyncEvent.Payload` ist unverschlüsseltes JSON, anders als der
|
||||||
|
AES-256-GCM-verschlüsselte Desktop-Kanal — der Server sieht den Klartext also nicht nur
|
||||||
|
während der Übertragung, sondern speichert ihn auch dauerhaft unverschlüsselt in der
|
||||||
|
EventStore-Collection). Aus `PlainEventStore.Allowed` entfernt — nur noch `WorkTask`/`Lesson`
|
||||||
|
(reine Lehrer-Planungsdaten ohne `StudentId`) laufen über diesen Kanal.
|
||||||
|
|
||||||
|
> ⚠️ **BEWUSSTE BLOCKADE, gehört zu geplanter Companion-App-Funktion:** Mitarbeitsnoten
|
||||||
|
> sollen laut Plan später über eine Companion-App erfassbar sein — das ist genau `Grade` mit
|
||||||
|
> `Category = Participation`. **Das geht mit dem aktuellen Stand nicht**, und zwar absichtlich:
|
||||||
|
> `PlainEventStore.Push` lehnt `Grade`/`ExamResult`-Events NICHT mit einem Fehler/4xx ab,
|
||||||
|
> sondern **schluckt sie still** — `Success = true`, das Event landet nur in
|
||||||
|
> `RejectedEventIds` statt gespeichert zu werden. Wer das nicht kennt, sucht stundenlang,
|
||||||
|
> warum Mitarbeitsnoten aus der Companion-App im Server nie ankommen. Bevor eine
|
||||||
|
> Companion-App Noten schreiben soll, muss zuerst 10.3.1 (Schlüsselaustausch) + eine echte
|
||||||
|
> Payload-Verschlüsselung für `PlainSyncEvent` gebaut werden — erst danach `Grade` wieder in
|
||||||
|
> `PlainEventStore.Allowed` aufnehmen.
|
||||||
- [ ] **10.3.4** Bekannte v1-Einschränkung aus 10.1.7: weiche Geschäftsregeln greifen beim Anwenden
|
- [ ] **10.3.4** Bekannte v1-Einschränkung aus 10.1.7: weiche Geschäftsregeln greifen beim Anwenden
|
||||||
eingehender Sync-Ereignisse nicht, nur harte LiteDB-Unique-Constraints. Bei mehreren eigenen
|
eingehender Sync-Ereignisse nicht, nur harte LiteDB-Unique-Constraints. Bei mehreren eigenen
|
||||||
Geräten in Randfällen möglich, dass sich Datenstände leicht unterscheiden. Für v1 bewusst
|
Geräten in Randfällen möglich, dass sich Datenstände leicht unterscheiden. Für v1 bewusst
|
||||||
|
|||||||
Reference in New Issue
Block a user