| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- package webhooks
- import (
- "io"
- "net/http"
- "github.com/OliveTin/OliveTin/internal/auth"
- "github.com/OliveTin/OliveTin/internal/config"
- "github.com/OliveTin/OliveTin/internal/executor"
- log "github.com/sirupsen/logrus"
- )
- type ActionWebhookConfig struct {
- Action *config.Action
- Config config.WebhookConfig
- }
- type WebhookHandler struct {
- cfg *config.Config
- executor *executor.Executor
- }
- func NewWebhookHandler(cfg *config.Config, ex *executor.Executor) *WebhookHandler {
- return &WebhookHandler{
- cfg: cfg,
- executor: ex,
- }
- }
- func (h *WebhookHandler) HandleWebhook(w http.ResponseWriter, r *http.Request) {
- if r.Method != http.MethodPost {
- http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
- return
- }
- payload, err := h.readPayload(r)
- if err != nil {
- http.Error(w, "Failed to read payload", http.StatusBadRequest)
- return
- }
- matchingActions := h.findMatchingActions(r, payload)
- if len(matchingActions) == 0 {
- h.writeOKResponse(w, "no matching webhook actions")
- return
- }
- processed := h.processMatchingActions(matchingActions, r, payload)
- log.WithFields(log.Fields{
- "matched": len(matchingActions),
- "processed": processed,
- }).Infof("Webhook processed")
- h.writeOKResponse(w, "webhook actions")
- }
- func (h *WebhookHandler) readPayload(r *http.Request) ([]byte, error) {
- maxSize := int64(1024 * 1024)
- payload, err := io.ReadAll(io.LimitReader(r.Body, maxSize))
- if err != nil {
- log.WithFields(log.Fields{
- "error": err,
- }).Warnf("Failed to read webhook payload")
- return nil, err
- }
- return payload, nil
- }
- func (h *WebhookHandler) writeOKResponse(w http.ResponseWriter, context string) {
- w.WriteHeader(http.StatusOK)
- if _, err := w.Write([]byte("OK")); err != nil {
- log.WithError(err).Warnf("Failed to write response for %s", context)
- }
- }
- func (h *WebhookHandler) processMatchingActions(matchingActions []ActionWebhookConfig, r *http.Request, payload []byte) int {
- processed := 0
- for _, actionConfig := range matchingActions {
- if h.processWebhook(actionConfig, r, payload) {
- processed++
- }
- }
- return processed
- }
- func (h *WebhookHandler) findMatchingActions(r *http.Request, payload []byte) []ActionWebhookConfig {
- var matches []ActionWebhookConfig
- for _, action := range h.cfg.Actions {
- matches = append(matches, h.findMatchingWebhooksForAction(action, r, payload)...)
- }
- return matches
- }
- func (h *WebhookHandler) findMatchingWebhooksForAction(action *config.Action, r *http.Request, payload []byte) []ActionWebhookConfig {
- var matches []ActionWebhookConfig
- for _, webhookConfig := range action.ExecOnWebhook {
- webhookConfigCopy := webhookConfig
- if webhookConfigCopy.Template != "" {
- ApplyGitHubTemplate(&webhookConfigCopy, webhookConfigCopy.Template)
- }
- matcher := NewWebhookMatcher(webhookConfigCopy, r, payload)
- if matcher.Matches() {
- matches = append(matches, ActionWebhookConfig{
- Action: action,
- Config: webhookConfigCopy,
- })
- }
- }
- return matches
- }
- func (h *WebhookHandler) processWebhook(actionConfig ActionWebhookConfig, r *http.Request, payload []byte) bool {
- verifier := NewAuthVerifier(actionConfig.Config)
- if !verifier.Verify(r, payload) {
- log.WithFields(log.Fields{
- "actionTitle": actionConfig.Action.Title,
- "authType": actionConfig.Config.AuthType,
- }).Warnf("Webhook authentication failed")
- return false
- }
- matcher := NewWebhookMatcher(actionConfig.Config, r, payload)
- args, err := matcher.ExtractArguments()
- if err != nil {
- log.WithFields(log.Fields{
- "actionTitle": actionConfig.Action.Title,
- "error": err,
- }).Warnf("Failed to extract webhook arguments")
- return false
- }
- justification, err := matcher.ExtractJustification()
- if err != nil {
- log.WithFields(log.Fields{
- "actionTitle": actionConfig.Action.Title,
- "error": err,
- }).Warnf("Failed to extract webhook justification")
- return false
- }
- h.executeAction(actionConfig.Action, args, justification)
- return true
- }
- func (h *WebhookHandler) executeAction(action *config.Action, args map[string]string, justification string) {
- binding := h.executor.FindBindingWithNoEntity(action)
- if binding == nil {
- log.WithFields(log.Fields{
- "actionTitle": action.Title,
- }).Warnf("Action binding not found, skipping execution")
- return
- }
- definedArgs := filterToDefinedArguments(args, action)
- req := &executor.ExecutionRequest{
- Binding: binding,
- Cfg: h.cfg,
- Tags: []string{"webhook"},
- Arguments: definedArgs,
- Justification: justification,
- AuthenticatedUser: auth.UserFromSystem(h.cfg, "webhook"),
- }
- h.executor.ExecRequest(req)
- }
- func filterToDefinedArguments(args map[string]string, action *config.Action) map[string]string {
- definedNames := make(map[string]struct{})
- for _, arg := range action.Arguments {
- definedNames[arg.Name] = struct{}{}
- }
- filtered := make(map[string]string)
- for k, v := range args {
- if _, ok := definedNames[k]; ok {
- filtered[k] = v
- }
- }
- return filtered
- }
|