fix(s3api): missing Vary: Origin header on non-CORS and OPTIONS requests (#8072)
* fix: Refactor CORS middleware to consistently apply the `Vary: Origin` header when a configuration exists and streamline request processing logic. * fix: Add Vary: Origin header to CORS OPTIONS responses and refactor request handling for clarity and correctness. * fix: update CORS middleware tests to correctly parse and check for 'Origin' in Vary header. * refactor: extract `hasVaryOrigin` helper function to simplify Vary header checks in tests. * test: Remove `Vary: Origin` header from CORS test expectations. * refactor: consolidate CORS request handling into a new `processCORS` method using a `next` callback.
This commit is contained in:
@@ -3,6 +3,7 @@ package cors
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -403,3 +404,161 @@ func TestMiddlewareFallbackWithError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiddlewareVaryHeader tests that the Vary: Origin header is correctly applied in various scenarios
|
||||
func TestMiddlewareVaryHeader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
bucketConfig *CORSConfiguration
|
||||
shouldHaveVaryHeader bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "Specific allowed origin",
|
||||
bucketConfig: &CORSConfiguration{
|
||||
CORSRules: []CORSRule{
|
||||
{
|
||||
AllowedOrigins: []string{"https://example.com"},
|
||||
AllowedMethods: []string{"GET"},
|
||||
AllowedHeaders: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldHaveVaryHeader: true,
|
||||
description: "Should have Vary: Origin header when CORS config exists with specific origin",
|
||||
},
|
||||
{
|
||||
name: "Wildcard allowed origin",
|
||||
bucketConfig: &CORSConfiguration{
|
||||
CORSRules: []CORSRule{
|
||||
{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET"},
|
||||
AllowedHeaders: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldHaveVaryHeader: true,
|
||||
description: "Should have Vary: Origin header even when CORS config has wildcard origin",
|
||||
},
|
||||
{
|
||||
name: "No CORS configuration",
|
||||
bucketConfig: nil,
|
||||
shouldHaveVaryHeader: false,
|
||||
description: "Should NOT have Vary: Origin header when no CORS config exists",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Setup mocks
|
||||
bucketChecker := &mockBucketChecker{bucketExists: true}
|
||||
|
||||
var errCode s3err.ErrorCode
|
||||
if tt.bucketConfig == nil {
|
||||
errCode = s3err.ErrNoSuchCORSConfiguration
|
||||
} else {
|
||||
errCode = s3err.ErrNone
|
||||
}
|
||||
|
||||
configGetter := &mockCORSConfigGetter{
|
||||
config: tt.bucketConfig,
|
||||
errCode: errCode,
|
||||
}
|
||||
|
||||
// Create middleware
|
||||
middleware := NewMiddleware(bucketChecker, configGetter, nil)
|
||||
|
||||
// Create request WITHOUT Origin header
|
||||
req := httptest.NewRequest("GET", "/testbucket/testobject", nil)
|
||||
req = mux.SetURLVars(req, map[string]string{
|
||||
"bucket": "testbucket",
|
||||
"object": "testobject",
|
||||
})
|
||||
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Create a simple handler that returns 200 OK
|
||||
nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Execute middleware
|
||||
middleware.Handler(nextHandler).ServeHTTP(w, req)
|
||||
|
||||
// Check Vary header
|
||||
hasVaryOrigin := hasVaryOrigin(w.Header())
|
||||
|
||||
if tt.shouldHaveVaryHeader && !hasVaryOrigin {
|
||||
t.Errorf("%s: expected Vary: Origin header, but got %v", tt.description, w.Header()["Vary"])
|
||||
} else if !tt.shouldHaveVaryHeader && hasVaryOrigin {
|
||||
t.Errorf("%s: expected NO Vary: Origin header, but got it", tt.description)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandleOptionsRequestVaryHeader reproduces the issue where HandleOptionsRequest misses Vary: Origin
|
||||
func TestHandleOptionsRequestVaryHeader(t *testing.T) {
|
||||
// Setup mocks
|
||||
bucketChecker := &mockBucketChecker{bucketExists: true}
|
||||
|
||||
config := &CORSConfiguration{
|
||||
CORSRules: []CORSRule{
|
||||
{
|
||||
AllowedOrigins: []string{"https://example.com"},
|
||||
AllowedMethods: []string{"GET", "POST"},
|
||||
AllowedHeaders: []string{"*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
configGetter := &mockCORSConfigGetter{
|
||||
config: config,
|
||||
errCode: s3err.ErrNone,
|
||||
}
|
||||
|
||||
// Create middleware
|
||||
middleware := NewMiddleware(bucketChecker, configGetter, nil)
|
||||
|
||||
// Create OPTIONS request
|
||||
req := httptest.NewRequest("OPTIONS", "/testbucket/testobject", nil)
|
||||
req = mux.SetURLVars(req, map[string]string{
|
||||
"bucket": "testbucket",
|
||||
"object": "testobject",
|
||||
})
|
||||
|
||||
// Set valid CORS headers
|
||||
req.Header.Set("Origin", "https://example.com")
|
||||
req.Header.Set("Access-Control-Request-Method", "GET")
|
||||
|
||||
// Create response recorder
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
// Execute HandleOptionsRequest
|
||||
middleware.HandleOptionsRequest(w, req)
|
||||
|
||||
// Check Response Status
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("Expected status 200, got %d", w.Code)
|
||||
}
|
||||
|
||||
// Check Vary header - verified to FAIL without fix
|
||||
if !hasVaryOrigin(w.Header()) {
|
||||
t.Errorf("Expected Vary: Origin header in OPTIONS response, but got %v", w.Header()["Vary"])
|
||||
}
|
||||
}
|
||||
|
||||
// hasVaryOrigin checks if the "Vary" header contains "Origin"
|
||||
func hasVaryOrigin(header http.Header) bool {
|
||||
varyHeaders := header["Vary"]
|
||||
for _, h := range varyHeaders {
|
||||
for _, v := range strings.Split(h, ",") {
|
||||
if strings.TrimSpace(v) == "Origin" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user