mirror of
https://github.com/adnanh/webhook.git
synced 2025-06-06 20:52:29 +00:00
commit
3978b6687d
3 changed files with 168 additions and 53 deletions
103
hook/hook.go
103
hook/hook.go
|
@ -3,11 +3,13 @@ package hook
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -100,6 +102,67 @@ func CheckPayloadSignature(payload []byte, secret string, signature string) (str
|
||||||
return expectedMAC, err
|
return expectedMAC, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckPayloadSignature256 calculates and verifies SHA256 signature of the given payload
|
||||||
|
func CheckPayloadSignature256(payload []byte, secret string, signature string) (string, error) {
|
||||||
|
if strings.HasPrefix(signature, "sha256=") {
|
||||||
|
signature = signature[7:]
|
||||||
|
}
|
||||||
|
|
||||||
|
mac := hmac.New(sha256.New, []byte(secret))
|
||||||
|
_, err := mac.Write(payload)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
expectedMAC := hex.EncodeToString(mac.Sum(nil))
|
||||||
|
|
||||||
|
if !hmac.Equal([]byte(signature), []byte(expectedMAC)) {
|
||||||
|
return expectedMAC, &SignatureError{signature}
|
||||||
|
}
|
||||||
|
return expectedMAC, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckIPWhitelist makes sure the provided remote address (of the form IP:port) falls within the provided IP range
|
||||||
|
// (in CIDR form or a single IP address).
|
||||||
|
func CheckIPWhitelist(remoteAddr string, ipRange string) (bool, error) {
|
||||||
|
// Extract IP address from remote address.
|
||||||
|
|
||||||
|
ip := remoteAddr
|
||||||
|
|
||||||
|
if strings.LastIndex(remoteAddr, ":") != -1 {
|
||||||
|
ip = remoteAddr[0:strings.LastIndex(remoteAddr, ":")]
|
||||||
|
}
|
||||||
|
|
||||||
|
ip = strings.TrimSpace(ip)
|
||||||
|
|
||||||
|
// IPv6 addresses will likely be surrounded by [], so don't forget to remove those.
|
||||||
|
|
||||||
|
if strings.HasPrefix(ip, "[") && strings.HasSuffix(ip, "]") {
|
||||||
|
ip = ip[1 : len(ip)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedIP := net.ParseIP(strings.TrimSpace(ip))
|
||||||
|
|
||||||
|
if parsedIP == nil {
|
||||||
|
return false, fmt.Errorf("invalid IP address found in remote address '%s'", remoteAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract IP range in CIDR form. If a single IP address is provided, turn it into CIDR form.
|
||||||
|
|
||||||
|
ipRange = strings.TrimSpace(ipRange)
|
||||||
|
|
||||||
|
if strings.Index(ipRange, "/") == -1 {
|
||||||
|
ipRange = ipRange + "/32"
|
||||||
|
}
|
||||||
|
|
||||||
|
_, cidr, err := net.ParseCIDR(ipRange)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidr.Contains(parsedIP), nil
|
||||||
|
}
|
||||||
|
|
||||||
// ReplaceParameter replaces parameter value with the passed value in the passed map
|
// ReplaceParameter replaces parameter value with the passed value in the passed map
|
||||||
// (please note you should pass pointer to the map, because we're modifying it)
|
// (please note you should pass pointer to the map, because we're modifying it)
|
||||||
// based on the passed string
|
// based on the passed string
|
||||||
|
@ -479,16 +542,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) (bool, error) {
|
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
||||||
switch {
|
switch {
|
||||||
case r.And != nil:
|
case r.And != nil:
|
||||||
return r.And.Evaluate(headers, query, payload, body)
|
return r.And.Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
case r.Or != nil:
|
case r.Or != nil:
|
||||||
return r.Or.Evaluate(headers, query, payload, body)
|
return r.Or.Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
case r.Not != nil:
|
case r.Not != nil:
|
||||||
return r.Not.Evaluate(headers, query, payload, body)
|
return r.Not.Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
case r.Match != nil:
|
case r.Match != nil:
|
||||||
return r.Match.Evaluate(headers, query, payload, body)
|
return r.Match.Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -498,11 +561,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) (bool, error) {
|
func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
||||||
res := true
|
res := true
|
||||||
|
|
||||||
for _, v := range r {
|
for _, v := range r {
|
||||||
rv, err := v.Evaluate(headers, query, payload, body)
|
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -520,11 +583,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) (bool, error) {
|
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
||||||
res := false
|
res := false
|
||||||
|
|
||||||
for _, v := range r {
|
for _, v := range r {
|
||||||
rv, err := v.Evaluate(headers, query, payload, body)
|
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -542,8 +605,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) (bool, error) {
|
func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
||||||
rv, err := Rules(r).Evaluate(headers, query, payload, body)
|
rv, err := Rules(r).Evaluate(headers, query, payload, body, remoteAddr)
|
||||||
return !rv, err
|
return !rv, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,17 +617,24 @@ type MatchRule struct {
|
||||||
Secret string `json:"secret,omitempty"`
|
Secret string `json:"secret,omitempty"`
|
||||||
Value string `json:"value,omitempty"`
|
Value string `json:"value,omitempty"`
|
||||||
Parameter Argument `json:"parameter,omitempty"`
|
Parameter Argument `json:"parameter,omitempty"`
|
||||||
|
IPRange string `json:"ip-range,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants for the MatchRule type
|
// Constants for the MatchRule type
|
||||||
const (
|
const (
|
||||||
MatchValue string = "value"
|
MatchValue string = "value"
|
||||||
MatchRegex string = "regex"
|
MatchRegex string = "regex"
|
||||||
MatchHashSHA1 string = "payload-hash-sha1"
|
MatchHashSHA1 string = "payload-hash-sha1"
|
||||||
|
MatchHashSHA256 string = "payload-hash-sha256"
|
||||||
|
IPWhitelist string = "ip-whitelist"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) (bool, error) {
|
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
|
||||||
|
if r.Type == IPWhitelist {
|
||||||
|
return CheckIPWhitelist(remoteAddr, r.IPRange)
|
||||||
|
}
|
||||||
|
|
||||||
if arg, ok := r.Parameter.Get(headers, query, payload); ok {
|
if arg, ok := r.Parameter.Get(headers, query, payload); ok {
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case MatchValue:
|
case MatchValue:
|
||||||
|
@ -574,6 +644,9 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
|
||||||
case MatchHashSHA1:
|
case MatchHashSHA1:
|
||||||
_, err := CheckPayloadSignature(*body, r.Secret, arg)
|
_, err := CheckPayloadSignature(*body, r.Secret, arg)
|
||||||
return err == nil, err
|
return err == nil, err
|
||||||
|
case MatchHashSHA256:
|
||||||
|
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
|
||||||
|
return err == nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|
|
@ -33,6 +33,32 @@ func TestCheckPayloadSignature(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var checkPayloadSignature256Tests = []struct {
|
||||||
|
payload []byte
|
||||||
|
secret string
|
||||||
|
signature string
|
||||||
|
mac string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "sha256=f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", true},
|
||||||
|
// failures
|
||||||
|
{[]byte(`{"a": "z"}`), "secret", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckPayloadSignature256(t *testing.T) {
|
||||||
|
for _, tt := range checkPayloadSignature256Tests {
|
||||||
|
mac, err := CheckPayloadSignature256(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))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && strings.Contains(err.Error(), tt.mac) {
|
||||||
|
t.Errorf("error message should not disclose expected mac: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var extractParameterTests = []struct {
|
var extractParameterTests = []struct {
|
||||||
s string
|
s string
|
||||||
params interface{}
|
params interface{}
|
||||||
|
@ -241,29 +267,45 @@ func TestHooksMatch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchRuleTests = []struct {
|
var matchRuleTests = []struct {
|
||||||
typ, regex, secret, value 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
|
||||||
ok bool
|
remoteAddr string
|
||||||
err bool
|
ok bool
|
||||||
|
err bool
|
||||||
}{
|
}{
|
||||||
{"value", "", "", "z", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
|
{"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},
|
{"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},
|
{"payload-hash-sha1", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
|
{"payload-hash-sha256", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), true, false},
|
||||||
// failures
|
// failures
|
||||||
{"value", "", "", "X", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, 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},
|
{"regex", "^X", "", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
|
||||||
{"value", "", "2", "X", Argument{"header", "a", ""}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, false, false}, // reference invalid header
|
{"value", "", "2", "X", "", Argument{"header", "a", ""}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, false}, // reference invalid header
|
||||||
// errors
|
// errors
|
||||||
{"regex", "*", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, true}, // invalid regex
|
{"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
|
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
|
||||||
|
{"payload-hash-sha256", "", "secret", "", Argument{"header", "a", ""}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, false, true}, // invalid hmac
|
||||||
|
// 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", Argument{}, nil, nil, nil, []byte{}, "192.168.0.1:9000", true, false}, // valid IPv4, no range
|
||||||
|
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, with range
|
||||||
|
{"ip-whitelist", "", "", "", "::1", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", true, false}, // valid IPv6, no range
|
||||||
|
// IP whitelisting, invalid cases
|
||||||
|
{"ip-whitelist", "", "", "", "192.168.0.1/a", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, with range
|
||||||
|
{"ip-whitelist", "", "", "", "192.168.0.a", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", false, true}, // invalid IPv4, no range
|
||||||
|
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.a:9000", false, true}, // invalid IPv4 address
|
||||||
|
{"ip-whitelist", "", "", "", "::1/a", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, with range
|
||||||
|
{"ip-whitelist", "", "", "", "::z", Argument{}, nil, nil, nil, []byte{}, "[::1]:9000", false, true}, // invalid IPv6, no range
|
||||||
|
{"ip-whitelist", "", "", "", "::1/24", Argument{}, nil, nil, nil, []byte{}, "[::z]:9000", false, true}, // invalid IPv6 address
|
||||||
}
|
}
|
||||||
|
|
||||||
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}
|
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)
|
ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, tt.remoteAddr)
|
||||||
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 != nil))
|
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))
|
||||||
}
|
}
|
||||||
|
@ -281,8 +323,8 @@ var andRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"(a=z, b=y): a=z && b=y",
|
"(a=z, b=y): a=z && b=y",
|
||||||
AndRule{
|
AndRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "y"}, nil, nil, []byte{},
|
&map[string]interface{}{"A": "z", "B": "y"}, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
|
@ -290,8 +332,8 @@ var andRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"(a=z, b=Y): a=z && b=y",
|
"(a=z, b=Y): a=z && b=y",
|
||||||
AndRule{
|
AndRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, []byte{},
|
&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, []byte{},
|
||||||
false, false,
|
false, false,
|
||||||
|
@ -300,22 +342,22 @@ var andRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"(a=z, b=y, c=x, d=w=, e=X, f=X): a=z && (b=y && c=x) && (d=w || e=v) && !f=u",
|
"(a=z, b=y, c=x, d=w=, e=X, f=X): a=z && (b=y && c=x) && (d=w || e=v) && !f=u",
|
||||||
AndRule{
|
AndRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
{
|
{
|
||||||
And: &AndRule{
|
And: &AndRule{
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "x", Argument{"header", "c", ""}}},
|
{Match: &MatchRule{"value", "", "", "x", Argument{"header", "c", ""}, ""}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Or: &OrRule{
|
Or: &OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "w", Argument{"header", "d", ""}}},
|
{Match: &MatchRule{"value", "", "", "w", Argument{"header", "d", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "v", Argument{"header", "e", ""}}},
|
{Match: &MatchRule{"value", "", "", "v", Argument{"header", "e", ""}, ""}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Not: &NotRule{
|
Not: &NotRule{
|
||||||
Match: &MatchRule{"value", "", "", "u", Argument{"header", "f", ""}},
|
Match: &MatchRule{"value", "", "", "u", Argument{"header", "f", ""}, ""},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -326,7 +368,7 @@ var andRuleTests = []struct {
|
||||||
// failures
|
// failures
|
||||||
{
|
{
|
||||||
"invalid rule",
|
"invalid rule",
|
||||||
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}}}},
|
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", ""}, ""}}},
|
||||||
&map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
&map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
|
@ -334,7 +376,7 @@ var andRuleTests = []struct {
|
||||||
|
|
||||||
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)
|
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -352,8 +394,8 @@ var orRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"(a=z, b=X): a=z || b=y",
|
"(a=z, b=X): a=z || b=y",
|
||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "z", "B": "X"}, nil, nil, []byte{},
|
&map[string]interface{}{"A": "z", "B": "X"}, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
|
@ -361,8 +403,8 @@ var orRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"(a=X, b=y): a=z || b=y",
|
"(a=X, b=y): a=z || b=y",
|
||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "X", "B": "y"}, nil, nil, []byte{},
|
&map[string]interface{}{"A": "X", "B": "y"}, nil, nil, []byte{},
|
||||||
true, false,
|
true, false,
|
||||||
|
@ -370,8 +412,8 @@ var orRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"(a=Z, b=Y): a=z || b=y",
|
"(a=Z, b=Y): a=z || b=y",
|
||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}}},
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", ""}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, []byte{},
|
&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, []byte{},
|
||||||
false, false,
|
false, false,
|
||||||
|
@ -380,7 +422,7 @@ var orRuleTests = []struct {
|
||||||
{
|
{
|
||||||
"invalid rule",
|
"invalid rule",
|
||||||
OrRule{
|
OrRule{
|
||||||
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}}},
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", ""}, ""}},
|
||||||
},
|
},
|
||||||
&map[string]interface{}{"Y": "Z"}, nil, nil, []byte{},
|
&map[string]interface{}{"Y": "Z"}, nil, nil, []byte{},
|
||||||
false, false,
|
false, false,
|
||||||
|
@ -389,7 +431,7 @@ 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)
|
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@ -404,13 +446,13 @@ var notRuleTests = []struct {
|
||||||
ok bool
|
ok bool
|
||||||
err bool
|
err bool
|
||||||
}{
|
}{
|
||||||
{"(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=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},
|
{"(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) {
|
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)
|
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "2.6.2"
|
version = "2.6.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -243,7 +243,7 @@ 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)
|
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body, r.RemoteAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("error evaluating hook: %s", err)
|
msg := fmt.Sprintf("error evaluating hook: %s", err)
|
||||||
log.Printf(msg)
|
log.Printf(msg)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue