mirror of
https://github.com/alibaba/higress.git
synced 2026-05-10 13:57:27 +08:00
feat: ext-auth plugin: Blacklist and whitelist modes support HTTP request method matching (#1798)
This commit is contained in:
@@ -3,6 +3,7 @@ package expr
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"ext-auth/util"
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,7 @@ type MatchRules struct {
|
||||
|
||||
type Rule struct {
|
||||
Domain string
|
||||
Method []string
|
||||
Path Matcher
|
||||
}
|
||||
|
||||
@@ -28,19 +30,19 @@ func MatchRulesDefaults() MatchRules {
|
||||
}
|
||||
}
|
||||
|
||||
// IsAllowedByMode checks if the given domain and path are allowed based on the configuration mode.
|
||||
func (config *MatchRules) IsAllowedByMode(domain, path string) bool {
|
||||
// IsAllowedByMode checks if the given domain, method and path are allowed based on the configuration mode.
|
||||
func (config *MatchRules) IsAllowedByMode(domain, method, path string) bool {
|
||||
switch config.Mode {
|
||||
case ModeWhitelist:
|
||||
for _, rule := range config.RuleList {
|
||||
if rule.matchDomainAndPath(domain, path) {
|
||||
if rule.matchesAllConditions(domain, method, path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
case ModeBlacklist:
|
||||
for _, rule := range config.RuleList {
|
||||
if rule.matchDomainAndPath(domain, path) {
|
||||
if rule.matchesAllConditions(domain, method, path) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -50,17 +52,21 @@ func (config *MatchRules) IsAllowedByMode(domain, path string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// matchDomainAndPath checks if the given domain and path match the rule.
|
||||
// If rule.Domain is empty, it only checks rule.Path.
|
||||
// If rule.Path is empty, it only checks rule.Domain.
|
||||
// If both are empty, it returns false.
|
||||
func (rule *Rule) matchDomainAndPath(domain, path string) bool {
|
||||
if rule.Domain == "" && rule.Path == nil {
|
||||
// matchesAllConditions checks if the given domain, method and path match all conditions of the rule.
|
||||
func (rule *Rule) matchesAllConditions(domain, method, path string) bool {
|
||||
// If all conditions are empty, return false
|
||||
if rule.Domain == "" && rule.Path == nil && len(rule.Method) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check domain and path matching
|
||||
domainMatch := rule.Domain == "" || matchDomain(domain, rule.Domain)
|
||||
pathMatch := rule.Path == nil || rule.Path.Match(path)
|
||||
return domainMatch && pathMatch
|
||||
|
||||
// Check HTTP method matching: if no methods are specified, any method is allowed
|
||||
methodMatch := len(rule.Method) == 0 || util.ContainsString(rule.Method, method)
|
||||
|
||||
return domainMatch && pathMatch && methodMatch
|
||||
}
|
||||
|
||||
// matchDomain checks if the given domain matches the pattern.
|
||||
|
||||
@@ -6,11 +6,20 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func createMatcher(pattern string, caseSensitive bool) Matcher {
|
||||
pathMatcher, err := newStringExactMatcher(pattern, caseSensitive)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return pathMatcher
|
||||
}
|
||||
|
||||
func TestIsAllowedByMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config MatchRules
|
||||
domain string
|
||||
method string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
@@ -21,17 +30,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
@@ -42,18 +47,14 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
path: "/bar",
|
||||
method: "POST",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
@@ -63,17 +64,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
@@ -84,18 +81,14 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
path: "/bar",
|
||||
method: "POST",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
@@ -107,6 +100,7 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
@@ -117,29 +111,25 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Both Domain and Path are empty",
|
||||
name: "All fields (Domain, Method, Path) are empty",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{Domain: "", Path: nil},
|
||||
{Domain: "", Method: []string{}, Path: nil},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
@@ -150,17 +140,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
@@ -171,17 +157,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "*.example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "sub.example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
@@ -192,20 +174,48 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "*.example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Whitelist mode, only method matches",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Method: []string{"GET"},
|
||||
Path: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Whitelist mode, only domain matches",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Path: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Blacklist mode, generic domain matches",
|
||||
config: MatchRules{
|
||||
@@ -213,17 +223,13 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "*.example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "sub.example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
@@ -234,25 +240,89 @@ func TestIsAllowedByMode(t *testing.T) {
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "*.example.com",
|
||||
Path: func() Matcher {
|
||||
pathMatcher, err := newStringExactMatcher("/foo", true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Matcher: %v", err)
|
||||
}
|
||||
return pathMatcher
|
||||
}(),
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Domain with special characters",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example-*.com",
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example-test.com",
|
||||
method: "GET",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Path with special characters",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Method: []string{"GET"},
|
||||
Path: createMatcher("/foo-bar", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "GET",
|
||||
path: "/foo-bar",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple methods, one matches",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Method: []string{"GET", "POST"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "POST",
|
||||
path: "/foo",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple methods, none match",
|
||||
config: MatchRules{
|
||||
Mode: ModeWhitelist,
|
||||
RuleList: []Rule{
|
||||
{
|
||||
Domain: "example.com",
|
||||
Method: []string{"GET", "POST"},
|
||||
Path: createMatcher("/foo", true),
|
||||
},
|
||||
},
|
||||
},
|
||||
domain: "example.com",
|
||||
method: "PUT",
|
||||
path: "/foo",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := tt.config.IsAllowedByMode(tt.domain, tt.path)
|
||||
result := tt.config.IsAllowedByMode(tt.domain, tt.method, tt.path)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user