package audit import ( "context" "net/http" "time" ) // Authorize entscheidet, ob caller den Export ausfuehren darf. Die // eigentliche Rollen-/Rechtepruefung (RBAC-02 Policy-Enforcement) ist nicht // Teil dieser Kachel — ExportHandler kennt nur diese schmale Schnittstelle, // analog zum RetentionRegistrar-Muster aus AUD-05. type Authorize func(ctx context.Context, caller string) bool // ExportHandler stellt den Export als HTTP-Endpunkt bereit // (Akzeptanzkriterium 2: fuer berechtigte Rollen verfuegbar). type ExportHandler struct { log *Log authorize Authorize } func NewExportHandler(log *Log, authorize Authorize) *ExportHandler { return &ExportHandler{log: log, authorize: authorize} } // Export liest Filter-Query-Parameter (tenant, actor, action, from, to, // format) und schreibt DIREKT auf den ResponseWriter (io.Writer) — dieselbe // Streaming-Funktion wie in export.go, kein zusaetzlicher Pufferungsschritt. func (h *ExportHandler) Export(w http.ResponseWriter, r *http.Request) { // "caller" identifiziert die anfragende Person fuer die Berechtigungs- // pruefung — bewusst getrennt vom Filterfeld "actor" (das den // AUDIT-Akteur meint, ueber den gefiltert wird). caller := r.URL.Query().Get("caller") if caller == "" || !h.authorize(r.Context(), caller) { http.Error(w, "keine berechtigung fuer audit-log-export", http.StatusForbidden) return } filter := Filter{ TenantSlug: r.URL.Query().Get("tenant"), Actor: r.URL.Query().Get("actor"), Action: r.URL.Query().Get("action"), } if from := r.URL.Query().Get("from"); from != "" { t, err := time.Parse(time.RFC3339, from) if err != nil { http.Error(w, "ungueltiges from-datum, erwartet RFC3339", http.StatusBadRequest) return } filter.From = &t } if to := r.URL.Query().Get("to"); to != "" { t, err := time.Parse(time.RFC3339, to) if err != nil { http.Error(w, "ungueltiges to-datum, erwartet RFC3339", http.StatusBadRequest) return } filter.To = &t } switch r.URL.Query().Get("format") { case "json": w.Header().Set("Content-Type", "application/x-ndjson") if err := h.log.StreamJSON(r.Context(), filter, w); err != nil { http.Error(w, "export fehlgeschlagen", http.StatusInternalServerError) } default: w.Header().Set("Content-Type", "text/csv") if err := h.log.StreamCSV(r.Context(), filter, w); err != nil { http.Error(w, "export fehlgeschlagen", http.StatusInternalServerError) } } }