Postgres-RLS-Policies auf allen Tabellen mit echten Mandanten- Geschäftsdaten (antrag und alles darüber verkettete, abteilung, werkzeug/werkzeug_sperre, genehmiger_rolle/freigabe_regel, loeschfrist_einstellung). Kritischer Fund vor der Umsetzung: die Anwendung verbindet als postgres-Superuser, der RLS immer umgeht - Migration 0021 legt deshalb zusätzlich eine eingeschränkte Rolle "deklarix_app" an, nur für die greifen die Policies tatsächlich. internal/store/tenant_scope.go: WithTenantScope öffnet eine Transaktion und setzt Sitzungsvariablen (app.account_id/app.is_betreiber) per set_config mit Parameterbindung; alle Store-Methoden laufen jetzt über s.db(ctx) statt direkt s.Pool. Jede require*-Middleware umschließt die komplette Handler-Ausführung damit - jeder Request läuft dadurch auch atomar in einer Transaktion (positiver Nebeneffekt). Live end-to-end verifiziert (echter HTTP-Server + DATABASE_URL_APP auf die eingeschränkte Rolle gesetzt): zwei Firmen registriert, Isolation über Abteilung/Antrag/Bewertung bestätigt, zentraler NULL-Katalog für beide sichtbar. Produktivbetrieb braucht noch einen manuellen Schritt (Passwort für deklarix_app setzen + DATABASE_URL_APP konfigurieren, siehe CLAUDE.md) - die Migration allein aktiviert noch nichts, solange die App weiter als Superuser verbindet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
70 lines
2.6 KiB
Go
70 lines
2.6 KiB
Go
// Löschfristen je Datenklasse (Migration 0020) — schließt die in
|
|
// rules/OPEN.md, Punkt 4 dokumentierte Lücke: loeschfrist_max_tage war
|
|
// bisher nur als Anforderung "vorhanden", ohne Tageswerte, und wurde
|
|
// deshalb nicht hart gefiltert. Die konkreten Tageswerte sind KEINE
|
|
// gesetzliche Vorgabe (die DSGVO nennt keine festen Fristen, nur den
|
|
// Grundsatz "so lange wie für den Zweck nötig", Art. 5 Abs. 1 lit. e) —
|
|
// deshalb pro Mandant einstellbar statt hartkodiert, vom
|
|
// Datenschutzbeauftragten hinterlegt. Bei Firmenanlage werden
|
|
// risikogestaffelte Vorschlagswerte vorbelegt (siehe
|
|
// internal/web/loeschfrist_handlers.go), frei editierbar.
|
|
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
type LoeschfristEinstellung struct {
|
|
ID string
|
|
AccountID string
|
|
DatenklasseID string
|
|
MaxTage int
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// UpsertLoeschfristEinstellung legt eine Einstellung an oder
|
|
// aktualisiert sie — ein Mandant hat höchstens eine Frist je
|
|
// Datenklasse (UNIQUE-Constraint).
|
|
func (s *Store) UpsertLoeschfristEinstellung(ctx context.Context, accountID, datenklasseID string, maxTage int) (LoeschfristEinstellung, error) {
|
|
var e LoeschfristEinstellung
|
|
err := s.db(ctx).QueryRow(ctx, `
|
|
INSERT INTO loeschfrist_einstellung (account_id, datenklasse_id, max_tage)
|
|
VALUES ($1, $2, $3)
|
|
ON CONFLICT (account_id, datenklasse_id)
|
|
DO UPDATE SET max_tage = $3, updated_at = now()
|
|
RETURNING id, account_id, datenklasse_id, max_tage, updated_at
|
|
`, accountID, datenklasseID, maxTage).Scan(&e.ID, &e.AccountID, &e.DatenklasseID, &e.MaxTage, &e.UpdatedAt)
|
|
if err != nil {
|
|
return LoeschfristEinstellung{}, fmt.Errorf("store: upsert loeschfrist einstellung: %w", err)
|
|
}
|
|
return e, nil
|
|
}
|
|
|
|
// ListLoeschfristEinstellungenForAccount liefert alle konfigurierten
|
|
// Fristen eines Mandanten.
|
|
func (s *Store) ListLoeschfristEinstellungenForAccount(ctx context.Context, accountID string) ([]LoeschfristEinstellung, error) {
|
|
rows, err := s.db(ctx).Query(ctx, `
|
|
SELECT id, account_id, datenklasse_id, max_tage, updated_at
|
|
FROM loeschfrist_einstellung WHERE account_id = $1 ORDER BY datenklasse_id
|
|
`, accountID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("store: list loeschfrist einstellungen: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []LoeschfristEinstellung
|
|
for rows.Next() {
|
|
var e LoeschfristEinstellung
|
|
if err := rows.Scan(&e.ID, &e.AccountID, &e.DatenklasseID, &e.MaxTage, &e.UpdatedAt); err != nil {
|
|
return nil, fmt.Errorf("store: scan loeschfrist einstellung: %w", err)
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("store: list loeschfrist einstellungen: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|