fix(s3api): correct wildcard matching (#8052)

* fix(s3api): correct wildcard matching

* chore(tests): add multi-slash test case

in ref. to cases provided here https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_resource.html\#reference_policies_elements_resource_wildcards

* fix: gemini suggestions
This commit is contained in:
SoSweetHam
2026-01-19 04:24:03 +05:30
committed by GitHub
parent 753e1db096
commit 2662420194
3 changed files with 158 additions and 59 deletions

View File

@@ -7,6 +7,7 @@ import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/credential"
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/stretchr/testify/assert"
@@ -159,6 +160,102 @@ func TestCanDo(t *testing.T) {
assert.Equal(t, true, ident7.canDo(ACTION_DELETE_BUCKET, "bucket1", ""))
}
func TestMatchWildcardPattern(t *testing.T) {
tests := []struct {
pattern string
target string
match bool
}{
// Basic * wildcard tests
{"Bucket/*", "Bucket/a/b", true},
{"Bucket/*", "x/Bucket/a", false},
{"Bucket/*/admin", "Bucket/x/admin", true},
{"Bucket/*/admin", "Bucket/x/y/admin", true},
{"Bucket/*/admin", "Bucket////x////uwu////y////admin", true},
{"abc*def", "abcXYZdef", true},
{"abc*def", "abcXYZdefZZ", false},
{"syr/*", "syr/a/b", true},
// ? wildcard tests (matches exactly one character)
{"ab?d", "abcd", true},
{"ab?d", "abXd", true},
{"ab?d", "abd", false}, // ? must match exactly one character
{"ab?d", "abcXd", false}, // ? matches only one character
{"a?c", "abc", true},
{"a?c", "aXc", true},
{"a?c", "ac", false},
{"???", "abc", true},
{"???", "ab", false},
{"???", "abcd", false},
// Combined * and ? wildcards
{"a*?", "ab", true}, // * matches empty, ? matches 'b'
{"a*?", "abc", true}, // * matches 'b', ? matches 'c'
{"a*?", "a", false}, // ? must match something
{"a?*", "ab", true}, // ? matches 'b', * matches empty
{"a?*", "abc", true}, // ? matches 'b', * matches 'c'
{"a?*b", "aXb", true}, // ? matches 'X', * matches empty
{"a?*b", "aXYZb", true},
{"*?*", "a", true},
{"*?*", "", false}, // ? requires at least one character
// Edge cases: * matches empty string
{"a*b", "ab", true}, // * matches empty string
{"a**b", "ab", true}, // multiple stars match empty
{"a**b", "axb", true}, // multiple stars match 'x'
{"a**b", "axyb", true},
{"*", "", true},
{"*", "anything", true},
{"**", "", true},
{"**", "anything", true},
// Edge cases: empty strings
{"", "", true},
{"a", "", false},
{"", "a", false},
// Trailing * matches empty
{"a*", "a", true},
{"a*", "abc", true},
{"abc*", "abc", true},
{"abc*", "abcdef", true},
// Leading * matches empty
{"*a", "a", true},
{"*a", "XXXa", true},
{"*abc", "abc", true},
{"*abc", "XXXabc", true},
// Multiple wildcards
{"*a*", "a", true},
{"*a*", "Xa", true},
{"*a*", "aX", true},
{"*a*", "XaX", true},
{"*a*b*", "ab", true},
{"*a*b*", "XaYbZ", true},
// Exact match (no wildcards)
{"exact", "exact", true},
{"exact", "notexact", false},
{"exact", "exactnot", false},
// S3-style action patterns
{"Read:bucket*", "Read:bucket-test", true},
{"Read:bucket*", "Read:bucket", true},
{"Write:bucket/path/*", "Write:bucket/path/file.txt", true},
{"Admin:*", "Admin:anything", true},
}
for _, tt := range tests {
t.Run(tt.pattern+"_"+tt.target, func(t *testing.T) {
result := policy_engine.MatchesWildcard(tt.pattern, tt.target)
if result != tt.match {
t.Errorf("policy_engine.MatchesWildcard(%q, %q) = %v, want %v", tt.pattern, tt.target, result, tt.match)
}
})
}
}
type LoadS3ApiConfigurationTestCase struct {
pbAccount *iam_pb.Account
pbIdent *iam_pb.Identity