- 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
102 lines
3.9 KiB
Go
102 lines
3.9 KiB
Go
package retentionapi
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
|
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionengine"
|
|
)
|
|
|
|
// retentionConfigurePermission ist das bei Core RBAC-02/RBAC-06
|
|
// geprüfte Recht für alle RET-06-API-Endpunkte.
|
|
const retentionConfigurePermission = "retention.configure"
|
|
|
|
// Mount registriert alle RET-06-API-Endpunkte auf mux, jeweils hinter
|
|
// RequireRBAC (RET-08, echter RBAC-06-Aufruf) — Akzeptanzkriterium 3:
|
|
// Änderungen an Fristen sind nur berechtigten Rollen zugänglich.
|
|
func Mount(mux *http.ServeMux, pool *pgxpool.Pool, rbac *rbacclient.Client) {
|
|
mux.HandleFunc("POST /retention-classes", RequireRBAC(rbac, retentionConfigurePermission, configureHandler(pool)))
|
|
mux.HandleFunc("POST /retention-classes/{class}/deactivate", RequireRBAC(rbac, retentionConfigurePermission, deactivateHandler(pool)))
|
|
mux.HandleFunc("GET /retention-classes", RequireRBAC(rbac, retentionConfigurePermission, listHandler(pool)))
|
|
mux.HandleFunc("GET /retention-classes/preview", RequireRBAC(rbac, retentionConfigurePermission, previewHandler(pool)))
|
|
}
|
|
|
|
type configureRequest struct {
|
|
RetentionClass string `json:"retention_class"`
|
|
Duration string `json:"duration"`
|
|
}
|
|
|
|
// configureHandler: Aufbewahrungsklasse anlegen ODER ändern
|
|
// (Akzeptanzkriterium 1) — `retentionengine.ConfigureClassRule` ist ein
|
|
// UPSERT, eine Änderung wirkt erst ab jetzt auf künftige
|
|
// Stichtagsberechnungen (Pflichtprüfung: nicht rückwirkend).
|
|
func configureHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req configureRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "ungültiger request-body: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.RetentionClass == "" || req.Duration == "" {
|
|
http.Error(w, "retention_class und duration sind pflichtfelder", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := retentionengine.ConfigureClassRule(r.Context(), pool, req.RetentionClass, req.Duration); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
// deactivateHandler: Aufbewahrungsklasse deaktivieren (Akzeptanzkriterium 1).
|
|
func deactivateHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
class := r.PathValue("class")
|
|
if err := retentionengine.DeactivateClassRule(r.Context(), pool, class); err != nil {
|
|
http.Error(w, err.Error(), http.StatusNotFound)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|
|
|
|
// listHandler liefert alle konfigurierten Aufbewahrungsklassen (aktiv
|
|
// und deaktiviert) — Grundlage der künftigen Konfigurationsoberfläche.
|
|
func listHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
rules, err := retentionengine.ListClassRules(r.Context(), pool)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(rules)
|
|
}
|
|
}
|
|
|
|
// previewHandler liefert die Vorschauliste bald ablaufender Objekte
|
|
// (Akzeptanzkriterium 2: Standard 30 Tage, per `days`-Query-Parameter
|
|
// überschreibbar). Nutzt DIESELBE `ListExpiringObjects`-Funktion wie
|
|
// der periodische Job (RET-02) — Pflichtprüfung: Vorschauliste stimmt
|
|
// mit dem Ergebnis des periodischen Jobs überein (keine zweite,
|
|
// abweichende Implementierung).
|
|
func previewHandler(pool *pgxpool.Pool) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
days := 30
|
|
asOf := time.Now().UTC().AddDate(0, 0, days)
|
|
|
|
objects, err := retentionengine.ListExpiringObjects(r.Context(), pool, asOf)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(objects)
|
|
}
|
|
}
|