Files
Miguel da Costa Martins Marcelino cdcd702aa4 chore: Add upper bounds to supplied emails
This prevents users from abusing `--allowed-mail` policies by limiting the nr. of rules to 100 and defining a max per rule of 320 bytes. Validation errors now identify only the rule position and category, preventing recipient values from appearing in logs. Parsing also stops once limits are exceeded while preserving normalisation and deduplication.
2026-09-23 15:59:32 +00:00

147 lines
5.7 KiB
Go

package quicktunnelauth
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValidateQuickTunnelAllowedMail(t *testing.T) {
t.Parallel()
tests := []struct {
name string
values []string
expectedEmails []string
expectedDomains []string
}{
{name: "multiple without spaces", values: []string{"[email protected],[email protected]"}, expectedEmails: []string{"[email protected]", "[email protected]"}},
{name: "multiple with consistent spaces", values: []string{"[email protected], [email protected]"}, expectedEmails: []string{"[email protected]", "[email protected]"}},
{name: "multiple with inconsistent spaces", values: []string{" [email protected],[email protected] , [email protected] "}, expectedEmails: []string{"[email protected]", "[email protected]", "[email protected]"}},
{name: "single with leading spaces", values: []string{" [email protected]"}, expectedEmails: []string{"[email protected]"}},
{name: "single with trailing spaces", values: []string{"[email protected] "}, expectedEmails: []string{"[email protected]"}},
{name: "single with surrounding spaces", values: []string{" [email protected] "}, expectedEmails: []string{"[email protected]"}},
{name: "repeated flags", values: []string{"[email protected]", "[email protected],*@Example.org"}, expectedEmails: []string{"[email protected]", "[email protected]"}, expectedDomains: []string{"example.org"}},
{name: "deduplicated", values: []string{"[email protected],[email protected]", "*@Example.org,*@example.org"}, expectedEmails: []string{"[email protected]"}, expectedDomains: []string{"example.org"}},
{name: "punycode IDN", values: []string{"[email protected],*@xn--fiqs8s"}, expectedEmails: []string{"[email protected]"}, expectedDomains: []string{"xn--fiqs8s"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
emails, domains, err := validateQuickTunnelAllowedMail(test.values)
require.NoError(t, err)
assert.Len(t, emails, len(test.expectedEmails))
assert.Len(t, domains, len(test.expectedDomains))
for _, email := range test.expectedEmails {
assert.Contains(t, emails, email)
}
for _, domain := range test.expectedDomains {
assert.Contains(t, domains, domain)
}
})
}
}
func TestValidateQuickTunnelAllowedMailRejectsMalformedEntries(t *testing.T) {
t.Parallel()
tests := []struct {
name string
values []string
}{
{name: "empty"},
{name: "blank", values: []string{" , "}},
{name: "display name", values: []string{"User <[email protected]>"}},
{name: "missing wildcard at", values: []string{"*example.com"}},
{name: "double at", values: []string{"user@@example.com"}},
{name: "invalid exact domain", values: []string{"[email protected]"}},
{name: "empty wildcard domain", values: []string{"*@"}},
{name: "empty domain label", values: []string{"*@example..com"}},
{name: "leading domain hyphen", values: []string{"*@-example.com"}},
{name: "trailing domain hyphen", values: []string{"*@example-.com"}},
{name: "invalid domain character", values: []string{"*@exam_ple.com"}},
{name: "unicode IDN", values: []string{"jim@something.\u4e2d\u56fd"}},
{name: "long domain label", values: []string{"*@" + strings.Repeat("a", 64) + ".com"}},
{name: "long domain", values: []string{"*@" + strings.Repeat("a.", 126) + "aa"}},
{name: "domain path", values: []string{"*@example.com/path"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, _, err := validateQuickTunnelAllowedMail(test.values)
require.Error(t, err)
})
}
}
func TestValidateQuickTunnelAllowedMailRejectsEmptyRuleWithoutLeakingValues(t *testing.T) {
t.Parallel()
values := []string{"[email protected],,[email protected]"}
_, _, err := validateQuickTunnelAllowedMail(values)
require.EqualError(t, err, "allowed mail rule 2 is empty")
assert.NotContains(t, err.Error(), "example.com")
}
func TestValidateQuickTunnelAllowedMailReportsInvalidRule(t *testing.T) {
t.Parallel()
tests := []struct {
name string
values []string
expectedError string
}{
{name: "email", values: []string{"[email protected]", "[email protected]/path"}, expectedError: "allowed mail rule 2 is not a valid email address"},
{name: "wildcard", values: []string{"[email protected]", "*@example.com/path"}, expectedError: "allowed mail rule 2 has an invalid wildcard domain"},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, _, err := validateQuickTunnelAllowedMail(test.values)
require.EqualError(t, err, test.expectedError)
for _, value := range test.values {
assert.NotContains(t, err.Error(), value)
}
})
}
}
func TestValidateQuickTunnelAllowedMailBoundsInput(t *testing.T) {
t.Parallel()
tooManyRules := make([]string, quickTunnelAuthMaxAllowedMailRules+1)
for index := range tooManyRules {
tooManyRules[index] = "[email protected]"
}
tests := []struct {
name string
values []string
expectedError string
}{
{
name: "too many rules",
values: tooManyRules,
expectedError: fmt.Sprintf("allowed mail rules exceed the %d-entry limit", quickTunnelAuthMaxAllowedMailRules),
},
{
name: "rule too long before trimming",
values: []string{strings.Repeat(" ", quickTunnelAuthMaxAllowedMailRuleBytes) + "a"},
expectedError: fmt.Sprintf("allowed mail rule 1 exceeds the %d-byte limit", quickTunnelAuthMaxAllowedMailRuleBytes),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
_, _, err := validateQuickTunnelAllowedMail(test.values)
require.EqualError(t, err, test.expectedError)
})
}
}