Files
nexarch/archive/internal/retentionapi/authz.go
T
sysops b30e16cb4f RET-08: ret-06-api auf rbac-06 migrieren
- internal/rbacclient: HTTP-Client fuer Core RBAC-06 (POST /authorize)
- retentionapi.RequireRole (Header-Provisorium) ersetzt durch
  RequireRBAC, echter Aufruf gegen RBAC-06, fail-closed bei Fehlern
- Mount nimmt jetzt rbacclient.Client entgegen
- alle bestehenden RET-06-API-Tests weiterhin gruen
- neue Tests: verweigerte Rolle (403 gegen echte RBAC-06-Antwort),
  erlaubte Rolle (200), RBAC-06 nicht erreichbar -> fail-closed (403)
- real deployed auf 131, end-zu-ende per curl nachgewiesen (403/403/200/403)

Pruefungen siehe archive/docs/RET-08-PRUEFPROTOKOLL.md
2026-08-30 08:43:00 +02:00

51 lines
2.1 KiB
Go

// Package retentionapi implementiert RET-06-API: die Backend-Seite der
// Aufbewahrungsfristen-Konfigurationsoberfläche (CRUD auf
// Aufbewahrungsklassen + Vorschauliste ablaufender Objekte). Das
// Next.js-Frontend selbst ist NICHT Teil dieses Tickets (Board-
// Entscheidung: Backend-API zuerst, Frontend als eigenes Folgeticket).
package retentionapi
import (
"errors"
"net/http"
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
)
// requestedRoleHeader trägt die vom Aufrufer beanspruchte Rolle. Die
// eigentliche Autorisierungsentscheidung trifft NICHT dieser Header,
// sondern Core RBAC-06 (siehe RequireRBAC) — der Header ist nur die
// Behauptung, RBAC-06 prüft sie gegen die echten Policy-Rules.
const requestedRoleHeader = "X-User-Role"
// RequireRBAC ist RET-08: die Ablösung des früheren, header-basierten
// Provisoriums (siehe archive/docs/RET-06-API-PRUEFPROTOKOLL.md) durch
// einen echten Aufruf von Core RBAC-06 (POST /authorize). Fail-closed:
// jeder Fehler beim Aufruf von RBAC-06 (Transport, Timeout, Ablehnung)
// führt zur Verweigerung, niemals zu stillschweigendem Zugriff.
func RequireRBAC(client *rbacclient.Client, permission string, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
role := r.Header.Get(requestedRoleHeader)
if role == "" {
http.Error(w, "zugriff verweigert: header "+requestedRoleHeader+" erforderlich", http.StatusForbidden)
return
}
allowed, err := client.Authorize(r.Context(), role, permission)
if err != nil {
if errors.Is(err, rbacclient.ErrDenied) {
http.Error(w, "zugriff verweigert: rolle "+role+" hat kein recht "+permission, http.StatusForbidden)
return
}
// RBAC-06 nicht erreichbar oder fehlerhafte Antwort:
// fail-closed, kein Fallback auf offenen Zugriff.
http.Error(w, "zugriff verweigert: autorisierungsdienst nicht erreichbar", http.StatusForbidden)
return
}
if !allowed {
http.Error(w, "zugriff verweigert: rolle "+role+" hat kein recht "+permission, http.StatusForbidden)
return
}
next(w, r)
}
}