2 Commits
Author SHA1 Message Date
adminandClaude Sonnet 5 788eb42fd2 fix: Docker-SDK-Image auf global.json-Version pinnen
docker compose Build schlug bei Dokploy fehl: "A compatible .NET SDK
was not found". global.json pinnt exakt 10.0.301 mit
rollForward: latestPatch (erlaubt nur neuere Patches im selben
Feature-Band), der treibende Tag mcr.microsoft.com/dotnet/sdk:10.0
zeigte inzwischen aber auf 10.0.400 (neues Feature-Band) - latestPatch
lehnt das ab. Build-Stage in Dockerfile.api auf den exakten Tag
10.0.301 gepinnt, TODO.md 10.2.4 um den Vorfall ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:27:35 +02:00
adminandClaude Sonnet 5 858499e8ec 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>
2026-08-17 23:27:08 +02:00
4 changed files with 90 additions and 5 deletions
@@ -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 -2
View File
@@ -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)
{ {
+28 -2
View File
@@ -1408,11 +1408,37 @@ die Docker-Verifikation unter 10.2.4 (kein Docker im Entwicklungsstand verfügba
Ein tatsächlicher `docker compose up`-Durchlauf konnte in dieser Umgebung nicht verifiziert Ein tatsächlicher `docker compose up`-Durchlauf konnte in dieser Umgebung nicht verifiziert
werden (kein Docker verfügbar) — steht vor dem ersten echten Deployment noch aus. werden (kein Docker verfügbar) — steht vor dem ersten echten Deployment noch aus.
**Nachtrag (echter Dokploy-Deploy-Versuch):** Build schlug fehl mit "A compatible .NET SDK
was not found" — `global.json` pinnt exakt `10.0.301` mit `rollForward: latestPatch`, das
erlaubt nur neuere Patches im selben Feature-Band (300er). Der treibende Docker-Image-Tag
`mcr.microsoft.com/dotnet/sdk:10.0` zeigte inzwischen auf `10.0.400` (Feature-Band 400) —
`latestPatch` lehnt das ab. Fix in `docker/Dockerfile.api`: Build-Stage exakt auf
`mcr.microsoft.com/dotnet/sdk:10.0.301` gepinnt. **Merke:** wird `global.json` je auf eine
neuere SDK-Version gehoben, muss dieser Tag mitgezogen werden, sonst bricht der Docker-Build
auf die gleiche Art wieder ab.
### 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
+4 -1
View File
@@ -1,7 +1,10 @@
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build # Muss exakt zu global.json (rollForward: latestPatch) passen - der treibende ":10.0"-Tag zeigt
# irgendwann auf ein neueres Feature-Band (z.B. 10.0.400), das latestPatch nicht akzeptiert und
# den Build mit "A compatible .NET SDK was not found" abbrechen lässt.
FROM mcr.microsoft.com/dotnet/sdk:10.0.301 AS build
WORKDIR /src WORKDIR /src
COPY ["LehrerApp.Api/LehrerApp.Api.csproj", "LehrerApp.Api/"] COPY ["LehrerApp.Api/LehrerApp.Api.csproj", "LehrerApp.Api/"]
COPY ["LehrerApp.Sync/LehrerApp.Sync.csproj", "LehrerApp.Sync/"] COPY ["LehrerApp.Sync/LehrerApp.Sync.csproj", "LehrerApp.Sync/"]