mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-12 08:34:43 +00:00
Add Request object to hook package to simplify API
To avoid having to pass around so many parameters to the hook package, create a Request object to store all request-specific data. Update APIs accordingly.
This commit is contained in:
parent
b8498c564d
commit
c6603894c1
4 changed files with 269 additions and 183 deletions
|
@ -17,6 +17,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -47,6 +48,30 @@ const (
|
||||||
EnvNamespace string = "HOOK_"
|
EnvNamespace string = "HOOK_"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Request represents a webhook request.
|
||||||
|
type Request struct {
|
||||||
|
// The request ID set by the RequestID middleware.
|
||||||
|
ID string
|
||||||
|
|
||||||
|
// The Content-Type of the request.
|
||||||
|
ContentType string
|
||||||
|
|
||||||
|
// The raw request body.
|
||||||
|
Body []byte
|
||||||
|
|
||||||
|
// Headers is a map of the parsed headers.
|
||||||
|
Headers map[string]interface{}
|
||||||
|
|
||||||
|
// Query is a map of the parsed URL query values.
|
||||||
|
Query map[string]interface{}
|
||||||
|
|
||||||
|
// Payload is a map of the parsed payload.
|
||||||
|
Payload map[string]interface{}
|
||||||
|
|
||||||
|
// The underlying HTTP request.
|
||||||
|
RawRequest *http.Request
|
||||||
|
}
|
||||||
|
|
||||||
// ParameterNodeError describes an error walking a parameter node.
|
// ParameterNodeError describes an error walking a parameter node.
|
||||||
type ParameterNodeError struct {
|
type ParameterNodeError struct {
|
||||||
key string
|
key string
|
||||||
|
@ -222,22 +247,26 @@ func CheckPayloadSignature512(payload []byte, secret, signature string) (string,
|
||||||
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
|
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
|
func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
|
||||||
// Check for the signature and date headers
|
if r.Headers == nil {
|
||||||
if _, ok := headers["X-Signature"]; !ok {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if _, ok := headers["Date"]; !ok {
|
|
||||||
|
// Check for the signature and date headers
|
||||||
|
if _, ok := r.Headers["X-Signature"]; !ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if _, ok := r.Headers["Date"]; !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
if signingKey == "" {
|
if signingKey == "" {
|
||||||
return false, errors.New("signature validation signing key can not be empty")
|
return false, errors.New("signature validation signing key can not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
providedSignature := headers["X-Signature"].(string)
|
providedSignature := r.Headers["X-Signature"].(string)
|
||||||
dateHeader := headers["Date"].(string)
|
dateHeader := r.Headers["Date"].(string)
|
||||||
mac := hmac.New(sha1.New, []byte(signingKey))
|
mac := hmac.New(sha1.New, []byte(signingKey))
|
||||||
mac.Write(body)
|
mac.Write(r.Body)
|
||||||
mac.Write([]byte(dateHeader))
|
mac.Write([]byte(dateHeader))
|
||||||
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
expectedSignature := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
@ -426,41 +455,41 @@ type Argument struct {
|
||||||
|
|
||||||
// Get Argument method returns the value for the Argument's key name
|
// Get Argument method returns the value for the Argument's key name
|
||||||
// based on the Argument's source
|
// based on the Argument's source
|
||||||
func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string, error) {
|
func (ha *Argument) Get(r *Request) (string, error) {
|
||||||
var source *map[string]interface{}
|
var source *map[string]interface{}
|
||||||
key := ha.Name
|
key := ha.Name
|
||||||
|
|
||||||
switch ha.Source {
|
switch ha.Source {
|
||||||
case SourceHeader:
|
case SourceHeader:
|
||||||
source = headers
|
source = &r.Headers
|
||||||
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
|
||||||
case SourceQuery, SourceQueryAlias:
|
case SourceQuery, SourceQueryAlias:
|
||||||
source = query
|
source = &r.Query
|
||||||
case SourcePayload:
|
case SourcePayload:
|
||||||
source = payload
|
source = &r.Payload
|
||||||
case SourceString:
|
case SourceString:
|
||||||
return ha.Name, nil
|
return ha.Name, nil
|
||||||
case SourceEntirePayload:
|
case SourceEntirePayload:
|
||||||
r, err := json.Marshal(payload)
|
res, err := json.Marshal(&r.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(r), nil
|
return string(res), nil
|
||||||
case SourceEntireHeaders:
|
case SourceEntireHeaders:
|
||||||
r, err := json.Marshal(headers)
|
res, err := json.Marshal(&r.Headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(r), nil
|
return string(res), nil
|
||||||
case SourceEntireQuery:
|
case SourceEntireQuery:
|
||||||
r, err := json.Marshal(query)
|
res, err := json.Marshal(&r.Query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(r), nil
|
return string(res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if source != nil {
|
if source != nil {
|
||||||
|
@ -545,11 +574,11 @@ type Hook struct {
|
||||||
|
|
||||||
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
|
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
|
||||||
// string with the newly created object
|
// string with the newly created object
|
||||||
func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) []error {
|
func (h *Hook) ParseJSONParameters(r *Request) []error {
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
|
|
||||||
for i := range h.JSONStringParameters {
|
for i := range h.JSONStringParameters {
|
||||||
arg, err := h.JSONStringParameters[i].Get(headers, query, payload)
|
arg, err := h.JSONStringParameters[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
|
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
|
||||||
} else {
|
} else {
|
||||||
|
@ -568,11 +597,11 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
|
||||||
|
|
||||||
switch h.JSONStringParameters[i].Source {
|
switch h.JSONStringParameters[i].Source {
|
||||||
case SourceHeader:
|
case SourceHeader:
|
||||||
source = headers
|
source = &r.Headers
|
||||||
case SourcePayload:
|
case SourcePayload:
|
||||||
source = payload
|
source = &r.Payload
|
||||||
case SourceQuery, SourceQueryAlias:
|
case SourceQuery, SourceQueryAlias:
|
||||||
source = query
|
source = &r.Query
|
||||||
}
|
}
|
||||||
|
|
||||||
if source != nil {
|
if source != nil {
|
||||||
|
@ -598,14 +627,14 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
|
||||||
|
|
||||||
// ExtractCommandArguments creates a list of arguments, based on the
|
// ExtractCommandArguments creates a list of arguments, based on the
|
||||||
// PassArgumentsToCommand property that is ready to be used with exec.Command()
|
// PassArgumentsToCommand property that is ready to be used with exec.Command()
|
||||||
func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) ([]string, []error) {
|
func (h *Hook) ExtractCommandArguments(r *Request) ([]string, []error) {
|
||||||
args := make([]string, 0)
|
args := make([]string, 0)
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
|
|
||||||
args = append(args, h.ExecuteCommand)
|
args = append(args, h.ExecuteCommand)
|
||||||
|
|
||||||
for i := range h.PassArgumentsToCommand {
|
for i := range h.PassArgumentsToCommand {
|
||||||
arg, err := h.PassArgumentsToCommand[i].Get(headers, query, payload)
|
arg, err := h.PassArgumentsToCommand[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
args = append(args, "")
|
args = append(args, "")
|
||||||
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
|
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
|
||||||
|
@ -625,11 +654,11 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter
|
||||||
// ExtractCommandArgumentsForEnv creates a list of arguments in key=value
|
// ExtractCommandArgumentsForEnv creates a list of arguments in key=value
|
||||||
// format, based on the PassEnvironmentToCommand property that is ready to be used
|
// format, based on the PassEnvironmentToCommand property that is ready to be used
|
||||||
// with exec.Command().
|
// with exec.Command().
|
||||||
func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string]interface{}) ([]string, []error) {
|
func (h *Hook) ExtractCommandArgumentsForEnv(r *Request) ([]string, []error) {
|
||||||
args := make([]string, 0)
|
args := make([]string, 0)
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
for i := range h.PassEnvironmentToCommand {
|
for i := range h.PassEnvironmentToCommand {
|
||||||
arg, err := h.PassEnvironmentToCommand[i].Get(headers, query, payload)
|
arg, err := h.PassEnvironmentToCommand[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
|
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
|
||||||
continue
|
continue
|
||||||
|
@ -661,11 +690,11 @@ type FileParameter struct {
|
||||||
// ExtractCommandArgumentsForFile creates a list of arguments in key=value
|
// ExtractCommandArgumentsForFile creates a list of arguments in key=value
|
||||||
// format, based on the PassFileToCommand property that is ready to be used
|
// format, based on the PassFileToCommand property that is ready to be used
|
||||||
// with exec.Command().
|
// with exec.Command().
|
||||||
func (h *Hook) ExtractCommandArgumentsForFile(headers, query, payload *map[string]interface{}) ([]FileParameter, []error) {
|
func (h *Hook) ExtractCommandArgumentsForFile(r *Request) ([]FileParameter, []error) {
|
||||||
args := make([]FileParameter, 0)
|
args := make([]FileParameter, 0)
|
||||||
errors := make([]error, 0)
|
errors := make([]error, 0)
|
||||||
for i := range h.PassFileToCommand {
|
for i := range h.PassFileToCommand {
|
||||||
arg, err := h.PassFileToCommand[i].Get(headers, query, payload)
|
arg, err := h.PassFileToCommand[i].Get(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
|
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
|
||||||
continue
|
continue
|
||||||
|
@ -772,16 +801,16 @@ type Rules struct {
|
||||||
|
|
||||||
// Evaluate finds the first rule property that is not nil and returns the value
|
// Evaluate finds the first rule property that is not nil and returns the value
|
||||||
// it evaluates to
|
// it evaluates to
|
||||||
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r Rules) Evaluate(req *Request) (bool, error) {
|
||||||
switch {
|
switch {
|
||||||
case r.And != nil:
|
case r.And != nil:
|
||||||
return r.And.Evaluate(headers, query, payload, body, remoteAddr)
|
return r.And.Evaluate(req)
|
||||||
case r.Or != nil:
|
case r.Or != nil:
|
||||||
return r.Or.Evaluate(headers, query, payload, body, remoteAddr)
|
return r.Or.Evaluate(req)
|
||||||
case r.Not != nil:
|
case r.Not != nil:
|
||||||
return r.Not.Evaluate(headers, query, payload, body, remoteAddr)
|
return r.Not.Evaluate(req)
|
||||||
case r.Match != nil:
|
case r.Match != nil:
|
||||||
return r.Match.Evaluate(headers, query, payload, body, remoteAddr)
|
return r.Match.Evaluate(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -791,11 +820,11 @@ func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[
|
||||||
type AndRule []Rules
|
type AndRule []Rules
|
||||||
|
|
||||||
// Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
|
// 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, remoteAddr string) (bool, error) {
|
func (r AndRule) Evaluate(req *Request) (bool, error) {
|
||||||
res := true
|
res := true
|
||||||
|
|
||||||
for _, v := range r {
|
for _, v := range r {
|
||||||
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
|
rv, err := v.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -813,11 +842,11 @@ func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body
|
||||||
type OrRule []Rules
|
type OrRule []Rules
|
||||||
|
|
||||||
// Evaluate OrRule will return true if any of ChildRules evaluate to true
|
// Evaluate OrRule will return true if any of ChildRules evaluate to true
|
||||||
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r OrRule) Evaluate(req *Request) (bool, error) {
|
||||||
res := false
|
res := false
|
||||||
|
|
||||||
for _, v := range r {
|
for _, v := range r {
|
||||||
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
|
rv, err := v.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -835,8 +864,8 @@ func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *
|
||||||
type NotRule Rules
|
type NotRule Rules
|
||||||
|
|
||||||
// Evaluate NotRule will return true if and only if ChildRule evaluates to false
|
// 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, remoteAddr string) (bool, error) {
|
func (r NotRule) Evaluate(req *Request) (bool, error) {
|
||||||
rv, err := Rules(r).Evaluate(headers, query, payload, body, remoteAddr)
|
rv, err := Rules(r).Evaluate(req)
|
||||||
return !rv, err
|
return !rv, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -862,15 +891,15 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Evaluate MatchRule will return based on the type
|
// Evaluate MatchRule will return based on the type
|
||||||
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
func (r MatchRule) Evaluate(req *Request) (bool, error) {
|
||||||
if r.Type == IPWhitelist {
|
if r.Type == IPWhitelist {
|
||||||
return CheckIPWhitelist(remoteAddr, r.IPRange)
|
return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
|
||||||
}
|
}
|
||||||
if r.Type == ScalrSignature {
|
if r.Type == ScalrSignature {
|
||||||
return CheckScalrSignature(*headers, *body, r.Secret, true)
|
return CheckScalrSignature(req, r.Secret, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
arg, err := r.Parameter.Get(headers, query, payload)
|
arg, err := r.Parameter.Get(req)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case MatchValue:
|
case MatchValue:
|
||||||
|
@ -878,13 +907,13 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
|
||||||
case MatchRegex:
|
case MatchRegex:
|
||||||
return regexp.MatchString(r.Regex, arg)
|
return regexp.MatchString(r.Regex, arg)
|
||||||
case MatchHashSHA1:
|
case MatchHashSHA1:
|
||||||
_, err := CheckPayloadSignature(*body, r.Secret, arg)
|
_, err := CheckPayloadSignature(req.Body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
case MatchHashSHA256:
|
case MatchHashSHA256:
|
||||||
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
|
_, err := CheckPayloadSignature256(req.Body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
case MatchHashSHA512:
|
case MatchHashSHA512:
|
||||||
_, err := CheckPayloadSignature512(*body, r.Secret, arg)
|
_, err := CheckPayloadSignature512(req.Body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package hook
|
package hook
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -136,7 +137,7 @@ func TestCheckPayloadSignature512(t *testing.T) {
|
||||||
var checkScalrSignatureTests = []struct {
|
var checkScalrSignatureTests = []struct {
|
||||||
description string
|
description string
|
||||||
headers map[string]interface{}
|
headers map[string]interface{}
|
||||||
payload []byte
|
body []byte
|
||||||
secret string
|
secret string
|
||||||
expectedSignature string
|
expectedSignature string
|
||||||
ok bool
|
ok bool
|
||||||
|
@ -175,7 +176,11 @@ var checkScalrSignatureTests = []struct {
|
||||||
|
|
||||||
func TestCheckScalrSignature(t *testing.T) {
|
func TestCheckScalrSignature(t *testing.T) {
|
||||||
for _, testCase := range checkScalrSignatureTests {
|
for _, testCase := range checkScalrSignatureTests {
|
||||||
valid, err := CheckScalrSignature(testCase.headers, testCase.payload, testCase.secret, false)
|
r := &Request{
|
||||||
|
Headers: testCase.headers,
|
||||||
|
Body: testCase.body,
|
||||||
|
}
|
||||||
|
valid, err := CheckScalrSignature(r, testCase.secret, false)
|
||||||
if valid != testCase.ok {
|
if valid != testCase.ok {
|
||||||
t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
|
t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
|
||||||
testCase.description, testCase.ok, valid)
|
testCase.description, testCase.ok, valid)
|
||||||
|
@ -249,25 +254,30 @@ func TestExtractParameter(t *testing.T) {
|
||||||
|
|
||||||
var argumentGetTests = []struct {
|
var argumentGetTests = []struct {
|
||||||
source, name string
|
source, name string
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
value string
|
value string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{"header", "a", &map[string]interface{}{"A": "z"}, nil, nil, "z", true},
|
{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, "z", true},
|
||||||
{"url", "a", nil, &map[string]interface{}{"a": "z"}, nil, "z", true},
|
{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, "z", true},
|
||||||
{"payload", "a", nil, nil, &map[string]interface{}{"a": "z"}, "z", true},
|
{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, "z", true},
|
||||||
{"string", "a", nil, nil, &map[string]interface{}{"a": "z"}, "a", true},
|
{"string", "a", nil, nil, map[string]interface{}{"a": "z"}, "a", true},
|
||||||
// failures
|
// failures
|
||||||
{"header", "a", nil, &map[string]interface{}{"a": "z"}, &map[string]interface{}{"a": "z"}, "", false}, // nil headers
|
{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, "", false}, // nil headers
|
||||||
{"url", "a", &map[string]interface{}{"A": "z"}, nil, &map[string]interface{}{"a": "z"}, "", false}, // nil query
|
{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, "", false}, // nil query
|
||||||
{"payload", "a", &map[string]interface{}{"A": "z"}, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload
|
{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload
|
||||||
{"foo", "a", &map[string]interface{}{"A": "z"}, nil, nil, "", false}, // invalid source
|
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, "", false}, // invalid source
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestArgumentGet(t *testing.T) {
|
func TestArgumentGet(t *testing.T) {
|
||||||
for _, tt := range argumentGetTests {
|
for _, tt := range argumentGetTests {
|
||||||
a := Argument{tt.source, tt.name, "", false}
|
a := Argument{tt.source, tt.name, "", false}
|
||||||
value, err := a.Get(tt.headers, tt.query, tt.payload)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
}
|
||||||
|
value, err := a.Get(r)
|
||||||
if (err == nil) != tt.ok || value != tt.value {
|
if (err == nil) != tt.ok || value != tt.value {
|
||||||
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%v}", tt.source, tt.name, tt.value, tt.ok, value, err)
|
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%v}", tt.source, tt.name, tt.value, tt.ok, value, err)
|
||||||
}
|
}
|
||||||
|
@ -276,26 +286,31 @@ func TestArgumentGet(t *testing.T) {
|
||||||
|
|
||||||
var hookParseJSONParametersTests = []struct {
|
var hookParseJSONParametersTests = []struct {
|
||||||
params []Argument
|
params []Argument
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
rheaders, rquery, rpayload *map[string]interface{}
|
rheaders, rquery, rpayload map[string]interface{}
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{[]Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
|
||||||
{[]Argument{Argument{"url", "a", "", false}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
|
||||||
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
|
||||||
{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": `{}`}, nil, nil, &map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
|
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
|
||||||
// failures
|
// failures
|
||||||
{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string
|
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string
|
||||||
{[]Argument{Argument{"header", "y", "", false}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
|
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
|
||||||
{[]Argument{Argument{"string", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
|
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookParseJSONParameters(t *testing.T) {
|
func TestHookParseJSONParameters(t *testing.T) {
|
||||||
for _, tt := range hookParseJSONParametersTests {
|
for _, tt := range hookParseJSONParametersTests {
|
||||||
h := &Hook{JSONStringParameters: tt.params}
|
h := &Hook{JSONStringParameters: tt.params}
|
||||||
err := h.ParseJSONParameters(tt.headers, tt.query, tt.payload)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
}
|
||||||
|
err := h.ParseJSONParameters(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
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))
|
t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, tt.rheaders, tt.ok, tt.headers, (err == nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,19 +318,24 @@ func TestHookParseJSONParameters(t *testing.T) {
|
||||||
var hookExtractCommandArgumentsTests = []struct {
|
var hookExtractCommandArgumentsTests = []struct {
|
||||||
exec string
|
exec string
|
||||||
args []Argument
|
args []Argument
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
value []string
|
value []string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
{"test", []Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
|
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
|
||||||
// failures
|
// failures
|
||||||
{"fail", []Argument{Argument{"payload", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
|
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHookExtractCommandArguments(t *testing.T) {
|
func TestHookExtractCommandArguments(t *testing.T) {
|
||||||
for _, tt := range hookExtractCommandArgumentsTests {
|
for _, tt := range hookExtractCommandArgumentsTests {
|
||||||
h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
|
h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
|
||||||
value, err := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
}
|
||||||
|
value, err := h.ExtractCommandArguments(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
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))
|
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))
|
||||||
}
|
}
|
||||||
|
@ -344,7 +364,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
|
||||||
var hookExtractCommandArgumentsForEnvTests = []struct {
|
var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||||
exec string
|
exec string
|
||||||
args []Argument
|
args []Argument
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
value []string
|
value []string
|
||||||
ok bool
|
ok bool
|
||||||
}{
|
}{
|
||||||
|
@ -352,14 +372,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||||
{
|
{
|
||||||
"test",
|
"test",
|
||||||
[]Argument{Argument{"header", "a", "", false}},
|
[]Argument{Argument{"header", "a", "", false}},
|
||||||
&map[string]interface{}{"A": "z"}, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil,
|
||||||
[]string{"HOOK_a=z"},
|
[]string{"HOOK_a=z"},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"test",
|
"test",
|
||||||
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
||||||
&map[string]interface{}{"A": "z"}, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil,
|
||||||
[]string{"MYKEY=z"},
|
[]string{"MYKEY=z"},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
@ -367,7 +387,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||||
{
|
{
|
||||||
"fail",
|
"fail",
|
||||||
[]Argument{Argument{"payload", "a", "", false}},
|
[]Argument{Argument{"payload", "a", "", false}},
|
||||||
&map[string]interface{}{"A": "z"}, nil, nil,
|
map[string]interface{}{"A": "z"}, nil, nil,
|
||||||
[]string{},
|
[]string{},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
@ -376,7 +396,12 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
|
||||||
func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
||||||
for _, tt := range hookExtractCommandArgumentsForEnvTests {
|
for _, tt := range hookExtractCommandArgumentsForEnvTests {
|
||||||
h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
|
h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
|
||||||
value, err := h.ExtractCommandArgumentsForEnv(tt.headers, tt.query, tt.payload)
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
}
|
||||||
|
value, err := h.ExtractCommandArgumentsForEnv(r)
|
||||||
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
||||||
t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
||||||
}
|
}
|
||||||
|
@ -454,24 +479,24 @@ func TestHooksMatch(t *testing.T) {
|
||||||
var matchRuleTests = []struct {
|
var matchRuleTests = []struct {
|
||||||
typ, regex, secret, value, ipRange string
|
typ, regex, secret, value, ipRange string
|
||||||
param Argument
|
param Argument
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
remoteAddr string
|
remoteAddr string
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"value", "", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||||
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
|
||||||
// failures
|
// failures
|
||||||
{"value", "", "", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
||||||
// errors
|
// errors
|
||||||
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
|
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
|
||||||
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
// IP whitelisting, valid cases
|
// IP whitelisting, valid cases
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
|
||||||
|
@ -490,7 +515,16 @@ var matchRuleTests = []struct {
|
||||||
func TestMatchRule(t *testing.T) {
|
func TestMatchRule(t *testing.T) {
|
||||||
for i, tt := range matchRuleTests {
|
for i, tt := range matchRuleTests {
|
||||||
r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
|
r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
|
||||||
ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, tt.remoteAddr)
|
req := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Body: tt.body,
|
||||||
|
RawRequest: &http.Request{
|
||||||
|
RemoteAddr: tt.remoteAddr,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ok, err := r.Evaluate(req)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
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)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -500,7 +534,7 @@ func TestMatchRule(t *testing.T) {
|
||||||
var andRuleTests = []struct {
|
var andRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule AndRule
|
rule AndRule
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
|
@ -511,7 +545,7 @@ var andRuleTests = []struct {
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
|
map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
|
@ -521,7 +555,7 @@ var andRuleTests = []struct {
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
|
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
|
@ -548,7 +582,7 @@ var andRuleTests = []struct {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
|
map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
|
@ -557,14 +591,20 @@ var andRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"invalid rule",
|
"invalid rule",
|
||||||
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
||||||
&map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAndRule(t *testing.T) {
|
func TestAndRule(t *testing.T) {
|
||||||
for _, tt := range andRuleTests {
|
for _, tt := range andRuleTests {
|
||||||
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Body: tt.body,
|
||||||
|
}
|
||||||
|
ok, err := tt.rule.Evaluate(r)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
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)
|
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
|
@ -574,7 +614,7 @@ func TestAndRule(t *testing.T) {
|
||||||
var orRuleTests = []struct {
|
var orRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule OrRule
|
rule OrRule
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
|
@ -585,7 +625,7 @@ var orRuleTests = []struct {
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
|
map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
|
@ -595,7 +635,7 @@ var orRuleTests = []struct {
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
|
map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
|
@ -605,7 +645,7 @@ var orRuleTests = []struct {
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
|
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
|
@ -615,7 +655,7 @@ var orRuleTests = []struct {
|
||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"Y": "Z"}, nil, nil,
|
map[string]interface{}{"Y": "Z"}, nil, nil,
|
||||||
[]byte{},
|
[]byte{},
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
|
@ -623,7 +663,13 @@ var orRuleTests = []struct {
|
||||||
|
|
||||||
func TestOrRule(t *testing.T) {
|
func TestOrRule(t *testing.T) {
|
||||||
for _, tt := range orRuleTests {
|
for _, tt := range orRuleTests {
|
||||||
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Body: tt.body,
|
||||||
|
}
|
||||||
|
ok, err := tt.rule.Evaluate(r)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
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)
|
t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
|
@ -633,18 +679,24 @@ func TestOrRule(t *testing.T) {
|
||||||
var notRuleTests = []struct {
|
var notRuleTests = []struct {
|
||||||
desc string // description of the test case
|
desc string // description of the test case
|
||||||
rule NotRule
|
rule NotRule
|
||||||
headers, query, payload *map[string]interface{}
|
headers, query, payload map[string]interface{}
|
||||||
body []byte
|
body []byte
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
|
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
|
||||||
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
|
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNotRule(t *testing.T) {
|
func TestNotRule(t *testing.T) {
|
||||||
for _, tt := range notRuleTests {
|
for _, tt := range notRuleTests {
|
||||||
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
r := &Request{
|
||||||
|
Headers: tt.headers,
|
||||||
|
Query: tt.query,
|
||||||
|
Payload: tt.payload,
|
||||||
|
Body: tt.body,
|
||||||
|
}
|
||||||
|
ok, err := tt.rule.Evaluate(r)
|
||||||
if ok != tt.ok || (err != nil) != tt.err {
|
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)
|
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err)
|
||||||
}
|
}
|
||||||
|
|
153
webhook.go
153
webhook.go
|
@ -302,10 +302,14 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
rid := middleware.GetReqID(r.Context())
|
req := &hook.Request{
|
||||||
|
ID: middleware.GetReqID(r.Context()),
|
||||||
|
RawRequest: r,
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[%s] incoming HTTP %s request from %s\n", rid, r.Method, r.RemoteAddr)
|
log.Printf("[%s] incoming HTTP %s request from %s\n", req.ID, r.Method, r.RemoteAddr)
|
||||||
|
|
||||||
|
// TODO: rename this to avoid confusion with Request.ID
|
||||||
id := mux.Vars(r)["id"]
|
id := mux.Vars(r)["id"]
|
||||||
|
|
||||||
matchedHook := matchLoadedHook(id)
|
matchedHook := matchLoadedHook(id)
|
||||||
|
@ -341,57 +345,54 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
if !allowedMethod {
|
if !allowedMethod {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||||
log.Printf("[%s] HTTP %s method not allowed for hook %q", rid, r.Method, id)
|
log.Printf("[%s] HTTP %s method not allowed for hook %q", req.ID, r.Method, id)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] %s got matched\n", rid, id)
|
log.Printf("[%s] %s got matched\n", req.ID, id)
|
||||||
|
|
||||||
for _, responseHeader := range responseHeaders {
|
for _, responseHeader := range responseHeaders {
|
||||||
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var err error
|
||||||
body []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// set contentType to IncomingPayloadContentType or header value
|
// set contentType to IncomingPayloadContentType or header value
|
||||||
contentType := r.Header.Get("Content-Type")
|
req.ContentType = r.Header.Get("Content-Type")
|
||||||
if len(matchedHook.IncomingPayloadContentType) != 0 {
|
if len(matchedHook.IncomingPayloadContentType) != 0 {
|
||||||
contentType = matchedHook.IncomingPayloadContentType
|
req.ContentType = matchedHook.IncomingPayloadContentType
|
||||||
}
|
}
|
||||||
|
|
||||||
isMultipart := strings.HasPrefix(contentType, "multipart/form-data;")
|
isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;")
|
||||||
|
|
||||||
if !isMultipart {
|
if !isMultipart {
|
||||||
body, err = ioutil.ReadAll(r.Body)
|
req.Body, err = ioutil.ReadAll(r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error reading the request body: %+v\n", rid, err)
|
log.Printf("[%s] error reading the request body: %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse headers
|
// parse headers
|
||||||
headers := valuesToMap(r.Header)
|
req.Headers = valuesToMap(r.Header)
|
||||||
|
|
||||||
// parse query variables
|
// parse query variables
|
||||||
query := valuesToMap(r.URL.Query())
|
req.Query = valuesToMap(r.URL.Query())
|
||||||
|
|
||||||
// parse body
|
// parse body
|
||||||
var payload map[string]interface{}
|
// var payload map[string]interface{}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case strings.Contains(contentType, "json"):
|
case strings.Contains(req.ContentType, "json"):
|
||||||
decoder := json.NewDecoder(bytes.NewReader(body))
|
decoder := json.NewDecoder(bytes.NewReader(req.Body))
|
||||||
decoder.UseNumber()
|
decoder.UseNumber()
|
||||||
|
|
||||||
var firstChar byte
|
var firstChar byte
|
||||||
for i := 0; i < len(body); i++ {
|
for i := 0; i < len(req.Body); i++ {
|
||||||
if unicode.IsSpace(rune(body[i])) {
|
if unicode.IsSpace(rune(req.Body[i])) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
firstChar = body[i]
|
firstChar = req.Body[i]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,36 +400,36 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var arrayPayload interface{}
|
var arrayPayload interface{}
|
||||||
err := decoder.Decode(&arrayPayload)
|
err := decoder.Decode(&arrayPayload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing JSON array payload %+v\n", rid, err)
|
log.Printf("[%s] error parsing JSON array payload %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
payload = make(map[string]interface{}, 1)
|
req.Payload = make(map[string]interface{}, 1)
|
||||||
payload["root"] = arrayPayload
|
req.Payload["root"] = arrayPayload
|
||||||
} else {
|
} else {
|
||||||
err := decoder.Decode(&payload)
|
err := decoder.Decode(&req.Payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
|
log.Printf("[%s] error parsing JSON payload %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.Contains(contentType, "x-www-form-urlencoded"):
|
case strings.Contains(req.ContentType, "x-www-form-urlencoded"):
|
||||||
fd, err := url.ParseQuery(string(body))
|
fd, err := url.ParseQuery(string(req.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing form payload %+v\n", rid, err)
|
log.Printf("[%s] error parsing form payload %+v\n", req.ID, err)
|
||||||
} else {
|
} else {
|
||||||
payload = valuesToMap(fd)
|
req.Payload = valuesToMap(fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
case strings.Contains(contentType, "xml"):
|
case strings.Contains(req.ContentType, "xml"):
|
||||||
payload, err = mxj.NewMapXmlReader(bytes.NewReader(body))
|
req.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(req.Body))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing XML payload: %+v\n", rid, err)
|
log.Printf("[%s] error parsing XML payload: %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
case isMultipart:
|
case isMultipart:
|
||||||
err = r.ParseMultipartForm(*maxMultipartMem)
|
err = r.ParseMultipartForm(*maxMultipartMem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", rid, err)
|
msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", req.ID, err)
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "Error occurred while parsing multipart form.")
|
fmt.Fprint(w, "Error occurred while parsing multipart form.")
|
||||||
|
@ -436,14 +437,14 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range r.MultipartForm.Value {
|
for k, v := range r.MultipartForm.Value {
|
||||||
log.Printf("[%s] found multipart form value %q", rid, k)
|
log.Printf("[%s] found multipart form value %q", req.ID, k)
|
||||||
|
|
||||||
if payload == nil {
|
if req.Payload == nil {
|
||||||
payload = make(map[string]interface{})
|
req.Payload = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(moorereason): support duplicate, named values
|
// TODO(moorereason): support duplicate, named values
|
||||||
payload[k] = v[0]
|
req.Payload[k] = v[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range r.MultipartForm.File {
|
for k, v := range r.MultipartForm.File {
|
||||||
|
@ -472,11 +473,11 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if parseAsJSON {
|
if parseAsJSON {
|
||||||
log.Printf("[%s] parsing multipart form file %q as JSON\n", rid, k)
|
log.Printf("[%s] parsing multipart form file %q as JSON\n", req.ID, k)
|
||||||
|
|
||||||
f, err := v[0].Open()
|
f, err := v[0].Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", rid, err)
|
msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", req.ID, err)
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "Error occurred while parsing multipart form file.")
|
fmt.Fprint(w, "Error occurred while parsing multipart form file.")
|
||||||
|
@ -489,24 +490,24 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var part map[string]interface{}
|
var part map[string]interface{}
|
||||||
err = decoder.Decode(&part)
|
err = decoder.Decode(&part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error parsing JSON payload file: %+v\n", rid, err)
|
log.Printf("[%s] error parsing JSON payload file: %+v\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if payload == nil {
|
if req.Payload == nil {
|
||||||
payload = make(map[string]interface{})
|
req.Payload = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
payload[k] = part
|
req.Payload[k] = part
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", rid, contentType)
|
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle hook
|
// handle hook
|
||||||
errors := matchedHook.ParseJSONParameters(&headers, &query, &payload)
|
errors := matchedHook.ParseJSONParameters(req)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error parsing JSON parameters: %s\n", rid, err)
|
log.Printf("[%s] error parsing JSON parameters: %s\n", req.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
|
@ -514,29 +515,29 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if matchedHook.TriggerRule == nil {
|
if matchedHook.TriggerRule == nil {
|
||||||
ok = true
|
ok = true
|
||||||
} else {
|
} else {
|
||||||
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body, r.RemoteAddr)
|
ok, err = matchedHook.TriggerRule.Evaluate(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !hook.IsParameterNodeError(err) {
|
if !hook.IsParameterNodeError(err) {
|
||||||
msg := fmt.Sprintf("[%s] error evaluating hook: %s", rid, err)
|
msg := fmt.Sprintf("[%s] error evaluating hook: %s", req.ID, err)
|
||||||
log.Println(msg)
|
log.Println(msg)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
fmt.Fprint(w, "Error occurred while evaluating hook rules.")
|
fmt.Fprint(w, "Error occurred while evaluating hook rules.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] %v", rid, err)
|
log.Printf("[%s] %v", req.ID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
log.Printf("[%s] %s hook triggered successfully\n", rid, matchedHook.ID)
|
log.Printf("[%s] %s hook triggered successfully\n", req.ID, matchedHook.ID)
|
||||||
|
|
||||||
for _, responseHeader := range matchedHook.ResponseHeaders {
|
for _, responseHeader := range matchedHook.ResponseHeaders {
|
||||||
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
w.Header().Set(responseHeader.Name, responseHeader.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if matchedHook.CaptureCommandOutput {
|
if matchedHook.CaptureCommandOutput {
|
||||||
response, err := handleHook(matchedHook, rid, &headers, &query, &payload, &body)
|
response, err := handleHook(matchedHook, req)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
@ -549,16 +550,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
} else {
|
} else {
|
||||||
// Check if a success return code is configured for the hook
|
// Check if a success return code is configured for the hook
|
||||||
if matchedHook.SuccessHttpResponseCode != 0 {
|
if matchedHook.SuccessHttpResponseCode != 0 {
|
||||||
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, response)
|
fmt.Fprint(w, response)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
go handleHook(matchedHook, rid, &headers, &query, &payload, &body)
|
go handleHook(matchedHook, req)
|
||||||
|
|
||||||
// Check if a success return code is configured for the hook
|
// Check if a success return code is configured for the hook
|
||||||
if matchedHook.SuccessHttpResponseCode != 0 {
|
if matchedHook.SuccessHttpResponseCode != 0 {
|
||||||
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprint(w, matchedHook.ResponseMessage)
|
fmt.Fprint(w, matchedHook.ResponseMessage)
|
||||||
|
@ -568,16 +569,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Check if a return code is configured for the hook
|
// Check if a return code is configured for the hook
|
||||||
if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 {
|
if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 {
|
||||||
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
|
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if none of the hooks got triggered
|
// if none of the hooks got triggered
|
||||||
log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", rid, matchedHook.ID)
|
log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", req.ID, matchedHook.ID)
|
||||||
|
|
||||||
fmt.Fprint(w, "Hook rules were not satisfied.")
|
fmt.Fprint(w, "Hook rules were not satisfied.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]interface{}, body *[]byte) (string, error) {
|
func handleHook(h *hook.Hook, r *hook.Request) (string, error) {
|
||||||
var errors []error
|
var errors []error
|
||||||
|
|
||||||
// check the command exists
|
// check the command exists
|
||||||
|
@ -590,12 +591,12 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
||||||
|
|
||||||
cmdPath, err := exec.LookPath(lookpath)
|
cmdPath, err := exec.LookPath(lookpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error in %s", rid, err)
|
log.Printf("[%s] error in %s", r.ID, err)
|
||||||
|
|
||||||
// check if parameters specified in execute-command by mistake
|
// check if parameters specified in execute-command by mistake
|
||||||
if strings.IndexByte(h.ExecuteCommand, ' ') != -1 {
|
if strings.IndexByte(h.ExecuteCommand, ' ') != -1 {
|
||||||
s := strings.Fields(h.ExecuteCommand)[0]
|
s := strings.Fields(h.ExecuteCommand)[0]
|
||||||
log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", rid, s)
|
log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", r.ID, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -604,37 +605,37 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
||||||
cmd := exec.Command(cmdPath)
|
cmd := exec.Command(cmdPath)
|
||||||
cmd.Dir = h.CommandWorkingDirectory
|
cmd.Dir = h.CommandWorkingDirectory
|
||||||
|
|
||||||
cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload)
|
cmd.Args, errors = h.ExtractCommandArguments(r)
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error extracting command arguments: %s\n", rid, err)
|
log.Printf("[%s] error extracting command arguments: %s\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var envs []string
|
var envs []string
|
||||||
envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload)
|
envs, errors = h.ExtractCommandArgumentsForEnv(r)
|
||||||
|
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error extracting command arguments for environment: %s\n", rid, err)
|
log.Printf("[%s] error extracting command arguments for environment: %s\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
files, errors := h.ExtractCommandArgumentsForFile(headers, query, payload)
|
files, errors := h.ExtractCommandArgumentsForFile(r)
|
||||||
|
|
||||||
for _, err := range errors {
|
for _, err := range errors {
|
||||||
log.Printf("[%s] error extracting command arguments for file: %s\n", rid, err)
|
log.Printf("[%s] error extracting command arguments for file: %s\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
|
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error creating temp file [%s]", rid, err)
|
log.Printf("[%s] error creating temp file [%s]", r.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Printf("[%s] writing env %s file %s", rid, files[i].EnvName, tmpfile.Name())
|
log.Printf("[%s] writing env %s file %s", r.ID, files[i].EnvName, tmpfile.Name())
|
||||||
if _, err := tmpfile.Write(files[i].Data); err != nil {
|
if _, err := tmpfile.Write(files[i].Data); err != nil {
|
||||||
log.Printf("[%s] error writing file %s [%s]", rid, tmpfile.Name(), err)
|
log.Printf("[%s] error writing file %s [%s]", r.ID, tmpfile.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := tmpfile.Close(); err != nil {
|
if err := tmpfile.Close(); err != nil {
|
||||||
log.Printf("[%s] error closing file %s [%s]", rid, tmpfile.Name(), err)
|
log.Printf("[%s] error closing file %s [%s]", r.ID, tmpfile.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -644,27 +645,27 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
|
||||||
|
|
||||||
cmd.Env = append(os.Environ(), envs...)
|
cmd.Env = append(os.Environ(), envs...)
|
||||||
|
|
||||||
log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", rid, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
|
log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", r.ID, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
|
||||||
|
|
||||||
out, err := cmd.CombinedOutput()
|
out, err := cmd.CombinedOutput()
|
||||||
|
|
||||||
log.Printf("[%s] command output: %s\n", rid, out)
|
log.Printf("[%s] command output: %s\n", r.ID, out)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error occurred: %+v\n", rid, err)
|
log.Printf("[%s] error occurred: %+v\n", r.ID, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range files {
|
for i := range files {
|
||||||
if files[i].File != nil {
|
if files[i].File != nil {
|
||||||
log.Printf("[%s] removing file %s\n", rid, files[i].File.Name())
|
log.Printf("[%s] removing file %s\n", r.ID, files[i].File.Name())
|
||||||
err := os.Remove(files[i].File.Name())
|
err := os.Remove(files[i].File.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[%s] error removing file %s [%s]", rid, files[i].File.Name(), err)
|
log.Printf("[%s] error removing file %s [%s]", r.ID, files[i].File.Name(), err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[%s] finished handling %s\n", rid, h.ID)
|
log.Printf("[%s] finished handling %s\n", r.ID, h.ID)
|
||||||
|
|
||||||
return string(out), err
|
return string(out), err
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,11 @@ func TestStaticParams(t *testing.T) {
|
||||||
b := &bytes.Buffer{}
|
b := &bytes.Buffer{}
|
||||||
log.SetOutput(b)
|
log.SetOutput(b)
|
||||||
|
|
||||||
_, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
|
r := &hook.Request{
|
||||||
|
ID: "test",
|
||||||
|
Headers: spHeaders,
|
||||||
|
}
|
||||||
|
_, err = handleHook(spHook, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unexpected error: %v\n", err)
|
t.Fatalf("Unexpected error: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue