4
0

handler.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package webhooks
  2. import (
  3. "io"
  4. "net/http"
  5. "github.com/OliveTin/OliveTin/internal/auth"
  6. "github.com/OliveTin/OliveTin/internal/config"
  7. "github.com/OliveTin/OliveTin/internal/executor"
  8. log "github.com/sirupsen/logrus"
  9. )
  10. type ActionWebhookConfig struct {
  11. Action *config.Action
  12. Config config.WebhookConfig
  13. }
  14. type WebhookHandler struct {
  15. cfg *config.Config
  16. executor *executor.Executor
  17. }
  18. func NewWebhookHandler(cfg *config.Config, ex *executor.Executor) *WebhookHandler {
  19. return &WebhookHandler{
  20. cfg: cfg,
  21. executor: ex,
  22. }
  23. }
  24. func (h *WebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
  25. if r.Method != http.MethodPost {
  26. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  27. return
  28. }
  29. payload, err := h.readPayload(r)
  30. if err != nil {
  31. http.Error(w, "Failed to read payload", http.StatusBadRequest)
  32. return
  33. }
  34. matchingActions := h.findMatchingActions(r, payload)
  35. if len(matchingActions) == 0 {
  36. h.writeOKResponse(w, "no matching webhook actions")
  37. return
  38. }
  39. processed := h.processMatchingActions(matchingActions, r, payload)
  40. log.WithFields(log.Fields{
  41. "matched": len(matchingActions),
  42. "processed": processed,
  43. }).Infof("Webhook processed")
  44. h.writeOKResponse(w, "webhook actions")
  45. }
  46. func (h *WebhookHandler) readPayload(r *http.Request) ([]byte, error) {
  47. maxSize := int64(1024 * 1024)
  48. payload, err := io.ReadAll(io.LimitReader(r.Body, maxSize))
  49. if err != nil {
  50. log.WithFields(log.Fields{
  51. "error": err,
  52. }).Warnf("Failed to read webhook payload")
  53. return nil, err
  54. }
  55. return payload, nil
  56. }
  57. func (h *WebhookHandler) writeOKResponse(w http.ResponseWriter, context string) {
  58. w.WriteHeader(http.StatusOK)
  59. if _, err := w.Write([]byte("OK")); err != nil {
  60. log.WithError(err).Warnf("Failed to write response for %s", context)
  61. }
  62. }
  63. func (h *WebhookHandler) processMatchingActions(matchingActions []ActionWebhookConfig, r *http.Request, payload []byte) int {
  64. processed := 0
  65. for _, actionConfig := range matchingActions {
  66. if h.processWebhook(actionConfig, r, payload) {
  67. processed++
  68. }
  69. }
  70. return processed
  71. }
  72. func (h *WebhookHandler) findMatchingActions(r *http.Request, payload []byte) []ActionWebhookConfig {
  73. var matches []ActionWebhookConfig
  74. for _, action := range h.cfg.Actions {
  75. matches = append(matches, h.findMatchingWebhooksForAction(action, r, payload)...)
  76. }
  77. return matches
  78. }
  79. func (h *WebhookHandler) findMatchingWebhooksForAction(action *config.Action, r *http.Request, payload []byte) []ActionWebhookConfig {
  80. var matches []ActionWebhookConfig
  81. for _, webhookConfig := range action.ExecOnWebhook {
  82. webhookConfigCopy := webhookConfig
  83. if webhookConfigCopy.Template != "" {
  84. ApplyGitHubTemplate(&webhookConfigCopy, webhookConfigCopy.Template)
  85. }
  86. matcher := NewWebhookMatcher(webhookConfigCopy, r, payload)
  87. if matcher.Matches() {
  88. matches = append(matches, ActionWebhookConfig{
  89. Action: action,
  90. Config: webhookConfigCopy,
  91. })
  92. }
  93. }
  94. return matches
  95. }
  96. func (h *WebhookHandler) processWebhook(actionConfig ActionWebhookConfig, r *http.Request, payload []byte) bool {
  97. verifier := NewAuthVerifier(actionConfig.Config)
  98. if !verifier.Verify(r, payload) {
  99. log.WithFields(log.Fields{
  100. "actionTitle": actionConfig.Action.Title,
  101. "authType": actionConfig.Config.AuthType,
  102. }).Warnf("Webhook authentication failed")
  103. return false
  104. }
  105. matcher := NewWebhookMatcher(actionConfig.Config, r, payload)
  106. args, err := matcher.ExtractArguments()
  107. if err != nil {
  108. log.WithFields(log.Fields{
  109. "actionTitle": actionConfig.Action.Title,
  110. "error": err,
  111. }).Warnf("Failed to extract webhook arguments")
  112. return false
  113. }
  114. justification, err := matcher.ExtractJustification()
  115. if err != nil {
  116. log.WithFields(log.Fields{
  117. "actionTitle": actionConfig.Action.Title,
  118. "error": err,
  119. }).Warnf("Failed to extract webhook justification")
  120. return false
  121. }
  122. h.executeAction(actionConfig.Action, args, justification)
  123. return true
  124. }
  125. func (h *WebhookHandler) executeAction(action *config.Action, args map[string]string, justification string) {
  126. binding := h.executor.FindBindingWithNoEntity(action)
  127. if binding == nil {
  128. log.WithFields(log.Fields{
  129. "actionTitle": action.Title,
  130. }).Warnf("Action binding not found, skipping execution")
  131. return
  132. }
  133. definedArgs := filterToDefinedArguments(args, action)
  134. req := &executor.ExecutionRequest{
  135. Binding: binding,
  136. Cfg: h.cfg,
  137. Tags: []string{"webhook"},
  138. Arguments: definedArgs,
  139. Justification: justification,
  140. AuthenticatedUser: auth.UserFromSystem(h.cfg, "webhook"),
  141. }
  142. h.executor.ExecRequest(req)
  143. }
  144. func filterToDefinedArguments(args map[string]string, action *config.Action) map[string]string {
  145. definedNames := make(map[string]struct{})
  146. for _, arg := range action.Arguments {
  147. definedNames[arg.Name] = struct{}{}
  148. }
  149. filtered := make(map[string]string)
  150. for k, v := range args {
  151. if _, ok := definedNames[k]; ok {
  152. filtered[k] = v
  153. }
  154. }
  155. return filtered
  156. }