munin/internal/web/handlers_workspace_admins.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"))
klartext, 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(klartext))
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: klartext, 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, aktion, 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: aktion, Source: store.SourceWeb,
IP: auth.ClientIP(r), Detail: name,
})
}