100 lines
3.4 KiB
Go
100 lines
3.4 KiB
Go
package web
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"munin/internal/auth"
|
|
"munin/internal/store"
|
|
)
|
|
|
|
// Workspace-Admins anlegen und zurückziehen (AUSBAUSTUFEN.md 4.8).
|
|
//
|
|
// Beides darf nur der Plattform-Admin (siehe Routes): Ein Workspace-Admin soll
|
|
// sich keine weiteren Workspace-Admins schaffen können — sonst ließe sich ein
|
|
// zurückgezogener Zugang durch einen vorher angelegten zweiten ersetzen, ohne
|
|
// dass der Plattform-Admin davon erfährt.
|
|
//
|
|
// Die Seite dazu ist die Bearbeitungsseite des Workspace
|
|
// (/admin/workspaces/{id}/edit), die Formulare senden an die Routen hier.
|
|
|
|
// handleWorkspaceAdminCreate erzeugt einen Workspace-Admin-Zugang und zeigt
|
|
// das Token genau einmal an.
|
|
func (s *Server) handleWorkspaceAdminCreate(w http.ResponseWriter, r *http.Request) {
|
|
ws, ok := s.workspaceFromPath(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
|
|
plaintext, err := auth.GenerateWorkspaceAdminToken()
|
|
if err != nil {
|
|
s.serverError(w, "Token konnte nicht erzeugt werden", err)
|
|
return
|
|
}
|
|
|
|
admin, err := s.control.CreateWorkspaceAdmin(r.Context(), ws.ID, name, auth.HashKey(plaintext))
|
|
if err != nil {
|
|
s.renderWorkspaceEdit(w, r, http.StatusBadRequest, ws, workspaceEditData{}, "", err.Error())
|
|
return
|
|
}
|
|
|
|
sess, _ := SessionFrom(r)
|
|
s.logWorkspaceAdminEvent(r, ws.ID, sess, store.ActionWorkspaceAdminCreated, admin.Name)
|
|
|
|
s.renderWorkspaceEdit(w, r, http.StatusOK, ws,
|
|
workspaceEditData{NewToken: plaintext, NewTokenName: admin.Name},
|
|
"Workspace-Admin angelegt. Das Token wird nur jetzt angezeigt — bitte gleich übernehmen.", "")
|
|
}
|
|
|
|
// handleWorkspaceAdminRevoke zieht einen Workspace-Admin-Zugang zurück. Eine
|
|
// laufende Sitzung endet damit beim nächsten Seitenaufruf (siehe
|
|
// workspaceAdminSession in middleware.go).
|
|
func (s *Server) handleWorkspaceAdminRevoke(w http.ResponseWriter, r *http.Request) {
|
|
ws, ok := s.workspaceFromPath(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
id := r.FormValue("id")
|
|
admin, err := s.control.WorkspaceAdminByID(r.Context(), id)
|
|
// Gehört der Zugang zu einem anderen Workspace, wie "nicht vorhanden"
|
|
// behandeln — dieselbe Regel wie bei den Keys (handleKeyRevoke).
|
|
if errors.Is(err, store.ErrNotFound) || (err == nil && admin.WorkspaceID != ws.ID) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
if err != nil {
|
|
s.serverError(w, "Workspace-Admin konnte nicht gelesen werden", err)
|
|
return
|
|
}
|
|
|
|
if err := s.control.RevokeWorkspaceAdmin(r.Context(), ws.ID, id); err != nil && !errors.Is(err, store.ErrNotFound) {
|
|
s.serverError(w, "Workspace-Admin konnte nicht zurückgezogen werden", err)
|
|
return
|
|
}
|
|
|
|
sess, _ := SessionFrom(r)
|
|
s.logWorkspaceAdminEvent(r, ws.ID, sess, store.ActionWorkspaceAdminRevoked, admin.Name)
|
|
|
|
s.renderWorkspaceEdit(w, r, http.StatusOK, ws, workspaceEditData{},
|
|
"Zugang zurückgezogen. Er gilt ab sofort nicht mehr.", "")
|
|
}
|
|
|
|
// logWorkspaceAdminEvent schreibt ein Ereignis ins Protokoll des betroffenen
|
|
// Workspace — dort, wo auch alles steht, was der Zugang später tut. Der
|
|
// Workspace muss dafür nicht der gerade gewählte des Plattform-Admins sein.
|
|
func (s *Server) logWorkspaceAdminEvent(r *http.Request, workspaceID string, sess Session, action, name string) {
|
|
ws, err := s.workspaces.Get(workspaceID)
|
|
if err != nil {
|
|
log.Printf("Protokoll von Workspace %s nicht erreichbar: %v", workspaceID, err)
|
|
return
|
|
}
|
|
ws.Store.LogAudit(r.Context(), store.AuditEvent{
|
|
Actor: sess.ActorName(), Action: action, Source: store.SourceWeb,
|
|
IP: auth.ClientIP(r), Detail: name,
|
|
})
|
|
}
|