Files
seaweedfs/weed/s3api/s3api_subresource_ambiguity_test.go
T
Chris LuandDevin cec96c02ea s3: reject requests carrying more than one routed subresource (#11942)
* s3: reject requests carrying more than one routed subresource

Routing picks the first matching subresource route while authorization
resolves a single action from the query, and the two can disagree. A
bucket-level PUT ?tagging&policy is authorized as s3:PutBucketTagging but
served by PutBucketPolicyHandler, letting a tagger rewrite the bucket
policy; the same shape lets a list grant read the policy via
?list-type=2&policy or ?versions&policy, and lets an object ACL writer
retag via PUT ?acl&tagging. Declining ambiguous requests keeps the
authorized action and the served handler identical.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* s3: reuse hasAmbiguousSubresource in the routed-subresource guard

The standalone key list dropped versioning, so ?versioning&policy still
reached route matching and auth with two operations. The bucket-level
check already keys off operationSubresources plus bucketQueryActions;
running the same predicate keeps the lists from drifting apart.

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-21 22:45:53 -07:00

57 lines
1.4 KiB
Go

package s3api
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestRejectAmbiguousSubresource(t *testing.T) {
passthrough := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) })
for _, query := range []string{
"policy",
"policy=",
"tagging&versionId=v1",
"list-type=2&prefix=a&continuation-token=x",
"uploadId=abc&partNumber=3",
"uploads",
"acl&X-Amz-Signature=x&X-Amz-Expires=900",
"analytics&id=weekly",
"delete",
"versions&versionId=",
"versioning",
"prefix=a&expected-bucket-owner=123",
"tagging&tagging=x",
"",
} {
r := httptest.NewRequest(http.MethodPut, "http://localhost/bucket?"+query, nil)
w := httptest.NewRecorder()
rejectAmbiguousSubresource(passthrough).ServeHTTP(w, r)
if w.Code != http.StatusTeapot {
t.Errorf("%q should pass through, got %d", query, w.Code)
}
}
for _, query := range []string{
"tagging&policy",
"policy&tagging",
"list-type=2&policy",
"versions&policy",
"versioning&policy",
"delete&policy",
"cors&policy",
"acl&tagging",
"tagging&seaweedfs-quota",
"uploadId=x&acl",
"retention&legal-hold",
"ownershipControls&encryption",
} {
r := httptest.NewRequest(http.MethodPut, "http://localhost/bucket?"+query, nil)
w := httptest.NewRecorder()
rejectAmbiguousSubresource(passthrough).ServeHTTP(w, r)
if w.Code != http.StatusBadRequest {
t.Errorf("%q should be rejected, got %d", query, w.Code)
}
}
}