authelia/internal/configuration/decode_hooks_test.go

2219 lines
56 KiB
Go

package configuration_test
import (
"bytes"
"crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"math"
"net/mail"
"net/url"
"os"
"reflect"
"regexp"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/authelia/authelia/v4/internal/configuration"
"github.com/authelia/authelia/v4/internal/configuration/schema"
)
func TestStringToMailAddressHookFunc(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeMailAddress",
have: "james@example.com",
want: mail.Address{Name: "", Address: "james@example.com"},
decode: true,
},
{
desc: "ShouldDecodeMailAddressWithName",
have: "James <james@example.com>",
want: mail.Address{Name: "James", Address: "james@example.com"},
decode: true,
},
{
desc: "ShouldDecodeMailAddressWithEmptyString",
have: "",
want: mail.Address{},
decode: true,
},
{
desc: "ShouldNotDecodeInvalidMailAddress",
have: "fred",
want: mail.Address{},
err: "could not decode 'fred' to a mail.Address (RFC5322): mail: missing '@' or angle-addr",
decode: true,
},
}
hook := configuration.StringToMailAddressHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToMailAddressHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeMailAddress",
have: "james@example.com",
want: &mail.Address{Name: "", Address: "james@example.com"},
decode: true,
},
{
desc: "ShouldDecodeMailAddressWithName",
have: "James <james@example.com>",
want: &mail.Address{Name: "James", Address: "james@example.com"},
decode: true,
},
{
desc: "ShouldDecodeMailAddressWithEmptyString",
have: "",
want: (*mail.Address)(nil),
decode: true,
},
{
desc: "ShouldNotDecodeInvalidMailAddress",
have: "fred",
want: &mail.Address{},
err: "could not decode 'fred' to a *mail.Address (RFC5322): mail: missing '@' or angle-addr",
decode: true,
},
{
desc: "ShouldNotDecodeToInt",
have: "fred",
want: testInt32Ptr(4),
decode: false,
},
}
hook := configuration.StringToMailAddressHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToURLHookFunc(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeURL",
have: "https://www.example.com:9090/abc?test=true",
want: url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true"},
decode: true,
},
{
desc: "ShouldDecodeURLEmptyString",
have: "",
want: url.URL{},
decode: true,
},
{
desc: "ShouldNotDecodeToString",
have: "abc",
want: "",
decode: false,
},
{
desc: "ShouldDecodeURLWithUserAndPassword",
have: "https://john:abc123@www.example.com:9090/abc?test=true",
want: url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true", User: url.UserPassword("john", "abc123")},
decode: true,
},
{
desc: "ShouldNotDecodeInt",
have: 5,
want: url.URL{},
decode: false,
},
{
desc: "ShouldNotDecodeBool",
have: true,
want: url.URL{},
decode: false,
},
{
desc: "ShouldNotDecodeBadURL",
have: "*(!&@#(!*^$%",
want: url.URL{},
err: "could not decode '*(!&@#(!*^$%' to a url.URL: parse \"*(!&@#(!*^$%\": invalid URL escape \"%\"",
decode: true,
},
}
hook := configuration.StringToURLHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToURLHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeURL",
have: "https://www.example.com:9090/abc?test=true",
want: &url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true"},
decode: true,
},
{
desc: "ShouldDecodeURLEmptyString",
have: "",
want: (*url.URL)(nil),
decode: true,
},
{
desc: "ShouldDecodeURLWithUserAndPassword",
have: "https://john:abc123@www.example.com:9090/abc?test=true",
want: &url.URL{Scheme: "https", Host: "www.example.com:9090", Path: "/abc", RawQuery: "test=true", User: url.UserPassword("john", "abc123")},
decode: true,
},
{
desc: "ShouldNotDecodeInt",
have: 5,
want: &url.URL{},
decode: false,
},
{
desc: "ShouldNotDecodeBool",
have: true,
want: &url.URL{},
decode: false,
},
{
desc: "ShouldNotDecodeBadURL",
have: "*(!&@#(!*^$%",
want: &url.URL{},
err: "could not decode '*(!&@#(!*^$%' to a *url.URL: parse \"*(!&@#(!*^$%\": invalid URL escape \"%\"",
decode: true,
},
{
desc: "ShouldNotDecodeToInt",
have: "fred",
want: testInt32Ptr(4),
decode: false,
},
}
hook := configuration.StringToURLHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestToTimeDurationHookFunc(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeFourtyFiveSeconds",
have: "45s",
want: time.Second * 45,
decode: true,
},
{
desc: "ShouldDecodeOneMinute",
have: "1m",
want: time.Minute,
decode: true,
},
{
desc: "ShouldDecodeTwoHours",
have: "2h",
want: time.Hour * 2,
decode: true,
},
{
desc: "ShouldDecodeThreeDays",
have: "3d",
want: time.Hour * 24 * 3,
decode: true,
},
{
desc: "ShouldDecodeFourWeeks",
have: "4w",
want: time.Hour * 24 * 7 * 4,
decode: true,
},
{
desc: "ShouldDecodeFiveMonths",
have: "5M",
want: time.Hour * 24 * 30 * 5,
decode: true,
},
{
desc: "ShouldDecodeSixYears",
have: "6y",
want: time.Hour * 24 * 365 * 6,
decode: true,
},
{
desc: "ShouldNotDecodeInvalidString",
have: "abc",
want: time.Duration(0),
err: "could not decode 'abc' to a time.Duration: could not parse 'abc' as a duration",
decode: true,
},
{
desc: "ShouldDecodeIntToSeconds",
have: 60,
want: time.Second * 60,
decode: true,
},
{
desc: "ShouldDecodeInt8ToSeconds",
have: int8(90),
want: time.Second * 90,
decode: true,
},
{
desc: "ShouldDecodeInt16ToSeconds",
have: int16(90),
want: time.Second * 90,
decode: true,
},
{
desc: "ShouldDecodeInt32ToSeconds",
have: int32(90),
want: time.Second * 90,
decode: true,
},
{
desc: "ShouldDecodeFloat64ToSeconds",
have: float64(90),
want: time.Second * 90,
decode: true,
},
{
desc: "ShouldDecodeFloat64ToSeconds",
have: math.MaxFloat64,
want: time.Duration(math.MaxInt64),
decode: true,
},
{
desc: "ShouldDecodeInt64ToSeconds",
have: int64(120),
want: time.Second * 120,
decode: true,
},
{
desc: "ShouldDecodeTimeDuration",
have: time.Second * 30,
want: time.Second * 30,
decode: true,
},
{
desc: "ShouldNotDecodeToString",
have: int64(30),
want: "",
decode: false,
},
{
desc: "ShouldDecodeFromIntZero",
have: 0,
want: time.Duration(0),
decode: true,
},
{
desc: "ShouldSkipParsingBoolean",
have: true,
want: time.Duration(0),
decode: false,
},
{
desc: "ShouldNotDecodeFromBool",
have: true,
want: true,
},
}
hook := configuration.ToTimeDurationHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestToTimeDurationHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeFourtyFiveSeconds",
have: "45s",
want: testTimeDurationPtr(time.Second * 45),
decode: true,
},
{
desc: "ShouldDecodeOneMinute",
have: "1m",
want: testTimeDurationPtr(time.Minute),
decode: true,
},
{
desc: "ShouldDecodeTwoHours",
have: "2h",
want: testTimeDurationPtr(time.Hour * 2),
decode: true,
},
{
desc: "ShouldDecodeThreeDays",
have: "3d",
want: testTimeDurationPtr(time.Hour * 24 * 3),
decode: true,
},
{
desc: "ShouldDecodeFourWeeks",
have: "4w",
want: testTimeDurationPtr(time.Hour * 24 * 7 * 4),
decode: true,
},
{
desc: "ShouldDecodeFiveMonths",
have: "5M",
want: testTimeDurationPtr(time.Hour * 24 * 30 * 5),
decode: true,
},
{
desc: "ShouldDecodeSixYears",
have: "6y",
want: testTimeDurationPtr(time.Hour * 24 * 365 * 6),
decode: true,
},
{
desc: "ShouldNotDecodeInvalidString",
have: "abc",
want: testTimeDurationPtr(time.Duration(0)),
err: "could not decode 'abc' to a *time.Duration: could not parse 'abc' as a duration",
decode: true,
},
{
desc: "ShouldDecodeIntToSeconds",
have: 60,
want: testTimeDurationPtr(time.Second * 60),
decode: true,
},
{
desc: "ShouldDecodeInt32ToSeconds",
have: int32(90),
want: testTimeDurationPtr(time.Second * 90),
decode: true,
},
{
desc: "ShouldDecodeInt64ToSeconds",
have: int64(120),
want: testTimeDurationPtr(time.Second * 120),
decode: true,
},
{
desc: "ShouldDecodeTimeDuration",
have: time.Second * 30,
want: testTimeDurationPtr(time.Second * 30),
decode: true,
},
{
desc: "ShouldNotDecodeToString",
have: int64(30),
want: &testString,
decode: false,
},
{
desc: "ShouldDecodeFromIntZero",
have: 0,
want: testTimeDurationPtr(time.Duration(0)),
decode: true,
},
{
desc: "ShouldNotDecodeFromBool",
have: true,
want: &testTrue,
decode: false,
},
}
hook := configuration.ToTimeDurationHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestToRefreshIntervalDurationHookFunc(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeFourtyFiveSeconds",
have: "45s",
want: schema.NewRefreshIntervalDuration(time.Second * 45),
decode: true,
},
{
desc: "ShouldDecodeOneMinute",
have: "1m",
want: schema.NewRefreshIntervalDuration(time.Minute),
decode: true,
},
{
desc: "ShouldDecodeTwoHours",
have: "2h",
want: schema.NewRefreshIntervalDuration(time.Hour * 2),
decode: true,
},
{
desc: "ShouldDecodeThreeDays",
have: "3d",
want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 3),
decode: true,
},
{
desc: "ShouldDecodeFourWeeks",
have: "4w",
want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 7 * 4),
decode: true,
},
{
desc: "ShouldDecodeFiveMonths",
have: "5M",
want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 30 * 5),
decode: true,
},
{
desc: "ShouldDecodeSixYears",
have: "6y",
want: schema.NewRefreshIntervalDuration(time.Hour * 24 * 365 * 6),
decode: true,
},
{
desc: "ShouldNotDecodeInvalidString",
have: "abc",
want: schema.RefreshIntervalDuration{},
err: "could not decode 'abc' to a schema.RefreshIntervalDuration: could not parse 'abc' as a duration",
decode: true,
},
{
desc: "ShouldDecodeIntToSeconds",
have: 60,
want: schema.NewRefreshIntervalDuration(time.Second * 60),
decode: true,
},
{
desc: "ShouldDecodeInt8ToSeconds",
have: int8(90),
want: schema.NewRefreshIntervalDuration(time.Second * 90),
decode: true,
},
{
desc: "ShouldDecodeInt16ToSeconds",
have: int16(90),
want: schema.NewRefreshIntervalDuration(time.Second * 90),
decode: true,
},
{
desc: "ShouldDecodeInt32ToSeconds",
have: int32(90),
want: schema.NewRefreshIntervalDuration(time.Second * 90),
decode: true,
},
{
desc: "ShouldDecodeFloat64ToSeconds",
have: float64(90),
want: schema.NewRefreshIntervalDuration(time.Second * 90),
decode: true,
},
{
desc: "ShouldDecodeFloat64ToSeconds",
have: math.MaxFloat64,
want: schema.NewRefreshIntervalDuration(time.Duration(math.MaxInt64)),
decode: true,
},
{
desc: "ShouldDecodeInt64ToSeconds",
have: int64(120),
want: schema.NewRefreshIntervalDuration(time.Second * 120),
decode: true,
},
{
desc: "ShouldDecodeTimeDuration",
have: time.Second * 30,
want: schema.NewRefreshIntervalDuration(time.Second * 30),
decode: true,
},
{
desc: "ShouldNotDecodeToString",
have: int64(30),
want: "",
decode: false,
},
{
desc: "ShouldDecodeFromIntZero",
have: 0,
want: schema.NewRefreshIntervalDuration(time.Duration(0)),
decode: true,
},
{
desc: "ShouldSkipParsingBoolean",
have: true,
want: schema.RefreshIntervalDuration{},
decode: false,
},
{
desc: "ShouldNotDecodeFromBool",
have: true,
want: true,
},
}
hook := configuration.ToRefreshIntervalDurationHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestTestToRefreshIntervalDurationHookFuncPointer(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeFourtyFiveSeconds",
have: "45s",
want: testRefreshIntervalDurationPtr(time.Second * 45),
decode: true,
},
{
desc: "ShouldDecodeOneMinute",
have: "1m",
want: testRefreshIntervalDurationPtr(time.Minute),
decode: true,
},
{
desc: "ShouldDecodeTwoHours",
have: "2h",
want: testRefreshIntervalDurationPtr(time.Hour * 2),
decode: true,
},
{
desc: "ShouldDecodeThreeDays",
have: "3d",
want: testRefreshIntervalDurationPtr(time.Hour * 24 * 3),
decode: true,
},
{
desc: "ShouldDecodeFourWeeks",
have: "4w",
want: testRefreshIntervalDurationPtr(time.Hour * 24 * 7 * 4),
decode: true,
},
{
desc: "ShouldDecodeFiveMonths",
have: "5M",
want: testRefreshIntervalDurationPtr(time.Hour * 24 * 30 * 5),
decode: true,
},
{
desc: "ShouldDecodeSixYears",
have: "6y",
want: testRefreshIntervalDurationPtr(time.Hour * 24 * 365 * 6),
decode: true,
},
{
desc: "ShouldNotDecodeInvalidString",
have: "abc",
want: testRefreshIntervalDurationPtr(time.Duration(0)),
err: "could not decode 'abc' to a *schema.RefreshIntervalDuration: could not parse 'abc' as a duration",
decode: true,
},
{
desc: "ShouldDecodeIntToSeconds",
have: 60,
want: testRefreshIntervalDurationPtr(time.Second * 60),
decode: true,
},
{
desc: "ShouldDecodeInt32ToSeconds",
have: int32(90),
want: testRefreshIntervalDurationPtr(time.Second * 90),
decode: true,
},
{
desc: "ShouldDecodeInt64ToSeconds",
have: int64(120),
want: testRefreshIntervalDurationPtr(time.Second * 120),
decode: true,
},
{
desc: "ShouldDecodeTimeDuration",
have: time.Second * 30,
want: testRefreshIntervalDurationPtr(time.Second * 30),
decode: true,
},
{
desc: "ShouldNotDecodeToString",
have: int64(30),
want: &testString,
decode: false,
},
{
desc: "ShouldDecodeFromIntZero",
have: 0,
want: testRefreshIntervalDurationPtr(time.Duration(0)),
decode: true,
},
{
desc: "ShouldNotDecodeFromBool",
have: true,
want: &testTrue,
decode: false,
},
}
hook := configuration.ToRefreshIntervalDurationHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToRegexpFunc(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
wantGrps []string
}{
{
desc: "ShouldNotDecodeRegexpWithOpenParenthesis",
have: "hello(test one two",
want: regexp.Regexp{},
err: "could not decode 'hello(test one two' to a regexp.Regexp: error parsing regexp: missing closing ): `hello(test one two`",
decode: true,
},
{
desc: "ShouldDecodeValidRegex",
have: "^(api|admin)$",
want: *regexp.MustCompile(`^(api|admin)$`),
decode: true,
},
{
desc: "ShouldDecodeValidRegexWithGroupNames",
have: "^(?P<area>api|admin)(one|two)$",
want: *regexp.MustCompile(`^(?P<area>api|admin)(one|two)$`),
decode: true,
wantGrps: []string{"area"},
},
{
desc: "ShouldNotDecodeFromInt32",
have: int32(20),
want: regexp.Regexp{},
decode: false,
},
{
desc: "ShouldNotDecodeFromBool",
have: false,
want: regexp.Regexp{},
decode: false,
},
{
desc: "ShouldNotDecodeToBool",
have: "^(?P<area>api|admin)(one|two)$",
want: testTrue,
decode: false,
},
{
desc: "ShouldNotDecodeToInt32",
have: "^(?P<area>api|admin)(one|two)$",
want: testInt32Ptr(0),
decode: false,
},
{
desc: "ShouldNotDecodeToMailAddress",
have: "^(?P<area>api|admin)(one|two)$",
want: mail.Address{},
decode: false,
},
{
desc: "ShouldErrOnDecodeEmptyString",
have: "",
want: regexp.Regexp{},
err: "could not decode an empty value to a regexp.Regexp: must have a non-empty value",
decode: true,
},
}
hook := configuration.StringToRegexpHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
var names []string
pattern := result.(regexp.Regexp)
for _, name := range pattern.SubexpNames() {
if name != "" {
names = append(names, name)
}
}
if len(tc.wantGrps) != 0 {
t.Run("MustHaveAllExpectedSubexpGroupNames", func(t *testing.T) {
for _, name := range tc.wantGrps {
assert.Contains(t, names, name)
}
})
t.Run("MustNotHaveUnexpectedSubexpGroupNames", func(t *testing.T) {
for _, name := range names {
assert.Contains(t, tc.wantGrps, name)
}
})
} else {
t.Run("MustHaveNoSubexpGroupNames", func(t *testing.T) {
assert.Len(t, names, 0)
})
}
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToRegexpFuncPointers(t *testing.T) {
testCases := []struct {
desc string
have any
want any
err string
decode bool
wantGrps []string
}{
{
desc: "ShouldNotDecodeRegexpWithOpenParenthesis",
have: "hello(test one two",
want: &regexp.Regexp{},
err: "could not decode 'hello(test one two' to a *regexp.Regexp: error parsing regexp: missing closing ): `hello(test one two`",
decode: true,
},
{
desc: "ShouldDecodeValidRegex",
have: "^(api|admin)$",
want: regexp.MustCompile(`^(api|admin)$`),
decode: true,
},
{
desc: "ShouldDecodeValidRegexWithGroupNames",
have: "^(?P<area>api|admin)(one|two)$",
want: regexp.MustCompile(`^(?P<area>api|admin)(one|two)$`),
decode: true,
wantGrps: []string{"area"},
},
{
desc: "ShouldNotDecodeFromInt32",
have: int32(20),
want: &regexp.Regexp{},
decode: false,
},
{
desc: "ShouldNotDecodeFromBool",
have: false,
want: &regexp.Regexp{},
decode: false,
},
{
desc: "ShouldNotDecodeToBool",
have: "^(?P<area>api|admin)(one|two)$",
want: &testTrue,
decode: false,
},
{
desc: "ShouldNotDecodeToInt32",
have: "^(?P<area>api|admin)(one|two)$",
want: &testZero,
decode: false,
},
{
desc: "ShouldNotDecodeToMailAddress",
have: "^(?P<area>api|admin)(one|two)$",
want: &mail.Address{},
decode: false,
},
{
desc: "ShouldDecodeEmptyStringToNil",
have: "",
want: (*regexp.Regexp)(nil),
decode: true,
},
}
hook := configuration.StringToRegexpHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
pattern := result.(*regexp.Regexp)
if tc.want == (*regexp.Regexp)(nil) {
assert.Nil(t, pattern)
} else {
var names []string
for _, name := range pattern.SubexpNames() {
if name != "" {
names = append(names, name)
}
}
if len(tc.wantGrps) != 0 {
t.Run("MustHaveAllExpectedSubexpGroupNames", func(t *testing.T) {
for _, name := range tc.wantGrps {
assert.Contains(t, names, name)
}
})
t.Run("MustNotHaveUnexpectedSubexpGroupNames", func(t *testing.T) {
for _, name := range names {
assert.Contains(t, tc.wantGrps, name)
}
})
} else {
t.Run("MustHaveNoSubexpGroupNames", func(t *testing.T) {
assert.Len(t, names, 0)
})
}
}
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToAddressHookFunc(t *testing.T) {
testCases := []struct {
name string
have any
expected any
err string
decode bool
}{
{
name: "ShouldDecodeNonPtr",
have: "tcp://0.0.0.0:2020",
expected: MustParseAddress("tcp://0.0.0.0:2020"),
decode: true,
},
{
name: "ShouldDecodePtr",
have: "tcp://0.0.0.0:2020",
expected: MustParseAddressPtr("tcp://0.0.0.0:2020"),
decode: true,
},
{
name: "ShouldNotDecodeIntegerToCorrectType",
have: 1,
expected: schema.Address{},
decode: false,
},
{
name: "ShouldNotDecodeIntegerToCorrectTypePtr",
have: 1,
expected: &schema.Address{},
decode: false,
},
{
name: "ShouldNotDecodeIntegerPtrToCorrectType",
have: testInt32Ptr(1),
expected: schema.Address{},
decode: false,
},
{
name: "ShouldNotDecodeIntegerPtrToCorrectTypePtr",
have: testInt32Ptr(1),
expected: &schema.Address{},
decode: false,
},
{
name: "ShouldNotDecodeToString",
have: "tcp://0.0.0.0:2020",
expected: "",
decode: false,
},
{
name: "ShouldNotDecodeToIntPtr",
have: "tcp://0.0.0.0:2020",
expected: testInt32Ptr(1),
decode: false,
},
{
name: "ShouldNotDecodeToIntPtr",
have: "tcp://0.0.0.0:2020",
expected: testInt32Ptr(1),
decode: false,
},
{
name: "ShouldFailDecode",
have: "tcp://&!@^#*&!@#&*@!:2020",
expected: schema.Address{},
err: "could not decode 'tcp://&!@^#*&!@#&*@!:2020' to a schema.Address: could not parse string 'tcp://&!@^#*&!@#&*@!:2020' as address: expected format is [<scheme>://]<hostname>[:<port>]: parse \"tcp://&!@^\": invalid character \"^\" in host name",
decode: false,
},
{
name: "ShouldDecodeTCP",
have: "tcp://127.0.0.1",
expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeTCPPtr",
have: "tcp://127.0.0.1",
expected: &schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeUDP",
have: "udp://127.0.0.1",
expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeUDPPtr",
have: "udp://127.0.0.1",
expected: &schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeLDAP",
have: "ldap://127.0.0.1",
expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeLDAPPtr",
have: "ldap://127.0.0.1",
expected: &schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeSMTP",
have: "smtp://127.0.0.1",
expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldDecodeSMTPPtr",
have: "smtp://127.0.0.1",
expected: &schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
err: "",
decode: true,
},
{
name: "ShouldFailDecodeTCP",
have: "@@@@@@@",
expected: schema.AddressTCP{Address: MustParseAddress("tcp://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressTCP: error validating the address: the url 'tcp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
{
name: "ShouldFailDecodeUDP",
have: "@@@@@@@",
expected: schema.AddressUDP{Address: MustParseAddress("udp://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressUDP: error validating the address: the url 'udp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
{
name: "ShouldFailDecodeLDAP",
have: "@@@@@@@",
expected: schema.AddressLDAP{Address: MustParseAddress("ldap://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressLDAP: error validating the address: the url 'ldaps://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
{
name: "ShouldFailDecodeSMTP",
have: "@@@@@@@",
expected: schema.AddressSMTP{Address: MustParseAddress("smtp://127.0.0.1")},
err: "could not decode '@@@@@@@' to a schema.AddressSMTP: error validating the address: the url 'smtp://%40%40%40%40%40%40@' appears to have user info but this is not valid for addresses",
decode: false,
},
}
hook := configuration.StringToAddressHookFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
if !tc.decode {
assert.Nil(t, actual)
}
} else {
assert.NoError(t, err)
if tc.decode {
assert.Equal(t, tc.expected, actual)
} else {
assert.Equal(t, tc.have, actual)
}
}
})
}
}
func TestStringToPrivateKeyHookFunc(t *testing.T) {
var (
nilRSA *rsa.PrivateKey
nilECDSA *ecdsa.PrivateKey
nilCert *x509.Certificate
)
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeRSA1024PrivateKey",
have: x509PrivateKeyRSA1024,
want: MustParsePKCS8RSAPrivateKey(x509PrivateKeyRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA2048PrivateKey",
have: x509PrivateKeyRSA2048,
want: MustParsePKCS8RSAPrivateKey(x509PrivateKeyRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA4096PrivateKey",
have: x509PrivateKeyRSA4096,
want: MustParsePKCS8RSAPrivateKey(x509PrivateKeyRSA4096),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224PrivateKey",
have: x509PrivateKeyECDSAP224,
want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256PrivateKey",
have: x509PrivateKeyECDSAP256,
want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384PrivateKey",
have: x509PrivateKeyECDSAP384,
want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521PrivateKey",
have: x509PrivateKeyECDSAP521,
want: MustParsePKCS8ECDSAPrivateKey(x509PrivateKeyECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeEd25519PrivateKey",
have: x509PrivateKeyEd25519,
want: MustParsePKCS8Ed25519PrivateKey(x509PrivateKeyEd25519),
decode: true,
},
{
desc: "ShouldNotDecodeToECDSAPrivateKey",
have: x509PrivateKeyRSA2048,
want: &ecdsa.PrivateKey{},
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey",
},
{
desc: "ShouldNotDecodeEmptyRSAKey",
have: "",
want: nilRSA,
decode: true,
},
{
desc: "ShouldNotDecodeEmptyECDSAKey",
have: "",
want: nilECDSA,
decode: true,
},
{
desc: "ShouldNotDecodeECDSAKeyToRSAKey",
have: x509PrivateKeyECDSAP521,
want: nilRSA,
decode: true,
err: "could not decode to a *rsa.PrivateKey: the data is for a *ecdsa.PrivateKey not a *rsa.PrivateKey",
},
{
desc: "ShouldNotDecodeRSAKeyToECDSAKey",
have: x509PrivateKeyRSA2048,
want: nilECDSA,
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: the data is for a *rsa.PrivateKey not a *ecdsa.PrivateKey",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKey",
have: x509PrivateKeyRSABad,
want: nilRSA,
decode: true,
err: "could not decode to a *rsa.PrivateKey: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKeyTrailingData",
have: strings.ReplaceAll(x509PrivateKeyRSA2048, "END PRIVATE KEY-----", "END PRIVATE KEY---------"),
want: nilRSA,
decode: true,
err: "could not decode to a *rsa.PrivateKey: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKeyTrailingDataDoubled",
have: x509PrivateKeyRSA2048 + x509PrivateKeyRSA2048,
want: nilRSA,
decode: true,
err: "could not decode to a *rsa.PrivateKey: error occurred attempting to parse PEM block: the block either had trailing data or was otherwise malformed",
},
{
desc: "ShouldNotDecodeBadECDSAPrivateKey",
have: x509PrivateKeyECBad,
want: nilECDSA,
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed",
},
{
desc: "ShouldNotDecodeCertificateToRSAPrivateKey",
have: x509CertificateRSA2048,
want: nilRSA,
decode: true,
err: "could not decode to a *rsa.PrivateKey: the data is for a *x509.Certificate not a *rsa.PrivateKey",
},
{
desc: "ShouldNotDecodeCertificateToECDSAPrivateKey",
have: x509CertificateRSA2048,
want: nilECDSA,
decode: true,
err: "could not decode to a *ecdsa.PrivateKey: the data is for a *x509.Certificate not a *ecdsa.PrivateKey",
},
{
desc: "ShouldNotDecodeRSAKeyToCertificate",
have: x509PrivateKeyRSA2048,
want: nilCert,
decode: false,
},
{
desc: "ShouldNotDecodeECDSAKeyToCertificate",
have: x509PrivateKeyECDSAP521,
want: nilCert,
decode: false,
},
}
hook := configuration.StringToPrivateKeyHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToX509CertificateHookFunc(t *testing.T) {
var nilkey *x509.Certificate
testCases := []struct {
desc string
have any
want any
err string
decode bool
}{
{
desc: "ShouldDecodeRSACertificate",
have: x509CertificateRSA2048,
want: MustParseX509Certificate(x509CertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeECDSACertificate",
have: x509CertificateECDSAP521,
want: MustParseX509Certificate(x509CertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeEd25519Certificate",
have: x509CertificateEd25519,
want: MustParseX509Certificate(x509CertificateEd25519),
decode: true,
},
{
desc: "ShouldDecodeRSACACertificate",
have: x509CACertificateRSA2048,
want: MustParseX509Certificate(x509CACertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeECDSACACertificate",
have: x509CACertificateECDSAP521,
want: MustParseX509Certificate(x509CACertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeEd25519CACertificate",
have: x509CACertificateEd25519,
want: MustParseX509Certificate(x509CACertificateEd25519),
decode: true,
},
{
desc: "ShouldDecodeEmptyCertificateToNil",
have: "",
want: nilkey,
decode: true,
},
{
desc: "ShouldNotDecodeECDSAKeyToCertificate",
have: x509PrivateKeyECDSAP224,
want: nilkey,
decode: true,
err: "could not decode to a *x509.Certificate: the data is for a *ecdsa.PrivateKey not a *x509.Certificate",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate",
have: x509PrivateKeyRSABad,
want: nilkey,
decode: true,
err: "could not decode to a *x509.Certificate: error occurred attempting to parse PEM block: either no PEM block was supplied or it was malformed",
},
}
hook := configuration.StringToX509CertificateHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
result, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.want), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, result)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.want, result)
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, result)
}
})
}
}
func TestStringToPasswordDigestHookFunc(t *testing.T) {
var nilvalue *schema.PasswordDigest
testCases := []struct {
name string
have any
expected any
err string
decode bool
}{
{
"ShouldParse",
"$plaintext$example",
MustParsePasswordDigest("$plaintext$example"),
"",
true,
},
{
"ShouldParsePtr",
"$plaintext$example",
MustParsePasswordDigestPtr("$plaintext$example"),
"",
true,
},
{
"ShouldNotParseUnknown",
"$abc$example",
schema.PasswordDigest{},
"could not decode '$abc$example' to a schema.PasswordDigest: provided encoded hash has an invalid identifier: the identifier 'abc' is unknown to the decoder",
false,
},
{
"ShouldNotParseWrongType",
"$abc$example",
schema.TLSVersion{},
"",
false,
},
{
"ShouldNotParseWrongTypePtr",
"$abc$example",
&schema.TLSVersion{},
"",
false,
},
{
"ShouldNotParseEmptyString",
"",
schema.PasswordDigest{},
"could not decode an empty value to a schema.PasswordDigest: must have a non-empty value",
false,
},
{
"ShouldParseEmptyStringPtr",
"",
nilvalue,
"",
true,
},
}
hook := configuration.StringToPasswordDigestHookFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
if !tc.decode {
assert.Nil(t, actual)
}
} else {
assert.NoError(t, err)
if tc.decode {
assert.Equal(t, tc.expected, actual)
} else {
assert.Equal(t, tc.have, actual)
}
}
})
}
}
func TestStringToTLSVersionHookFunc(t *testing.T) {
testCases := []struct {
name string
have any
expected any
err string
decode bool
}{
{
"ShouldParseTLS1.3",
"TLS1.3",
schema.TLSVersion{Value: tls.VersionTLS13},
"",
true,
},
{
"ShouldParseTLS1.3PTR",
"TLS1.3",
&schema.TLSVersion{Value: tls.VersionTLS13},
"",
true,
},
{
"ShouldParseTLS1.2",
"TLS1.2",
schema.TLSVersion{Value: tls.VersionTLS12},
"",
true,
},
{
"ShouldNotParseInt",
1,
&schema.TLSVersion{},
"",
false,
},
{
"ShouldNotParseNonVersion",
"1",
&schema.TLSVersion{},
"could not decode '1' to a *schema.TLSVersion: supplied tls version isn't supported",
false,
},
}
hook := configuration.StringToTLSVersionHookFunc()
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
if tc.err != "" {
assert.EqualError(t, err, tc.err)
if !tc.decode {
assert.Nil(t, actual)
}
} else {
assert.NoError(t, err)
if tc.decode {
assert.Equal(t, tc.expected, actual)
} else {
assert.Equal(t, tc.have, actual)
}
}
})
}
}
func TestStringToX509CertificateChainHookFunc(t *testing.T) {
var nilkey *schema.X509CertificateChain
testCases := []struct {
desc string
have any
expected any
err, verr string
decode bool
}{
{
desc: "ShouldDecodeRSA1024Certificate",
have: x509CertificateRSA1024,
expected: MustParseX509CertificateChain(x509CertificateRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA1024CertificateNoPtr",
have: x509CertificateRSA1024,
expected: *MustParseX509CertificateChain(x509CertificateRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA1024CACertificate",
have: x509CACertificateRSA1024,
expected: MustParseX509CertificateChain(x509CACertificateRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA1024CACertificateNoPtr",
have: x509CACertificateRSA1024,
expected: *MustParseX509CertificateChain(x509CACertificateRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA1024CertificateChain",
have: BuildChain(x509CertificateRSA1024, x509CACertificateRSA1024),
expected: MustParseX509CertificateChain(x509CertificateRSA1024, x509CACertificateRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA1024CertificateChainNoPtr",
have: BuildChain(x509CertificateRSA1024, x509CACertificateRSA1024),
expected: *MustParseX509CertificateChain(x509CertificateRSA1024, x509CACertificateRSA1024),
decode: true,
},
{
desc: "ShouldDecodeRSA2048Certificate",
have: x509CertificateRSA2048,
expected: MustParseX509CertificateChain(x509CertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA2048CertificateNoPtr",
have: x509CertificateRSA2048,
expected: *MustParseX509CertificateChain(x509CertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA2048CACertificate",
have: x509CACertificateRSA2048,
expected: MustParseX509CertificateChain(x509CACertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA2048CACertificateNoPtr",
have: x509CACertificateRSA2048,
expected: *MustParseX509CertificateChain(x509CACertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA2048CertificateChain",
have: BuildChain(x509CertificateRSA2048, x509CACertificateRSA2048),
expected: MustParseX509CertificateChain(x509CertificateRSA2048, x509CACertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA2048CertificateChainNoPtr",
have: BuildChain(x509CertificateRSA2048, x509CACertificateRSA2048),
expected: *MustParseX509CertificateChain(x509CertificateRSA2048, x509CACertificateRSA2048),
decode: true,
},
{
desc: "ShouldDecodeRSA4096Certificate",
have: x509CertificateRSA4096,
expected: MustParseX509CertificateChain(x509CertificateRSA4096),
decode: true,
},
{
desc: "ShouldDecodeRSA4096CertificateNoPtr",
have: x509CertificateRSA4096,
expected: *MustParseX509CertificateChain(x509CertificateRSA4096),
decode: true,
},
{
desc: "ShouldDecodeRSA4096CACertificate",
have: x509CACertificateRSA4096,
expected: MustParseX509CertificateChain(x509CACertificateRSA4096),
decode: true,
},
{
desc: "ShouldDecodeRSA4096CACertificateNoPtr",
have: x509CACertificateRSA4096,
expected: *MustParseX509CertificateChain(x509CACertificateRSA4096),
decode: true,
},
{
desc: "ShouldDecodeRSA4096CertificateChain",
have: BuildChain(x509CertificateRSA4096, x509CACertificateRSA4096),
expected: MustParseX509CertificateChain(x509CertificateRSA4096, x509CACertificateRSA4096),
decode: true,
},
{
desc: "ShouldDecodeRSA4096CertificateChainNoPtr",
have: BuildChain(x509CertificateRSA4096, x509CACertificateRSA4096),
expected: *MustParseX509CertificateChain(x509CertificateRSA4096, x509CACertificateRSA4096),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224Certificate",
have: x509CertificateECDSAP224,
expected: MustParseX509CertificateChain(x509CertificateECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224CertificateNoPtr",
have: x509CertificateECDSAP224,
expected: *MustParseX509CertificateChain(x509CertificateECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224CACertificate",
have: x509CACertificateECDSAP224,
expected: MustParseX509CertificateChain(x509CACertificateECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224CACertificateNoPtr",
have: x509CACertificateECDSAP224,
expected: *MustParseX509CertificateChain(x509CACertificateECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224CertificateChain",
have: BuildChain(x509CertificateECDSAP224, x509CACertificateECDSAP224),
expected: MustParseX509CertificateChain(x509CertificateECDSAP224, x509CACertificateECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP224CertificateChainNoPtr",
have: BuildChain(x509CertificateECDSAP224, x509CACertificateECDSAP224),
expected: *MustParseX509CertificateChain(x509CertificateECDSAP224, x509CACertificateECDSAP224),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256Certificate",
have: x509CertificateECDSAP256,
expected: MustParseX509CertificateChain(x509CertificateECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256CertificateNoPtr",
have: x509CertificateECDSAP256,
expected: *MustParseX509CertificateChain(x509CertificateECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256CACertificate",
have: x509CACertificateECDSAP256,
expected: MustParseX509CertificateChain(x509CACertificateECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256CACertificateNoPtr",
have: x509CACertificateECDSAP256,
expected: *MustParseX509CertificateChain(x509CACertificateECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256CertificateChain",
have: BuildChain(x509CertificateECDSAP256, x509CACertificateECDSAP256),
expected: MustParseX509CertificateChain(x509CertificateECDSAP256, x509CACertificateECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP256CertificateChainNoPtr",
have: BuildChain(x509CertificateECDSAP256, x509CACertificateECDSAP256),
expected: *MustParseX509CertificateChain(x509CertificateECDSAP256, x509CACertificateECDSAP256),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384Certificate",
have: x509CertificateECDSAP384,
expected: MustParseX509CertificateChain(x509CertificateECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384CertificateNoPtr",
have: x509CertificateECDSAP384,
expected: *MustParseX509CertificateChain(x509CertificateECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384CACertificate",
have: x509CACertificateECDSAP384,
expected: MustParseX509CertificateChain(x509CACertificateECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384CACertificateNoPtr",
have: x509CACertificateECDSAP384,
expected: *MustParseX509CertificateChain(x509CACertificateECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384CertificateChain",
have: BuildChain(x509CertificateECDSAP384, x509CACertificateECDSAP384),
expected: MustParseX509CertificateChain(x509CertificateECDSAP384, x509CACertificateECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP384CertificateChainNoPtr",
have: BuildChain(x509CertificateECDSAP384, x509CACertificateECDSAP384),
expected: *MustParseX509CertificateChain(x509CertificateECDSAP384, x509CACertificateECDSAP384),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521Certificate",
have: x509CertificateECDSAP521,
expected: MustParseX509CertificateChain(x509CertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521CertificateNoPtr",
have: x509CertificateECDSAP521,
expected: *MustParseX509CertificateChain(x509CertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521CACertificate",
have: x509CACertificateECDSAP521,
expected: MustParseX509CertificateChain(x509CACertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521CACertificateNoPtr",
have: x509CACertificateECDSAP521,
expected: *MustParseX509CertificateChain(x509CACertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521CertificateChain",
have: BuildChain(x509CertificateECDSAP521, x509CACertificateECDSAP521),
expected: MustParseX509CertificateChain(x509CertificateECDSAP521, x509CACertificateECDSAP521),
decode: true,
},
{
desc: "ShouldDecodeECDSAP521CertificateChainNoPtr",
have: BuildChain(x509CertificateECDSAP521, x509CACertificateECDSAP521),
expected: *MustParseX509CertificateChain(x509CertificateECDSAP521, x509CACertificateECDSAP521),
decode: true,
},
{
desc: "ShouldNotDecodeBadRSACertificateChain",
have: BuildChain(x509CertificateRSA2048, x509CACertificateECDSAP521),
expected: MustParseX509CertificateChain(x509CertificateRSA2048, x509CACertificateECDSAP521),
verr: "certificate #1 in chain is not signed properly by certificate #2 in chain: x509: signature algorithm specifies an RSA public key, but have public key of type *ecdsa.PublicKey",
decode: true,
},
{
desc: "ShouldDecodeEmptyCertificateToNil",
have: "",
expected: nilkey,
decode: true,
},
{
desc: "ShouldDecodeEmptyCertificateToEmptyStruct",
have: "",
expected: schema.X509CertificateChain{},
decode: true,
},
{
desc: "ShouldNotDecodeECDSAKeyToCertificate",
have: x509PrivateKeyECDSAP224,
expected: nilkey,
decode: true,
err: "could not decode to a *schema.X509CertificateChain: the PEM data chain contains a PRIVATE KEY but only certificates are expected",
},
{
desc: "ShouldNotDecodeBadRSAPrivateKeyToCertificate",
have: x509PrivateKeyRSABad,
expected: nilkey,
decode: true,
err: "could not decode to a *schema.X509CertificateChain: invalid PEM block",
},
}
hook := configuration.StringToX509CertificateChainHookFunc()
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
actual, err := hook(reflect.TypeOf(tc.have), reflect.TypeOf(tc.expected), tc.have)
switch {
case !tc.decode:
assert.NoError(t, err)
assert.Equal(t, tc.have, actual)
case tc.err == "":
assert.NoError(t, err)
require.Equal(t, tc.expected, actual)
if tc.expected == nilkey {
break
}
switch chain := actual.(type) {
case *schema.X509CertificateChain:
require.NotNil(t, chain)
if tc.verr == "" {
assert.NoError(t, chain.Validate())
} else {
assert.EqualError(t, chain.Validate(), tc.verr)
}
case schema.X509CertificateChain:
require.NotNil(t, chain)
if tc.verr == "" {
assert.NoError(t, chain.Validate())
} else {
assert.EqualError(t, chain.Validate(), tc.verr)
}
}
default:
assert.EqualError(t, err, tc.err)
assert.Nil(t, actual)
}
})
}
}
var (
//nolint:gosec
x509PrivateKeyRSABad = `
-----BEGIN RSA PRIVATE KEY-----
bad key
-----END RSA PRIVATE KEY-----`
//nolint:gosec
x509PrivateKeyECBad = `
-----BEGIN EC PRIVATE KEY-----
bad key
-----END EC PRIVATE KEY-----`
)
func MustParsePKCS8RSAPrivateKey(data string) *rsa.PrivateKey {
return MustParsePKCS8PrivateKey(data).(*rsa.PrivateKey)
}
func MustParsePKCS8ECDSAPrivateKey(data string) *ecdsa.PrivateKey {
return MustParsePKCS8PrivateKey(data).(*ecdsa.PrivateKey)
}
func MustParsePKCS8Ed25519PrivateKey(data string) *ed25519.PrivateKey {
key := MustParsePKCS8PrivateKey(data).(ed25519.PrivateKey)
return &key
}
func MustParsePKCS8PrivateKey(data string) schema.CryptographicPrivateKey {
block, _ := pem.Decode([]byte(data))
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
panic("not pem encoded")
}
if block.Type != "PRIVATE KEY" {
panic("not PKCS8 private key")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
panic(err)
}
if pkey, ok := key.(schema.CryptographicPrivateKey); ok {
return pkey
}
panic("key does not implement the required members")
}
func MustParseX509Certificate(data string) *x509.Certificate {
block, _ := pem.Decode([]byte(data))
if block == nil || len(block.Bytes) == 0 {
panic("not a PEM")
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
panic(err)
}
return cert
}
func BuildChain(pems ...string) string {
buf := bytes.Buffer{}
for i, data := range pems {
if i != 0 {
buf.WriteString("\n")
}
buf.WriteString(data)
}
return buf.String()
}
func MustParseX509CertificateChain(datas ...string) *schema.X509CertificateChain {
chain, err := schema.NewX509CertificateChain(BuildChain(datas...))
if err != nil {
panic(err)
}
return chain
}
func testInt32Ptr(i int32) *int32 {
return &i
}
func testTimeDurationPtr(t time.Duration) *time.Duration {
return &t
}
func testRefreshIntervalDurationPtr(t time.Duration) *schema.RefreshIntervalDuration {
x := schema.NewRefreshIntervalDuration(t)
return &x
}
var (
testTrue = true
testZero int32
testString = ""
)
func MustParseAddress(input string) schema.Address {
address, err := schema.NewAddress(input)
if err != nil {
panic(err)
}
addr := *address
return addr
}
func MustParseAddressPtr(input string) *schema.Address {
address, err := schema.NewAddress(input)
if err != nil {
panic(err)
}
return address
}
func MustParsePasswordDigest(input string) schema.PasswordDigest {
digest, err := schema.DecodePasswordDigest(input)
if err != nil {
panic(err)
}
return *digest
}
func MustParsePasswordDigestPtr(input string) *schema.PasswordDigest {
digest, err := schema.DecodePasswordDigest(input)
if err != nil {
panic(err)
}
return digest
}
const (
pathCrypto = "./test_resources/crypto/%s.%s"
)
func MustLoadCryptoSet(alg string, legacy bool, extra ...string) (certCA, keyCA, cert, key string) {
extraAlt := make([]string, len(extra))
copy(extraAlt, extra)
if legacy {
extraAlt = append(extraAlt, "legacy")
}
return MustLoadCryptoRaw(true, alg, "crt", extra...), MustLoadCryptoRaw(true, alg, "pem", extra...), MustLoadCryptoRaw(false, alg, "crt", extraAlt...), MustLoadCryptoRaw(false, alg, "pem", extraAlt...)
}
func MustLoadCryptoRaw(ca bool, alg, ext string, extra ...string) string {
var fparts []string
if ca {
fparts = append(fparts, "ca")
}
fparts = append(fparts, strings.ToLower(alg))
if len(extra) != 0 {
fparts = append(fparts, extra...)
}
var (
data []byte
err error
)
if data, err = os.ReadFile(fmt.Sprintf(pathCrypto, strings.Join(fparts, "."), ext)); err != nil {
panic(err)
}
return string(data)
}
var (
x509CertificateRSA1024, x509CertificateRSA2048, x509CertificateRSA4096, x509CertificateEd25519 string
x509CACertificateRSA1024, x509CACertificateRSA2048, x509CACertificateRSA4096, x509CACertificateEd25519 string
x509PrivateKeyRSA1024, x509PrivateKeyRSA2048, x509PrivateKeyRSA4096, x509PrivateKeyEd25519 string
x509CAPrivateKeyRSA1024, x509CAPrivateKeyRSA2048, x509CAPrivateKeyRSA4096, x509CAPrivateKeyEd25519 string
x509CertificateECDSAP224, x509CertificateECDSAP256, x509CertificateECDSAP384, x509CertificateECDSAP521 string
x509CACertificateECDSAP224, x509CACertificateECDSAP256, x509CACertificateECDSAP384, x509CACertificateECDSAP521 string
x509PrivateKeyECDSAP224, x509PrivateKeyECDSAP256, x509PrivateKeyECDSAP384, x509PrivateKeyECDSAP521 string
x509CAPrivateKeyECDSAP224, x509CAPrivateKeyECDSAP256, x509CAPrivateKeyECDSAP384, x509CAPrivateKeyECDSAP521 string
)
func init() {
x509CACertificateRSA1024, x509CAPrivateKeyRSA1024, x509CertificateRSA1024, x509PrivateKeyRSA1024 = MustLoadCryptoSet("RSA", false, "1024")
x509CACertificateRSA2048, x509CAPrivateKeyRSA2048, x509CertificateRSA2048, x509PrivateKeyRSA2048 = MustLoadCryptoSet("RSA", false, "2048")
x509CACertificateRSA4096, x509CAPrivateKeyRSA4096, x509CertificateRSA4096, x509PrivateKeyRSA4096 = MustLoadCryptoSet("RSA", false, "4096")
x509CACertificateECDSAP224, x509CAPrivateKeyECDSAP224, x509CertificateECDSAP224, x509PrivateKeyECDSAP224 = MustLoadCryptoSet("ECDSA", false, "P224")
x509CACertificateECDSAP256, x509CAPrivateKeyECDSAP256, x509CertificateECDSAP256, x509PrivateKeyECDSAP256 = MustLoadCryptoSet("ECDSA", false, "P256")
x509CACertificateECDSAP384, x509CAPrivateKeyECDSAP384, x509CertificateECDSAP384, x509PrivateKeyECDSAP384 = MustLoadCryptoSet("ECDSA", false, "P384")
x509CACertificateECDSAP521, x509CAPrivateKeyECDSAP521, x509CertificateECDSAP521, x509PrivateKeyECDSAP521 = MustLoadCryptoSet("ECDSA", false, "P521")
x509CACertificateEd25519, x509CAPrivateKeyEd25519, x509CertificateEd25519, x509PrivateKeyEd25519 = MustLoadCryptoSet("Ed25519", false)
}