diff --git a/hook/hook.go b/hook/hook.go index 16f4a47..232970f 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -5,9 +5,9 @@ import ( "crypto/sha1" "encoding/hex" "encoding/json" + "errors" "fmt" "io/ioutil" - "log" "reflect" "regexp" "strconv" @@ -25,17 +25,62 @@ const ( SourceEntireHeaders string = "entire-headers" ) +// ErrInvalidPayloadSignature describes an invalid payload signature. +var ErrInvalidPayloadSignature = errors.New("invalid payload signature") + +// ArgumentError describes an invalid argument passed to Hook. +type ArgumentError struct { + Argument Argument +} + +func (e *ArgumentError) Error() string { + if e == nil { + return "" + } + return fmt.Sprintf("couldn't retrieve argument for %+v", e.Argument) +} + +// SourceError describes an invalid source passed to Hook. +type SourceError struct { + Argument Argument +} + +func (e *SourceError) Error() string { + if e == nil { + return "" + } + return fmt.Sprintf("invalid source for argument %+v", e.Argument) +} + +// ParseError describes an error parsing user input. +type ParseError struct { + Err error +} + +func (e *ParseError) Error() string { + if e == nil { + return "" + } + return e.Err.Error() +} + // CheckPayloadSignature calculates and verifies SHA1 signature of the given payload -func CheckPayloadSignature(payload []byte, secret string, signature string) (string, bool) { +func CheckPayloadSignature(payload []byte, secret string, signature string) (string, error) { if strings.HasPrefix(signature, "sha1=") { signature = signature[5:] } mac := hmac.New(sha1.New, []byte(secret)) - mac.Write(payload) + _, err := mac.Write(payload) + if err != nil { + return "", err + } expectedMAC := hex.EncodeToString(mac.Sum(nil)) - return expectedMAC, hmac.Equal([]byte(signature), []byte(expectedMAC)) + if !hmac.Equal([]byte(signature), []byte(expectedMAC)) { + err = ErrInvalidPayloadSignature + } + return expectedMAC, err } // ReplaceParameter replaces parameter value with the passed value in the passed map @@ -201,7 +246,7 @@ type Hook struct { // ParseJSONParameters decodes specified arguments to JSON objects and replaces the // string with the newly created object -func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) { +func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) error { for i := range h.JSONStringParameters { if arg, ok := h.JSONStringParameters[i].Get(headers, query, payload); ok { var newArg map[string]interface{} @@ -212,7 +257,7 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface err := decoder.Decode(&newArg) if err != nil { - log.Printf("error parsing argument as JSON payload %+v\n", err) + return &ParseError{err} } else { var source *map[string]interface{} @@ -228,18 +273,20 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface if source != nil { ReplaceParameter(h.JSONStringParameters[i].Name, source, newArg) } else { - log.Printf("invalid source for argument %+v\n", h.JSONStringParameters[i]) + return &SourceError{h.JSONStringParameters[i]} } } } else { - log.Printf("couldn't retrieve argument for %+v\n", h.JSONStringParameters[i]) + return &ArgumentError{h.JSONStringParameters[i]} } } + + return nil } // ExtractCommandArguments creates a list of arguments, based on the // PassArgumentsToCommand property that is ready to be used with exec.Command() -func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) []string { +func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) ([]string, error) { var args = make([]string, 0) args = append(args, h.ExecuteCommand) @@ -249,11 +296,11 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter args = append(args, arg) } else { args = append(args, "") - log.Printf("couldn't retrieve argument for %+v\n", h.PassArgumentsToCommand[i]) + return args, &ArgumentError{h.PassArgumentsToCommand[i]} } } - return args + return args, nil } // Hooks is an array of Hook objects @@ -291,7 +338,7 @@ func (h *Hooks) Match(id string) *Hook { // MatchAll iterates through Hooks and returns all of the hooks that match the // given ID, if no hook matches the given ID, nil is returned func (h *Hooks) MatchAll(id string) []*Hook { - matchedHooks := make([]*Hook, 0) + var matchedHooks []*Hook for i := range *h { if (*h)[i].ID == id { matchedHooks = append(matchedHooks, &(*h)[i]) @@ -315,7 +362,7 @@ type Rules struct { // Evaluate finds the first rule property that is not nil and returns the value // it evaluates to -func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool { +func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) { switch { case r.And != nil: return r.And.Evaluate(headers, query, payload, body) @@ -327,49 +374,60 @@ func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[ return r.Match.Evaluate(headers, query, payload, body) } - return false + return false, nil } // AndRule will evaluate to true if and only if all of the ChildRules evaluate to true type AndRule []Rules // Evaluate AndRule will return true if and only if all of ChildRules evaluate to true -func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool { +func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) { res := true for _, v := range r { - res = res && v.Evaluate(headers, query, payload, body) + rv, err := v.Evaluate(headers, query, payload, body) + if err != nil { + return false, err + } + + res = res && rv if res == false { - return res + return res, nil } } - return res + return res, nil } // OrRule will evaluate to true if any of the ChildRules evaluate to true type OrRule []Rules // Evaluate OrRule will return true if any of ChildRules evaluate to true -func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool { +func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) { res := false for _, v := range r { - res = res || v.Evaluate(headers, query, payload, body) + rv, err := v.Evaluate(headers, query, payload, body) + if err != nil { + return false, err + } + + res = res || rv if res == true { - return res + return res, nil } } - return res + return res, nil } // NotRule will evaluate to true if any and only if the ChildRule evaluates to false type NotRule Rules // Evaluate NotRule will return true if and only if ChildRule evaluates to false -func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool { - return !Rules(r).Evaluate(headers, query, payload, body) +func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) { + rv, err := Rules(r).Evaluate(headers, query, payload, body) + return !rv, err } // MatchRule will evaluate to true based on the type @@ -389,34 +447,24 @@ const ( ) // Evaluate MatchRule will return based on the type -func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) bool { +func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte) (bool, error) { if arg, ok := r.Parameter.Get(headers, query, payload); ok { switch r.Type { case MatchValue: - return arg == r.Value + return arg == r.Value, nil case MatchRegex: - ok, err := regexp.MatchString(r.Regex, arg) - if err != nil { - log.Printf("error while trying to evaluate regex: %+v", err) - } - return ok + return regexp.MatchString(r.Regex, arg) case MatchHashSHA1: - expected, ok := CheckPayloadSignature(*body, r.Secret, arg) - if !ok { - log.Printf("payload signature mismatch, expected %s got %s", expected, arg) - } - - return ok + _, err := CheckPayloadSignature(*body, r.Secret, arg) + return err == nil, err } - } else { - log.Printf("couldn't retrieve argument for %+v\n", r.Parameter) } - return false + return false, &ArgumentError{r.Parameter} } // CommandStatusResponse type encapsulates the executed command exit code, message, stdout and stderr type CommandStatusResponse struct { - ResponseMessage string `json:"message"` - Output string `json:"output"` - Error string `json:"error"` + ResponseMessage string `json:"message,omitempty"` + Output string `json:"output,omitempty"` + Error string `json:"error,omitempty"` } diff --git a/hook/hook_test.go b/hook/hook_test.go index ad77b36..fbd7b0a 100644 --- a/hook/hook_test.go +++ b/hook/hook_test.go @@ -21,9 +21,9 @@ var checkPayloadSignatureTests = []struct { func TestCheckPayloadSignature(t *testing.T) { for _, tt := range checkPayloadSignatureTests { - mac, ok := CheckPayloadSignature(tt.payload, tt.secret, tt.signature) - if ok != tt.ok || mac != tt.mac { - t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, ok) + mac, err := CheckPayloadSignature(tt.payload, tt.secret, tt.signature) + if (err == nil) != tt.ok || mac != tt.mac { + t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil)) } } } @@ -92,23 +92,24 @@ var hookParseJSONParametersTests = []struct { params []Argument headers, query, payload *map[string]interface{} rheaders, rquery, rpayload *map[string]interface{} + ok bool }{ - {[]Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil}, - {[]Argument{Argument{"url", "a"}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil}, - {[]Argument{Argument{"payload", "a"}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}}, - {[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": `{}`}, nil, nil, &map[string]interface{}{"z": map[string]interface{}{}}, nil, nil}, + {[]Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, nil, true}, + {[]Argument{Argument{"url", "a"}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true}, + {[]Argument{Argument{"payload", "a"}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true}, + {[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": `{}`}, nil, nil, &map[string]interface{}{"z": map[string]interface{}{}}, nil, nil, true}, // failures - {[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil}, // empty string - {[]Argument{Argument{"header", "y"}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil}, // missing parameter - {[]Argument{Argument{"string", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil}, // invalid argument source + {[]Argument{Argument{"header", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // empty string + {[]Argument{Argument{"header", "y"}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter + {[]Argument{Argument{"string", "z"}}, &map[string]interface{}{"z": ``}, nil, nil, &map[string]interface{}{"z": ``}, nil, nil, false}, // invalid argument source } func TestHookParseJSONParameters(t *testing.T) { for _, tt := range hookParseJSONParametersTests { h := &Hook{JSONStringParameters: tt.params} - h.ParseJSONParameters(tt.headers, tt.query, tt.payload) - if !reflect.DeepEqual(tt.headers, tt.rheaders) { - t.Errorf("failed to parse %v:\nexpected %#v,\ngot %#v", tt.params, *tt.rheaders, *tt.headers) + err := h.ParseJSONParameters(tt.headers, tt.query, tt.payload) + if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) { + t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, *tt.rheaders, tt.ok, *tt.headers, (err == nil)) } } } @@ -118,18 +119,19 @@ var hookExtractCommandArgumentsTests = []struct { args []Argument headers, query, payload *map[string]interface{} value []string + ok bool }{ - {"test", []Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"test", "z"}}, + {"test", []Argument{Argument{"header", "a"}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"test", "z"}, true}, // failures - {"fail", []Argument{Argument{"payload", "a"}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"fail", ""}}, + {"fail", []Argument{Argument{"payload", "a"}}, &map[string]interface{}{"a": "z"}, nil, nil, []string{"fail", ""}, false}, } func TestHookExtractCommandArguments(t *testing.T) { for _, tt := range hookExtractCommandArgumentsTests { h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args} - value := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload) - if !reflect.DeepEqual(value, tt.value) { - t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v,\ngot %#v", tt.exec, tt.args, tt.value, value) + value, err := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload) + if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) { + t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil)) } } } @@ -178,24 +180,26 @@ var matchRuleTests = []struct { headers, query, payload *map[string]interface{} body []byte ok bool + err bool }{ - {"value", "", "", "z", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true}, - {"regex", "^z", "", "z", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true}, - {"payload-hash-sha1", "", "secret", "", Argument{"header", "a"}, &map[string]interface{}{"a": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true}, + {"value", "", "", "z", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, + {"regex", "^z", "", "z", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, + {"payload-hash-sha1", "", "secret", "", Argument{"header", "a"}, &map[string]interface{}{"a": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false}, // failures - {"value", "", "", "X", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false}, - {"value", "", "", "X", Argument{"header", "a"}, &map[string]interface{}{"y": "z"}, nil, nil, []byte{}, false}, - {"regex", "^X", "", "", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false}, - {"regex", "*", "", "", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false}, - {"payload-hash-sha1", "", "secret", "", Argument{"header", "a"}, &map[string]interface{}{"a": ""}, nil, nil, []byte{}, false}, + {"value", "", "", "X", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, + {"regex", "^X", "", "", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, + // errors + {"value", "", "2", "X", Argument{"header", "a"}, &map[string]interface{}{"y": "z"}, nil, nil, []byte{}, false, true}, // reference invalid header + {"regex", "*", "", "", Argument{"header", "a"}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, true}, // invalid regex + {"payload-hash-sha1", "", "secret", "", Argument{"header", "a"}, &map[string]interface{}{"a": ""}, nil, nil, []byte{}, false, true}, // invalid hmac } func TestMatchRule(t *testing.T) { - for _, tt := range matchRuleTests { + for i, tt := range matchRuleTests { r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param} - ok := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) - if ok != tt.ok { - t.Errorf("failed to match %#v:\nexpected %#v,\ngot %#v", r, tt.ok, ok) + ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) + if ok != tt.ok || (err != nil) != tt.err { + t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, (err != nil)) } } } @@ -206,6 +210,7 @@ var andRuleTests = []struct { headers, query, payload *map[string]interface{} body []byte ok bool + err bool }{ { "(a=z, b=y): a=z && b=y", @@ -214,7 +219,7 @@ var andRuleTests = []struct { {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, }, &map[string]interface{}{"a": "z", "b": "y"}, nil, nil, []byte{}, - true, + true, false, }, { "(a=z, b=Y): a=z && b=y", @@ -223,7 +228,7 @@ var andRuleTests = []struct { {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, }, &map[string]interface{}{"a": "z", "b": "Y"}, nil, nil, []byte{}, - false, + false, false, }, // Complex test to cover Rules.Evaluate { @@ -249,16 +254,23 @@ var andRuleTests = []struct { }, }, &map[string]interface{}{"a": "z", "b": "y", "c": "x", "d": "w", "e": "X", "f": "X"}, nil, nil, []byte{}, - true, + true, false, + }, + {"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false}, + // failures + { + "invalid rule", + AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a"}}}}, + &map[string]interface{}{"y": "z"}, nil, nil, nil, + false, true, }, - {"empty rule", AndRule{{}}, nil, nil, nil, nil, false}, } func TestAndRule(t *testing.T) { for _, tt := range andRuleTests { - ok := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) - if ok != tt.ok { - t.Errorf("failed to match %#v:\nexpected %#v,\ngot %#v", tt.desc, tt.ok, ok) + ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) + if ok != tt.ok || (err != nil) != tt.err { + t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err) } } } @@ -269,6 +281,7 @@ var orRuleTests = []struct { headers, query, payload *map[string]interface{} body []byte ok bool + err bool }{ { "(a=z, b=X): a=z || b=y", @@ -277,7 +290,7 @@ var orRuleTests = []struct { {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, }, &map[string]interface{}{"a": "z", "b": "X"}, nil, nil, []byte{}, - true, + true, false, }, { "(a=X, b=y): a=z || b=y", @@ -286,7 +299,7 @@ var orRuleTests = []struct { {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, }, &map[string]interface{}{"a": "X", "b": "y"}, nil, nil, []byte{}, - true, + true, false, }, { "(a=Z, b=Y): a=z || b=y", @@ -295,15 +308,24 @@ var orRuleTests = []struct { {Match: &MatchRule{"value", "", "", "y", Argument{"header", "b"}}}, }, &map[string]interface{}{"a": "Z", "b": "Y"}, nil, nil, []byte{}, - false, + false, false, + }, + // failures + { + "invalid rule", + OrRule{ + {Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, + }, + &map[string]interface{}{"y": "Z"}, nil, nil, []byte{}, + false, true, }, } func TestOrRule(t *testing.T) { for _, tt := range orRuleTests { - ok := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) - if ok != tt.ok { - t.Errorf("%#v:\nexpected %#v,\ngot %#v", tt.desc, tt.ok, ok) + ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) + if ok != tt.ok || (err != nil) != tt.err { + t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err) } } } @@ -314,16 +336,17 @@ var notRuleTests = []struct { headers, query, payload *map[string]interface{} body []byte ok bool + err bool }{ - {"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a"}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true}, - {"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false}, + {"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a"}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, true, false}, + {"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a"}}}, &map[string]interface{}{"a": "z"}, nil, nil, []byte{}, false, false}, } func TestNotRule(t *testing.T) { for _, tt := range notRuleTests { - ok := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) - if ok != tt.ok { - t.Errorf("failed to match %#v:\nexpected %#v,\ngot %#v", tt.rule, tt.ok, ok) + ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body) + if ok != tt.ok || (err != nil) != tt.err { + t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err) } } } diff --git a/webhook.go b/webhook.go index a01baa5..5d18de0 100644 --- a/webhook.go +++ b/webhook.go @@ -191,8 +191,31 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { // handle hook for _, h := range matchedHooks { - h.ParseJSONParameters(&headers, &query, &payload) - if h.TriggerRule == nil || h.TriggerRule != nil && h.TriggerRule.Evaluate(&headers, &query, &payload, &body) { + err := h.ParseJSONParameters(&headers, &query, &payload) + if err != nil { + msg := fmt.Sprintf("error parsing JSON: %s", err) + log.Printf(msg) + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, msg) + return + } + + var ok bool + + if h.TriggerRule == nil { + ok = true + } else { + ok, err = h.TriggerRule.Evaluate(&headers, &query, &payload, &body) + if err != nil { + msg := fmt.Sprintf("error evaluating hook: %s", err) + log.Printf(msg) + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, msg) + return + } + } + + if ok { log.Printf("%s hook triggered successfully\n", h.ID) if h.CaptureCommandOutput { @@ -219,9 +242,15 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) string { + var err error + cmd := exec.Command(h.ExecuteCommand) - cmd.Args = h.ExtractCommandArguments(headers, query, payload) cmd.Dir = h.CommandWorkingDirectory + cmd.Args, err = h.ExtractCommandArguments(headers, query, payload) + if err != nil { + log.Printf("error extracting command arguments: %s", err) + return "" + } log.Printf("executing %s (%s) with arguments %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, cmd.Dir)