cb1a686e3b
* Strip empty strings from database revocation stmts It's technically valid to give empty strings as statements to run on most databases. However, in the case of revocation statements, it's not only generally inadvisable but can lead to lack of revocations when you expect them. This strips empty strings from the array of revocation statements. It also makes two other changes: * Return statements on read as empty but valid arrays rather than nulls, so that typing information is inferred (this is more in line with the rest of Vault these days) * Changes field data for TypeStringSlice and TypeCommaStringSlice such that a client-supplied value of `""` doesn't turn into `[]string{""}` but rather `[]string{}`. The latter and the explicit revocation statement changes are related, and defense in depth.
534 lines
13 KiB
Go
534 lines
13 KiB
Go
package strutil
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func TestStrUtil_StrListDelete(t *testing.T) {
|
|
output := StrListDelete([]string{"item1", "item2", "item3"}, "item1")
|
|
if StrListContains(output, "item1") {
|
|
t.Fatal("bad: 'item1' should not have been present")
|
|
}
|
|
|
|
output = StrListDelete([]string{"item1", "item2", "item3"}, "item2")
|
|
if StrListContains(output, "item2") {
|
|
t.Fatal("bad: 'item2' should not have been present")
|
|
}
|
|
|
|
output = StrListDelete([]string{"item1", "item2", "item3"}, "item3")
|
|
if StrListContains(output, "item3") {
|
|
t.Fatal("bad: 'item3' should not have been present")
|
|
}
|
|
|
|
output = StrListDelete([]string{"item1", "item1", "item3"}, "item1")
|
|
if !StrListContains(output, "item1") {
|
|
t.Fatal("bad: 'item1' should have been present")
|
|
}
|
|
|
|
output = StrListDelete(output, "item1")
|
|
if StrListContains(output, "item1") {
|
|
t.Fatal("bad: 'item1' should not have been present")
|
|
}
|
|
|
|
output = StrListDelete(output, "random")
|
|
if len(output) != 1 {
|
|
t.Fatalf("bad: expected: 1, actual: %d", len(output))
|
|
}
|
|
|
|
output = StrListDelete(output, "item3")
|
|
if StrListContains(output, "item3") {
|
|
t.Fatal("bad: 'item3' should not have been present")
|
|
}
|
|
}
|
|
|
|
func TestStrutil_EquivalentSlices(t *testing.T) {
|
|
slice1 := []string{"test2", "test1", "test3"}
|
|
slice2 := []string{"test3", "test2", "test1"}
|
|
if !EquivalentSlices(slice1, slice2) {
|
|
t.Fatalf("bad: expected a match")
|
|
}
|
|
|
|
slice2 = append(slice2, "test4")
|
|
if EquivalentSlices(slice1, slice2) {
|
|
t.Fatalf("bad: expected a mismatch")
|
|
}
|
|
}
|
|
|
|
func TestStrutil_ListContainsGlob(t *testing.T) {
|
|
haystack := []string{
|
|
"dev",
|
|
"ops*",
|
|
"root/*",
|
|
"*-dev",
|
|
"_*_",
|
|
}
|
|
if StrListContainsGlob(haystack, "tubez") {
|
|
t.Fatalf("Value shouldn't exist")
|
|
}
|
|
if !StrListContainsGlob(haystack, "root/test") {
|
|
t.Fatalf("Value should exist")
|
|
}
|
|
if !StrListContainsGlob(haystack, "ops_test") {
|
|
t.Fatalf("Value should exist")
|
|
}
|
|
if !StrListContainsGlob(haystack, "ops") {
|
|
t.Fatalf("Value should exist")
|
|
}
|
|
if !StrListContainsGlob(haystack, "dev") {
|
|
t.Fatalf("Value should exist")
|
|
}
|
|
if !StrListContainsGlob(haystack, "test-dev") {
|
|
t.Fatalf("Value should exist")
|
|
}
|
|
if !StrListContainsGlob(haystack, "_test_") {
|
|
t.Fatalf("Value should exist")
|
|
}
|
|
|
|
}
|
|
|
|
func TestStrutil_ListContains(t *testing.T) {
|
|
haystack := []string{
|
|
"dev",
|
|
"ops",
|
|
"prod",
|
|
"root",
|
|
}
|
|
if StrListContains(haystack, "tubez") {
|
|
t.Fatalf("Bad")
|
|
}
|
|
if !StrListContains(haystack, "root") {
|
|
t.Fatalf("Bad")
|
|
}
|
|
}
|
|
|
|
func TestStrutil_ListSubset(t *testing.T) {
|
|
parent := []string{
|
|
"dev",
|
|
"ops",
|
|
"prod",
|
|
"root",
|
|
}
|
|
child := []string{
|
|
"prod",
|
|
"ops",
|
|
}
|
|
if !StrListSubset(parent, child) {
|
|
t.Fatalf("Bad")
|
|
}
|
|
if !StrListSubset(parent, parent) {
|
|
t.Fatalf("Bad")
|
|
}
|
|
if !StrListSubset(child, child) {
|
|
t.Fatalf("Bad")
|
|
}
|
|
if !StrListSubset(child, nil) {
|
|
t.Fatalf("Bad")
|
|
}
|
|
if StrListSubset(child, parent) {
|
|
t.Fatalf("Bad")
|
|
}
|
|
if StrListSubset(nil, child) {
|
|
t.Fatalf("Bad")
|
|
}
|
|
}
|
|
|
|
func TestStrutil_ParseKeyValues(t *testing.T) {
|
|
actual := make(map[string]string)
|
|
expected := map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
}
|
|
var input string
|
|
var err error
|
|
|
|
input = "key1=value1,key2=value2"
|
|
err = ParseKeyValues(input, actual, ",")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
input = "key1 = value1, key2 = value2"
|
|
err = ParseKeyValues(input, actual, ",")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
input = "key1 = value1, key2 = "
|
|
err = ParseKeyValues(input, actual, ",")
|
|
if err == nil {
|
|
t.Fatalf("expected an error")
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
input = "key1 = value1, = value2 "
|
|
err = ParseKeyValues(input, actual, ",")
|
|
if err == nil {
|
|
t.Fatalf("expected an error")
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
input = "key1"
|
|
err = ParseKeyValues(input, actual, ",")
|
|
if err == nil {
|
|
t.Fatalf("expected an error")
|
|
}
|
|
}
|
|
|
|
func TestStrutil_ParseArbitraryKeyValues(t *testing.T) {
|
|
actual := make(map[string]string)
|
|
expected := map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
}
|
|
var input string
|
|
var err error
|
|
|
|
// Test <key>=<value> as comma separated string
|
|
input = "key1=value1,key2=value2"
|
|
err = ParseArbitraryKeyValues(input, actual, ",")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
// Test <key>=<value> as base64 encoded comma separated string
|
|
input = base64.StdEncoding.EncodeToString([]byte(input))
|
|
err = ParseArbitraryKeyValues(input, actual, ",")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
// Test JSON encoded <key>=<value> tuples
|
|
input = `{"key1":"value1", "key2":"value2"}`
|
|
err = ParseArbitraryKeyValues(input, actual, ",")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
|
|
// Test base64 encoded JSON string of <key>=<value> tuples
|
|
input = base64.StdEncoding.EncodeToString([]byte(input))
|
|
err = ParseArbitraryKeyValues(input, actual, ",")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("bad: expected: %#v\nactual: %#v", expected, actual)
|
|
}
|
|
for k, _ := range actual {
|
|
delete(actual, k)
|
|
}
|
|
}
|
|
|
|
func TestStrutil_ParseArbitraryStringSlice(t *testing.T) {
|
|
input := `CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';GRANT "foo-role" TO "{{name}}";ALTER ROLE "{{name}}" SET search_path = foo;GRANT CONNECT ON DATABASE "postgres" TO "{{name}}";`
|
|
|
|
jsonExpected := []string{
|
|
`DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT * FROM pg_catalog.pg_roles WHERE rolname='foo-role') THEN
|
|
CREATE ROLE "foo-role";
|
|
CREATE SCHEMA IF NOT EXISTS foo AUTHORIZATION "foo-role";
|
|
ALTER ROLE "foo-role" SET search_path = foo;
|
|
GRANT TEMPORARY ON DATABASE "postgres" TO "foo-role";
|
|
GRANT ALL PRIVILEGES ON SCHEMA foo TO "foo-role";
|
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA foo TO "foo-role";
|
|
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA foo TO "foo-role";
|
|
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA foo TO "foo-role";
|
|
END IF;
|
|
END
|
|
$$`,
|
|
`CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'`,
|
|
`GRANT "foo-role" TO "{{name}}"`,
|
|
`ALTER ROLE "{{name}}" SET search_path = foo`,
|
|
`GRANT CONNECT ON DATABASE "postgres" TO "{{name}}"`,
|
|
``,
|
|
}
|
|
|
|
nonJSONExpected := jsonExpected[1:]
|
|
|
|
var actual []string
|
|
var inputB64 string
|
|
var err error
|
|
|
|
// Test non-JSON string
|
|
actual = ParseArbitraryStringSlice(input, ";")
|
|
if !reflect.DeepEqual(nonJSONExpected, actual) {
|
|
t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", nonJSONExpected, actual)
|
|
}
|
|
|
|
// Test base64-encoded non-JSON string
|
|
inputB64 = base64.StdEncoding.EncodeToString([]byte(input))
|
|
actual = ParseArbitraryStringSlice(inputB64, ";")
|
|
if !reflect.DeepEqual(nonJSONExpected, actual) {
|
|
t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", nonJSONExpected, actual)
|
|
}
|
|
|
|
// Test JSON encoded
|
|
inputJSON, err := json.Marshal(jsonExpected)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
actual = ParseArbitraryStringSlice(string(inputJSON), ";")
|
|
if !reflect.DeepEqual(jsonExpected, actual) {
|
|
t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", string(inputJSON), actual)
|
|
}
|
|
|
|
// Test base64 encoded JSON string of <key>=<value> tuples
|
|
inputB64 = base64.StdEncoding.EncodeToString(inputJSON)
|
|
actual = ParseArbitraryStringSlice(inputB64, ";")
|
|
if !reflect.DeepEqual(jsonExpected, actual) {
|
|
t.Fatalf("bad: expected:\n%#v\nactual:\n%#v", jsonExpected, actual)
|
|
}
|
|
}
|
|
|
|
func TestGlobbedStringsMatch(t *testing.T) {
|
|
type tCase struct {
|
|
item string
|
|
val string
|
|
expect bool
|
|
}
|
|
|
|
tCases := []tCase{
|
|
tCase{"", "", true},
|
|
tCase{"*", "*", true},
|
|
tCase{"**", "**", true},
|
|
tCase{"*t", "t", true},
|
|
tCase{"*t", "test", true},
|
|
tCase{"t*", "test", true},
|
|
tCase{"*test", "test", true},
|
|
tCase{"*test", "a test", true},
|
|
tCase{"test", "a test", false},
|
|
tCase{"*test", "tests", false},
|
|
tCase{"test*", "test", true},
|
|
tCase{"test*", "testsss", true},
|
|
tCase{"test**", "testsss", false},
|
|
tCase{"test**", "test*", true},
|
|
tCase{"**test", "*test", true},
|
|
tCase{"TEST", "test", false},
|
|
tCase{"test", "test", true},
|
|
}
|
|
|
|
for _, tc := range tCases {
|
|
actual := GlobbedStringsMatch(tc.item, tc.val)
|
|
|
|
if actual != tc.expect {
|
|
t.Fatalf("Bad testcase %#v, expected %t, got %t", tc, tc.expect, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTrimStrings(t *testing.T) {
|
|
input := []string{"abc", "123", "abcd ", "123 "}
|
|
expected := []string{"abc", "123", "abcd", "123"}
|
|
actual := TrimStrings(input)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("Bad TrimStrings: expected:%#v, got:%#v", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestRemoveEmpty(t *testing.T) {
|
|
input := []string{"abc", "", "abc", ""}
|
|
expected := []string{"abc", "abc"}
|
|
actual := RemoveEmpty(input)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("Bad TrimStrings: expected:%#v, got:%#v", expected, actual)
|
|
}
|
|
|
|
input = []string{""}
|
|
expected = []string{}
|
|
actual = RemoveEmpty(input)
|
|
if !reflect.DeepEqual(expected, actual) {
|
|
t.Fatalf("Bad TrimStrings: expected:%#v, got:%#v", expected, actual)
|
|
}
|
|
}
|
|
|
|
func TestStrutil_AppendIfMissing(t *testing.T) {
|
|
keys := []string{}
|
|
|
|
keys = AppendIfMissing(keys, "foo")
|
|
|
|
if len(keys) != 1 {
|
|
t.Fatalf("expected slice to be length of 1: %v", keys)
|
|
}
|
|
if keys[0] != "foo" {
|
|
t.Fatalf("expected slice to contain key 'foo': %v", keys)
|
|
}
|
|
|
|
keys = AppendIfMissing(keys, "bar")
|
|
|
|
if len(keys) != 2 {
|
|
t.Fatalf("expected slice to be length of 2: %v", keys)
|
|
}
|
|
if keys[0] != "foo" {
|
|
t.Fatalf("expected slice to contain key 'foo': %v", keys)
|
|
}
|
|
if keys[1] != "bar" {
|
|
t.Fatalf("expected slice to contain key 'bar': %v", keys)
|
|
}
|
|
|
|
keys = AppendIfMissing(keys, "foo")
|
|
|
|
if len(keys) != 2 {
|
|
t.Fatalf("expected slice to still be length of 2: %v", keys)
|
|
}
|
|
if keys[0] != "foo" {
|
|
t.Fatalf("expected slice to still contain key 'foo': %v", keys)
|
|
}
|
|
if keys[1] != "bar" {
|
|
t.Fatalf("expected slice to still contain key 'bar': %v", keys)
|
|
}
|
|
}
|
|
|
|
func TestStrUtil_RemoveDuplicates(t *testing.T) {
|
|
type tCase struct {
|
|
input []string
|
|
expect []string
|
|
lowercase bool
|
|
}
|
|
|
|
tCases := []tCase{
|
|
tCase{[]string{}, []string{}, false},
|
|
tCase{[]string{}, []string{}, true},
|
|
tCase{[]string{"a", "b", "a"}, []string{"a", "b"}, false},
|
|
tCase{[]string{"A", "b", "a"}, []string{"A", "a", "b"}, false},
|
|
tCase{[]string{"A", "b", "a"}, []string{"a", "b"}, true},
|
|
}
|
|
|
|
for _, tc := range tCases {
|
|
actual := RemoveDuplicates(tc.input, tc.lowercase)
|
|
|
|
if !reflect.DeepEqual(actual, tc.expect) {
|
|
t.Fatalf("Bad testcase %#v, expected %v, got %v", tc, tc.expect, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStrUtil_ParseStringSlice(t *testing.T) {
|
|
type tCase struct {
|
|
input string
|
|
sep string
|
|
expect []string
|
|
}
|
|
|
|
tCases := []tCase{
|
|
tCase{"", "", []string{}},
|
|
tCase{" ", ",", []string{}},
|
|
tCase{", ", ",", []string{"", ""}},
|
|
tCase{"a", ",", []string{"a"}},
|
|
tCase{" a, b, c ", ",", []string{"a", "b", "c"}},
|
|
tCase{" a; b; c ", ";", []string{"a", "b", "c"}},
|
|
tCase{" a :: b :: c ", "::", []string{"a", "b", "c"}},
|
|
}
|
|
|
|
for _, tc := range tCases {
|
|
actual := ParseStringSlice(tc.input, tc.sep)
|
|
|
|
if !reflect.DeepEqual(actual, tc.expect) {
|
|
t.Fatalf("Bad testcase %#v, expected %v, got %v", tc, tc.expect, actual)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStrUtil_MergeSlices(t *testing.T) {
|
|
res := MergeSlices([]string{"a", "c", "d"}, []string{}, []string{"c", "f", "a"}, nil, []string{"foo"})
|
|
|
|
expect := []string{"a", "c", "d", "f", "foo"}
|
|
|
|
if !reflect.DeepEqual(res, expect) {
|
|
t.Fatalf("expected %v, got %v", expect, res)
|
|
}
|
|
}
|
|
|
|
func TestDifference(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
SetA []string
|
|
SetB []string
|
|
Lowercase bool
|
|
ExpectedResult []string
|
|
}{
|
|
{
|
|
Name: "case_sensitive",
|
|
SetA: []string{"a", "b", "c"},
|
|
SetB: []string{"b", "c"},
|
|
Lowercase: false,
|
|
ExpectedResult: []string{"a"},
|
|
},
|
|
{
|
|
Name: "case_insensitive",
|
|
SetA: []string{"a", "B", "c"},
|
|
SetB: []string{"b", "C"},
|
|
Lowercase: true,
|
|
ExpectedResult: []string{"a"},
|
|
},
|
|
{
|
|
Name: "no_match",
|
|
SetA: []string{"a", "b", "c"},
|
|
SetB: []string{"d"},
|
|
Lowercase: false,
|
|
ExpectedResult: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
Name: "empty_set_a",
|
|
SetA: []string{},
|
|
SetB: []string{"d", "e"},
|
|
Lowercase: false,
|
|
ExpectedResult: []string{},
|
|
},
|
|
{
|
|
Name: "empty_set_b",
|
|
SetA: []string{"a", "b"},
|
|
SetB: []string{},
|
|
Lowercase: false,
|
|
ExpectedResult: []string{"a", "b"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
actualResult := Difference(tc.SetA, tc.SetB, tc.Lowercase)
|
|
|
|
if !reflect.DeepEqual(actualResult, tc.ExpectedResult) {
|
|
t.Fatalf("expected %v, got %v", tc.ExpectedResult, actualResult)
|
|
}
|
|
})
|
|
}
|
|
}
|