mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 06:20:48 +02:00
fix(s3): set Access-Control-Allow-Credentials on CORS responses (#11886)
* fix(s3): set Access-Control-Allow-Credentials on CORS responses (fixes #11885) * fix(s3): expand wildcard exposed headers for credentialed CORS responses * fix(s3): keep wildcard in exposed headers for non-credentialed CORS requests * fix(s3): expose x-amz-tagging-count on credentialed CORS responses --------- Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
This commit is contained in:
1 parent
8c3d8fcea0
commit
e73c23a4ee
2 files changed
+98
-26
No files matched your search
+47
-3
@@ -235,13 +235,15 @@ func buildPreflightResponse(rule *CORSRule, corsReq *CORSRequest) *CORSResponse
|
||||
|
||||
// Set exposed headers
|
||||
if len(rule.ExposeHeaders) > 0 {
|
||||
response.ExposeHeaders = strings.Join(rule.ExposeHeaders, ", ")
|
||||
response.ExposeHeaders = formatExposeHeaders(rule.ExposeHeaders)
|
||||
}
|
||||
|
||||
// Set max age
|
||||
if rule.MaxAgeSeconds != nil {
|
||||
response.MaxAge = strconv.Itoa(*rule.MaxAgeSeconds)
|
||||
}
|
||||
|
||||
response.AllowCredentials = true
|
||||
}
|
||||
|
||||
return response
|
||||
@@ -250,7 +252,8 @@ func buildPreflightResponse(rule *CORSRule, corsReq *CORSRequest) *CORSResponse
|
||||
// buildResponse builds a CORS response from a matching rule
|
||||
func buildResponse(rule *CORSRule, corsReq *CORSRequest) *CORSResponse {
|
||||
response := &CORSResponse{
|
||||
AllowOrigin: corsReq.Origin,
|
||||
AllowOrigin: corsReq.Origin,
|
||||
AllowCredentials: true,
|
||||
}
|
||||
|
||||
// Set allowed methods
|
||||
@@ -263,7 +266,7 @@ func buildResponse(rule *CORSRule, corsReq *CORSRequest) *CORSResponse {
|
||||
|
||||
// Set expose headers
|
||||
if len(rule.ExposeHeaders) > 0 {
|
||||
response.ExposeHeaders = strings.Join(rule.ExposeHeaders, ", ")
|
||||
response.ExposeHeaders = formatExposeHeaders(rule.ExposeHeaders)
|
||||
}
|
||||
|
||||
// Set max age
|
||||
@@ -274,6 +277,47 @@ func buildResponse(rule *CORSRule, corsReq *CORSRequest) *CORSResponse {
|
||||
return response
|
||||
}
|
||||
|
||||
// defaultExposeHeaders are emitted alongside "*" so credentialed CORS requests
|
||||
// can read standard response headers ("*" is treated literally when credentials
|
||||
// are included, per the Fetch spec).
|
||||
var defaultExposeHeaders = []string{
|
||||
"ETag",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Last-Modified",
|
||||
"Content-Range",
|
||||
"x-amz-request-id",
|
||||
"x-amz-version-id",
|
||||
"x-amz-tagging-count",
|
||||
}
|
||||
|
||||
// formatExposeHeaders formats the exposed headers slice into a comma-separated string.
|
||||
func formatExposeHeaders(exposeHeaders []string) string {
|
||||
var headers []string
|
||||
hasWildcard := false
|
||||
seen := map[string]bool{}
|
||||
for _, h := range exposeHeaders {
|
||||
if h == "*" {
|
||||
hasWildcard = true
|
||||
continue
|
||||
}
|
||||
if lower := strings.ToLower(h); !seen[lower] {
|
||||
seen[lower] = true
|
||||
headers = append(headers, h)
|
||||
}
|
||||
}
|
||||
if hasWildcard {
|
||||
for _, h := range defaultExposeHeaders {
|
||||
if lower := strings.ToLower(h); !seen[lower] {
|
||||
seen[lower] = true
|
||||
headers = append(headers, h)
|
||||
}
|
||||
}
|
||||
headers = append(headers, "*")
|
||||
}
|
||||
return strings.Join(headers, ", ")
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// matchesOrigin checks if the request origin matches any allowed origin
|
||||
|
||||
@@ -405,11 +405,12 @@ func TestEvaluateRequest(t *testing.T) {
|
||||
Method: "GET",
|
||||
},
|
||||
want: &CORSResponse{
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET, POST",
|
||||
AllowHeaders: "Content-Type",
|
||||
ExposeHeaders: "ETag",
|
||||
MaxAge: "3600",
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET, POST",
|
||||
AllowHeaders: "Content-Type",
|
||||
ExposeHeaders: "ETag",
|
||||
MaxAge: "3600",
|
||||
AllowCredentials: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -421,8 +422,9 @@ func TestEvaluateRequest(t *testing.T) {
|
||||
Method: "PUT",
|
||||
},
|
||||
want: &CORSResponse{
|
||||
AllowOrigin: "http://other.com",
|
||||
AllowMethods: "PUT",
|
||||
AllowOrigin: "http://other.com",
|
||||
AllowMethods: "PUT",
|
||||
AllowCredentials: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -447,11 +449,12 @@ func TestEvaluateRequest(t *testing.T) {
|
||||
AccessControlRequestHeaders: []string{"Content-Type"},
|
||||
},
|
||||
want: &CORSResponse{
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET, POST",
|
||||
AllowHeaders: "Content-Type",
|
||||
ExposeHeaders: "ETag",
|
||||
MaxAge: "3600",
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET, POST",
|
||||
AllowHeaders: "Content-Type",
|
||||
ExposeHeaders: "ETag",
|
||||
MaxAge: "3600",
|
||||
AllowCredentials: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -467,7 +470,30 @@ func TestEvaluateRequest(t *testing.T) {
|
||||
},
|
||||
want: &CORSResponse{
|
||||
AllowOrigin: "http://example.com",
|
||||
// No AllowMethods or AllowHeaders because the requested header is forbidden
|
||||
// No AllowMethods, AllowHeaders, or AllowCredentials because the requested header is forbidden
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "wildcard expose headers expands for credentialed responses",
|
||||
config: &CORSConfiguration{
|
||||
CORSRules: []CORSRule{
|
||||
{
|
||||
AllowedOrigins: []string{"http://example.com"},
|
||||
AllowedMethods: []string{"GET"},
|
||||
ExposeHeaders: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
corsReq: &CORSRequest{
|
||||
Origin: "http://example.com",
|
||||
Method: "GET",
|
||||
},
|
||||
want: &CORSResponse{
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET",
|
||||
ExposeHeaders: "ETag, Content-Length, Content-Type, Last-Modified, Content-Range, x-amz-request-id, x-amz-version-id, x-amz-tagging-count, *",
|
||||
AllowCredentials: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
@@ -511,18 +537,20 @@ func TestApplyHeaders(t *testing.T) {
|
||||
{
|
||||
name: "complete response",
|
||||
corsResp: &CORSResponse{
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET, POST",
|
||||
AllowHeaders: "Content-Type",
|
||||
ExposeHeaders: "ETag",
|
||||
MaxAge: "3600",
|
||||
AllowOrigin: "http://example.com",
|
||||
AllowMethods: "GET, POST",
|
||||
AllowHeaders: "Content-Type",
|
||||
ExposeHeaders: "ETag",
|
||||
MaxAge: "3600",
|
||||
AllowCredentials: true,
|
||||
},
|
||||
want: map[string]string{
|
||||
"Access-Control-Allow-Origin": "http://example.com",
|
||||
"Access-Control-Allow-Methods": "GET, POST",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
"Access-Control-Expose-Headers": "ETag",
|
||||
"Access-Control-Max-Age": "3600",
|
||||
"Access-Control-Allow-Origin": "http://example.com",
|
||||
"Access-Control-Allow-Methods": "GET, POST",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
"Access-Control-Expose-Headers": "ETag",
|
||||
"Access-Control-Max-Age": "3600",
|
||||
"Access-Control-Allow-Credentials": "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user