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
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
|
||||
)
|
||||
|
||||
@@ -25,6 +26,14 @@ func main() {
|
||||
if addr == "" {
|
||||
addr = "127.0.0.1:8092"
|
||||
}
|
||||
rbacBaseURL := os.Getenv("NEXARCH_RETENTION_RBAC_BASE_URL")
|
||||
if rbacBaseURL == "" {
|
||||
log.Fatal("NEXARCH_RETENTION_RBAC_BASE_URL muss gesetzt sein (RBAC-06, z. B. http://127.0.0.1:8093)")
|
||||
}
|
||||
rbacServiceToken := os.Getenv("NEXARCH_RETENTION_RBAC_SERVICE_TOKEN")
|
||||
if rbacServiceToken == "" {
|
||||
log.Fatal("NEXARCH_RETENTION_RBAC_SERVICE_TOKEN muss gesetzt sein")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
@@ -33,8 +42,10 @@ func main() {
|
||||
}
|
||||
defer pool.Close()
|
||||
|
||||
rbac := rbacclient.New(rbacBaseURL, rbacServiceToken)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
retentionapi.Mount(mux, pool)
|
||||
retentionapi.Mount(mux, pool, rbac)
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })
|
||||
|
||||
log.Printf("retention-api: listening on %s", addr)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# RET-08 – Prüfprotokoll: RET-06-API auf RBAC-06 migrieren (Provisorium abbauen)
|
||||
|
||||
Voraussetzung RBAC-06 (Core) und RET-06-API – beide bereits Fertig.
|
||||
|
||||
## Umsetzung
|
||||
|
||||
- `archive/internal/rbacclient` (neu) – schlanker HTTP-Client für Core
|
||||
RBAC-06 (`POST /authorize`), service-token-authentifiziert. Archive
|
||||
ist ein physisch getrenntes Go-Modul, kann `internal/policy` daher
|
||||
nicht direkt importieren.
|
||||
- `archive/internal/retentionapi/authz.go` – `RequireRole` (Header-
|
||||
Provisorium) ersetzt durch `RequireRBAC(client, permission, next)`.
|
||||
Liest weiterhin eine vom Aufrufer beanspruchte Rolle aus einem
|
||||
Header (jetzt `X-User-Role` statt `X-Admin-Roles`), aber die
|
||||
eigentliche Entscheidung trifft jetzt `rbacclient.Client.Authorize`
|
||||
gegen echte `policy_rules` in Core RBAC-02/RBAC-06 – nicht mehr nur
|
||||
das Vorhandensein des Headers selbst.
|
||||
- **Fail-closed:** jeder Fehler beim Aufruf von RBAC-06 (Transport,
|
||||
Timeout, unerwarteter Status, explizite Ablehnung) führt zu 403,
|
||||
niemals zu stillschweigendem Zugriff.
|
||||
- `archive/internal/retentionapi/handler.go` – `Mount` nimmt jetzt
|
||||
zusätzlich einen `*rbacclient.Client` entgegen, alle vier Endpunkte
|
||||
gegen dasselbe Recht `retention.configure` geprüft.
|
||||
- `archive/cmd/retention-api/main.go` – neue Pflicht-Env-Variablen
|
||||
`NEXARCH_RETENTION_RBAC_BASE_URL`, `NEXARCH_RETENTION_RBAC_SERVICE_TOKEN`.
|
||||
|
||||
## Prüfungen
|
||||
|
||||
| # | Prüfung | Ergebnis |
|
||||
|---|---|---|
|
||||
| 1 | 403-Nachweis gegen echte Policy-Rules über RBAC-06, nicht nur Header | **bestanden** – `TestRequireRBAC_DeniedRoleReturns403` (fehlender Header UND unbekannte Rolle gegen eine echte fake-RBAC-06-HTTP-Antwort); real auf 131: `curl` mit `X-User-Role: archive_admin` ohne existierende `policy_rules`-Zeile → 403, mit real per `INSERT INTO policy_rules` gesetzter Regel → 200, nach `DELETE` wieder 403 |
|
||||
| 2 | Bestehende RET-06-API-Tests laufen weiterhin grün nach der Migration | **bestanden** – `TestConfigureAndListHandler_RealHTTPRoundTrip`, `TestDeactivateHandler_RealHTTPRoundTrip`, `TestPreviewHandler_MatchesPeriodicJobResult` alle unverändert grün (Mount jetzt mit fake-RBAC-06-Server statt Header-Provisorium) |
|
||||
| 3 | Ausfall von RBAC-06 führt zu Ablehnung, kein Fallback auf offenen Zugriff | **bestanden** – `TestRequireRBAC_UnreachableRBACFailsClosed`: `rbacclient.Client` zeigt auf nicht erreichbaren Endpunkt (`http://127.0.0.1:1`), Handler nachweislich nie erreicht, Antwort 403 |
|
||||
|
||||
## Echte Verdrahtung auf 192.168.1.131
|
||||
|
||||
- `retention-api` neu gebaut, `/etc/nexarch/archive-retention-api.env`
|
||||
um `NEXARCH_RETENTION_RBAC_BASE_URL=http://127.0.0.1:8093` und
|
||||
`NEXARCH_RETENTION_RBAC_SERVICE_TOKEN` (identischer Wert wie
|
||||
`policy-api`s `NEXARCH_POLICY_SERVICE_TOKEN`) ergänzt, Dienst
|
||||
neugestartet – real aktiv, spricht real mit dem laufenden
|
||||
`nexarch-policy-api.service` (RBAC-06).
|
||||
- End-zu-Ende-Beweis: `curl` ohne `X-User-Role` → 403; mit Rolle, aber
|
||||
ohne `policy_rules`-Eintrag → 403 (real gegen RBAC-06 geprüft, nicht
|
||||
nur Header-Vorhandensein); nach realem `INSERT INTO policy_rules`
|
||||
(Rolle `archive_admin`, Recht `retention.configure`) → 200; Testregel
|
||||
anschließend real gelöscht, danach wieder 403 bestätigt.
|
||||
- Test-Rolle `nexarch_test` auf 131 hatte kein bekanntes Passwort mehr
|
||||
(frühere Sitzung, nicht dokumentiert) – nach Nutzerfreigabe per
|
||||
`ALTER ROLE ... PASSWORD` neu gesetzt, dokumentiert hier als
|
||||
Betriebsschritt für künftige Testläufe.
|
||||
|
||||
## Build/Test-Ergebnis (192.168.1.131)
|
||||
|
||||
```
|
||||
go build ./... -> clean
|
||||
go vet ./... -> clean
|
||||
golangci-lint run ./... -> 0 issues
|
||||
go test ./... -p 1 -> alle Pakete bestanden (inkl. objectbackup, restoretest, retentionapi)
|
||||
```
|
||||
|
||||
## Gesamtergebnis
|
||||
|
||||
**Bestanden.** Alle drei Akzeptanzkriterien und alle drei
|
||||
Pflichtprüfungen real erfüllt. Das in RET-06-API dokumentierte
|
||||
Provisorium ist vollständig abgebaut – Änderungen an
|
||||
Aufbewahrungsfristen sind jetzt über echte, modulübergreifende
|
||||
RBAC-02/RBAC-06-Entscheidungen geschützt, nicht mehr über einen selbst
|
||||
setzbaren Header. RET-06 (Frontend) ist damit ohne weiteren
|
||||
RBAC-Blocker startbar.
|
||||
@@ -0,0 +1,82 @@
|
||||
// Package rbacclient ist ein schlanker HTTP-Client für Core RBAC-06
|
||||
// (internal/policyapi, POST /authorize). Archive ist ein physisch
|
||||
// getrenntes Go-Modul und kann Core RBAC-02 (internal/policy) daher
|
||||
// nicht direkt importieren — nur über die HTTP-Schnittstelle von
|
||||
// RBAC-06 aufrufen.
|
||||
package rbacclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// ErrDenied wird zurückgegeben, wenn RBAC-06 die Anfrage ausdrücklich
|
||||
// ablehnt (kein Transport-/Serverfehler, sondern eine bewusste
|
||||
// Policy-Entscheidung).
|
||||
var ErrDenied = errors.New("rbac: zugriff verweigert")
|
||||
|
||||
// Client ruft den RBAC-06-Endpunkt auf.
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
ServiceToken string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func New(baseURL, serviceToken string) *Client {
|
||||
return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: http.DefaultClient}
|
||||
}
|
||||
|
||||
type authorizeRequest struct {
|
||||
Role string `json:"role"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
|
||||
type authorizeResponse struct {
|
||||
Allowed bool `json:"allowed"`
|
||||
}
|
||||
|
||||
// Authorize fragt RBAC-06, ob role die permission besitzt. Jeder
|
||||
// Fehler (Transport, Timeout, unerwarteter Status) wird als Fehler
|
||||
// zurückgegeben, NICHT stillschweigend als "erlaubt" behandelt — der
|
||||
// Aufrufer muss bei einem Fehler fail-closed (verweigern) reagieren.
|
||||
func (c *Client) Authorize(ctx context.Context, role, permission string) (bool, error) {
|
||||
body, err := json.Marshal(authorizeRequest{Role: role, Permission: permission})
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("rbac: request kodieren: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/authorize", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("rbac: request bauen: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Service-Token", c.ServiceToken)
|
||||
|
||||
resp, err := c.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("rbac: aufruf fehlgeschlagen: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf("rbac: unerwarteter status %d", resp.StatusCode)
|
||||
}
|
||||
var out authorizeResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
|
||||
return false, fmt.Errorf("rbac: antwort dekodieren: %w", err)
|
||||
}
|
||||
if !out.Allowed {
|
||||
return false, ErrDenied
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return http.DefaultClient
|
||||
}
|
||||
@@ -6,43 +6,45 @@
|
||||
package retentionapi
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
)
|
||||
|
||||
// requiredRoleHeader ist der Header-Name des PROVISORISCHEN Rollen-
|
||||
// Checks (siehe RequireRole-Dokumentation).
|
||||
const requiredRoleHeader = "X-Admin-Roles"
|
||||
// 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"
|
||||
|
||||
// RequireRole ist ein PROVISORISCHER Rollen-Check, KEIN RBAC-02-Aufruf.
|
||||
//
|
||||
// Core RBAC-02 (internal/policy, Enforcer.Authorize) ist reiner
|
||||
// Go-Code innerhalb des Core-Moduls, hat keine HTTP-Schnittstelle, über
|
||||
// die Archive (physisch getrenntes Go-Modul) es aufrufen könnte —
|
||||
// derselbe "gefunden, aber nicht modulübergreifend verdrahtet"-Befund
|
||||
// wie bei Core FDN-03/FDN-09. Bis ein Core-seitiger HTTP-Wrapper um
|
||||
// RBAC-02 existiert (eigenes, künftiges Core-Ticket, z. B. RBAC-06),
|
||||
// prüft dieser Middleware NUR einen einfachen, selbst gesetzten Header
|
||||
// (`X-Admin-Roles`, kommagetrennt) auf das Vorhandensein der
|
||||
// geforderten Rolle — KEINE echte Autorisierung gegen Core, leicht zu
|
||||
// fälschen von jedem, der den Header selbst setzen kann. Muss ersetzt
|
||||
// werden, sobald der Core-HTTP-Wrapper existiert.
|
||||
func RequireRole(requiredRole string, next http.HandlerFunc) http.HandlerFunc {
|
||||
// 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) {
|
||||
roles := r.Header.Get(requiredRoleHeader)
|
||||
if !containsRole(roles, requiredRole) {
|
||||
http.Error(w, "zugriff verweigert: rolle "+requiredRole+" erforderlich (provisorischer check, siehe RequireRole)", http.StatusForbidden)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func containsRole(commaSeparated, role string) bool {
|
||||
for _, r := range strings.Split(commaSeparated, ",") {
|
||||
if strings.TrimSpace(r) == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,22 +1,57 @@
|
||||
package retentionapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
)
|
||||
|
||||
// TestRequireRole_MissingRoleReturns403 ist die vom Nutzer geforderte
|
||||
// Negativpruefung fuer den provisorischen Rollen-Check: keine/falsche
|
||||
// Rolle => 403, sonst waere der Check nicht pruefbar.
|
||||
func TestRequireRole_MissingRoleReturns403(t *testing.T) {
|
||||
handler := RequireRole(adminRole, func(w http.ResponseWriter, r *http.Request) {
|
||||
const testServiceToken = "test-service-token"
|
||||
|
||||
// fakeRBACServer simuliert Core RBAC-06 (POST /authorize): erlaubt nur
|
||||
// die genau angegebene role/permission-Kombination, verlangt den
|
||||
// Service-Token. Damit werden RequireRBAC-Aufrufe real über HTTP
|
||||
// getestet, ohne echtes Core-Modul.
|
||||
func fakeRBACServer(t *testing.T, allowRole, allowPermission string) *rbacclient.Client {
|
||||
t.Helper()
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /authorize", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("X-Service-Token") != testServiceToken {
|
||||
http.Error(w, "kein service-token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
var req struct {
|
||||
Role string `json:"role"`
|
||||
Permission string `json:"permission"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
allowed := req.Role == allowRole && req.Permission == allowPermission
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]bool{"allowed": allowed})
|
||||
})
|
||||
server := httptest.NewServer(mux)
|
||||
t.Cleanup(server.Close)
|
||||
return rbacclient.New(server.URL, testServiceToken)
|
||||
}
|
||||
|
||||
// TestRequireRBAC_DeniedRoleReturns403 ist die vom Nutzer geforderte
|
||||
// Negativpruefung, jetzt gegen echte Policy-Rules ueber RBAC-06 (nicht
|
||||
// mehr nur ein Header-Wert wie im frueheren Provisorium).
|
||||
func TestRequireRBAC_DeniedRoleReturns403(t *testing.T) {
|
||||
client := fakeRBACServer(t, "archive_admin", "retention.configure")
|
||||
handler := RequireRBAC(client, "retention.configure", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
// Kein Header gesetzt.
|
||||
// Kein Rollen-Header.
|
||||
resp, err := http.Get(server.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
@@ -26,9 +61,9 @@ func TestRequireRole_MissingRoleReturns403(t *testing.T) {
|
||||
t.Fatalf("ohne rollen-header: status = %d, want 403", resp.StatusCode)
|
||||
}
|
||||
|
||||
// Falsche Rolle gesetzt.
|
||||
// Falsche Rolle - RBAC-06 lehnt real ab.
|
||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
||||
req.Header.Set("X-Admin-Roles", "irgendwas_anderes")
|
||||
req.Header.Set(requestedRoleHeader, "irgendwas_anderes")
|
||||
resp2, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
@@ -39,16 +74,18 @@ func TestRequireRole_MissingRoleReturns403(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequireRole_CorrectRoleAllowsAccess ist der positive Gegentest.
|
||||
func TestRequireRole_CorrectRoleAllowsAccess(t *testing.T) {
|
||||
handler := RequireRole(adminRole, func(w http.ResponseWriter, r *http.Request) {
|
||||
// TestRequireRBAC_AllowedRoleGrantsAccess ist der positive Gegentest,
|
||||
// real gegen die fake-RBAC-06-Antwort geprueft.
|
||||
func TestRequireRBAC_AllowedRoleGrantsAccess(t *testing.T) {
|
||||
client := fakeRBACServer(t, "archive_admin", "retention.configure")
|
||||
handler := RequireRBAC(client, "retention.configure", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
||||
req.Header.Set("X-Admin-Roles", "irgendwas_anderes, archive_admin")
|
||||
req.Header.Set(requestedRoleHeader, "archive_admin")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
@@ -58,3 +95,31 @@ func TestRequireRole_CorrectRoleAllowsAccess(t *testing.T) {
|
||||
t.Fatalf("mit korrekter rolle: status = %d, want 200", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRequireRBAC_UnreachableRBACFailsClosed ist die geforderte
|
||||
// Pflichtpruefung: Ausfall von RBAC-06 fuehrt zur Ablehnung, kein
|
||||
// Fallback auf offenen Zugriff.
|
||||
func TestRequireRBAC_UnreachableRBACFailsClosed(t *testing.T) {
|
||||
unreachable := rbacclient.New("http://127.0.0.1:1", testServiceToken)
|
||||
called := false
|
||||
handler := RequireRBAC(unreachable, "retention.configure", func(w http.ResponseWriter, r *http.Request) {
|
||||
called = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
||||
req.Header.Set(requestedRoleHeader, "archive_admin")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("get: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Fatalf("bei nicht erreichbarem rbac-06: status = %d, want 403 (fail-closed)", resp.StatusCode)
|
||||
}
|
||||
if called {
|
||||
t.Fatal("handler haette bei nicht erreichbarem rbac-06 nie erreicht werden duerfen")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,22 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionengine"
|
||||
)
|
||||
|
||||
const adminRole = "archive_admin"
|
||||
// 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
|
||||
// dem provisorischen Rollen-Check (siehe authz.go) — Akzeptanzkriterium
|
||||
// 3: Änderungen an Fristen sind nur berechtigten Rollen zugänglich.
|
||||
func Mount(mux *http.ServeMux, pool *pgxpool.Pool) {
|
||||
mux.HandleFunc("POST /retention-classes", RequireRole(adminRole, configureHandler(pool)))
|
||||
mux.HandleFunc("POST /retention-classes/{class}/deactivate", RequireRole(adminRole, deactivateHandler(pool)))
|
||||
mux.HandleFunc("GET /retention-classes", RequireRole(adminRole, listHandler(pool)))
|
||||
mux.HandleFunc("GET /retention-classes/preview", RequireRole(adminRole, previewHandler(pool)))
|
||||
// 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 {
|
||||
|
||||
@@ -67,7 +67,7 @@ func authedRequest(t *testing.T, method, url string, body []byte) *http.Response
|
||||
if err != nil {
|
||||
t.Fatalf("request erstellen: %v", err)
|
||||
}
|
||||
req.Header.Set("X-Admin-Roles", adminRole)
|
||||
req.Header.Set(requestedRoleHeader, "archive_admin")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("request senden: %v", err)
|
||||
@@ -80,7 +80,7 @@ func authedRequest(t *testing.T, method, url string, body []byte) *http.Response
|
||||
func TestConfigureAndListHandler_RealHTTPRoundTrip(t *testing.T) {
|
||||
pool := requireTestPool(t)
|
||||
mux := http.NewServeMux()
|
||||
Mount(mux, pool)
|
||||
Mount(mux, pool, fakeRBACServer(t, "archive_admin", retentionConfigurePermission))
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
@@ -110,7 +110,7 @@ func TestDeactivateHandler_RealHTTPRoundTrip(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mux := http.NewServeMux()
|
||||
Mount(mux, pool)
|
||||
Mount(mux, pool, fakeRBACServer(t, "archive_admin", retentionConfigurePermission))
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestPreviewHandler_MatchesPeriodicJobResult(t *testing.T) {
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
Mount(mux, pool)
|
||||
Mount(mux, pool, fakeRBACServer(t, "archive_admin", retentionConfigurePermission))
|
||||
server := httptest.NewServer(mux)
|
||||
defer server.Close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user