package api import ( "fmt" "strings" "connectrpc.com/connect" apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1" authpublic "github.com/OliveTin/OliveTin/internal/auth/authpublic" "github.com/OliveTin/OliveTin/internal/config" "github.com/OliveTin/OliveTin/internal/entities" "github.com/OliveTin/OliveTin/internal/executor" "github.com/OliveTin/OliveTin/internal/tpl" log "github.com/sirupsen/logrus" ) func validateJustificationRequired(action *config.Action, justification string, user *authpublic.AuthenticatedUser) error { if !actionRequiresJustificationConfig(action) || justificationProvided(justification, user) { return nil } return fmt.Errorf("justification is required for this action") } func actionRequiresJustificationConfig(action *config.Action) bool { return action != nil && action.RequiresJustification() } func justificationProvided(justification string, user *authpublic.AuthenticatedUser) bool { return strings.TrimSpace(justification) != "" || executor.IsSystemExecution(user) } func connectInvalidJustification(err error) error { return connect.NewError(connect.CodeInvalidArgument, err) } func startActionArgumentsFromProto(args []*apiv1.StartActionArgument) map[string]string { result := make(map[string]string, len(args)) for _, arg := range args { result[arg.Name] = arg.Value } return result } func resolveStartJustification(action *config.Action, binding *executor.ActionBinding, clientJustification string, args map[string]string) string { if strings.TrimSpace(clientJustification) != "" { return clientJustification } return resolveJustificationFromTemplate(action, binding, clientJustification, args) } func resolveJustificationFromTemplate(action *config.Action, binding *executor.ActionBinding, fallback string, args map[string]string) string { templateText := action.JustificationTemplateText() if templateText == "" { return fallback } resolved, err := tpl.ParseTemplateWithActionContext(templateText, bindingEntity(binding), args) if err != nil { log.WithFields(justificationTemplateErrorFields(templateText, binding, err)).Warn("Failed to resolve justification template") return fallback } return resolved } func justificationTemplateErrorFields(templateText string, binding *executor.ActionBinding, err error) log.Fields { fields := log.Fields{ "template": templateText, "error": err, } entity := bindingEntity(binding) if entity == nil { return fields } fields["entityKey"] = entity.UniqueKey if binding.Action != nil && binding.Action.Entity != "" { fields["entityType"] = binding.Action.Entity } return fields } func bindingEntity(binding *executor.ActionBinding) *entities.Entity { if binding == nil { return nil } return binding.Entity } func restartRequiresJustificationError() error { return connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("justification is required for this action; use StartAction with a justification instead")) }