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 11c45e8..816ec62 100644
Binary files a/backend/spaces.db-shm and b/backend/spaces.db-shm differ
diff --git a/backend/spaces.db-wal b/backend/spaces.db-wal
index 3394225..5c13d9c 100644
Binary files a/backend/spaces.db-wal and b/backend/spaces.db-wal differ
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 = () => {