mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: allow suffix after wildcard in wildcard access URL (#4524)
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
package httpapi_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -8,64 +9,6 @@ import (
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
)
|
||||
|
||||
func TestSplitSubdomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Host string
|
||||
ExpectedSubdomain string
|
||||
ExpectedRest string
|
||||
}{
|
||||
{
|
||||
Name: "Empty",
|
||||
Host: "",
|
||||
ExpectedSubdomain: "",
|
||||
ExpectedRest: "",
|
||||
},
|
||||
{
|
||||
Name: "NoSubdomain",
|
||||
Host: "com",
|
||||
ExpectedSubdomain: "com",
|
||||
ExpectedRest: "",
|
||||
},
|
||||
{
|
||||
Name: "Domain",
|
||||
Host: "coder.com",
|
||||
ExpectedSubdomain: "coder",
|
||||
ExpectedRest: "com",
|
||||
},
|
||||
{
|
||||
Name: "Subdomain",
|
||||
Host: "subdomain.coder.com",
|
||||
ExpectedSubdomain: "subdomain",
|
||||
ExpectedRest: "coder.com",
|
||||
},
|
||||
{
|
||||
Name: "DoubleSubdomain",
|
||||
Host: "subdomain1.subdomain2.coder.com",
|
||||
ExpectedSubdomain: "subdomain1",
|
||||
ExpectedRest: "subdomain2.coder.com",
|
||||
},
|
||||
{
|
||||
Name: "WithPort",
|
||||
Host: "subdomain.coder.com:8080",
|
||||
ExpectedSubdomain: "subdomain",
|
||||
ExpectedRest: "coder.com:8080",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
c := c
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
subdomain, rest := httpapi.SplitSubdomain(c.Host)
|
||||
require.Equal(t, c.ExpectedSubdomain, subdomain)
|
||||
require.Equal(t, c.ExpectedRest, rest)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationURLString(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -214,3 +157,239 @@ func TestParseSubdomainAppURL(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileHostnamePattern(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type matchCase struct {
|
||||
input string
|
||||
// empty string denotes no match
|
||||
match string
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
pattern string
|
||||
errorContains string
|
||||
// expectedRegex only needs to contain the inner part of the regex, not
|
||||
// the prefix and suffix checks.
|
||||
expectedRegex string
|
||||
matchCases []matchCase
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
name: "Invalid_ContainsHTTP",
|
||||
pattern: "http://*.hi.com",
|
||||
errorContains: "must not contain a scheme",
|
||||
},
|
||||
{
|
||||
name: "Invalid_ContainsHTTPS",
|
||||
pattern: "https://*.hi.com",
|
||||
errorContains: "must not contain a scheme",
|
||||
},
|
||||
{
|
||||
name: "Invalid_ContainsPort",
|
||||
pattern: "*.hi.com:8080",
|
||||
errorContains: "must not contain a port",
|
||||
},
|
||||
{
|
||||
name: "Invalid_StartPeriod",
|
||||
pattern: ".hi.com",
|
||||
errorContains: "must not start or end with a period",
|
||||
},
|
||||
{
|
||||
name: "Invalid_EndPeriod",
|
||||
pattern: "hi.com.",
|
||||
errorContains: "must not start or end with a period",
|
||||
},
|
||||
{
|
||||
name: "Invalid_Empty",
|
||||
pattern: "",
|
||||
errorContains: "must contain at least two labels",
|
||||
},
|
||||
{
|
||||
name: "Invalid_SingleLabel",
|
||||
pattern: "hi",
|
||||
errorContains: "must contain at least two labels",
|
||||
},
|
||||
{
|
||||
name: "Invalid_NoWildcard",
|
||||
pattern: "hi.com",
|
||||
errorContains: "must contain exactly one asterisk",
|
||||
},
|
||||
{
|
||||
name: "Invalid_MultipleWildcards",
|
||||
pattern: "**.hi.com",
|
||||
errorContains: "must contain exactly one asterisk",
|
||||
},
|
||||
{
|
||||
name: "Invalid_WildcardNotFirst",
|
||||
pattern: "hi.*.com",
|
||||
errorContains: "must only contain an asterisk at the beginning",
|
||||
},
|
||||
{
|
||||
name: "Invalid_BadLabel1",
|
||||
pattern: "*.h_i.com",
|
||||
errorContains: "contains invalid label",
|
||||
},
|
||||
{
|
||||
name: "Invalid_BadLabel2",
|
||||
pattern: "*.hi-.com",
|
||||
errorContains: "contains invalid label",
|
||||
},
|
||||
{
|
||||
name: "Invalid_BadLabel3",
|
||||
pattern: "*.-hi.com",
|
||||
errorContains: "contains invalid label",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Valid_Simple",
|
||||
pattern: "*.hi",
|
||||
expectedRegex: `([^.]+)\.hi`,
|
||||
matchCases: []matchCase{
|
||||
{
|
||||
input: "hi",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "hi.hi.hi",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "abcd.hi",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: "abcd.hi.",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: " abcd.hi. ",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: "abcd.hi:8080",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: "ab__invalid__cd-.hi",
|
||||
// Invalid subdomains still match the pattern because they
|
||||
// managed to make it to the webserver anyways.
|
||||
match: "ab__invalid__cd-",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid_MultiLevel",
|
||||
pattern: "*.hi.com",
|
||||
expectedRegex: `([^.]+)\.hi\.com`,
|
||||
matchCases: []matchCase{
|
||||
{
|
||||
input: "hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "abcd.hi.com",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: "ab__invalid__cd-.hi.com",
|
||||
match: "ab__invalid__cd-",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid_WildcardSuffix1",
|
||||
pattern: `*a.hi.com`,
|
||||
expectedRegex: `([^.]+)a\.hi\.com`,
|
||||
matchCases: []matchCase{
|
||||
{
|
||||
input: "hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "abcd.hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "ab__invalid__cd-.hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "abcda.hi.com",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: "ab__invalid__cd-a.hi.com",
|
||||
match: "ab__invalid__cd-",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Valid_WildcardSuffix2",
|
||||
pattern: `*-test.hi.com`,
|
||||
expectedRegex: `([^.]+)-test\.hi\.com`,
|
||||
matchCases: []matchCase{
|
||||
{
|
||||
input: "hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "abcd.hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "ab__invalid__cd-.hi.com",
|
||||
match: "",
|
||||
},
|
||||
{
|
||||
input: "abcd-test.hi.com",
|
||||
match: "abcd",
|
||||
},
|
||||
{
|
||||
input: "ab__invalid__cd-test.hi.com",
|
||||
match: "ab__invalid__cd",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
c := c
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
regex, err := httpapi.CompileHostnamePattern(c.pattern)
|
||||
if c.errorContains == "" {
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := `^\s*` + c.expectedRegex + `\.?(:\d+)?\s*$`
|
||||
require.Equal(t, expected, regex.String(), "generated regex does not match")
|
||||
|
||||
for i, m := range c.matchCases {
|
||||
m := m
|
||||
t.Run(fmt.Sprintf("MatchCase%d", i), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
match, ok := httpapi.ExecuteHostnamePattern(regex, m.input)
|
||||
if m.match == "" {
|
||||
require.False(t, ok)
|
||||
} else {
|
||||
require.True(t, ok)
|
||||
require.Equal(t, m.match, match)
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, c.errorContains)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user