package retentionapi import ( "encoding/json" "net/http" "net/http/httptest" "testing" "gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient" ) 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 Rollen-Header. resp, err := http.Get(server.URL) if err != nil { t.Fatalf("get: %v", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusForbidden { t.Fatalf("ohne rollen-header: status = %d, want 403", resp.StatusCode) } // Falsche Rolle - RBAC-06 lehnt real ab. req, _ := http.NewRequest(http.MethodGet, server.URL, nil) req.Header.Set(requestedRoleHeader, "irgendwas_anderes") resp2, err := http.DefaultClient.Do(req) if err != nil { t.Fatalf("get: %v", err) } defer func() { _ = resp2.Body.Close() }() if resp2.StatusCode != http.StatusForbidden { t.Fatalf("mit falscher rolle: status = %d, want 403", resp2.StatusCode) } } // 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(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.StatusOK { 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") } }