package api import ( "encoding/json" "net/http" "strconv" "strings" ) // setupWAURoutes registriert alle WAU-Endpunkte func (h *Handler) setupWAURoutes(mux *http.ServeMux) { // Authentifizierte Endpunkte (API-Key oder JWT) mux.HandleFunc("GET /api/v1/customers/{id}/wau/rules", h.listWAURules) mux.HandleFunc("POST /api/v1/customers/{id}/wau/rules", h.addWAURule) mux.HandleFunc("DELETE /api/v1/customers/{id}/wau/rules/{rid}", h.deleteWAURule) mux.HandleFunc("GET /api/v1/customers/{id}/wau/inventory", h.getWAUInventory) mux.HandleFunc("GET /api/v1/customers/{id}/wau/compliance", h.getWAUCompliance) // Compliance-Summary für alle Kunden mux.HandleFunc("GET /api/v1/wau/compliance/summary", h.getWAUComplianceSummary) // Plaintext-Endpunkte für WAU (kein Auth nötig — enthält nur App-IDs) mux.HandleFunc("GET /api/v1/customers/{id}/wau/included", h.wauIncludedList) mux.HandleFunc("GET /api/v1/customers/{id}/wau/excluded", h.wauExcludedList) } // GET /api/v1/customers/{id}/wau/rules func (h *Handler) listWAURules(w http.ResponseWriter, r *http.Request) { customerID, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, http.StatusBadRequest, "Ungültige Kunden-ID") return } rules, err := h.db.ListWAURules(customerID) if err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Laden") return } writeJSON(w, http.StatusOK, rules) } // POST /api/v1/customers/{id}/wau/rules func (h *Handler) addWAURule(w http.ResponseWriter, r *http.Request) { customerID, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, http.StatusBadRequest, "Ungültige Kunden-ID") return } var req struct { WingetID string `json:"winget_id"` DisplayName string `json:"display_name"` RuleType string `json:"rule_type"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "Ungültige Anfrage") return } req.WingetID = strings.TrimSpace(req.WingetID) req.DisplayName = strings.TrimSpace(req.DisplayName) if req.WingetID == "" { writeError(w, http.StatusBadRequest, "winget_id erforderlich") return } if req.RuleType != "allow" && req.RuleType != "block" { writeError(w, http.StatusBadRequest, "rule_type muss 'allow' oder 'block' sein") return } if req.DisplayName == "" { req.DisplayName = req.WingetID } rule, err := h.db.AddWAURule(customerID, req.WingetID, req.DisplayName, req.RuleType) if err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Speichern") return } h.auditLog(r, "wau.rule.add", "customer", strconv.Itoa(customerID), "", req.RuleType+": "+req.WingetID) writeJSON(w, http.StatusCreated, rule) } // DELETE /api/v1/customers/{id}/wau/rules/{rid} func (h *Handler) deleteWAURule(w http.ResponseWriter, r *http.Request) { customerID, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, http.StatusBadRequest, "Ungültige Kunden-ID") return } ruleID, err := strconv.Atoi(r.PathValue("rid")) if err != nil { writeError(w, http.StatusBadRequest, "Ungültige Regel-ID") return } ok, err := h.db.DeleteWAURule(customerID, ruleID) if err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Löschen") return } if !ok { writeError(w, http.StatusNotFound, "Regel nicht gefunden") return } h.auditLog(r, "wau.rule.delete", "customer", strconv.Itoa(customerID), "", strconv.Itoa(ruleID)) writeJSON(w, http.StatusOK, map[string]string{"message": "Regel gelöscht"}) } // GET /api/v1/customers/{id}/wau/inventory func (h *Handler) getWAUInventory(w http.ResponseWriter, r *http.Request) { customerID, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, http.StatusBadRequest, "Ungültige Kunden-ID") return } entries, err := h.db.GetWAUSoftwareInventory(customerID) if err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Laden des Inventars") return } writeJSON(w, http.StatusOK, entries) } // GET /api/v1/customers/{id}/wau/included → Plaintext für WAU func (h *Handler) wauIncludedList(w http.ResponseWriter, r *http.Request) { h.serveWAUList(w, r, "allow") } // GET /api/v1/customers/{id}/wau/excluded → Plaintext für WAU func (h *Handler) wauExcludedList(w http.ResponseWriter, r *http.Request) { h.serveWAUList(w, r, "block") } func (h *Handler) serveWAUList(w http.ResponseWriter, r *http.Request, ruleType string) { customerID, err := strconv.Atoi(r.PathValue("id")) if err != nil { http.Error(w, "Ungültige Kunden-ID", http.StatusBadRequest) return } ids, err := h.db.GetWAUList(customerID, ruleType) if err != nil { http.Error(w, "Fehler", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusOK) w.Write([]byte(strings.Join(ids, "\r\n"))) if len(ids) > 0 { w.Write([]byte("\r\n")) } } // wauExpectedURLs leitet die erwarteten WAU-URLs aus dem Request-Host ab func wauExpectedURLs(r *http.Request, customerID int) (includeURL, excludeURL string) { scheme := "https" if r.TLS == nil && (r.Header.Get("X-Forwarded-Proto") == "" || r.Header.Get("X-Forwarded-Proto") == "http") { scheme = "http" } if fwd := r.Header.Get("X-Forwarded-Proto"); fwd != "" { scheme = fwd } host := r.Host base := scheme + "://" + host includeURL = base + "/api/v1/customers/" + strconv.Itoa(customerID) + "/wau/included" excludeURL = base + "/api/v1/customers/" + strconv.Itoa(customerID) + "/wau/excluded" return } // GET /api/v1/customers/{id}/wau/compliance func (h *Handler) getWAUCompliance(w http.ResponseWriter, r *http.Request) { customerID, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeError(w, http.StatusBadRequest, "Ungültige Kunden-ID") return } includeURL, excludeURL := wauExpectedURLs(r, customerID) agents, err := h.db.GetWAUCompliance(customerID, includeURL, excludeURL) if err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Laden der Compliance-Daten") return } writeJSON(w, http.StatusOK, map[string]interface{}{ "agents": agents, "include_url": includeURL, "exclude_url": excludeURL, }) } // GET /api/v1/wau/compliance/summary — Zusammenfassung für alle Kunden func (h *Handler) getWAUComplianceSummary(w http.ResponseWriter, r *http.Request) { // Ohne customer_id können wir keine URLs ableiten — wir geben Summary ohne URL-Check zurück // oder nutzen customer_id=0 als Wildcard (kein URL-Check) summaries, err := h.db.GetAllCustomersWAUSummary("", "") if err != nil { writeError(w, http.StatusInternalServerError, "Fehler beim Laden der Compliance-Daten") return } writeJSON(w, http.StatusOK, summaries) }