1032 lines
28 KiB
Go
1032 lines
28 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
type milestoneRoleResponse struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
Description string `json:"description"`
|
|
Users []milestoneRoleUserItem `json:"users,omitempty"`
|
|
UserCount int `json:"userCount"`
|
|
CanEdit bool `json:"canEdit"`
|
|
CanDelete bool `json:"canDelete"`
|
|
}
|
|
|
|
type milestoneRoleUserItem struct {
|
|
ID string `json:"id"`
|
|
DisplayName string `json:"displayName"`
|
|
Name string `json:"name"`
|
|
UserName string `json:"userName"`
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Domain string `json:"domain"`
|
|
SID string `json:"sid"`
|
|
DistinguishedName string `json:"distinguishedName"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
type saveMilestoneRoleRequest struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"displayName"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
type addMilestoneRoleUsersRequest struct {
|
|
Users []milestoneRoleUserInput `json:"users"`
|
|
}
|
|
|
|
type milestoneRoleUserInput struct {
|
|
ID string `json:"id"`
|
|
DisplayName string `json:"displayName"`
|
|
Username string `json:"username"`
|
|
UserName string `json:"userName"`
|
|
Name string `json:"name"`
|
|
Email string `json:"email"`
|
|
Domain string `json:"domain"`
|
|
DistinguishedName string `json:"distinguishedName"`
|
|
SID string `json:"sid"`
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
func writeMilestoneRoleActionError(w http.ResponseWriter, status int, message string, err error) {
|
|
if err != nil {
|
|
log.Printf("%s: %v", message, err)
|
|
}
|
|
|
|
payload := map[string]string{
|
|
"error": message,
|
|
}
|
|
if err != nil {
|
|
payload["details"] = err.Error()
|
|
}
|
|
|
|
writeJSON(w, status, payload)
|
|
}
|
|
|
|
func (s *Server) handleAdminListMilestoneRoles(w http.ResponseWriter, r *http.Request) {
|
|
currentUser, _ := userFromContext(r.Context())
|
|
isAdministrator := userHasRight(currentUser, "admin")
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
roles, err := listOperationMilestoneRoles(r.Context(), config)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rollen konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
responseRoles := make([]milestoneRoleResponse, 0, len(roles))
|
|
for _, role := range roles {
|
|
if milestoneRoleIsAdministrators(role) && !isAdministrator {
|
|
continue
|
|
}
|
|
|
|
responseRole := milestoneRoleResponseFromRole(role, nil)
|
|
responseRoles = append(responseRoles, responseRole)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"roles": responseRoles,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminGetMilestoneRole(w http.ResponseWriter, r *http.Request) {
|
|
roleID := strings.TrimSpace(r.PathValue("id"))
|
|
if roleID == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollen-ID fehlt")
|
|
return
|
|
}
|
|
currentUser, _ := userFromContext(r.Context())
|
|
isAdministrator := userHasRight(currentUser, "admin")
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
role, err := getOperationMilestoneRole(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht geladen werden")
|
|
return
|
|
}
|
|
if milestoneRoleIsAdministrators(*role) && !isAdministrator {
|
|
writeError(w, http.StatusNotFound, "Milestone-Rolle wurde nicht gefunden")
|
|
return
|
|
}
|
|
|
|
users, err := listOperationMilestoneRoleUsers(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rollenbenutzer konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"role": milestoneRoleResponseFromRole(*role, users),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminCreateMilestoneRole(w http.ResponseWriter, r *http.Request) {
|
|
var input saveMilestoneRoleRequest
|
|
if err := readJSON(r, &input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
|
return
|
|
}
|
|
|
|
roleName := strings.TrimSpace(input.Name)
|
|
if roleName == "" {
|
|
roleName = strings.TrimSpace(input.DisplayName)
|
|
}
|
|
if roleName == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollenname ist erforderlich")
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
existingRole, err := findOperationMilestoneRoleByName(r.Context(), config, roleName)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rollen konnten nicht geprüft werden")
|
|
return
|
|
}
|
|
if existingRole != nil {
|
|
writeError(w, http.StatusConflict, "Eine Rolle mit diesem Namen existiert bereits")
|
|
return
|
|
}
|
|
|
|
payload := map[string]any{
|
|
"name": roleName,
|
|
"displayName": strings.TrimSpace(input.DisplayName),
|
|
"description": strings.TrimSpace(input.Description),
|
|
}
|
|
if strings.TrimSpace(input.DisplayName) == "" {
|
|
payload["displayName"] = roleName
|
|
}
|
|
|
|
body, _, err := operationMilestoneRequestJSON(
|
|
r.Context(),
|
|
config,
|
|
http.MethodPost,
|
|
"/API/rest/v1/roles",
|
|
payload,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht angelegt werden")
|
|
return
|
|
}
|
|
|
|
role := operationMilestoneRoleFromItem(decodeOperationMilestoneObject(body))
|
|
if role.ID == "" {
|
|
roleFromList, err := findOperationMilestoneRoleByName(r.Context(), config, roleName)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle wurde angelegt, konnte aber nicht geladen werden")
|
|
return
|
|
}
|
|
if roleFromList == nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone hat keine Rollen-ID zurückgegeben")
|
|
return
|
|
}
|
|
role = *roleFromList
|
|
}
|
|
|
|
roleDetail, err := getOperationMilestoneRole(r.Context(), config, role.ID)
|
|
if err == nil && roleDetail != nil {
|
|
role = *roleDetail
|
|
}
|
|
|
|
writeJSON(w, http.StatusCreated, map[string]any{
|
|
"role": milestoneRoleResponseFromRole(role, nil),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminUpdateMilestoneRole(w http.ResponseWriter, r *http.Request) {
|
|
roleID := strings.TrimSpace(r.PathValue("id"))
|
|
if roleID == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollen-ID fehlt")
|
|
return
|
|
}
|
|
currentUser, _ := userFromContext(r.Context())
|
|
isAdministrator := userHasRight(currentUser, "admin")
|
|
|
|
var input saveMilestoneRoleRequest
|
|
if err := readJSON(r, &input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
|
return
|
|
}
|
|
|
|
roleName := strings.TrimSpace(input.Name)
|
|
if roleName == "" {
|
|
roleName = strings.TrimSpace(input.DisplayName)
|
|
}
|
|
if roleName == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollenname ist erforderlich")
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
role, err := getOperationMilestoneRole(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht geladen werden")
|
|
return
|
|
}
|
|
if milestoneRoleIsAdministrators(*role) && !isAdministrator {
|
|
writeError(w, http.StatusNotFound, "Milestone-Rolle wurde nicht gefunden")
|
|
return
|
|
}
|
|
|
|
role.Name = roleName
|
|
role.DisplayName = strings.TrimSpace(input.DisplayName)
|
|
if role.DisplayName == "" {
|
|
role.DisplayName = roleName
|
|
}
|
|
|
|
if err := patchOperationMilestoneRole(r.Context(), config, role.ID, map[string]any{
|
|
"name": roleName,
|
|
"displayName": role.DisplayName,
|
|
"description": strings.TrimSpace(input.Description),
|
|
}); err != nil {
|
|
writeMilestoneRoleActionError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht gespeichert werden", err)
|
|
return
|
|
}
|
|
|
|
role, err = getOperationMilestoneRole(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle wurde gespeichert, konnte aber nicht geladen werden")
|
|
return
|
|
}
|
|
users, _ := listOperationMilestoneRoleUsers(r.Context(), config, roleID)
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"role": milestoneRoleResponseFromRole(*role, users),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminDeleteMilestoneRole(w http.ResponseWriter, r *http.Request) {
|
|
roleID := strings.TrimSpace(r.PathValue("id"))
|
|
if roleID == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollen-ID fehlt")
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
role, err := getOperationMilestoneRole(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht geladen werden")
|
|
return
|
|
}
|
|
if milestoneRoleIsAdministrators(*role) {
|
|
writeError(w, http.StatusForbidden, "Die Rolle Administrators darf nicht gelöscht werden")
|
|
return
|
|
}
|
|
|
|
_, _, err = operationMilestoneRequestJSON(
|
|
r.Context(),
|
|
config,
|
|
http.MethodDelete,
|
|
"/API/rest/v1/roles/"+url.PathEscape(roleID),
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht gelöscht werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
|
|
}
|
|
|
|
func (s *Server) handleAdminAddMilestoneRoleUsers(w http.ResponseWriter, r *http.Request) {
|
|
roleID := strings.TrimSpace(r.PathValue("id"))
|
|
if roleID == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollen-ID fehlt")
|
|
return
|
|
}
|
|
currentUser, _ := userFromContext(r.Context())
|
|
isAdministrator := userHasRight(currentUser, "admin")
|
|
|
|
var input addMilestoneRoleUsersRequest
|
|
if err := readJSON(r, &input); err != nil {
|
|
writeError(w, http.StatusBadRequest, "Invalid JSON")
|
|
return
|
|
}
|
|
if len(input.Users) == 0 {
|
|
writeError(w, http.StatusBadRequest, "Keine Benutzer ausgewählt")
|
|
return
|
|
}
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
role, users, err := getMilestoneAdminRoleWithUsers(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht geladen werden")
|
|
return
|
|
}
|
|
if milestoneRoleIsAdministrators(*role) && !isAdministrator {
|
|
writeError(w, http.StatusNotFound, "Milestone-Rolle wurde nicht gefunden")
|
|
return
|
|
}
|
|
|
|
nextUsers := users
|
|
usersToAdd := []map[string]any{}
|
|
for _, userInput := range input.Users {
|
|
user := milestoneRoleUserReferenceFromInput(userInput)
|
|
if milestoneItemID(user) == "" || milestoneRoleUserItemIsHidden(milestoneRoleUserItemFromObject(user)) {
|
|
continue
|
|
}
|
|
if operationMilestoneObjectByID(nextUsers, milestoneItemID(user)) != nil {
|
|
continue
|
|
}
|
|
nextUsers = appendOperationMilestoneObjectByID(nextUsers, user)
|
|
usersToAdd = appendOperationMilestoneObjectByID(usersToAdd, user)
|
|
}
|
|
if len(usersToAdd) == 0 {
|
|
writeError(w, http.StatusBadRequest, "Kein gültiger Benutzer ausgewählt")
|
|
return
|
|
}
|
|
|
|
if err := addOperationMilestoneRoleMembers(r.Context(), config, role.ID, usersToAdd); err != nil {
|
|
writeMilestoneRoleActionError(w, http.StatusBadGateway, "Benutzer konnten der Milestone-Rolle nicht hinzugefügt werden", err)
|
|
return
|
|
}
|
|
|
|
role, users, err = getMilestoneAdminRoleWithUsers(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle wurde gespeichert, konnte aber nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"role": milestoneRoleResponseFromRole(*role, users),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminRemoveMilestoneRoleUser(w http.ResponseWriter, r *http.Request) {
|
|
roleID := strings.TrimSpace(r.PathValue("id"))
|
|
userID := strings.TrimSpace(r.PathValue("userId"))
|
|
if roleID == "" || userID == "" {
|
|
writeError(w, http.StatusBadRequest, "Rollen-ID oder Benutzer-ID fehlt")
|
|
return
|
|
}
|
|
currentUser, _ := userFromContext(r.Context())
|
|
isAdministrator := userHasRight(currentUser, "admin")
|
|
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
role, users, err := getMilestoneAdminRoleWithUsers(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle konnte nicht geladen werden")
|
|
return
|
|
}
|
|
if milestoneRoleIsAdministrators(*role) && !isAdministrator {
|
|
writeError(w, http.StatusNotFound, "Milestone-Rolle wurde nicht gefunden")
|
|
return
|
|
}
|
|
|
|
nextUsers := make([]map[string]any, 0, len(users))
|
|
usersToRemove := []map[string]any{}
|
|
for _, user := range users {
|
|
if strings.EqualFold(milestoneItemID(user), userID) {
|
|
if milestoneRoleUserItemIsHidden(milestoneRoleUserItemFromObject(user)) {
|
|
writeError(w, http.StatusForbidden, "Dieser Benutzer darf nicht entfernt werden")
|
|
return
|
|
}
|
|
usersToRemove = append(usersToRemove, user)
|
|
continue
|
|
}
|
|
nextUsers = append(nextUsers, user)
|
|
}
|
|
if len(usersToRemove) == 0 {
|
|
writeError(w, http.StatusNotFound, "Benutzer wurde in der Rolle nicht gefunden")
|
|
return
|
|
}
|
|
|
|
if err := removeOperationMilestoneRoleMembers(r.Context(), config, role.ID, usersToRemove); err != nil {
|
|
writeMilestoneRoleActionError(w, http.StatusBadGateway, "Benutzer konnte nicht aus der Milestone-Rolle entfernt werden", err)
|
|
return
|
|
}
|
|
|
|
role, users, err = getMilestoneAdminRoleWithUsers(r.Context(), config, roleID)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Rolle wurde gespeichert, konnte aber nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"role": milestoneRoleResponseFromRole(*role, users),
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminListActiveDirectoryUsers(w http.ResponseWriter, r *http.Request) {
|
|
credentials, err := s.getActiveDirectoryCredentials(r.Context())
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"configured": false,
|
|
"users": []activeDirectoryUser{},
|
|
})
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "AD-Einstellungen konnten nicht geladen werden")
|
|
return
|
|
}
|
|
if !credentials.Configured() {
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"configured": false,
|
|
"users": []activeDirectoryUser{},
|
|
})
|
|
return
|
|
}
|
|
|
|
users, err := listActiveDirectoryUsers(r.Context(), credentials)
|
|
if err != nil {
|
|
logError("AD-Benutzer konnten nicht geladen werden", err, LogFields{
|
|
"baseDn": credentials.BaseDN,
|
|
})
|
|
writeError(w, http.StatusBadGateway, "AD-Benutzer konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"configured": true,
|
|
"baseDn": credentials.BaseDN,
|
|
"users": users,
|
|
})
|
|
}
|
|
|
|
func (s *Server) handleAdminListMilestoneBasicUsers(w http.ResponseWriter, r *http.Request) {
|
|
config, err := s.getMilestoneRuntimeConfig(r.Context())
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Konfiguration konnte nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
items, err := listOperationMilestoneObjectsAtPath(r.Context(), config, "/API/rest/v1/basicUsers")
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Milestone-Basisbenutzer konnten nicht geladen werden")
|
|
return
|
|
}
|
|
|
|
users := make([]milestoneRoleUserItem, 0, len(items))
|
|
for _, item := range items {
|
|
user := milestoneRoleUserItemFromObject(item)
|
|
if user.ID == "" || milestoneRoleUserItemIsHidden(user) {
|
|
continue
|
|
}
|
|
user.Source = "basic"
|
|
user.Domain = ""
|
|
users = append(users, user)
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"users": users,
|
|
})
|
|
}
|
|
|
|
func getMilestoneAdminRoleWithUsers(
|
|
ctx context.Context,
|
|
config milestoneRuntimeConfig,
|
|
roleID string,
|
|
) (*operationMilestoneRole, []map[string]any, error) {
|
|
role, err := getOperationMilestoneRole(ctx, config, roleID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
users, err := listOperationMilestoneRoleUsers(ctx, config, roleID)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return role, users, nil
|
|
}
|
|
|
|
func milestoneRoleResponseFromRole(
|
|
role operationMilestoneRole,
|
|
users []map[string]any,
|
|
) milestoneRoleResponse {
|
|
roleData := role.Data
|
|
if roleData == nil {
|
|
roleData = map[string]any{}
|
|
}
|
|
|
|
if users == nil {
|
|
users = operationMilestoneObjectListFromValue(roleData["users"])
|
|
}
|
|
|
|
responseUsers := make([]milestoneRoleUserItem, 0, len(users))
|
|
for _, user := range users {
|
|
if item := milestoneRoleUserItemFromObject(user); item.ID != "" {
|
|
if milestoneRoleUserItemIsHidden(item) {
|
|
continue
|
|
}
|
|
responseUsers = append(responseUsers, item)
|
|
}
|
|
}
|
|
canDelete := !milestoneRoleIsAdministrators(role)
|
|
|
|
return milestoneRoleResponse{
|
|
ID: role.ID,
|
|
Name: role.Name,
|
|
DisplayName: operationMilestoneRoleDisplayName(role),
|
|
Description: strings.TrimSpace(milestoneStringValue(roleData["description"])),
|
|
Users: responseUsers,
|
|
UserCount: len(responseUsers),
|
|
CanEdit: true,
|
|
CanDelete: canDelete,
|
|
}
|
|
}
|
|
|
|
func milestoneRoleUserItemFromObject(user map[string]any) milestoneRoleUserItem {
|
|
id := milestoneItemID(user)
|
|
displayName := strings.TrimSpace(operationMilestoneItemDisplayName(user))
|
|
name := strings.TrimSpace(milestoneStringValue(user["name"]))
|
|
userName := strings.TrimSpace(milestoneStringValue(user["userName"]))
|
|
if userName == "" {
|
|
userName = strings.TrimSpace(milestoneStringValue(user["username"]))
|
|
}
|
|
if userName == "" {
|
|
userName = strings.TrimSpace(milestoneStringValue(user["accountName"]))
|
|
}
|
|
if userName == "" {
|
|
userName = strings.TrimSpace(milestoneStringValue(user["loginName"]))
|
|
}
|
|
email := strings.TrimSpace(milestoneStringValue(user["email"]))
|
|
if email == "" {
|
|
email = strings.TrimSpace(milestoneStringValue(user["mail"]))
|
|
}
|
|
sid := strings.TrimSpace(milestoneStringValue(user["sid"]))
|
|
if sid == "" {
|
|
sid = strings.TrimSpace(milestoneStringValue(user["objectSid"]))
|
|
}
|
|
distinguishedName := strings.TrimSpace(milestoneStringValue(user["distinguishedName"]))
|
|
source := milestoneRoleUserSourceFromObject(user)
|
|
domain := milestoneRoleUserDomainFromObject(user)
|
|
|
|
if splitDomain, splitUserName := splitMilestoneDomainUser(userName); splitDomain != "" {
|
|
if domain == "" {
|
|
domain = splitDomain
|
|
}
|
|
userName = splitUserName
|
|
}
|
|
if splitDomain, splitName := splitMilestoneDomainUser(name); splitDomain != "" {
|
|
if domain == "" {
|
|
domain = splitDomain
|
|
}
|
|
name = splitName
|
|
}
|
|
if splitDomain, splitDisplayName := splitMilestoneDomainUser(displayName); splitDomain != "" {
|
|
if domain == "" {
|
|
domain = splitDomain
|
|
}
|
|
if userName == "" {
|
|
userName = splitDisplayName
|
|
}
|
|
displayName = splitDisplayName
|
|
}
|
|
if domain == "" {
|
|
domain = activeDirectoryDomainFromPrincipal(milestoneStringValue(user["userPrincipalName"]))
|
|
}
|
|
if domain == "" && email != "" {
|
|
domain = activeDirectoryDomainFromPrincipal(email)
|
|
}
|
|
if domain == "" && distinguishedName != "" {
|
|
domain = activeDirectoryDomainFromDN(distinguishedName)
|
|
}
|
|
if milestoneRoleUserDomainIsBasic(domain) {
|
|
source = "basic"
|
|
domain = ""
|
|
}
|
|
if source == "basic" {
|
|
domain = ""
|
|
}
|
|
|
|
if displayName == "" {
|
|
displayName = userName
|
|
}
|
|
if displayName == "" {
|
|
displayName = name
|
|
}
|
|
if displayName == "" {
|
|
displayName = id
|
|
}
|
|
|
|
return milestoneRoleUserItem{
|
|
ID: id,
|
|
DisplayName: displayName,
|
|
Name: name,
|
|
UserName: userName,
|
|
Username: userName,
|
|
Email: email,
|
|
Domain: domain,
|
|
SID: sid,
|
|
DistinguishedName: distinguishedName,
|
|
Source: source,
|
|
}
|
|
}
|
|
|
|
func milestoneRoleUserReferenceFromInput(input milestoneRoleUserInput) map[string]any {
|
|
userID := strings.TrimSpace(input.ID)
|
|
userSID := strings.TrimSpace(input.SID)
|
|
userDN := strings.TrimSpace(input.DistinguishedName)
|
|
displayName := strings.TrimSpace(input.DisplayName)
|
|
username := strings.TrimSpace(input.Username)
|
|
if username == "" {
|
|
username = strings.TrimSpace(input.UserName)
|
|
}
|
|
name := strings.TrimSpace(input.Name)
|
|
email := strings.TrimSpace(input.Email)
|
|
domain := strings.TrimSpace(input.Domain)
|
|
source := milestoneRoleUserSource(input.Source)
|
|
if source == "" {
|
|
source = "ad"
|
|
}
|
|
|
|
if milestoneRoleUserDomainIsBasic(domain) {
|
|
source = "basic"
|
|
domain = ""
|
|
}
|
|
if splitDomain, splitUsername := splitMilestoneDomainUser(username); splitDomain != "" {
|
|
if domain == "" && source != "basic" {
|
|
domain = splitDomain
|
|
}
|
|
username = splitUsername
|
|
}
|
|
if splitDomain, splitName := splitMilestoneDomainUser(name); splitDomain != "" {
|
|
if domain == "" && source != "basic" {
|
|
domain = splitDomain
|
|
}
|
|
name = splitName
|
|
}
|
|
if splitDomain, splitDisplayName := splitMilestoneDomainUser(displayName); splitDomain != "" {
|
|
if domain == "" && source != "basic" {
|
|
domain = splitDomain
|
|
}
|
|
displayName = splitDisplayName
|
|
}
|
|
if source == "basic" {
|
|
domain = ""
|
|
}
|
|
|
|
if userID == "" {
|
|
userID = userSID
|
|
}
|
|
if userID == "" {
|
|
userID = userDN
|
|
}
|
|
if userID == "" {
|
|
userID = username
|
|
}
|
|
if displayName == "" {
|
|
displayName = name
|
|
}
|
|
if displayName == "" {
|
|
displayName = username
|
|
}
|
|
if displayName == "" {
|
|
displayName = email
|
|
}
|
|
if displayName == "" {
|
|
displayName = userID
|
|
}
|
|
|
|
user := map[string]any{
|
|
"id": userID,
|
|
"name": displayName,
|
|
"displayName": displayName,
|
|
"userName": username,
|
|
"username": username,
|
|
"email": email,
|
|
"relations": map[string]any{
|
|
"self": map[string]any{
|
|
"type": milestoneRoleUserRelationType(source),
|
|
"id": userID,
|
|
},
|
|
},
|
|
}
|
|
if domain != "" {
|
|
user["domain"] = domain
|
|
}
|
|
if userSID != "" {
|
|
user["sid"] = userSID
|
|
user["objectSid"] = userSID
|
|
}
|
|
if userDN != "" {
|
|
user["distinguishedName"] = userDN
|
|
}
|
|
|
|
return user
|
|
}
|
|
|
|
func addOperationMilestoneRoleMembers(
|
|
ctx context.Context,
|
|
config milestoneRuntimeConfig,
|
|
roleID string,
|
|
users []map[string]any,
|
|
) error {
|
|
return postOperationMilestoneRoleMemberTasks(ctx, config, roleID, "AddRoleMember", users)
|
|
}
|
|
|
|
func removeOperationMilestoneRoleMembers(
|
|
ctx context.Context,
|
|
config milestoneRuntimeConfig,
|
|
roleID string,
|
|
users []map[string]any,
|
|
) error {
|
|
return postOperationMilestoneRoleMemberTasks(ctx, config, roleID, "RemoveRoleMember", users)
|
|
}
|
|
|
|
func postOperationMilestoneRoleMemberTasks(
|
|
ctx context.Context,
|
|
config milestoneRuntimeConfig,
|
|
roleID string,
|
|
taskName string,
|
|
users []map[string]any,
|
|
) error {
|
|
roleID = strings.TrimSpace(roleID)
|
|
taskName = strings.TrimSpace(taskName)
|
|
if roleID == "" {
|
|
return errors.New("role id is missing")
|
|
}
|
|
if taskName == "" {
|
|
return errors.New("role member task name is missing")
|
|
}
|
|
|
|
for _, user := range users {
|
|
payload, err := milestoneRoleMemberTaskPayload(user)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(payload) == 0 {
|
|
continue
|
|
}
|
|
|
|
body, _, err := operationMilestoneRequestJSON(
|
|
ctx,
|
|
config,
|
|
http.MethodPost,
|
|
"/API/rest/v1/roles/"+url.PathEscape(roleID)+"/users?task="+url.QueryEscape(taskName),
|
|
payload,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if result, ok := milestoneTaskResultFromBody(body); ok && milestoneTaskResultIsError(result) {
|
|
return fmt.Errorf(
|
|
"milestone %s returned error state=%s errorCode=%s errorText=%s",
|
|
taskName,
|
|
result.State,
|
|
result.ErrorCode,
|
|
result.ErrorText,
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func milestoneRoleMemberTaskItemSelection(user map[string]any) map[string]any {
|
|
userID := strings.TrimSpace(milestoneItemID(user))
|
|
if userID == "" {
|
|
return nil
|
|
}
|
|
|
|
source := milestoneRoleUserSourceFromObject(user)
|
|
itemSelection := map[string]any{
|
|
"type": milestoneRoleUserRelationType(source),
|
|
"id": userID,
|
|
}
|
|
|
|
return itemSelection
|
|
}
|
|
|
|
func milestoneRoleMemberTaskPayload(user map[string]any) (map[string]any, error) {
|
|
itemSelection := milestoneRoleMemberTaskItemSelection(user)
|
|
userID := strings.TrimSpace(milestoneStringValue(itemSelection["id"]))
|
|
if userID == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
payload := map[string]any{
|
|
"itemSelection": itemSelection,
|
|
}
|
|
|
|
if milestoneRoleUserSourceFromObject(user) != "basic" {
|
|
sid := strings.TrimSpace(milestoneStringValue(user["sid"]))
|
|
if sid == "" {
|
|
sid = strings.TrimSpace(milestoneStringValue(user["objectSid"]))
|
|
}
|
|
if sid == "" && strings.HasPrefix(strings.ToUpper(userID), "S-") {
|
|
sid = userID
|
|
}
|
|
if sid == "" {
|
|
return nil, fmt.Errorf(
|
|
"Windows-Benutzer %q hat keine SID. AddRoleMember erwartet den Parameter sid.",
|
|
operationMilestoneItemDisplayName(user),
|
|
)
|
|
}
|
|
|
|
payload["sid"] = sid
|
|
}
|
|
|
|
return payload, nil
|
|
}
|
|
|
|
func operationMilestoneObjectByID(items []map[string]any, id string) map[string]any {
|
|
id = strings.TrimSpace(id)
|
|
if id == "" {
|
|
return nil
|
|
}
|
|
|
|
for _, item := range items {
|
|
if strings.EqualFold(milestoneItemID(item), id) {
|
|
return item
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func milestoneRoleUserSourceFromObject(user map[string]any) string {
|
|
if milestoneRoleUserDomainIsBasic(milestoneRoleUserDomainFromObject(user)) {
|
|
return "basic"
|
|
}
|
|
|
|
source := milestoneRoleUserSource(milestoneStringValue(user["source"]))
|
|
if source != "" {
|
|
return source
|
|
}
|
|
|
|
if relations, ok := user["relations"].(map[string]any); ok {
|
|
if self, ok := relations["self"].(map[string]any); ok {
|
|
relationType := strings.TrimSpace(milestoneStringValue(self["type"]))
|
|
if strings.EqualFold(relationType, "basicUsers") ||
|
|
strings.EqualFold(relationType, "basicUser") {
|
|
return "basic"
|
|
}
|
|
}
|
|
}
|
|
|
|
itemType := strings.TrimSpace(milestoneStringValue(user["type"]))
|
|
if strings.EqualFold(itemType, "basicUsers") ||
|
|
strings.EqualFold(itemType, "basicUser") {
|
|
return "basic"
|
|
}
|
|
|
|
return "ad"
|
|
}
|
|
|
|
func milestoneRoleUserDomainIsBasic(domain string) bool {
|
|
domain = strings.TrimSpace(domain)
|
|
domain = strings.TrimPrefix(domain, "[")
|
|
domain = strings.TrimSuffix(domain, "]")
|
|
return strings.EqualFold(domain, "basic")
|
|
}
|
|
|
|
func milestoneRoleUserSource(source string) string {
|
|
source = strings.TrimSpace(strings.ToLower(source))
|
|
switch source {
|
|
case "basic", "basicuser", "basicusers":
|
|
return "basic"
|
|
case "ad", "active-directory", "activedirectory", "directory":
|
|
return "ad"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func milestoneRoleUserRelationType(source string) string {
|
|
if milestoneRoleUserSource(source) == "basic" {
|
|
return "basicUsers"
|
|
}
|
|
|
|
return "users"
|
|
}
|
|
|
|
func milestoneRoleUserDomainFromObject(user map[string]any) string {
|
|
for _, key := range []string{
|
|
"domain",
|
|
"domainName",
|
|
"accountDomain",
|
|
"windowsDomain",
|
|
"netbiosDomain",
|
|
"directoryDomain",
|
|
} {
|
|
if value := strings.TrimSpace(milestoneStringValue(user[key])); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func milestoneRoleIsAdministrators(role operationMilestoneRole) bool {
|
|
for _, value := range []string{role.Name, role.DisplayName, operationMilestoneRoleDisplayName(role)} {
|
|
if normalizeMilestoneRoleProtectionValue(value) == "administrators" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func milestoneRoleUserItemIsHidden(user milestoneRoleUserItem) bool {
|
|
candidates := []string{
|
|
milestoneProtectedUserQualifiedName(user.Domain, user.Username),
|
|
milestoneProtectedUserQualifiedName(user.Domain, user.UserName),
|
|
milestoneProtectedUserQualifiedName(user.Domain, user.Name),
|
|
milestoneProtectedUserQualifiedName(user.Domain, user.DisplayName),
|
|
}
|
|
|
|
for _, candidate := range candidates {
|
|
if milestoneProtectedUserNameIsHidden(candidate) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func milestoneProtectedUserNameIsHidden(value string) bool {
|
|
value = normalizeMilestoneRoleProtectionValue(value)
|
|
switch value {
|
|
case "nt-autorität\\netzwerkdienst",
|
|
"nt authority\\network service",
|
|
"tegadmin",
|
|
"tegdssd\\tegadmin",
|
|
"vordefiniert\\administratoren",
|
|
"builtin\\administrators",
|
|
"tegdssd\\milestone_administratoren":
|
|
return true
|
|
}
|
|
|
|
domain, account, ok := strings.Cut(value, "\\")
|
|
return ok && domain == "tegdssd" && strings.HasSuffix(account, "$")
|
|
}
|
|
|
|
func milestoneProtectedUserQualifiedName(domain string, account string) string {
|
|
domain = strings.TrimSpace(domain)
|
|
account = strings.TrimSpace(account)
|
|
if account == "" {
|
|
return ""
|
|
}
|
|
if domain == "" {
|
|
return account
|
|
}
|
|
|
|
return domain + "\\" + account
|
|
}
|
|
|
|
func normalizeMilestoneRoleProtectionValue(value string) string {
|
|
return strings.ToLower(strings.Join(strings.Fields(strings.TrimSpace(value)), " "))
|
|
}
|
|
|
|
func splitMilestoneDomainUser(value string) (string, string) {
|
|
value = strings.TrimSpace(value)
|
|
if value == "" {
|
|
return "", ""
|
|
}
|
|
|
|
domain, username, ok := strings.Cut(value, "\\")
|
|
if !ok || strings.TrimSpace(domain) == "" || strings.TrimSpace(username) == "" {
|
|
return "", value
|
|
}
|
|
|
|
return strings.TrimSpace(domain), strings.TrimSpace(username)
|
|
}
|