From d23bfa0376194a486c8cf92d76247ada26e70f68 Mon Sep 17 00:00:00 2001 From: Nick Adam Date: Fri, 21 Nov 2025 00:58:34 +0100 Subject: [PATCH] fixed permission implementation for ressources --- backend/main.go | 735 +++++++++++++++++++++++++-- backend/spaces.db-shm | Bin 32768 -> 32768 bytes backend/spaces.db-wal | Bin 2809872 -> 3201272 bytes frontend/src/hooks/usePermissions.js | 86 ++++ frontend/src/pages/SpaceDetail.jsx | 61 ++- frontend/src/pages/Spaces.jsx | 29 +- 6 files changed, 857 insertions(+), 54 deletions(-) create mode 100644 frontend/src/hooks/usePermissions.js diff --git a/backend/main.go b/backend/main.go index a86dee6..41b4c48 100644 --- a/backend/main.go +++ b/backend/main.go @@ -823,16 +823,35 @@ func getSpacesHandler(w http.ResponseWriter, r *http.Request) { return } - // Verwende Prepared Statement für bessere Performance und Sicherheit - stmt, err := db.Prepare("SELECT id, name, description, created_at FROM spaces ORDER BY created_at DESC") + // Hole Benutzer-ID + userID, _ := getUserFromRequest(r) + + // Hole alle Spaces, auf die der Benutzer Zugriff hat + accessibleSpaceIDs, err := getAccessibleSpaceIDs(userID) if err != nil { - http.Error(w, "Fehler beim Vorbereiten der Abfrage", http.StatusInternalServerError) - log.Printf("Fehler beim Vorbereiten der Abfrage: %v", err) + http.Error(w, "Fehler beim Prüfen der Berechtigungen", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigungen: %v", err) return } - defer stmt.Close() - rows, err := stmt.Query() + // Wenn der Benutzer keinen Zugriff auf Spaces hat, gebe leeres Array zurück + if len(accessibleSpaceIDs) == 0 { + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode([]Space{}) + return + } + + // Baue Query mit IN-Klausel für die zugänglichen Spaces + placeholders := make([]string, len(accessibleSpaceIDs)) + args := make([]interface{}, len(accessibleSpaceIDs)) + for i, spaceID := range accessibleSpaceIDs { + placeholders[i] = "?" + args[i] = spaceID + } + + query := fmt.Sprintf("SELECT id, name, description, created_at FROM spaces WHERE id IN (%s) ORDER BY created_at DESC", strings.Join(placeholders, ",")) + + rows, err := db.Query(query, args...) if err != nil { http.Error(w, "Fehler beim Abrufen der Spaces", http.StatusInternalServerError) log.Printf("Fehler beim Abrufen der Spaces: %v", err) @@ -888,6 +907,33 @@ func createSpaceHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Nur FULL_ACCESS darf Spaces erstellen + userID, username := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + // Prüfe, ob der Benutzer FULL_ACCESS hat (ohne Space-Beschränkung) + permissions, err := getUserPermissions(userID) + if err != nil || len(permissions.Groups) == 0 { + http.Error(w, "Keine Berechtigung zum Erstellen von Spaces", http.StatusForbidden) + return + } + + hasFullAccess := false + for _, group := range permissions.Groups { + if group.Permission == PermissionFullAccess { + hasFullAccess = true + break + } + } + + if !hasFullAccess { + http.Error(w, "Keine Berechtigung zum Erstellen von Spaces. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + var req CreateSpaceRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) @@ -904,7 +950,7 @@ func createSpaceHandler(w http.ResponseWriter, r *http.Request) { createdAt := time.Now() // Speichere in Datenbank - _, err := db.Exec( + _, err = db.Exec( "INSERT INTO spaces (id, name, description, created_at) VALUES (?, ?, ?, ?)", id, req.Name, req.Description, createdAt, ) @@ -926,7 +972,6 @@ func createSpaceHandler(w http.ResponseWriter, r *http.Request) { // Audit-Log: Space erstellt if auditService != nil { - userID, username := getUserFromRequest(r) ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "CREATE", "space", id, userID, username, map[string]interface{}{ "name": req.Name, @@ -955,9 +1000,28 @@ func deleteSpaceHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Nur FULL_ACCESS darf Spaces löschen + userID, username := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasPermission, err := hasPermission(userID, id, PermissionFullAccess) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasPermission { + http.Error(w, "Keine Berechtigung zum Löschen von Spaces. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe ob der Space existiert var exists bool - err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", id).Scan(&exists) + err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", id).Scan(&exists) if err != nil { http.Error(w, "Fehler beim Prüfen des Space", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen des Space: %v", err) @@ -1040,7 +1104,6 @@ func deleteSpaceHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{"message": "Space erfolgreich gelöscht"}) // Audit-Log: Space gelöscht - userID, username := getUserFromRequest(r) ipAddress, userAgent := getRequestInfo(r) details := map[string]interface{}{ "message": fmt.Sprintf("Space gelöscht: %s", id), @@ -1071,8 +1134,27 @@ func getSpaceFqdnCountHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Benutzer muss Zugriff auf den Space haben + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasAccess, err := hasSpaceAccess(userID, spaceID) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasAccess { + http.Error(w, "Keine Berechtigung für diesen Space", http.StatusForbidden) + return + } + var count int - err := db.QueryRow("SELECT COUNT(*) FROM fqdns WHERE space_id = ?", spaceID).Scan(&count) + err = db.QueryRow("SELECT COUNT(*) FROM fqdns WHERE space_id = ?", spaceID).Scan(&count) if err != nil { http.Error(w, "Fehler beim Abrufen der FQDN-Anzahl", http.StatusInternalServerError) log.Printf("Fehler beim Abrufen der FQDN-Anzahl: %v", err) @@ -1101,9 +1183,28 @@ func getFqdnsHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Benutzer muss Zugriff auf den Space haben + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasAccess, err := hasSpaceAccess(userID, spaceID) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasAccess { + http.Error(w, "Keine Berechtigung für diesen Space", http.StatusForbidden) + return + } + // Prüfe ob der Space existiert var exists bool - err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists) + err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists) if err != nil { http.Error(w, "Fehler beim Prüfen des Space", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen des Space: %v", err) @@ -1175,9 +1276,28 @@ func createFqdnHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: READ_WRITE oder FULL_ACCESS erforderlich zum Erstellen von FQDNs + userID, username := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasPermission, err := hasPermission(userID, spaceID, PermissionReadWrite) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasPermission { + http.Error(w, "Keine Berechtigung zum Erstellen von FQDNs. Lesen/Schreiben oder Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe ob der Space existiert var exists bool - err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists) + err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists) if err != nil { http.Error(w, "Fehler beim Prüfen des Space", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen des Space: %v", err) @@ -1241,7 +1361,6 @@ func createFqdnHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(newFqdn) // Audit-Log: FQDN erstellt - userID, username := getUserFromRequest(r) ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "CREATE", "fqdn", id, userID, username, map[string]interface{}{ "fqdn": req.FQDN, @@ -1271,9 +1390,28 @@ func deleteFqdnHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Nur FULL_ACCESS darf FQDNs löschen + userID, username := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasPermission, err := hasPermission(userID, spaceID, PermissionFullAccess) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasPermission { + http.Error(w, "Keine Berechtigung zum Löschen von FQDNs. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe ob der FQDN existiert und zum Space gehört var exists bool - err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM fqdns WHERE id = ? AND space_id = ?)", fqdnID, spaceID).Scan(&exists) + err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM fqdns WHERE id = ? AND space_id = ?)", fqdnID, spaceID).Scan(&exists) if err != nil { http.Error(w, "Fehler beim Prüfen des FQDN", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen des FQDN: %v", err) @@ -1337,7 +1475,6 @@ func deleteFqdnHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]string{"message": "FQDN erfolgreich gelöscht"}) // Audit-Log: FQDN gelöscht - userID, username := getUserFromRequest(r) ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "DELETE", "fqdn", fqdnID, userID, username, map[string]interface{}{ "spaceId": spaceID, @@ -1364,9 +1501,28 @@ func deleteAllFqdnsHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Nur FULL_ACCESS darf alle FQDNs eines Spaces löschen + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasPermission, err := hasPermission(userID, spaceID, PermissionFullAccess) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasPermission { + http.Error(w, "Keine Berechtigung zum Löschen aller FQDNs. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe ob der Space existiert var exists bool - err := db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists) + err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists) if err != nil { http.Error(w, "Fehler beim Prüfen des Space", http.StatusInternalServerError) log.Printf("Fehler beim Prüfen des Space: %v", err) @@ -1438,6 +1594,32 @@ func deleteAllFqdnsGlobalHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Nur FULL_ACCESS darf alle FQDNs global löschen + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + permissions, err := getUserPermissions(userID) + if err != nil || len(permissions.Groups) == 0 { + http.Error(w, "Keine Berechtigung zum Löschen aller FQDNs. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + + hasFullAccess := false + for _, group := range permissions.Groups { + if group.Permission == PermissionFullAccess { + hasFullAccess = true + break + } + } + + if !hasFullAccess { + http.Error(w, "Keine Berechtigung zum Löschen aller FQDNs. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe Query-Parameter für Bestätigung (Sicherheitsmaßnahme) confirm := r.URL.Query().Get("confirm") if confirm != "true" { @@ -1447,7 +1629,7 @@ func deleteAllFqdnsGlobalHandler(w http.ResponseWriter, r *http.Request) { // Zähle zuerst die Anzahl der FQDNs var totalCount int - err := db.QueryRow("SELECT COUNT(*) FROM fqdns").Scan(&totalCount) + err = db.QueryRow("SELECT COUNT(*) FROM fqdns").Scan(&totalCount) if err != nil { http.Error(w, "Fehler beim Zählen der FQDNs", http.StatusInternalServerError) log.Printf("Fehler beim Zählen der FQDNs: %v", err) @@ -1523,6 +1705,32 @@ func deleteAllCSRsHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Nur FULL_ACCESS darf alle CSRs löschen + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + permissions, err := getUserPermissions(userID) + if err != nil || len(permissions.Groups) == 0 { + http.Error(w, "Keine Berechtigung zum Löschen aller CSRs. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + + hasFullAccess := false + for _, group := range permissions.Groups { + if group.Permission == PermissionFullAccess { + hasFullAccess = true + break + } + } + + if !hasFullAccess { + http.Error(w, "Keine Berechtigung zum Löschen aller CSRs. Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe Query-Parameter für Bestätigung (Sicherheitsmaßnahme) confirm := r.URL.Query().Get("confirm") if confirm != "true" { @@ -1532,7 +1740,7 @@ func deleteAllCSRsHandler(w http.ResponseWriter, r *http.Request) { // Zähle zuerst die Anzahl der CSRs var totalCount int - err := db.QueryRow("SELECT COUNT(*) FROM csrs").Scan(&totalCount) + err = db.QueryRow("SELECT COUNT(*) FROM csrs").Scan(&totalCount) if err != nil { http.Error(w, "Fehler beim Zählen der CSRs", http.StatusInternalServerError) log.Printf("Fehler beim Zählen der CSRs: %v", err) @@ -1615,6 +1823,25 @@ func uploadCSRHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: READ_WRITE oder FULL_ACCESS erforderlich zum Hochladen von CSRs + userID, username := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasPermission, err := hasPermission(userID, spaceID, PermissionReadWrite) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasPermission { + http.Error(w, "Keine Berechtigung zum Hochladen von CSRs. Lesen/Schreiben oder Vollzugriff erforderlich.", http.StatusForbidden) + return + } + // Prüfe ob Space existiert var spaceExists bool err = db.QueryRow("SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&spaceExists) @@ -1761,7 +1988,6 @@ func uploadCSRHandler(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(newCSR) // Audit-Log: CSR hochgeladen - userID, username := getUserFromRequest(r) ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "UPLOAD", "csr", csrID, userID, username, map[string]interface{}{ "fqdnId": fqdnID, @@ -1790,6 +2016,25 @@ func getCSRByFQDNHandler(w http.ResponseWriter, r *http.Request) { return } + // Prüfe Berechtigung: Benutzer muss Zugriff auf den Space haben (READ, READ_WRITE oder FULL_ACCESS) + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasAccess, err := hasSpaceAccess(userID, spaceID) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasAccess { + http.Error(w, "Keine Berechtigung für diesen Space", http.StatusForbidden) + return + } + // Prüfe ob nur der neueste CSR gewünscht ist latestOnly := r.URL.Query().Get("latest") == "true" @@ -2502,6 +2747,113 @@ components: // User Handler Functions +func getUserPermissionsHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + + if r.Method == "OPTIONS" { + w.WriteHeader(http.StatusOK) + return + } + + // Hole Benutzer-ID + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + // Hole Berechtigungen + permissions, err := getUserPermissions(userID) + if err != nil { + http.Error(w, "Fehler beim Abrufen der Berechtigungen", http.StatusInternalServerError) + log.Printf("Fehler beim Abrufen der Berechtigungen: %v", err) + return + } + + // Erstelle vereinfachte Antwort für Frontend + canCreateFqdn := make(map[string]bool) + canDeleteFqdn := make(map[string]bool) + canUploadCSR := make(map[string]bool) + canSignCSR := make(map[string]bool) + + response := map[string]interface{}{ + "userId": userID, + "hasFullAccess": permissions.HasFullAccess, + "accessibleSpaces": []string{}, + "permissions": map[string]interface{}{ + "canCreateSpace": false, + "canDeleteSpace": false, + "canCreateFqdn": canCreateFqdn, + "canDeleteFqdn": canDeleteFqdn, + "canUploadCSR": canUploadCSR, + "canSignCSR": canSignCSR, + }, + } + + // Hole alle Spaces + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + spaceRows, err := db.QueryContext(ctx, "SELECT id FROM spaces") + if err == nil { + defer spaceRows.Close() + var allSpaceIDs []string + for spaceRows.Next() { + var spaceID string + if err := spaceRows.Scan(&spaceID); err == nil { + allSpaceIDs = append(allSpaceIDs, spaceID) + } + } + spaceRows.Close() + + // Prüfe für jeden Space die Berechtigungen + accessibleSpaces := []string{} + + for _, spaceID := range allSpaceIDs { + hasAccess, _ := hasSpaceAccess(userID, spaceID) + if hasAccess { + accessibleSpaces = append(accessibleSpaces, spaceID) + + // Prüfe READ_WRITE für FQDN erstellen und CSR upload/sign + hasReadWrite, _ := hasPermission(userID, spaceID, PermissionReadWrite) + canCreateFqdn[spaceID] = hasReadWrite + canUploadCSR[spaceID] = hasReadWrite + canSignCSR[spaceID] = hasReadWrite + + // Prüfe FULL_ACCESS für FQDN löschen + hasFullAccess, _ := hasPermission(userID, spaceID, PermissionFullAccess) + canDeleteFqdn[spaceID] = hasFullAccess + } + } + + response["accessibleSpaces"] = accessibleSpaces + perms := response["permissions"].(map[string]interface{}) + perms["canCreateFqdn"] = canCreateFqdn + perms["canDeleteFqdn"] = canDeleteFqdn + perms["canUploadCSR"] = canUploadCSR + perms["canSignCSR"] = canSignCSR + } + + // Prüfe globale Berechtigungen (Space erstellen/löschen) + hasFullAccessGlobal := false + for _, group := range permissions.Groups { + if group.Permission == PermissionFullAccess { + hasFullAccessGlobal = true + break + } + } + + perms := response["permissions"].(map[string]interface{}) + perms["canCreateSpace"] = hasFullAccessGlobal + perms["canDeleteSpace"] = hasFullAccessGlobal + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + func getUsersHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") @@ -3612,6 +3964,262 @@ func getUserFromRequest(r *http.Request) (userID, username string) { return id, username } +// UserPermissionInfo enthält die Berechtigungsinformationen eines Benutzers +type UserPermissionInfo struct { + UserID string + Groups []PermissionGroupInfo + HasFullAccess bool // true wenn der Benutzer mindestens eine FULL_ACCESS Gruppe hat +} + +// PermissionGroupInfo enthält Informationen über eine Berechtigungsgruppe +type PermissionGroupInfo struct { + GroupID string + Permission PermissionLevel + SpaceIDs []string // Leer bedeutet Zugriff auf alle Spaces +} + +// getUserPermissions ruft die Berechtigungen eines Benutzers ab +func getUserPermissions(userID string) (*UserPermissionInfo, error) { + if userID == "" { + return nil, fmt.Errorf("userID ist leer") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + // Hole alle Gruppen des Benutzers mit ihren Berechtigungen + query := ` + SELECT pg.id, pg.permission + FROM permission_groups pg + INNER JOIN user_groups ug ON pg.id = ug.group_id + WHERE ug.user_id = ? + ` + rows, err := db.QueryContext(ctx, query, userID) + if err != nil { + return nil, fmt.Errorf("fehler beim Abrufen der Benutzergruppen: %w", err) + } + defer rows.Close() + + info := &UserPermissionInfo{ + UserID: userID, + Groups: []PermissionGroupInfo{}, + } + + var groupIDs []string + for rows.Next() { + var groupID string + var permission string + if err := rows.Scan(&groupID, &permission); err != nil { + continue + } + groupIDs = append(groupIDs, groupID) + + groupInfo := PermissionGroupInfo{ + GroupID: groupID, + Permission: PermissionLevel(permission), + SpaceIDs: []string{}, + } + + if PermissionLevel(permission) == PermissionFullAccess { + info.HasFullAccess = true + } + + info.Groups = append(info.Groups, groupInfo) + } + + // Hole Space-Zuweisungen für alle Gruppen + if len(groupIDs) > 0 { + placeholders := make([]string, len(groupIDs)) + args := make([]interface{}, len(groupIDs)) + for i, id := range groupIDs { + placeholders[i] = "?" + args[i] = id + } + + spaceQuery := fmt.Sprintf(` + SELECT group_id, space_id + FROM group_spaces + WHERE group_id IN (%s) + `, strings.Join(placeholders, ",")) + + spaceRows, err := db.QueryContext(ctx, spaceQuery, args...) + if err == nil { + spaceMap := make(map[string][]string) + for spaceRows.Next() { + var groupID, spaceID string + if err := spaceRows.Scan(&groupID, &spaceID); err == nil { + spaceMap[groupID] = append(spaceMap[groupID], spaceID) + } + } + spaceRows.Close() + + // Aktualisiere SpaceIDs für jede Gruppe + for i := range info.Groups { + if spaceIDs, exists := spaceMap[info.Groups[i].GroupID]; exists { + info.Groups[i].SpaceIDs = spaceIDs + } + } + } + } + + return info, nil +} + +// hasSpaceAccess prüft, ob ein Benutzer Zugriff auf einen bestimmten Space hat +func hasSpaceAccess(userID, spaceID string) (bool, error) { + if userID == "" { + return false, nil + } + + permissions, err := getUserPermissions(userID) + if err != nil { + return false, err + } + + // Wenn der Benutzer keine Gruppen hat, hat er keinen Zugriff + if len(permissions.Groups) == 0 { + return false, nil + } + + // Prüfe für jede Gruppe, ob der Benutzer Zugriff auf den Space hat + for _, group := range permissions.Groups { + // Wenn die Gruppe keine Spaces zugewiesen hat, hat der Benutzer Zugriff auf alle Spaces + if len(group.SpaceIDs) == 0 { + return true, nil + } + // Prüfe, ob der Space in der Liste der zugewiesenen Spaces ist + for _, assignedSpaceID := range group.SpaceIDs { + if assignedSpaceID == spaceID { + return true, nil + } + } + } + + return false, nil +} + +// hasPermission prüft, ob ein Benutzer eine bestimmte Berechtigung für einen Space hat +// requiredPermission kann READ, READ_WRITE oder FULL_ACCESS sein +func hasPermission(userID, spaceID string, requiredPermission PermissionLevel) (bool, error) { + if userID == "" { + return false, nil + } + + permissions, err := getUserPermissions(userID) + if err != nil { + return false, err + } + + // Wenn der Benutzer keine Gruppen hat, hat er keine Berechtigung + if len(permissions.Groups) == 0 { + return false, nil + } + + // Prüfe für jede Gruppe + for _, group := range permissions.Groups { + hasAccess := false + + // Prüfe, ob der Benutzer Zugriff auf den Space hat + if len(group.SpaceIDs) == 0 { + // Keine Space-Zuweisungen = Zugriff auf alle Spaces + hasAccess = true + } else { + // Prüfe, ob der Space in der Liste ist + for _, assignedSpaceID := range group.SpaceIDs { + if assignedSpaceID == spaceID { + hasAccess = true + break + } + } + } + + if !hasAccess { + continue + } + + // Prüfe die Berechtigungsstufe + switch requiredPermission { + case PermissionRead: + // READ, READ_WRITE und FULL_ACCESS haben alle READ-Berechtigung + return true, nil + case PermissionReadWrite: + // READ_WRITE und FULL_ACCESS haben READ_WRITE-Berechtigung + if group.Permission == PermissionReadWrite || group.Permission == PermissionFullAccess { + return true, nil + } + case PermissionFullAccess: + // Nur FULL_ACCESS hat FULL_ACCESS-Berechtigung + if group.Permission == PermissionFullAccess { + return true, nil + } + } + } + + return false, nil +} + +// getAccessibleSpaceIDs gibt alle Space-IDs zurück, auf die der Benutzer Zugriff hat +func getAccessibleSpaceIDs(userID string) ([]string, error) { + if userID == "" { + return []string{}, nil + } + + permissions, err := getUserPermissions(userID) + if err != nil { + return []string{}, err + } + + // Wenn der Benutzer keine Gruppen hat, hat er keinen Zugriff + if len(permissions.Groups) == 0 { + return []string{}, nil + } + + // Sammle alle zugewiesenen Spaces + spaceIDMap := make(map[string]bool) + hasUnrestrictedAccess := false + + for _, group := range permissions.Groups { + // Wenn eine Gruppe keine Spaces zugewiesen hat, hat der Benutzer Zugriff auf alle Spaces + if len(group.SpaceIDs) == 0 { + hasUnrestrictedAccess = true + break + } + // Sammle alle zugewiesenen Spaces + for _, spaceID := range group.SpaceIDs { + spaceIDMap[spaceID] = true + } + } + + // Wenn der Benutzer Zugriff auf alle Spaces hat, hole alle Spaces aus der DB + if hasUnrestrictedAccess { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + + rows, err := db.QueryContext(ctx, "SELECT id FROM spaces") + if err != nil { + return []string{}, err + } + defer rows.Close() + + var spaceIDs []string + for rows.Next() { + var spaceID string + if err := rows.Scan(&spaceID); err == nil { + spaceIDs = append(spaceIDs, spaceID) + } + } + return spaceIDs, nil + } + + // Konvertiere Map zu Slice + spaceIDs := make([]string, 0, len(spaceIDMap)) + for spaceID := range spaceIDMap { + spaceIDs = append(spaceIDs, spaceID) + } + + return spaceIDs, nil +} + // Helper-Funktion zum Extrahieren von IP-Adresse und User-Agent aus Request func getRequestInfo(r *http.Request) (ipAddress, userAgent string) { // Hole IP-Adresse @@ -4159,18 +4767,18 @@ func main() { // Protected Routes (Basic Auth erforderlich) api.HandleFunc("/stats", basicAuthMiddleware(getStatsHandler)).Methods("GET", "OPTIONS") - api.HandleFunc("/spaces", getSpacesHandler).Methods("GET", "OPTIONS") - api.HandleFunc("/spaces", createSpaceHandler).Methods("POST", "OPTIONS") - api.HandleFunc("/spaces/{id}", deleteSpaceHandler).Methods("DELETE", "OPTIONS") - api.HandleFunc("/spaces/{id}/fqdns/count", getSpaceFqdnCountHandler).Methods("GET", "OPTIONS") - api.HandleFunc("/spaces/{id}/fqdns", getFqdnsHandler).Methods("GET", "OPTIONS") - api.HandleFunc("/spaces/{id}/fqdns", createFqdnHandler).Methods("POST", "OPTIONS") - api.HandleFunc("/spaces/{id}/fqdns", deleteAllFqdnsHandler).Methods("DELETE", "OPTIONS") - api.HandleFunc("/spaces/{id}/fqdns/{fqdnId}", deleteFqdnHandler).Methods("DELETE", "OPTIONS") - api.HandleFunc("/fqdns", deleteAllFqdnsGlobalHandler).Methods("DELETE", "OPTIONS") - api.HandleFunc("/spaces/{spaceId}/fqdns/{fqdnId}/csr", uploadCSRHandler).Methods("POST", "OPTIONS") - api.HandleFunc("/spaces/{spaceId}/fqdns/{fqdnId}/csr", getCSRByFQDNHandler).Methods("GET", "OPTIONS") - api.HandleFunc("/csrs", deleteAllCSRsHandler).Methods("DELETE", "OPTIONS") + api.HandleFunc("/spaces", basicAuthMiddleware(getSpacesHandler)).Methods("GET", "OPTIONS") + api.HandleFunc("/spaces", basicAuthMiddleware(createSpaceHandler)).Methods("POST", "OPTIONS") + api.HandleFunc("/spaces/{id}", basicAuthMiddleware(deleteSpaceHandler)).Methods("DELETE", "OPTIONS") + api.HandleFunc("/spaces/{id}/fqdns/count", basicAuthMiddleware(getSpaceFqdnCountHandler)).Methods("GET", "OPTIONS") + api.HandleFunc("/spaces/{id}/fqdns", basicAuthMiddleware(getFqdnsHandler)).Methods("GET", "OPTIONS") + api.HandleFunc("/spaces/{id}/fqdns", basicAuthMiddleware(createFqdnHandler)).Methods("POST", "OPTIONS") + api.HandleFunc("/spaces/{id}/fqdns", basicAuthMiddleware(deleteAllFqdnsHandler)).Methods("DELETE", "OPTIONS") + api.HandleFunc("/spaces/{id}/fqdns/{fqdnId}", basicAuthMiddleware(deleteFqdnHandler)).Methods("DELETE", "OPTIONS") + api.HandleFunc("/fqdns", basicAuthMiddleware(deleteAllFqdnsGlobalHandler)).Methods("DELETE", "OPTIONS") + api.HandleFunc("/spaces/{spaceId}/fqdns/{fqdnId}/csr", basicAuthMiddleware(uploadCSRHandler)).Methods("POST", "OPTIONS") + api.HandleFunc("/spaces/{spaceId}/fqdns/{fqdnId}/csr", basicAuthMiddleware(getCSRByFQDNHandler)).Methods("GET", "OPTIONS") + api.HandleFunc("/csrs", basicAuthMiddleware(deleteAllCSRsHandler)).Methods("DELETE", "OPTIONS") // User Routes api.HandleFunc("/users", getUsersHandler).Methods("GET", "OPTIONS") @@ -4180,6 +4788,7 @@ func main() { api.HandleFunc("/users/{id}", deleteUserHandler).Methods("DELETE", "OPTIONS") api.HandleFunc("/users/{id}/avatar", basicAuthMiddleware(getAvatarHandler)).Methods("GET", "OPTIONS") api.HandleFunc("/users/{id}/avatar", basicAuthMiddleware(uploadAvatarHandler)).Methods("POST", "OPTIONS") + api.HandleFunc("/user/permissions", basicAuthMiddleware(getUserPermissionsHandler)).Methods("GET", "OPTIONS") // Permission Groups Routes (Protected) api.HandleFunc("/permission-groups", basicAuthMiddleware(getPermissionGroupsHandler)).Methods("GET", "OPTIONS") @@ -4477,6 +5086,25 @@ func signCSRHandler(w http.ResponseWriter, r *http.Request) { spaceID := vars["spaceId"] fqdnID := vars["fqdnId"] + // Prüfe Berechtigung: READ_WRITE oder FULL_ACCESS erforderlich zum Signieren von CSRs + userID, username := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasPermission, err := hasPermission(userID, spaceID, PermissionReadWrite) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasPermission { + http.Error(w, "Keine Berechtigung zum Signieren von CSRs. Lesen/Schreiben oder Vollzugriff erforderlich.", http.StatusForbidden) + return + } + var req struct { ProviderID string `json:"providerId"` CSRID string `json:"csrId,omitempty"` // Optional: spezifischer CSR, sonst neuester @@ -4495,7 +5123,7 @@ func signCSRHandler(w http.ResponseWriter, r *http.Request) { // Hole neuesten CSR für den FQDN var csrPEM string var csrID string - err := db.QueryRow(` + err = db.QueryRow(` SELECT id, csr_pem FROM csrs WHERE fqdn_id = ? AND space_id = ? @@ -4579,7 +5207,6 @@ func signCSRHandler(w http.ResponseWriter, r *http.Request) { }) // Audit-Log: CSR signiert - userID, username := getUserFromRequest(r) ipAddress, userAgent := getRequestInfo(r) auditService.Track(r.Context(), "SIGN", "csr", csrID, userID, username, map[string]interface{}{ "providerId": req.ProviderID, @@ -4606,6 +5233,25 @@ func getCertificatesHandler(w http.ResponseWriter, r *http.Request) { spaceID := vars["spaceId"] fqdnID := vars["fqdnId"] + // Prüfe Berechtigung: Benutzer muss Zugriff auf den Space haben (READ, READ_WRITE oder FULL_ACCESS) + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasAccess, err := hasSpaceAccess(userID, spaceID) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasAccess { + http.Error(w, "Keine Berechtigung für diesen Space", http.StatusForbidden) + return + } + // Hole alle Zertifikate für diesen FQDN rows, err := db.Query(` SELECT id, csr_id, certificate_id, provider_id, certificate_pem, status, created_at @@ -4661,9 +5307,28 @@ func refreshCertificateHandler(w http.ResponseWriter, r *http.Request) { fqdnID := vars["fqdnId"] certID := vars["certId"] + // Prüfe Berechtigung: Benutzer muss Zugriff auf den Space haben (READ, READ_WRITE oder FULL_ACCESS) + userID, _ := getUserFromRequest(r) + if userID == "" { + http.Error(w, "Nicht authentifiziert", http.StatusUnauthorized) + return + } + + hasAccess, err := hasSpaceAccess(userID, spaceID) + if err != nil { + http.Error(w, "Fehler beim Prüfen der Berechtigung", http.StatusInternalServerError) + log.Printf("Fehler beim Prüfen der Berechtigung: %v", err) + return + } + + if !hasAccess { + http.Error(w, "Keine Berechtigung für diesen Space", http.StatusForbidden) + return + } + // Hole Zertifikat aus DB var certificateID, providerID string - err := db.QueryRow(` + err = db.QueryRow(` SELECT certificate_id, provider_id FROM certificates WHERE id = ? AND fqdn_id = ? AND space_id = ? diff --git a/backend/spaces.db-shm b/backend/spaces.db-shm index 11c45e890d993e23f248d84670e8efe86d300d4c..816ec6264894df6408f763490fce45508cae9d81 100644 GIT binary patch delta 660 zcmb7=%S%*I9LMkZ+_}!&>n5)Qy#~U~I1o`r?c5kf(4rQ#Xwf3eB0&fOAy9IP`yZU3 zZLNad)6KHdsj;`2O^%t_WHyaGcI=KAr#VZza!uim?a2${4ClBq$448o!t7y?Q9hAZ+G646 z8kP2tXT0GvUwloD?5huUGeC|HN@x&eCu!q4cggUQ_k5=?mmA){E%<<^yyl}4ngpiz zteBA5rdo6lwfN}Gs03`_ToEH-(n78>;hz1)>ylwNL7%pa@QF4rv zUnOY3%I!570%YrK9V@A|t2(G5WQFV`9YPq?>h9{0kcDhyL1~r- z(a{+g)Ea>So%cl4hfD^sQCwggpCX{ZsDL2Er;OXUGb6}6efL&nCrAZhJnu|8Cpk$c zb?a8u_y2#t``vr9cJCAF^W870mwD74ACJc4>+$pWd$b;%C%~ik7(9U<#AEcJo*>US z&v;L;C&Ux#3G;+|CU_=#CV3(}lRZ;BQ$3NMC{MH}#uMu?dEz|ro&-;#C+R73P>;AM zJWe|$UZqm`tr#EwWp~+;8yB8=uO0z!sL{6vSv93=1?e|GsnB{=xgU`<{SO!~A1*uf13w%t3Z5TisdlDCm zr*AQv{J<{@ZBi>DdV69*WGr~2zZZY~^O>aA>gN~h``Qq^e>fiZqES5aL}HTo+|wqL z_=bob;{SX^U?Hlb5KP8c^(W;IB4+yb_P6; z8r`WvcV6~9ZIgqgR(spsy>Z3aQ-{u!!Xs04UE&tE`6eG-m$&3lBCQjn9`*B$vokCs z;53O@ova0;87qm|Dc+7btWMra*)2{B;Y{^TTVt-*hHQG*cAnnYxk_rz@Sw`@prPAa zvN`EqgIIhz01+EDn*IH(X$+U@Y)5a~vyNzweIYz6|WJHngyg_@-J&5>NL{bPi5AnJ= z#2fLt`7?D$df=ve`IVoohWFbi5LX!k0&#c>Q((@@id4i#*}^Acww0RX<#UwW9}*`S zj36+Qh{EwS3fuuvePTQ=HagIk7@7kEI|p_w4ZW>G<8MnwLen`5#gcZ0!6+-?#Au6y#CXc;!e|G}35=8G z?E*Cr@pVFr&@ekYBWs|K<)Du=o<@+#IFW|qSJFyyWHh)-{lQrALGtOXzwmnX=y8<~ zx>|*<_8xvAz=W>H*Hy|9rKE!zv6ziZ64w?cak|&0PI|ij_`OTKT)KH7YEp8zSgKZ= z#62nI@^SqgTm9?jc7E_D;UjNJvAK3M%^bbiTsl-XdB^Zq-#FE@07e$BD}}PbLUg72 z_X7T8pio-(cjH?9uk|;{LJE+DRAhBptQ29zF&1a6;Azlyj3+n_!*TFfc!qO19XOt- zv8k0rRIlQ~EBm^y-!?xL%%gvk}L88c6DAN7CBE=<<4$*2@ z=MOd?-*xXi10|u+FP5~bUkoo`fj(y?i}$H@MK(~Yl7}#$1gKVc>%{jSPB4pKZVdMq z@u%W~l!ahWb-X!on9yQwh-u*Kg_yLMVZRU`dMd8mk0A*QE~;JeuI!)5Wg?;Bflv67 zGS9g`Is&^M4egOL&*P77xg{iMAkWUzywLfx1bn6@;e zbEtG;Prdx`TJ)>`f=3X#L@1qzL|vkOx8WB7x#((i2YMXI3p^C?;N|3WS_|`npPRC9 z6a`(5rZ|jdStkZ^5r;W>mLN!)VQ{BFKs70O;>u+QTO>g?tv(%nfCZ&YF)$9C-;1^V2|Fr zn~pt(ij8;0TXj=5`rCr?Pu=f5oE3W!HMKZfoW3AR#(_`xlI*I@zw*Ey-9b{1ehm!i z9j|*&LO>AenAKCA@?GNN@Pq*7y)UwHPE<_3n6PO=KORKf^oH?+w+}mEK*PU@T$%)q zJn+S&s*gR9d*HjI_6NzX%$K(-?GKmc|D9UN3*9_HS?mnY0~!PPOgkt$W@T{#a|jlP z%kHpRow$R9{-PwDM{jM>3L$<{!RgpzFO6Syk95g~Qk9`}u&JfXrZ!DBHJ}8F9NE-H zV|I5QLyO)l_+!0vN?ojEb~@SYbg?ilyuEfu%64~o*NbU5`~KbcRlG1$Y6^Mx64yiC z9l`gS2KinL34JdsyCAzf`^PPC`h=%$d|7<fLAFPe98QblHt1a0V-`$_|1Swolq~pFHhsJQ=~MDJ(s&43 z@S#2n7WeN$?{hB?_0`%ULN*&qDdU*ATWmPGuDILz-Qd`@il)Ab(i;MDH zx%C!XXML4}oHK)M&TeQ59!S{Ic$7>7k(YkIu{&h<5`*C-{~cL+bVu(16Fv5HjJm@J zMwlmL?7XlhzAIlzw_*MO#U1G-<&Rq*IiGh={`Rl*=(nKpGb;2m^bGo~x9BOKBWNz7 ziB$Jrs4rA6@2szB(161O24|!GoU(gs>_+vH5AT54$Oql8Lf=96qbDIC??Qv2ZSu|_ zO`O!GbkD@10|{xm6RvmP`eOgNzdhZMv>P=kxeU2%wDx3zc3`+WJwZ1s?&H!E-U&-b z*UWK4Vh|dbb#}|M^BUWrw^7=+rM!28^1P>g8M_!}cxqtP_i$|9(8!tbp@1)$R@5`Oq?_id=r#E|2a`wDp5;dL0l0QF~LH$l@ z69Jj6e{_!=l?M+arctMyMk#sFKn>(k#VM8EHNNDoWv4Ra69d6qQnEwN_Kzxba!N(^ z!)u%;C)E&2rwLUXx$>%;|&roeQ99jBd`!)4(}D@WK_j_r$K zsY2Zs!F?dHc!8s|ZiY~ipP6b~P@TH4EsrR738idXS?PkV%(7~-s)C$bpVOLW%P(Wf zDyTNWR^!ee5TgVVR2t67h3$U{;b50XoM9d7C!Da4Ur%51%kXnJBpeMoUxm&Gg|TDM z<%&^pZ==D_q~K2aNoFtR+!c>q9SZ%!c`~{Ekp(9q3TeBNb?k$^icg~9r^X~m94onIt zi7knelJeCWn@x!aj|5dM`4i^pR{q26J$^9(%poW~tJ$Z(*JcY)ep(5__GTadtuB5^ z63m9QcDn>&DOZLt?e@Ou2N}cW_ZA@RBxU7E5|kY%7YjHHh>)Wx3ud)~!j?sF(N=+= zAzmp-aia$|_s?0bC3wo4yT;6Gr&7!FySvxk`>u3T4bdt?^l*GL$oRHU#y98}^a%KN zow2U_pUM8aKmU7|{OpfNSQj{eb=o7)4)|WM17mV9N2*rJck%1+U;X~IkX^x26b!C&Q zc-L-NZ@R8%wC&S`Al=TAbIu3y9v&~_pIeEoDp@z87!G-kWt@z_V{1 ze``NC?U{G5yP9QGyk(b-dS~KOH0_s|AuLo(Rv_NhFY{9x|mxG{K2PP1V1EZZ*C&p109%HNy+)i>%2k+t;$d*d7=+!`|`)8Y2 zljqeUuDCibYHQqvjMH7=(pnwH5TG&yT*{tb>7|u&?mqS;31$TLyzWfu3s!%Puw3X! zdA0U+`m zJo7AxPxYD10Ga+oNc}SAHFN3C`*z5k0VCBPt4Ib8Kq}&e{mGUwHJ<6i)T`sws6VgR z{!ZbP4SL9{I*rdTUe)*jV*SJ?YefswN36?jfLIy)%Ay=jtfKnmP>UkB8W~dsP;IPk zZfsjL)7cV}HZMkr>&5`89a5nTlP8mg5Gq44;=bP?)ni<48>>T)ym(@a%m0nJ+|L+< z9{ts{C%0Cgy#UQR2x$_vg~5?NNE3A8nTRwsoaD0dg571cU_7XOVKmS3pd98TF&D=P zl;GePg0%)gP!ZR*2b;WqstV5V37rhu>z~vOo0fJz`ptaL>D-eeocJ4ZOxpS)V(x zOi9}!u?{hbJ(0$l;*mRb-$ry6c9r6VwKMpfcD%EwHoK#+Hlz2wol~k4eZ+Z7f*E&a zu0A)1u(d99*#w$vvN&tAEL2Hd<7}eBWyvY26)Ku-)l8<%Ry-@;N|kkH%;FZ>3mQ9k z8$;Fzi<`Ka`TA<2wS6&XFU~1Utu0D!Y@U(Rz?YXbw&ixV6jxPo8MB&bzKyQnGn2b! zv@?rKIvjaVmM#rcIKUz?gYyS^>8vS2aWp5M`#i{DaP#nlugI|#j%&aPrQN^1G4 zl8VB)jzU*?Q)3-#Ws=*78oa8w#5t2LayhCis^(;~%>`AJ#GK@M%96}yG|sZ+*W@CcV@7gSKHt>5zVxQh^`F*;PLipMP9yOYir^RvOZCkacvrLscl!-p z%37N^-{-J+bUTbTi*}v_(%j`hT03uGQET5SvEhYUZEvBLbFqX4Ovz!ag}^Zy3^g#0 zu&|he2%&xBKUutOs-TIoEQ9e9V;B-e3B~6RFZdT8sWGmHw-d zxck@;?uHRy8IilMGmlSBBSy1s&bdSkwwLV-BnW%L-bQ!M$=lqD@c9>nkI5-)LqxP8K$~jm1Nxsx_A&d|f4|xVsv^9mn z37Q}+Rz|$BA;dAT?z6q{)Yqj2=Q6!f|J1G?*oKv4QgTl5i*n5kp=!{1_>xw{;(K5I z&BX}n4KeB)b%fsn@OFZ@D94oUedShjH#&8H<+?-EtSyJ({$)S(LwHyMdJ+8x`W5;& z^h59KG2us0_`7(ESm!pI(1?3_nt$`r`_iN@t0g69&bwM@Mm7ZgXP_!zs$mrpX}noW zdKayVecfN)ZXw{L%fY%_81AxyfgVRWKt&pS!D-Gx;h=PHcX2F)XeFt1^tA2%8CP1s zg%<9gYhxT(X#qymv^Rf^dA`>4N)x00BJ{I0khfU<36=iD$o&2MF#e{k>>@R;Qx;;aL#;-PRRvD##VsM`^bH%N!T$Bf! y6_WwEEAgdaelHSlOv2#yV?(S06Bnm$3QtKLlsyJE9S?qU@U|Y-B6e44tN#y63O2w1 delta 95 zcmV~$*%84|006*wZh|DPxCx1?N*`N*K75%`fEkUX44-y?uWxvKp5ZR2scUFzY3u0f miS!K&#S*EJv5Bdf%-q7#%G$=(&R*`|s8BjNySTn@N%aT*B_BBe diff --git a/frontend/src/hooks/usePermissions.js b/frontend/src/hooks/usePermissions.js new file mode 100644 index 0000000..51a8719 --- /dev/null +++ b/frontend/src/hooks/usePermissions.js @@ -0,0 +1,86 @@ +import { useState, useEffect, useCallback } from 'react' +import { useAuth } from '../contexts/AuthContext' + +export const usePermissions = () => { + const { authFetch, isAuthenticated } = useAuth() + const [permissions, setPermissions] = useState({ + hasFullAccess: false, + accessibleSpaces: [], + canCreateSpace: false, + canDeleteSpace: false, + canCreateFqdn: {}, + canDeleteFqdn: {}, + canUploadCSR: {}, + canSignCSR: {}, + }) + const [loading, setLoading] = useState(true) + + const fetchPermissions = useCallback(async () => { + if (!isAuthenticated) { + setLoading(false) + return + } + + try { + setLoading(true) + const response = await authFetch('/api/user/permissions') + if (response.ok) { + const data = await response.json() + setPermissions({ + hasFullAccess: data.hasFullAccess || false, + accessibleSpaces: data.accessibleSpaces || [], + canCreateSpace: data.permissions?.canCreateSpace || false, + canDeleteSpace: data.permissions?.canDeleteSpace || false, + canCreateFqdn: data.permissions?.canCreateFqdn || {}, + canDeleteFqdn: data.permissions?.canDeleteFqdn || {}, + canUploadCSR: data.permissions?.canUploadCSR || {}, + canSignCSR: data.permissions?.canSignCSR || {}, + }) + } + } catch (err) { + console.error('Error fetching permissions:', err) + } finally { + setLoading(false) + } + }, [isAuthenticated, authFetch]) + + useEffect(() => { + if (isAuthenticated) { + fetchPermissions() + } else { + setPermissions({ + hasFullAccess: false, + accessibleSpaces: [], + canCreateSpace: false, + canDeleteSpace: false, + canCreateFqdn: {}, + canDeleteFqdn: {}, + canUploadCSR: {}, + canSignCSR: {}, + }) + setLoading(false) + } + }, [isAuthenticated, fetchPermissions]) + + const canCreateSpace = () => permissions.canCreateSpace + const canDeleteSpace = (spaceId) => permissions.canDeleteSpace + const canCreateFqdn = (spaceId) => permissions.canCreateFqdn[spaceId] === true + const canDeleteFqdn = (spaceId) => permissions.canDeleteFqdn[spaceId] === true + const canUploadCSR = (spaceId) => permissions.canUploadCSR[spaceId] === true + const canSignCSR = (spaceId) => permissions.canSignCSR[spaceId] === true + const hasAccessToSpace = (spaceId) => permissions.accessibleSpaces.includes(spaceId) + + return { + permissions, + loading, + refreshPermissions: fetchPermissions, + canCreateSpace, + canDeleteSpace, + canCreateFqdn, + canDeleteFqdn, + canUploadCSR, + canSignCSR, + hasAccessToSpace, + } +} + diff --git a/frontend/src/pages/SpaceDetail.jsx b/frontend/src/pages/SpaceDetail.jsx index cede620..0f28d2d 100644 --- a/frontend/src/pages/SpaceDetail.jsx +++ b/frontend/src/pages/SpaceDetail.jsx @@ -1,11 +1,13 @@ import { useState, useEffect } from 'react' import { useParams, useNavigate } from 'react-router-dom' import { useAuth } from '../contexts/AuthContext' +import { usePermissions } from '../hooks/usePermissions' const SpaceDetail = () => { const { id } = useParams() const navigate = useNavigate() const { authFetch } = useAuth() + const { canCreateFqdn, canDeleteFqdn, canSignCSR, canUploadCSR, refreshPermissions } = usePermissions() const [space, setSpace] = useState(null) const [fqdns, setFqdns] = useState([]) const [showForm, setShowForm] = useState(false) @@ -123,6 +125,8 @@ const SpaceDetail = () => { setFqdns([...fqdns, newFqdn]) setFormData({ fqdn: '', description: '' }) setShowForm(false) + // Aktualisiere Berechtigungen nach dem Erstellen eines FQDNs + refreshPermissions() } else { let errorMessage = 'Fehler beim Erstellen des FQDN' try { @@ -176,6 +180,8 @@ const SpaceDetail = () => { setShowDeleteModal(false) setFqdnToDelete(null) setConfirmChecked(false) + // Aktualisiere Berechtigungen nach dem Löschen eines FQDNs + refreshPermissions() } else { const errorData = await response.json().catch(() => ({ error: 'Fehler beim Löschen' })) alert(errorData.error || 'Fehler beim Löschen des FQDN') @@ -586,7 +592,13 @@ const SpaceDetail = () => { @@ -630,11 +642,18 @@ const SpaceDetail = () => {