added main permission group feature

This commit is contained in:
2025-11-21 00:28:53 +01:00
parent 1234fbda00
commit 9c2d649adf
7 changed files with 1326 additions and 65 deletions

View File

@@ -5,9 +5,9 @@ import (
"crypto/x509"
"database/sql"
"encoding/asn1"
"encoding/base64"
"encoding/hex"
"encoding/json"
"encoding/base64"
"encoding/pem"
"fmt"
"io"
@@ -258,25 +258,63 @@ type CSR struct {
// User struct für Benutzer
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
CreatedAt string `json:"createdAt"`
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
CreatedAt string `json:"createdAt"`
GroupIDs []string `json:"groupIds,omitempty"`
}
// CreateUserRequest struct für Benutzer-Erstellung
type CreateUserRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
// PermissionLevel definiert die Berechtigungsstufen
type PermissionLevel string
const (
PermissionRead PermissionLevel = "READ"
PermissionReadWrite PermissionLevel = "READ_WRITE"
PermissionFullAccess PermissionLevel = "FULL_ACCESS"
)
// PermissionGroup struct für Berechtigungsgruppen
type PermissionGroup struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Permission PermissionLevel `json:"permission"`
SpaceIDs []string `json:"spaceIds"`
CreatedAt string `json:"createdAt"`
}
// CreatePermissionGroupRequest struct für Gruppen-Erstellung
type CreatePermissionGroupRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Permission PermissionLevel `json:"permission"`
SpaceIDs []string `json:"spaceIds"`
}
// UpdatePermissionGroupRequest struct für Gruppen-Update
type UpdatePermissionGroupRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Permission PermissionLevel `json:"permission"`
SpaceIDs []string `json:"spaceIds"`
}
// UpdateUserRequest struct für Benutzer-Update
type UpdateUserRequest struct {
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
OldPassword string `json:"oldPassword,omitempty"`
Password string `json:"password,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
Password string `json:"password,omitempty"`
OldPassword string `json:"oldPassword,omitempty"`
GroupIDs []string `json:"groupIds,omitempty"`
}
// CreateUserRequest struct für Benutzer-Erstellung
type CreateUserRequest struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
GroupIDs []string `json:"groupIds,omitempty"`
}
// MessageResponse struct für einfache Nachrichten
@@ -318,7 +356,7 @@ func initDB() {
log.Println("Teste Datenbank-Verbindung...")
maxRetries := 5
for i := 0; i < maxRetries; i++ {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
err := db.PingContext(ctx)
cancel()
if err != nil {
@@ -570,10 +608,75 @@ func initDB() {
} else {
log.Printf("Avatar-Ordner erstellt: %s", avatarDir)
}
// Erstelle Permission Groups-Tabelle
log.Println("Erstelle permission_groups-Tabelle...")
createPermissionGroupsTableSQL := `
CREATE TABLE IF NOT EXISTS permission_groups (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
permission TEXT NOT NULL,
created_at DATETIME NOT NULL
);`
ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
_, err = db.ExecContext(ctx, createPermissionGroupsTableSQL)
cancel()
if err != nil {
if strings.Contains(err.Error(), "database is locked") {
log.Fatal("datenbank ist gesperrt. Bitte beenden Sie alle anderen Prozesse, die die Datenbank verwenden.")
}
log.Fatal("Fehler beim Erstellen der permission_groups-Tabelle:", err)
}
// Erstelle group_spaces-Tabelle für Space-Zuweisungen
log.Println("Erstelle group_spaces-Tabelle...")
createGroupSpacesTableSQL := `
CREATE TABLE IF NOT EXISTS group_spaces (
group_id TEXT NOT NULL,
space_id TEXT NOT NULL,
PRIMARY KEY (group_id, space_id),
FOREIGN KEY (group_id) REFERENCES permission_groups(id) ON DELETE CASCADE,
FOREIGN KEY (space_id) REFERENCES spaces(id) ON DELETE CASCADE
);`
ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
_, err = db.ExecContext(ctx, createGroupSpacesTableSQL)
cancel()
if err != nil {
if strings.Contains(err.Error(), "database is locked") {
log.Fatal("datenbank ist gesperrt. Bitte beenden Sie alle anderen Prozesse, die die Datenbank verwenden.")
}
log.Fatal("Fehler beim Erstellen der group_spaces-Tabelle:", err)
}
// Erstelle user_groups-Tabelle für Benutzer-Gruppen-Zuweisungen
log.Println("Erstelle user_groups-Tabelle...")
createUserGroupsTableSQL := `
CREATE TABLE IF NOT EXISTS user_groups (
user_id TEXT NOT NULL,
group_id TEXT NOT NULL,
PRIMARY KEY (user_id, group_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (group_id) REFERENCES permission_groups(id) ON DELETE CASCADE
);`
ctx, cancel = context.WithTimeout(context.Background(), time.Second*5)
_, err = db.ExecContext(ctx, createUserGroupsTableSQL)
cancel()
if err != nil {
if strings.Contains(err.Error(), "database is locked") {
log.Fatal("datenbank ist gesperrt. Bitte beenden Sie alle anderen Prozesse, die die Datenbank verwenden.")
}
log.Fatal("Fehler beim Erstellen der user_groups-Tabelle:", err)
}
log.Println("Berechtigungssystem-Tabellen erfolgreich erstellt")
}
func createDefaultAdmin() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Prüfe ob bereits ein Admin-User existiert
@@ -699,11 +802,11 @@ func getStatsHandler(w http.ResponseWriter, r *http.Request) {
}
response := StatsResponse{
Spaces: spacesCount,
FQDNs: fqdnsCount,
CSRs: csrsCount,
Spaces: spacesCount,
FQDNs: fqdnsCount,
CSRs: csrsCount,
Certificates: certificatesCount,
Users: usersCount,
Users: usersCount,
}
json.NewEncoder(w).Encode(response)
@@ -2410,9 +2513,10 @@ func getUsersHandler(w http.ResponseWriter, r *http.Request) {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Lade alle Benutzer
rows, err := db.QueryContext(ctx, "SELECT id, username, email, created_at FROM users ORDER BY created_at DESC")
if err != nil {
http.Error(w, "Fehler beim Abrufen der Benutzer", http.StatusInternalServerError)
@@ -2422,6 +2526,7 @@ func getUsersHandler(w http.ResponseWriter, r *http.Request) {
defer rows.Close()
var users []User
var userIDs []string
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Username, &user.Email, &user.CreatedAt)
@@ -2430,7 +2535,42 @@ func getUsersHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Fehler beim Lesen der Benutzerdaten: %v", err)
return
}
user.GroupIDs = []string{} // Initialisiere als leeres Array
users = append(users, user)
userIDs = append(userIDs, user.ID)
}
rows.Close()
// Lade alle Gruppen-Zuweisungen in einem einzigen Query
if len(userIDs) > 0 {
// Erstelle Platzhalter für IN-Clause
placeholders := make([]string, len(userIDs))
args := make([]interface{}, len(userIDs))
for i, id := range userIDs {
placeholders[i] = "?"
args[i] = id
}
query := fmt.Sprintf("SELECT user_id, group_id FROM user_groups WHERE user_id IN (%s)", strings.Join(placeholders, ","))
groupRows, err := db.QueryContext(ctx, query, args...)
if err == nil {
// Erstelle eine Map von user_id zu group_ids
groupMap := make(map[string][]string)
for groupRows.Next() {
var userID, groupID string
if err := groupRows.Scan(&userID, &groupID); err == nil {
groupMap[userID] = append(groupMap[userID], groupID)
}
}
groupRows.Close()
// Weise Gruppen-IDs den Benutzern zu
for i := range users {
if groupIDs, exists := groupMap[users[i].ID]; exists {
users[i].GroupIDs = groupIDs
}
}
}
}
json.NewEncoder(w).Encode(users)
@@ -2450,7 +2590,7 @@ func getUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["id"]
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var user User
@@ -2466,6 +2606,22 @@ func getUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Lade Gruppen-IDs für diesen Benutzer
groupRows, err := db.QueryContext(ctx, "SELECT group_id FROM user_groups WHERE user_id = ?", userID)
if err == nil {
var groupIDs []string
for groupRows.Next() {
var groupID string
if err := groupRows.Scan(&groupID); err == nil {
groupIDs = append(groupIDs, groupID)
}
}
groupRows.Close()
user.GroupIDs = groupIDs
} else {
user.GroupIDs = []string{} // Initialisiere als leeres Array bei Fehler
}
json.NewEncoder(w).Encode(user)
}
@@ -2511,7 +2667,7 @@ func createUserHandler(w http.ResponseWriter, r *http.Request) {
userID := uuid.New().String()
createdAt := time.Now().Format(time.RFC3339)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, err = db.ExecContext(ctx,
@@ -2531,11 +2687,27 @@ func createUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Weise Gruppen zu, falls angegeben
if len(req.GroupIDs) > 0 {
for _, groupID := range req.GroupIDs {
// Prüfe ob Gruppe existiert
var exists bool
err := db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM permission_groups WHERE id = ?)", groupID).Scan(&exists)
if err == nil && exists {
_, err = db.ExecContext(ctx, "INSERT OR IGNORE INTO user_groups (user_id, group_id) VALUES (?, ?)", userID, groupID)
if err != nil {
log.Printf("Fehler beim Zuweisen der Gruppe %s zum Benutzer: %v", groupID, err)
}
}
}
}
user := User{
ID: userID,
Username: req.Username,
Email: req.Email,
CreatedAt: createdAt,
GroupIDs: req.GroupIDs,
}
w.WriteHeader(http.StatusCreated)
@@ -2547,6 +2719,7 @@ func createUserHandler(w http.ResponseWriter, r *http.Request) {
auditService.Track(r.Context(), "CREATE", "user", userID, requestUserID, requestUsername, map[string]interface{}{
"username": req.Username,
"email": req.Email,
"groupIds": req.GroupIDs,
"message": fmt.Sprintf("User erstellt: %s (%s)", req.Username, req.Email),
}, ipAddress, userAgent)
}
@@ -2571,7 +2744,7 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Prüfe ob Benutzer existiert
@@ -2659,6 +2832,28 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Aktualisiere Gruppen-Zuweisungen, falls angegeben
if req.GroupIDs != nil {
// Lösche alle bestehenden Gruppen-Zuweisungen
_, err = db.ExecContext(ctx, "DELETE FROM user_groups WHERE user_id = ?", userID)
if err != nil {
log.Printf("Fehler beim Löschen der Gruppen-Zuweisungen: %v", err)
}
// Füge neue Gruppen-Zuweisungen hinzu
for _, groupID := range req.GroupIDs {
// Prüfe ob Gruppe existiert
var exists bool
err := db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM permission_groups WHERE id = ?)", groupID).Scan(&exists)
if err == nil && exists {
_, err = db.ExecContext(ctx, "INSERT INTO user_groups (user_id, group_id) VALUES (?, ?)", userID, groupID)
if err != nil {
log.Printf("Fehler beim Zuweisen der Gruppe %s zum Benutzer: %v", groupID, err)
}
}
}
}
// Lade aktualisierten Benutzer
var user User
err = db.QueryRowContext(ctx, "SELECT id, username, email, created_at FROM users WHERE id = ?", userID).
@@ -2669,10 +2864,28 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Lade Gruppen-IDs
if req.GroupIDs != nil {
user.GroupIDs = req.GroupIDs
} else {
groupRows, err := db.QueryContext(ctx, "SELECT group_id FROM user_groups WHERE user_id = ?", userID)
if err == nil {
var groupIDs []string
for groupRows.Next() {
var groupID string
if err := groupRows.Scan(&groupID); err == nil {
groupIDs = append(groupIDs, groupID)
}
}
groupRows.Close()
user.GroupIDs = groupIDs
}
}
json.NewEncoder(w).Encode(user)
// Audit-Log: User aktualisiert
userID, username := getUserFromRequest(r)
requestUserID, requestUsername := getUserFromRequest(r)
ipAddress, userAgent := getRequestInfo(r)
details := map[string]interface{}{}
if req.Username != "" {
@@ -2684,7 +2897,10 @@ func updateUserHandler(w http.ResponseWriter, r *http.Request) {
if req.Password != "" {
details["passwordChanged"] = true
}
auditService.Track(r.Context(), "UPDATE", "user", vars["id"], userID, username, details, ipAddress, userAgent)
if req.GroupIDs != nil {
details["groupIds"] = req.GroupIDs
}
auditService.Track(r.Context(), "UPDATE", "user", vars["id"], requestUserID, requestUsername, details, ipAddress, userAgent)
}
func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
@@ -2701,7 +2917,7 @@ func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["id"]
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
result, err := db.ExecContext(ctx, "DELETE FROM users WHERE id = ?", userID)
@@ -2921,10 +3137,400 @@ func getAvatarHandler(w http.ResponseWriter, r *http.Request) {
io.Copy(w, file)
}
// Permission Groups Handler Functions
func getPermissionGroupsHandler(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
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Lade alle Gruppen
rows, err := db.QueryContext(ctx, "SELECT id, name, description, permission, created_at FROM permission_groups ORDER BY created_at DESC")
if err != nil {
http.Error(w, "Fehler beim Abrufen der Berechtigungsgruppen", http.StatusInternalServerError)
log.Printf("Fehler beim Abrufen der Berechtigungsgruppen: %v", err)
return
}
defer rows.Close()
var groups []PermissionGroup
var groupIDs []string
for rows.Next() {
var group PermissionGroup
err := rows.Scan(&group.ID, &group.Name, &group.Description, &group.Permission, &group.CreatedAt)
if err != nil {
http.Error(w, "Fehler beim Lesen der Gruppen-Daten", http.StatusInternalServerError)
log.Printf("Fehler beim Lesen der Gruppen-Daten: %v", err)
return
}
group.SpaceIDs = []string{} // Initialisiere als leeres Array
groups = append(groups, group)
groupIDs = append(groupIDs, group.ID)
}
rows.Close()
// Lade alle Space-Zuweisungen in einem einzigen Query
if len(groupIDs) > 0 {
// Erstelle Platzhalter für IN-Clause
placeholders := make([]string, len(groupIDs))
args := make([]interface{}, len(groupIDs))
for i, id := range groupIDs {
placeholders[i] = "?"
args[i] = id
}
query := fmt.Sprintf("SELECT group_id, space_id FROM group_spaces WHERE group_id IN (%s)", strings.Join(placeholders, ","))
spaceRows, err := db.QueryContext(ctx, query, args...)
if err == nil {
// Erstelle eine Map von group_id zu space_ids
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()
// Weise Space-IDs den Gruppen zu
for i := range groups {
if spaceIDs, exists := spaceMap[groups[i].ID]; exists {
groups[i].SpaceIDs = spaceIDs
}
}
}
}
json.NewEncoder(w).Encode(groups)
}
func getPermissionGroupHandler(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
}
vars := mux.Vars(r)
groupID := vars["id"]
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var group PermissionGroup
err := db.QueryRowContext(ctx, "SELECT id, name, description, permission, created_at FROM permission_groups WHERE id = ?", groupID).
Scan(&group.ID, &group.Name, &group.Description, &group.Permission, &group.CreatedAt)
if err != nil {
if err == sql.ErrNoRows {
http.Error(w, "Berechtigungsgruppe nicht gefunden", http.StatusNotFound)
return
}
http.Error(w, "Fehler beim Abrufen der Berechtigungsgruppe", http.StatusInternalServerError)
log.Printf("Fehler beim Abrufen der Berechtigungsgruppe: %v", err)
return
}
// Lade Space-IDs für diese Gruppe
spaceRows, err := db.QueryContext(ctx, "SELECT space_id FROM group_spaces WHERE group_id = ?", groupID)
if err == nil {
var spaceIDs []string
for spaceRows.Next() {
var spaceID string
if err := spaceRows.Scan(&spaceID); err == nil {
spaceIDs = append(spaceIDs, spaceID)
}
}
spaceRows.Close()
group.SpaceIDs = spaceIDs
}
json.NewEncoder(w).Encode(group)
}
func createPermissionGroupHandler(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", "POST, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
var req CreatePermissionGroupRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Ungültige Anfrage", http.StatusBadRequest)
return
}
// Validierung
if req.Name == "" {
http.Error(w, "Name ist erforderlich", http.StatusBadRequest)
return
}
// Validiere Berechtigungsstufe
if req.Permission != PermissionRead && req.Permission != PermissionReadWrite && req.Permission != PermissionFullAccess {
http.Error(w, "Ungültige Berechtigungsstufe. Erlaubt: READ, READ_WRITE, FULL_ACCESS", http.StatusBadRequest)
return
}
// Erstelle Gruppe
groupID := uuid.New().String()
createdAt := time.Now().Format(time.RFC3339)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, err := db.ExecContext(ctx,
"INSERT INTO permission_groups (id, name, description, permission, created_at) VALUES (?, ?, ?, ?, ?)",
groupID, req.Name, req.Description, string(req.Permission), createdAt)
if err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
http.Error(w, "Gruppenname bereits vergeben", http.StatusConflict)
return
}
http.Error(w, "Fehler beim Erstellen der Berechtigungsgruppe", http.StatusInternalServerError)
log.Printf("Fehler beim Erstellen der Berechtigungsgruppe: %v", err)
return
}
// Weise Spaces zu, falls angegeben
if len(req.SpaceIDs) > 0 {
for _, spaceID := range req.SpaceIDs {
// Prüfe ob Space existiert
var exists bool
err := db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists)
if err == nil && exists {
_, err = db.ExecContext(ctx, "INSERT OR IGNORE INTO group_spaces (group_id, space_id) VALUES (?, ?)", groupID, spaceID)
if err != nil {
log.Printf("Fehler beim Zuweisen des Space %s zur Gruppe: %v", spaceID, err)
}
}
}
}
group := PermissionGroup{
ID: groupID,
Name: req.Name,
Description: req.Description,
Permission: req.Permission,
SpaceIDs: req.SpaceIDs,
CreatedAt: createdAt,
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(group)
// Audit-Log
requestUserID, requestUsername := getUserFromRequest(r)
ipAddress, userAgent := getRequestInfo(r)
auditService.Track(r.Context(), "CREATE", "permission_group", groupID, requestUserID, requestUsername, map[string]interface{}{
"name": req.Name,
"permission": req.Permission,
"spaceIds": req.SpaceIDs,
"message": fmt.Sprintf("Berechtigungsgruppe erstellt: %s", req.Name),
}, ipAddress, userAgent)
}
func updatePermissionGroupHandler(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", "PUT, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
vars := mux.Vars(r)
groupID := vars["id"]
var req UpdatePermissionGroupRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "Ungültige Anfrage", http.StatusBadRequest)
return
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
// Prüfe ob Gruppe existiert
var exists bool
err := db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM permission_groups WHERE id = ?)", groupID).Scan(&exists)
if err != nil || !exists {
http.Error(w, "Berechtigungsgruppe nicht gefunden", http.StatusNotFound)
return
}
// Validiere Berechtigungsstufe, falls angegeben
if req.Permission != "" {
if req.Permission != PermissionRead && req.Permission != PermissionReadWrite && req.Permission != PermissionFullAccess {
http.Error(w, "Ungültige Berechtigungsstufe. Erlaubt: READ, READ_WRITE, FULL_ACCESS", http.StatusBadRequest)
return
}
}
// Update Felder
updates := []string{}
args := []interface{}{}
if req.Name != "" {
updates = append(updates, "name = ?")
args = append(args, req.Name)
}
if req.Description != "" {
updates = append(updates, "description = ?")
args = append(args, req.Description)
}
if req.Permission != "" {
updates = append(updates, "permission = ?")
args = append(args, string(req.Permission))
}
if len(updates) > 0 {
args = append(args, groupID)
query := fmt.Sprintf("UPDATE permission_groups SET %s WHERE id = ?", strings.Join(updates, ", "))
_, err = db.ExecContext(ctx, query, args...)
if err != nil {
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
http.Error(w, "Gruppenname bereits vergeben", http.StatusConflict)
return
}
http.Error(w, "Fehler beim Aktualisieren der Berechtigungsgruppe", http.StatusInternalServerError)
log.Printf("Fehler beim Aktualisieren der Berechtigungsgruppe: %v", err)
return
}
}
// Aktualisiere Space-Zuweisungen, falls angegeben
if req.SpaceIDs != nil {
// Lösche alle bestehenden Space-Zuweisungen
_, err = db.ExecContext(ctx, "DELETE FROM group_spaces WHERE group_id = ?", groupID)
if err != nil {
log.Printf("Fehler beim Löschen der Space-Zuweisungen: %v", err)
}
// Füge neue Space-Zuweisungen hinzu
for _, spaceID := range req.SpaceIDs {
// Prüfe ob Space existiert
var exists bool
err := db.QueryRowContext(ctx, "SELECT EXISTS(SELECT 1 FROM spaces WHERE id = ?)", spaceID).Scan(&exists)
if err == nil && exists {
_, err = db.ExecContext(ctx, "INSERT INTO group_spaces (group_id, space_id) VALUES (?, ?)", groupID, spaceID)
if err != nil {
log.Printf("Fehler beim Zuweisen des Space %s zur Gruppe: %v", spaceID, err)
}
}
}
}
// Lade aktualisierte Gruppe
var group PermissionGroup
err = db.QueryRowContext(ctx, "SELECT id, name, description, permission, created_at FROM permission_groups WHERE id = ?", groupID).
Scan(&group.ID, &group.Name, &group.Description, &group.Permission, &group.CreatedAt)
if err != nil {
http.Error(w, "Fehler beim Abrufen der aktualisierten Gruppe", http.StatusInternalServerError)
log.Printf("Fehler beim Abrufen der aktualisierten Gruppe: %v", err)
return
}
// Lade Space-IDs
if req.SpaceIDs != nil {
group.SpaceIDs = req.SpaceIDs
} else {
spaceRows, err := db.QueryContext(ctx, "SELECT space_id FROM group_spaces WHERE group_id = ?", groupID)
if err == nil {
var spaceIDs []string
for spaceRows.Next() {
var spaceID string
if err := spaceRows.Scan(&spaceID); err == nil {
spaceIDs = append(spaceIDs, spaceID)
}
}
spaceRows.Close()
group.SpaceIDs = spaceIDs
}
}
json.NewEncoder(w).Encode(group)
// Audit-Log
requestUserID, requestUsername := getUserFromRequest(r)
ipAddress, userAgent := getRequestInfo(r)
auditService.Track(r.Context(), "UPDATE", "permission_group", groupID, requestUserID, requestUsername, map[string]interface{}{
"name": req.Name,
"permission": req.Permission,
"spaceIds": req.SpaceIDs,
"message": fmt.Sprintf("Berechtigungsgruppe aktualisiert: %s", groupID),
}, ipAddress, userAgent)
}
func deletePermissionGroupHandler(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", "DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
vars := mux.Vars(r)
groupID := vars["id"]
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
result, err := db.ExecContext(ctx, "DELETE FROM permission_groups WHERE id = ?", groupID)
if err != nil {
http.Error(w, "Fehler beim Löschen der Berechtigungsgruppe", http.StatusInternalServerError)
log.Printf("Fehler beim Löschen der Berechtigungsgruppe: %v", err)
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
http.Error(w, "Fehler beim Prüfen der gelöschten Zeilen", http.StatusInternalServerError)
return
}
if rowsAffected == 0 {
http.Error(w, "Berechtigungsgruppe nicht gefunden", http.StatusNotFound)
return
}
response := MessageResponse{Message: "Berechtigungsgruppe erfolgreich gelöscht"}
json.NewEncoder(w).Encode(response)
// Audit-Log
requestUserID, requestUsername := getUserFromRequest(r)
ipAddress, userAgent := getRequestInfo(r)
auditService.Track(r.Context(), "DELETE", "permission_group", groupID, requestUserID, requestUsername, map[string]interface{}{
"message": fmt.Sprintf("Berechtigungsgruppe gelöscht: %s", groupID),
}, ipAddress, userAgent)
}
// Passwortvalidierung nach Richtlinien
func validatePassword(password string) error {
if len(password) < 8 {
return fmt.Errorf("Passwort muss mindestens 8 Zeichen lang sein")
return fmt.Errorf("passwort muss mindestens 8 Zeichen lang sein")
}
hasUpper := false
@@ -2963,7 +3569,7 @@ func validatePassword(password string) error {
}
if len(missing) > 0 {
return fmt.Errorf("Passwort muss enthalten: %s", strings.Join(missing, ", "))
return fmt.Errorf("passwort muss enthalten: %s", strings.Join(missing, ", "))
}
return nil
@@ -2990,12 +3596,16 @@ func getUserFromRequest(r *http.Request) (userID, username string) {
username = parts[0]
// Hole User-ID aus der Datenbank
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var id string
err = db.QueryRowContext(ctx, "SELECT id FROM users WHERE username = ?", username).Scan(&id)
if err != nil {
// Logge Fehler nur wenn es nicht "no rows" ist
if err != sql.ErrNoRows {
log.Printf("Fehler beim Abrufen der User-ID für %s: %v", username, err)
}
return "", username
}
@@ -3014,13 +3624,13 @@ func getRequestInfo(r *http.Request) (ipAddress, userAgent string) {
// Hole User-Agent
userAgent = r.Header.Get("User-Agent")
// Prüfe, ob es sich um einen API-Aufruf handelt
// API-Aufrufe haben entweder keinen User-Agent oder einen speziellen Header
if userAgent == "" || r.Header.Get("X-API-Request") == "true" || r.Header.Get("X-Request-Source") == "api" {
userAgent = "API"
}
return ipAddress, userAgent
}
@@ -3099,7 +3709,7 @@ func getAuditLogsHandler(w http.ResponseWriter, r *http.Request) {
// Hole Logs - Verwende Prepared Statement für bessere Kompatibilität
var querySQL string
var queryArgs []interface{}
if whereSQL != "" {
querySQL = fmt.Sprintf(`
SELECT id, timestamp, user_id, username, action, resource_type, resource_id, details, ip_address, user_agent
@@ -3123,7 +3733,7 @@ func getAuditLogsHandler(w http.ResponseWriter, r *http.Request) {
queryCtx, queryCancel := context.WithTimeout(context.Background(), time.Second*10)
defer queryCancel() // Cancel wird erst aufgerufen, wenn die Funktion beendet ist
rows, err := db.QueryContext(queryCtx, querySQL, queryArgs...)
if err != nil {
http.Error(w, "Fehler beim Abrufen der Logs", http.StatusInternalServerError)
@@ -3201,11 +3811,11 @@ func getAuditLogsHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Zeilen gelesen: %d, Scan-Fehler: %d, Logs hinzugefügt: %d", rowCount, scanErrors, len(logs))
response := map[string]interface{}{
"logs": logs,
"total": totalCount,
"limit": limit,
"offset": offset,
"hasMore": offset+limit < totalCount,
"logs": logs,
"total": totalCount,
"limit": limit,
"offset": offset,
"hasMore": offset+limit < totalCount,
}
log.Printf("Audit-Logs abgerufen: %d Einträge gefunden (Total: %d, Limit: %d, Offset: %d)", len(logs), totalCount, limit, offset)
@@ -3275,14 +3885,14 @@ func createTestAuditLogHandler(w http.ResponseWriter, r *http.Request) {
}
var req struct {
Action string `json:"action"`
Entity string `json:"entity"`
EntityID string `json:"entityID"`
UserID string `json:"userID"`
Username string `json:"username"`
Details map[string]interface{} `json:"details"`
IPAddress string `json:"ipAddress"`
UserAgent string `json:"userAgent"`
Action string `json:"action"`
Entity string `json:"entity"`
EntityID string `json:"entityID"`
UserID string `json:"userID"`
Username string `json:"username"`
Details map[string]interface{} `json:"details"`
IPAddress string `json:"ipAddress"`
UserAgent string `json:"userAgent"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -3303,7 +3913,7 @@ func createTestAuditLogHandler(w http.ResponseWriter, r *http.Request) {
// Erstelle Context für die Anfrage
ctx := r.Context()
// Track das Event
auditService.Track(ctx, req.Action, req.Entity, req.EntityID, req.UserID, req.Username, req.Details, req.IPAddress, req.UserAgent)
@@ -3383,7 +3993,7 @@ func basicAuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
password := parts[1]
// Validiere Benutzer
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var storedHash string
@@ -3474,7 +4084,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Login-Versuch für Benutzer: %s", username)
// Validiere Benutzer
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
var user User
@@ -3518,7 +4128,7 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
func main() {
log.Println("Starte certigo-addon Backend...")
// Initialisiere Datenbank
log.Println("Initialisiere Datenbank...")
initDB()
@@ -3542,11 +4152,11 @@ func main() {
// API Routes
api := r.PathPrefix("/api").Subrouter()
// Public Routes (keine Auth erforderlich)
api.HandleFunc("/health", healthHandler).Methods("GET", "OPTIONS")
api.HandleFunc("/login", loginHandler).Methods("POST", "OPTIONS")
// Protected Routes (Basic Auth erforderlich)
api.HandleFunc("/stats", basicAuthMiddleware(getStatsHandler)).Methods("GET", "OPTIONS")
api.HandleFunc("/spaces", getSpacesHandler).Methods("GET", "OPTIONS")
@@ -3571,6 +4181,13 @@ func main() {
api.HandleFunc("/users/{id}/avatar", basicAuthMiddleware(getAvatarHandler)).Methods("GET", "OPTIONS")
api.HandleFunc("/users/{id}/avatar", basicAuthMiddleware(uploadAvatarHandler)).Methods("POST", "OPTIONS")
// Permission Groups Routes (Protected)
api.HandleFunc("/permission-groups", basicAuthMiddleware(getPermissionGroupsHandler)).Methods("GET", "OPTIONS")
api.HandleFunc("/permission-groups", basicAuthMiddleware(createPermissionGroupHandler)).Methods("POST", "OPTIONS")
api.HandleFunc("/permission-groups/{id}", basicAuthMiddleware(getPermissionGroupHandler)).Methods("GET", "OPTIONS")
api.HandleFunc("/permission-groups/{id}", basicAuthMiddleware(updatePermissionGroupHandler)).Methods("PUT", "OPTIONS")
api.HandleFunc("/permission-groups/{id}", basicAuthMiddleware(deletePermissionGroupHandler)).Methods("DELETE", "OPTIONS")
// Provider Routes (Protected)
api.HandleFunc("/providers", basicAuthMiddleware(getProvidersHandler)).Methods("GET", "OPTIONS")
api.HandleFunc("/providers/{id}", basicAuthMiddleware(getProviderHandler)).Methods("GET", "OPTIONS")
@@ -3965,12 +4582,12 @@ func signCSRHandler(w http.ResponseWriter, r *http.Request) {
userID, username := getUserFromRequest(r)
ipAddress, userAgent := getRequestInfo(r)
auditService.Track(r.Context(), "SIGN", "csr", csrID, userID, username, map[string]interface{}{
"providerId": req.ProviderID,
"fqdnId": fqdnID,
"spaceId": spaceID,
"providerId": req.ProviderID,
"fqdnId": fqdnID,
"spaceId": spaceID,
"certificateId": result.OrderID,
"status": result.Status,
"message": fmt.Sprintf("CSR signiert mit Provider %s für FQDN %s (Certificate ID: %s)", req.ProviderID, fqdnID, result.OrderID),
"status": result.Status,
"message": fmt.Sprintf("CSR signiert mit Provider %s für FQDN %s (Certificate ID: %s)", req.ProviderID, fqdnID, result.OrderID),
}, ipAddress, userAgent)
}

Binary file not shown.

Binary file not shown.