mirror of
https://github.com/adnanh/webhook.git
synced 2025-05-12 08:34:43 +00:00
When attempting to match a JSON path for initial setup, it would be helpful to know where the path failed. This change logs the failed parameter node. For example, if you are trying to match path "a.b.d.e", but you failed to include the "c" node, webhook will log an error "parameter node not found: d.e" to assist in troubleshooting.
644 lines
28 KiB
Go
644 lines
28 KiB
Go
package hook
|
|
|
|
import (
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestGetParameter(t *testing.T) {
|
|
for _, test := range []struct {
|
|
key string
|
|
val interface{}
|
|
expect interface{}
|
|
ok bool
|
|
}{
|
|
// True
|
|
{"a", map[string]interface{}{"a": "1"}, "1", true},
|
|
{"a.b", map[string]interface{}{"a.b": "1"}, "1", true},
|
|
{"a.c", map[string]interface{}{"a": map[string]interface{}{"c": 2}}, 2, true},
|
|
{"a.1", map[string]interface{}{"a": map[string]interface{}{"1": 3}}, 3, true},
|
|
{"a.1", map[string]interface{}{"a": []interface{}{"a", "b"}}, "b", true},
|
|
{"0", []interface{}{"a", "b"}, "a", true},
|
|
|
|
// False
|
|
{"z", map[string]interface{}{"a": "1"}, nil, false},
|
|
{"a.z", map[string]interface{}{"a": map[string]interface{}{"b": 2}}, nil, false},
|
|
{"z.b", map[string]interface{}{"a": map[string]interface{}{"z": 2}}, nil, false},
|
|
{"a.2", map[string]interface{}{"a": []interface{}{"a", "b"}}, nil, false},
|
|
} {
|
|
res, err := GetParameter(test.key, test.val)
|
|
if (err == nil) != test.ok {
|
|
t.Errorf("unexpected result given {%q, %q}: %s\n", test.key, test.val, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(res, test.expect) {
|
|
t.Errorf("failed given {%q, %q}:\nexpected {%#v}\ngot {%#v}\n", test.key, test.val, test.expect, res)
|
|
}
|
|
}
|
|
}
|
|
|
|
var checkPayloadSignatureTests = []struct {
|
|
payload []byte
|
|
secret string
|
|
signature string
|
|
mac string
|
|
ok bool
|
|
}{
|
|
{[]byte(`{"a": "z"}`), "secret", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
|
{[]byte(`{"a": "z"}`), "secret", "sha1=b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", true},
|
|
// failures
|
|
{[]byte(`{"a": "z"}`), "secret", "XXXe04cbb22afa8ffbff8796fc1894ed27badd9e", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", false},
|
|
{[]byte(`{"a": "z"}`), "secreX", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "900225703e9342328db7307692736e2f7cc7b36e", false},
|
|
{[]byte(`{"a": "z"}`), "", "b17e04cbb22afa8ffbff8796fc1894ed27badd9e", "", false},
|
|
}
|
|
|
|
func TestCheckPayloadSignature(t *testing.T) {
|
|
for _, tt := range checkPayloadSignatureTests {
|
|
mac, err := CheckPayloadSignature(tt.payload, tt.secret, tt.signature)
|
|
if (err == nil) != tt.ok || mac != tt.mac {
|
|
t.Errorf("failed to check payload signature {%q, %q, %q}:\nexpected {mac:%#v, ok:%#v},\ngot {mac:%#v, ok:%#v}", tt.payload, tt.secret, tt.signature, tt.mac, tt.ok, mac, (err == nil))
|
|
}
|
|
|
|
if err != nil && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
|
|
t.Errorf("error message should not disclose expected mac: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
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},
|
|
{[]byte(`{"a": "z"}`), "", "XXX7af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89", "", 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 && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
|
|
t.Errorf("error message should not disclose expected mac: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var checkPayloadSignature512Tests = []struct {
|
|
payload []byte
|
|
secret string
|
|
signature string
|
|
mac string
|
|
ok bool
|
|
}{
|
|
{[]byte(`{"a": "z"}`), "secret", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
|
{[]byte(`{"a": "z"}`), "secret", "sha512=4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", true},
|
|
// failures
|
|
{[]byte(`{"a": "z"}`), "secret", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "4ab17cc8ec668ead8bf498f87f8f32848c04d5ca3c9bcfcd3db9363f0deb44e580b329502a7fdff633d4d8fca301cc5c94a55a2fec458c675fb0ff2655898324", false},
|
|
{[]byte(`{"a": "z"}`), "", "74a0081f5b5988f4f3e8b8dd34dadc6291611f2e6260635a7e1535f8e95edb97ff520ba8b152e8ca5760ac42639854f3242e29efc81be73a8bf52d474d31ffea", "", false},
|
|
}
|
|
|
|
func TestCheckPayloadSignature512(t *testing.T) {
|
|
for _, tt := range checkPayloadSignature512Tests {
|
|
mac, err := CheckPayloadSignature512(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 && tt.mac != "" && strings.Contains(err.Error(), tt.mac) {
|
|
t.Errorf("error message should not disclose expected mac: %s", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var checkScalrSignatureTests = []struct {
|
|
description string
|
|
headers map[string]interface{}
|
|
payload []byte
|
|
secret string
|
|
expectedSignature string
|
|
ok bool
|
|
}{
|
|
{
|
|
"Valid signature",
|
|
map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC", "X-Signature": "48e395e38ac48988929167df531eb2da00063a7d"},
|
|
[]byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+",
|
|
"48e395e38ac48988929167df531eb2da00063a7d", true,
|
|
},
|
|
{
|
|
"Wrong signature",
|
|
map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC", "X-Signature": "999395e38ac48988929167df531eb2da00063a7d"},
|
|
[]byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+",
|
|
"48e395e38ac48988929167df531eb2da00063a7d", false,
|
|
},
|
|
{
|
|
"Missing Date header",
|
|
map[string]interface{}{"X-Signature": "999395e38ac48988929167df531eb2da00063a7d"},
|
|
[]byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+",
|
|
"48e395e38ac48988929167df531eb2da00063a7d", false,
|
|
},
|
|
{
|
|
"Missing X-Signature header",
|
|
map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC"},
|
|
[]byte(`{"a": "b"}`), "bilFGi4ZVZUdG+C6r0NIM9tuRq6PaG33R3eBUVhLwMAErGBaazvXe4Gq2DcJs5q+",
|
|
"48e395e38ac48988929167df531eb2da00063a7d", false,
|
|
},
|
|
{
|
|
"Missing signing key",
|
|
map[string]interface{}{"Date": "Thu 07 Sep 2017 06:30:04 UTC", "X-Signature": "48e395e38ac48988929167df531eb2da00063a7d"},
|
|
[]byte(`{"a": "b"}`), "",
|
|
"48e395e38ac48988929167df531eb2da00063a7d", false,
|
|
},
|
|
}
|
|
|
|
func TestCheckScalrSignature(t *testing.T) {
|
|
for _, testCase := range checkScalrSignatureTests {
|
|
valid, err := CheckScalrSignature(testCase.headers, testCase.payload, testCase.secret, false)
|
|
if valid != testCase.ok {
|
|
t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
|
|
testCase.description, testCase.ok, valid)
|
|
}
|
|
|
|
if err != nil && testCase.secret != "" && strings.Contains(err.Error(), testCase.expectedSignature) {
|
|
t.Errorf("error message should not disclose expected mac: %s on test case %s", err, testCase.description)
|
|
}
|
|
}
|
|
}
|
|
|
|
var checkIPWhitelistTests = []struct {
|
|
addr string
|
|
ipRange string
|
|
expect bool
|
|
ok bool
|
|
}{
|
|
{"[ 10.0.0.1:1234 ] ", " 10.0.0.1 ", true, true},
|
|
{"[ 10.0.0.1:1234 ] ", " 10.0.0.0 ", false, true},
|
|
{"[ 10.0.0.1:1234 ] ", " 10.0.0.1 10.0.0.1 ", true, true},
|
|
{"[ 10.0.0.1:1234 ] ", " 10.0.0.0/31 ", true, true},
|
|
{" [2001:db8:1:2::1:1234] ", " 2001:db8:1::/48 ", true, true},
|
|
{" [2001:db8:1:2::1:1234] ", " 2001:db8:1::/48 2001:db8:1::/64", true, true},
|
|
{" [2001:db8:1:2::1:1234] ", " 2001:db8:1::/64 ", false, true},
|
|
}
|
|
|
|
func TestCheckIPWhitelist(t *testing.T) {
|
|
for _, tt := range checkIPWhitelistTests {
|
|
result, err := CheckIPWhitelist(tt.addr, tt.ipRange)
|
|
if (err == nil) != tt.ok || result != tt.expect {
|
|
t.Errorf("ip whitelist test failed {%q, %q}:\nwant {expect:%#v, ok:%#v},\ngot {result:%#v, ok:%#v}", tt.addr, tt.ipRange, tt.expect, tt.ok, result, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var extractParameterTests = []struct {
|
|
s string
|
|
params interface{}
|
|
value string
|
|
ok bool
|
|
}{
|
|
{"a", map[string]interface{}{"a": "z"}, "z", true},
|
|
{"a.b", map[string]interface{}{"a": map[string]interface{}{"b": "z"}}, "z", true},
|
|
{"a.b.c", map[string]interface{}{"a": map[string]interface{}{"b": map[string]interface{}{"c": "z"}}}, "z", true},
|
|
{"a.b.0", map[string]interface{}{"a": map[string]interface{}{"b": []interface{}{"x", "y", "z"}}}, "x", true},
|
|
{"a.1.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "z", true},
|
|
{"a.1.b.c", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": map[string]interface{}{"c": "y"}}, map[string]interface{}{"b": map[string]interface{}{"c": "z"}}}}, "z", true},
|
|
// failures
|
|
{"check_nil", nil, "", false},
|
|
{"a.X", map[string]interface{}{"a": map[string]interface{}{"b": "z"}}, "", false}, // non-existent parameter reference
|
|
{"a.X.c", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "", false}, // non-integer slice index
|
|
{"a.-1.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "", false}, // negative slice index
|
|
{"a.500.b", map[string]interface{}{"a": map[string]interface{}{"b": "z"}}, "", false}, // non-existent slice
|
|
{"a.501.b", map[string]interface{}{"a": []interface{}{map[string]interface{}{"b": "y"}, map[string]interface{}{"b": "z"}}}, "", false}, // non-existent slice index
|
|
{"a.502.b", map[string]interface{}{"a": []interface{}{}}, "", false}, // non-existent slice index
|
|
{"a.b.503", map[string]interface{}{"a": map[string]interface{}{"b": []interface{}{"x", "y", "z"}}}, "", false}, // trailing, non-existent slice index
|
|
{"a.b", interface{}("a"), "", false}, // non-map, non-slice input
|
|
}
|
|
|
|
func TestExtractParameter(t *testing.T) {
|
|
for _, tt := range extractParameterTests {
|
|
value, err := ExtractParameterAsString(tt.s, tt.params)
|
|
if (err == nil) != tt.ok || value != tt.value {
|
|
t.Errorf("failed to extract parameter %q:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%s}", tt.s, tt.value, tt.ok, value, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var argumentGetTests = []struct {
|
|
source, name string
|
|
headers, query, payload *map[string]interface{}
|
|
value string
|
|
ok bool
|
|
}{
|
|
{"header", "a", &map[string]interface{}{"A": "z"}, nil, 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},
|
|
{"string", "a", nil, nil, &map[string]interface{}{"a": "z"}, "a", true},
|
|
// failures
|
|
{"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
|
|
{"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
|
|
}
|
|
|
|
func TestArgumentGet(t *testing.T) {
|
|
for _, tt := range argumentGetTests {
|
|
a := Argument{tt.source, tt.name, "", false}
|
|
value, err := a.Get(tt.headers, tt.query, tt.payload)
|
|
if (err == nil) != tt.ok || value != tt.value {
|
|
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%s}", tt.source, tt.name, tt.value, tt.ok, value, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var hookParseJSONParametersTests = []struct {
|
|
params []Argument
|
|
headers, query, payload *map[string]interface{}
|
|
rheaders, rquery, rpayload *map[string]interface{}
|
|
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{"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{"header", "z", "", false}}, &map[string]interface{}{"Z": `{}`}, nil, nil, &map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
|
|
// failures
|
|
{[]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{"string", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
|
|
}
|
|
|
|
func TestHookParseJSONParameters(t *testing.T) {
|
|
for _, tt := range hookParseJSONParametersTests {
|
|
h := &Hook{JSONStringParameters: tt.params}
|
|
err := h.ParseJSONParameters(tt.headers, tt.query, tt.payload)
|
|
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
|
|
t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, *tt.rheaders, tt.ok, *tt.headers, (err == nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
var hookExtractCommandArgumentsTests = []struct {
|
|
exec string
|
|
args []Argument
|
|
headers, query, payload *map[string]interface{}
|
|
value []string
|
|
ok bool
|
|
}{
|
|
{"test", []Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
|
|
// failures
|
|
{"fail", []Argument{Argument{"payload", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
|
|
}
|
|
|
|
func TestHookExtractCommandArguments(t *testing.T) {
|
|
for _, tt := range hookExtractCommandArgumentsTests {
|
|
h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
|
|
value, err := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload)
|
|
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
|
|
t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Here we test the extraction of env variables when the user defined a hook
|
|
// with the "pass-environment-to-command" directive
|
|
// we test both cases where the name of the data is used as the name of the
|
|
// env key & the case where the hook definition sets the env var name to a
|
|
// fixed value using the envname construct like so::
|
|
// [
|
|
// {
|
|
// "id": "push",
|
|
// "execute-command": "bb2mm",
|
|
// "command-working-directory": "/tmp",
|
|
// "pass-environment-to-command":
|
|
// [
|
|
// {
|
|
// "source": "entire-payload",
|
|
// "envname": "PAYLOAD"
|
|
// },
|
|
// ]
|
|
// }
|
|
// ]
|
|
var hookExtractCommandArgumentsForEnvTests = []struct {
|
|
exec string
|
|
args []Argument
|
|
headers, query, payload *map[string]interface{}
|
|
value []string
|
|
ok bool
|
|
}{
|
|
// successes
|
|
{
|
|
"test",
|
|
[]Argument{Argument{"header", "a", "", false}},
|
|
&map[string]interface{}{"A": "z"}, nil, nil,
|
|
[]string{"HOOK_a=z"},
|
|
true,
|
|
},
|
|
{
|
|
"test",
|
|
[]Argument{Argument{"header", "a", "MYKEY", false}},
|
|
&map[string]interface{}{"A": "z"}, nil, nil,
|
|
[]string{"MYKEY=z"},
|
|
true,
|
|
},
|
|
// failures
|
|
{
|
|
"fail",
|
|
[]Argument{Argument{"payload", "a", "", false}},
|
|
&map[string]interface{}{"A": "z"}, nil, nil,
|
|
[]string{},
|
|
false,
|
|
},
|
|
}
|
|
|
|
func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
|
|
for _, tt := range hookExtractCommandArgumentsForEnvTests {
|
|
h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
|
|
value, err := h.ExtractCommandArgumentsForEnv(tt.headers, tt.query, tt.payload)
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
|
|
var hooksLoadFromFileTests = []struct {
|
|
path string
|
|
asTemplate bool
|
|
ok bool
|
|
}{
|
|
{"../../hooks.json.example", false, true},
|
|
{"../../hooks.yaml.example", false, true},
|
|
{"../../hooks.json.tmpl.example", true, true},
|
|
{"../../hooks.yaml.tmpl.example", true, true},
|
|
{"", false, true},
|
|
// failures
|
|
{"missing.json", false, false},
|
|
}
|
|
|
|
func TestHooksLoadFromFile(t *testing.T) {
|
|
secret := `foo"123`
|
|
os.Setenv("XXXTEST_SECRET", secret)
|
|
|
|
for _, tt := range hooksLoadFromFileTests {
|
|
h := &Hooks{}
|
|
err := h.LoadFromFile(tt.path, tt.asTemplate)
|
|
if (err == nil) != tt.ok {
|
|
t.Errorf(err.Error())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHooksTemplateLoadFromFile(t *testing.T) {
|
|
secret := `foo"123`
|
|
os.Setenv("XXXTEST_SECRET", secret)
|
|
|
|
for _, tt := range hooksLoadFromFileTests {
|
|
if !tt.asTemplate {
|
|
continue
|
|
}
|
|
|
|
h := &Hooks{}
|
|
err := h.LoadFromFile(tt.path, tt.asTemplate)
|
|
if (err == nil) != tt.ok {
|
|
t.Errorf(err.Error())
|
|
continue
|
|
}
|
|
|
|
s := (*h.Match("webhook").TriggerRule.And)[0].Match.Secret
|
|
if s != secret {
|
|
t.Errorf("Expected secret of %q, got %q", secret, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
var hooksMatchTests = []struct {
|
|
id string
|
|
hooks Hooks
|
|
value *Hook
|
|
}{
|
|
{"a", Hooks{Hook{ID: "a"}}, &Hook{ID: "a"}},
|
|
{"X", Hooks{Hook{ID: "a"}}, new(Hook)},
|
|
}
|
|
|
|
func TestHooksMatch(t *testing.T) {
|
|
for _, tt := range hooksMatchTests {
|
|
value := tt.hooks.Match(tt.id)
|
|
if reflect.DeepEqual(reflect.ValueOf(value), reflect.ValueOf(tt.value)) {
|
|
t.Errorf("failed to match %q:\nexpected %#v,\ngot %#v", tt.id, tt.value, value)
|
|
}
|
|
}
|
|
}
|
|
|
|
var matchRuleTests = []struct {
|
|
typ, regex, secret, value, ipRange string
|
|
param Argument
|
|
headers, query, payload *map[string]interface{}
|
|
body []byte
|
|
remoteAddr string
|
|
ok bool
|
|
err bool
|
|
}{
|
|
{"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},
|
|
{"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},
|
|
// failures
|
|
{"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},
|
|
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
|
|
// errors
|
|
{"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-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &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) {
|
|
for i, tt := range matchRuleTests {
|
|
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)
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
var andRuleTests = []struct {
|
|
desc string // description of the test case
|
|
rule AndRule
|
|
headers, query, payload *map[string]interface{}
|
|
body []byte
|
|
ok bool
|
|
err bool
|
|
}{
|
|
{
|
|
"(a=z, b=y): a=z && b=y",
|
|
AndRule{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
|
},
|
|
&map[string]interface{}{"A": "z", "B": "y"}, nil, nil, []byte{},
|
|
true, false,
|
|
},
|
|
{
|
|
"(a=z, b=Y): a=z && b=y",
|
|
AndRule{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
|
},
|
|
&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil, []byte{},
|
|
false, false,
|
|
},
|
|
// Complex test to cover Rules.Evaluate
|
|
{
|
|
"(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{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
{
|
|
And: &AndRule{
|
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "x", Argument{"header", "c", "", false}, ""}},
|
|
},
|
|
},
|
|
{
|
|
Or: &OrRule{
|
|
{Match: &MatchRule{"value", "", "", "w", Argument{"header", "d", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "v", Argument{"header", "e", "", false}, ""}},
|
|
},
|
|
},
|
|
{
|
|
Not: &NotRule{
|
|
Match: &MatchRule{"value", "", "", "u", Argument{"header", "f", "", false}, ""},
|
|
},
|
|
},
|
|
},
|
|
&map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil, []byte{},
|
|
true, false,
|
|
},
|
|
{"empty rule", AndRule{{}}, nil, nil, nil, nil, false, false},
|
|
// failures
|
|
{
|
|
"invalid rule",
|
|
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
|
|
&map[string]interface{}{"Y": "z"}, nil, nil, nil,
|
|
false, true,
|
|
},
|
|
}
|
|
|
|
func TestAndRule(t *testing.T) {
|
|
for _, tt := range andRuleTests {
|
|
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
|
if ok != tt.ok || (err != nil) != tt.err {
|
|
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var orRuleTests = []struct {
|
|
desc string // description of the test case
|
|
rule OrRule
|
|
headers, query, payload *map[string]interface{}
|
|
body []byte
|
|
ok bool
|
|
err bool
|
|
}{
|
|
{
|
|
"(a=z, b=X): a=z || b=y",
|
|
OrRule{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
|
},
|
|
&map[string]interface{}{"A": "z", "B": "X"}, nil, nil, []byte{},
|
|
true, false,
|
|
},
|
|
{
|
|
"(a=X, b=y): a=z || b=y",
|
|
OrRule{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
|
},
|
|
&map[string]interface{}{"A": "X", "B": "y"}, nil, nil, []byte{},
|
|
true, false,
|
|
},
|
|
{
|
|
"(a=Z, b=Y): a=z || b=y",
|
|
OrRule{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
|
|
},
|
|
&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil, []byte{},
|
|
false, false,
|
|
},
|
|
// failures
|
|
{
|
|
"invalid rule",
|
|
OrRule{
|
|
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
|
|
},
|
|
&map[string]interface{}{"Y": "Z"}, nil, nil, []byte{},
|
|
false, true,
|
|
},
|
|
}
|
|
|
|
func TestOrRule(t *testing.T) {
|
|
for _, tt := range orRuleTests {
|
|
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
|
if ok != tt.ok || (err != nil) != tt.err {
|
|
t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var notRuleTests = []struct {
|
|
desc string // description of the test case
|
|
rule NotRule
|
|
headers, query, payload *map[string]interface{}
|
|
body []byte
|
|
ok 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=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) {
|
|
for _, tt := range notRuleTests {
|
|
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
|
|
if ok != tt.ok || (err != nil) != tt.err {
|
|
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompare(t *testing.T) {
|
|
for _, tt := range []struct {
|
|
a, b string
|
|
ok bool
|
|
}{
|
|
{"abcd", "abcd", true},
|
|
{"zyxw", "abcd", false},
|
|
} {
|
|
if ok := compare(tt.a, tt.b); ok != tt.ok {
|
|
t.Errorf("compare failed for %q and %q: got %v\n", tt.a, tt.b, ok)
|
|
}
|
|
}
|
|
}
|