package webhooks import ( "net/http" "regexp" "strings" "github.com/OliveTin/OliveTin/internal/config" log "github.com/sirupsen/logrus" ) type WebhookMatcher struct { config config.WebhookConfig req *http.Request bodyBytes []byte } func NewWebhookMatcher(cfg config.WebhookConfig, r *http.Request, bodyBytes []byte) *WebhookMatcher { return &WebhookMatcher{ config: cfg, req: r, bodyBytes: bodyBytes, } } func (m *WebhookMatcher) Matches() bool { if !m.matchHeaders() { return false } if !m.matchQuery() { return false } if !m.matchPath() { return false } return true } func (m *WebhookMatcher) matchHeaders() bool { if len(m.config.MatchHeaders) == 0 { return true } for key, expectedValue := range m.config.MatchHeaders { actualValue := m.req.Header.Get(key) if !m.compareValues(actualValue, expectedValue) { log.WithFields(log.Fields{ "header": key, "expected": expectedValue, "actual": actualValue, }).Debugf("Header mismatch") return false } } return true } func (m *WebhookMatcher) matchQuery() bool { if len(m.config.MatchQuery) == 0 { return true } query := m.req.URL.Query() for key, expectedValue := range m.config.MatchQuery { actualValue := query.Get(key) if !m.compareValues(actualValue, expectedValue) { log.WithFields(log.Fields{ "query": key, "expected": expectedValue, "actual": actualValue, }).Debugf("Query parameter mismatch") return false } } return true } func (m *WebhookMatcher) matchPath() bool { if m.config.MatchPath == "" { return true } jsonPath, expectedValue := m.parseMatchPath() matcher, err := NewJSONMatcher(m.bodyBytes) if err != nil { log.WithFields(log.Fields{ "error": err, }).Debugf("Failed to create JSON matcher") return false } return m.matchPathValue(matcher, jsonPath, expectedValue) } func (m *WebhookMatcher) parseMatchPath() (string, string) { parts := strings.SplitN(m.config.MatchPath, "=", 2) jsonPath := parts[0] expectedValue := "" if len(parts) == 2 { expectedValue = parts[1] } return jsonPath, expectedValue } func (m *WebhookMatcher) matchPathValue(matcher *JSONMatcher, jsonPath, expectedValue string) bool { if expectedValue == "" { _, err := matcher.ExtractValue(jsonPath) return err == nil } matches, err := matcher.MatchPath(jsonPath, expectedValue) if err != nil { log.WithFields(log.Fields{ "jsonPath": jsonPath, "error": err, }).Debugf("Failed to match JSONPath") return false } return matches } func (m *WebhookMatcher) compareValues(actual, expected string) bool { if strings.HasPrefix(expected, "regex:") { pattern := strings.TrimPrefix(expected, "regex:") matched, err := regexp.MatchString(pattern, actual) if err != nil { log.WithFields(log.Fields{ "pattern": pattern, "error": err, }).Warnf("Invalid regex pattern") return false } return matched } return actual == expected } func (m *WebhookMatcher) ExtractJustification() (string, error) { if m.config.Justification == "" { return "", nil } matcher, err := NewJSONMatcher(m.bodyBytes) if err != nil { return "", err } return matcher.ExtractValue(m.config.Justification) } func (m *WebhookMatcher) ExtractArguments() (map[string]string, error) { matcher, err := NewJSONMatcher(m.bodyBytes) if err != nil { return nil, err } args := m.extractJSONPathValues(matcher) m.addWebhookMetadata(args) m.addWebhookHeaders(args) return args, nil } func (m *WebhookMatcher) extractJSONPathValues(matcher *JSONMatcher) map[string]string { args := make(map[string]string) for argName, jsonPath := range m.config.Extract { value, err := matcher.ExtractValue(jsonPath) if err != nil { log.WithFields(log.Fields{ "argName": argName, "jsonPath": jsonPath, "error": err, }).Debugf("Failed to extract value") continue } args[argName] = value } return args } func (m *WebhookMatcher) addWebhookMetadata(args map[string]string) { args["webhook_method"] = m.req.Method args["webhook_path"] = m.req.URL.Path args["webhook_query"] = m.req.URL.RawQuery } func (m *WebhookMatcher) addWebhookHeaders(args map[string]string) { for key, values := range m.req.Header { if len(values) > 0 { args["webhook_header_"+strings.ToLower(key)] = values[0] } } }