All checks were successful
/ DCO (pull_request) Successful in 35s
/ Builds (pull_request) Successful in 1m8s
/ Vulncheck (pull_request) Successful in 1m8s
/ OCI image (pull_request) Successful in 2m5s
/ Lint (pull_request) Successful in 3m34s
/ Tests (pull_request) Successful in 1m18s
/ Vulncheck (push) Successful in 1m10s
/ Builds (push) Successful in 1m5s
/ OCI image (push) Successful in 1m52s
/ Lint (push) Successful in 3m3s
/ Tests (push) Successful in 1m24s
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
1057 lines
32 KiB
Go
1057 lines
32 KiB
Go
package handler
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/base64"
|
|
"encoding/xml"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestCORSOriginWildcard(t *testing.T) {
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
bodyNoXmlns := `
|
|
<CORSConfiguration>
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
</CORSRule>
|
|
</CORSConfiguration>`
|
|
hc := prepareHandlerContextWithMinCache(t)
|
|
|
|
bktName := "bucket-for-cors"
|
|
box, _ := createAccessBox(t)
|
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
|
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
|
|
r = r.WithContext(ctx)
|
|
r.Header.Add(api.AmzACL, "public-read")
|
|
hc.Handler().CreateBucketHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
putBucketCORS(hc, bktName, body)
|
|
|
|
getBucketCORS(hc, bktName)
|
|
|
|
hc.config.useDefaultXMLNS = true
|
|
putBucketCORS(hc, bktName, bodyNoXmlns)
|
|
|
|
getBucketCORS(hc, bktName)
|
|
}
|
|
|
|
func TestPreflight(t *testing.T) {
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedOrigin>http://www.example.com</AllowedOrigin>
|
|
<AllowedHeader>Authorization</AllowedHeader>
|
|
<ExposeHeader>x-amz-request-id</ExposeHeader>
|
|
<ExposeHeader>X-Amz-Request-Id</ExposeHeader>
|
|
<MaxAgeSeconds>600</MaxAgeSeconds>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-preflight-test"
|
|
box, _ := createAccessBox(t)
|
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
|
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
|
|
r = r.WithContext(ctx)
|
|
hc.Handler().CreateBucketHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
w, r = prepareTestPayloadRequest(hc, bktName, "", strings.NewReader(body))
|
|
ctx = middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
|
|
r = r.WithContext(ctx)
|
|
hc.Handler().PutBucketCorsHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
origin string
|
|
method string
|
|
headers string
|
|
expectedStatus int
|
|
}{
|
|
{
|
|
name: "Valid",
|
|
origin: "http://www.example.com",
|
|
method: "GET",
|
|
headers: "Authorization",
|
|
expectedStatus: http.StatusOK,
|
|
},
|
|
{
|
|
name: "Empty origin",
|
|
method: "GET",
|
|
headers: "Authorization",
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Empty request method",
|
|
origin: "http://www.example.com",
|
|
headers: "Authorization",
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Not allowed method",
|
|
origin: "http://www.example.com",
|
|
method: "PUT",
|
|
headers: "Authorization",
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "Not allowed headers",
|
|
origin: "http://www.example.com",
|
|
method: "GET",
|
|
headers: "Authorization, Last-Modified",
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w, r = prepareTestPayloadRequest(hc, bktName, "", nil)
|
|
r.Header.Set(api.Origin, tc.origin)
|
|
r.Header.Set(api.AccessControlRequestMethod, tc.method)
|
|
r.Header.Set(api.AccessControlRequestHeaders, tc.headers)
|
|
hc.Handler().Preflight(w, r)
|
|
assertStatus(t, w, tc.expectedStatus)
|
|
|
|
if tc.expectedStatus == http.StatusOK {
|
|
require.Equal(t, tc.origin, w.Header().Get(api.AccessControlAllowOrigin))
|
|
require.Equal(t, tc.method, w.Header().Get(api.AccessControlAllowMethods))
|
|
require.Equal(t, tc.headers, w.Header().Get(api.AccessControlAllowHeaders))
|
|
require.Equal(t, "x-amz-request-id, X-Amz-Request-Id", w.Header().Get(api.AccessControlExposeHeaders))
|
|
require.Equal(t, "true", w.Header().Get(api.AccessControlAllowCredentials))
|
|
require.Equal(t, "600", w.Header().Get(api.AccessControlMaxAge))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreflightWildcardOrigin(t *testing.T) {
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedMethod>PUT</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
<AllowedHeader>*</AllowedHeader>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-preflight-wildcard-test"
|
|
box, _ := createAccessBox(t)
|
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
|
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
|
|
r = r.WithContext(ctx)
|
|
hc.Handler().CreateBucketHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
putBucketCORS(hc, bktName, body)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
origin string
|
|
method string
|
|
headers string
|
|
expectedStatus int
|
|
}{
|
|
{
|
|
name: "Valid get",
|
|
origin: "http://www.example.com",
|
|
method: "GET",
|
|
headers: "Authorization, Last-Modified",
|
|
expectedStatus: http.StatusOK,
|
|
},
|
|
{
|
|
name: "Valid put",
|
|
origin: "http://example.com",
|
|
method: "PUT",
|
|
headers: "Authorization, Content-Type",
|
|
expectedStatus: http.StatusOK,
|
|
},
|
|
{
|
|
name: "Empty origin",
|
|
method: "GET",
|
|
headers: "Authorization, Last-Modified",
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Empty request method",
|
|
origin: "http://www.example.com",
|
|
headers: "Authorization, Last-Modified",
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "Not allowed method",
|
|
origin: "http://www.example.com",
|
|
method: "DELETE",
|
|
headers: "Authorization, Last-Modified",
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w, r = prepareTestPayloadRequest(hc, bktName, "", nil)
|
|
r.Header.Set(api.Origin, tc.origin)
|
|
r.Header.Set(api.AccessControlRequestMethod, tc.method)
|
|
r.Header.Set(api.AccessControlRequestHeaders, tc.headers)
|
|
hc.Handler().Preflight(w, r)
|
|
assertStatus(t, w, tc.expectedStatus)
|
|
|
|
if tc.expectedStatus == http.StatusOK {
|
|
require.Equal(t, tc.origin, w.Header().Get(api.AccessControlAllowOrigin))
|
|
require.Equal(t, tc.method, w.Header().Get(api.AccessControlAllowMethods))
|
|
require.Equal(t, tc.headers, w.Header().Get(api.AccessControlAllowHeaders))
|
|
require.Empty(t, w.Header().Get(api.AccessControlExposeHeaders))
|
|
require.Empty(t, w.Header().Get(api.AccessControlAllowCredentials))
|
|
require.Equal(t, "0", w.Header().Get(api.AccessControlMaxAge))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAppendCORSHeadersWildcardOrigin(t *testing.T) {
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedMethod>PUT</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-append-cors-headers-wildcard-test"
|
|
box, _ := createAccessBox(t)
|
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
|
ctx := middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box})
|
|
r = r.WithContext(ctx)
|
|
hc.Handler().CreateBucketHandler(w, r)
|
|
assertStatus(t, w, http.StatusOK)
|
|
|
|
putBucketCORS(hc, bktName, body)
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
requestHeaders map[string]string
|
|
expectedHeaders map[string]string
|
|
}{
|
|
{
|
|
name: "Valid get",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://www.example.com",
|
|
api.AccessControlRequestMethod: "GET",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "*",
|
|
api.AccessControlAllowCredentials: "",
|
|
api.Vary: "",
|
|
api.AccessControlAllowMethods: "GET, PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "Valid get with Authorization",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://www.example.com",
|
|
api.AccessControlRequestMethod: "GET",
|
|
api.Authorization: "value",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "http://www.example.com",
|
|
api.AccessControlAllowCredentials: "true",
|
|
api.Vary: api.Origin,
|
|
api.AccessControlAllowMethods: "GET, PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "Empty origin",
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowCredentials: "",
|
|
api.Vary: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "Empty request method",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://www.example.com",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "*",
|
|
api.AccessControlAllowCredentials: "",
|
|
api.Vary: "",
|
|
api.AccessControlAllowMethods: "GET, PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "Not allowed method",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://www.example.com",
|
|
api.AccessControlRequestMethod: "DELETE",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowCredentials: "",
|
|
api.Vary: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w, r = prepareTestPayloadRequest(hc, bktName, "", nil)
|
|
for k, v := range tc.requestHeaders {
|
|
r.Header.Set(k, v)
|
|
}
|
|
hc.Handler().AppendCORSHeaders(w, r)
|
|
|
|
for k, v := range tc.expectedHeaders {
|
|
require.Equal(t, v, w.Header().Get(k))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetLatestCORSVersion(t *testing.T) {
|
|
bodyTree := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedMethod>PUT</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
<AllowedHeader>*</AllowedHeader>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>DELETE</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
<AllowedHeader>*</AllowedHeader>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
hc := prepareHandlerContextWithMinCache(t)
|
|
|
|
bktName := "bucket-get-latest-cors"
|
|
info := createBucket(hc, bktName)
|
|
|
|
addCORSToTree(hc, bodyTree, info.BktInfo, info.BktInfo.CID)
|
|
|
|
w := getBucketCORS(hc, bktName)
|
|
requireEqualCORS(hc.t, bodyTree, w.Body.String())
|
|
|
|
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, body)
|
|
|
|
w = getBucketCORS(hc, bktName)
|
|
requireEqualCORS(hc.t, body, w.Body.String())
|
|
|
|
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, bodyTree)
|
|
w = getBucketCORS(hc, bktName)
|
|
requireEqualCORS(hc.t, bodyTree, w.Body.String())
|
|
}
|
|
|
|
func TestDeleteCORSVersions(t *testing.T) {
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedMethod>PUT</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
<AllowedHeader>*</AllowedHeader>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
newBody := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>HEAD</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
<AllowedHeader>*</AllowedHeader>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-delete-tree-cors-versions"
|
|
info := createBucket(hc, bktName)
|
|
|
|
addCORSToTree(hc, body, info.BktInfo, info.BktInfo.CID)
|
|
addCORSToTree(hc, body, info.BktInfo, hc.corsCnrID)
|
|
require.Len(t, hc.tp.Objects(), 2)
|
|
|
|
putBucketCORS(hc, bktName, body)
|
|
require.Len(t, hc.tp.Objects(), 1)
|
|
require.Equal(t, body, string(hc.tp.Objects()[0].Payload()))
|
|
|
|
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, body)
|
|
require.Len(t, hc.tp.Objects(), 2)
|
|
|
|
putBucketCORS(hc, bktName, newBody)
|
|
require.Len(t, hc.tp.Objects(), 1)
|
|
require.Equal(t, newBody, string(hc.tp.Objects()[0].Payload()))
|
|
|
|
addCORSToTree(hc, body, info.BktInfo, info.BktInfo.CID)
|
|
addCORSToTree(hc, body, info.BktInfo, hc.corsCnrID)
|
|
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, body)
|
|
require.Len(t, hc.tp.Objects(), 4)
|
|
|
|
deleteBucketCORS(hc, bktName)
|
|
require.Len(t, hc.tp.Objects(), 0)
|
|
}
|
|
|
|
func TestDeleteCORSInDeleteBucket(t *testing.T) {
|
|
body := `
|
|
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
<CORSRule>
|
|
<AllowedMethod>GET</AllowedMethod>
|
|
<AllowedMethod>PUT</AllowedMethod>
|
|
<AllowedOrigin>*</AllowedOrigin>
|
|
<AllowedHeader>*</AllowedHeader>
|
|
</CORSRule>
|
|
</CORSConfiguration>
|
|
`
|
|
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-delete-cors-in-delete-bucket"
|
|
info := createBucket(hc, bktName)
|
|
|
|
addCORSToTree(hc, body, info.BktInfo, hc.corsCnrID)
|
|
addCORSToTree(hc, body, info.BktInfo, info.BktInfo.CID)
|
|
hc.tp.AddCORSObject(info.BktInfo, hc.corsCnrID, body)
|
|
require.Len(t, hc.tp.Objects(), 3)
|
|
|
|
hc.owner = info.BktInfo.Owner
|
|
deleteBucket(t, hc, bktName, http.StatusNoContent)
|
|
require.Len(t, hc.tp.Objects(), 1) // CORS object in bucket container is not deleted
|
|
}
|
|
|
|
func TestAllowedOriginWildcards(t *testing.T) {
|
|
hc := prepareHandlerContext(t)
|
|
bktName := "bucket-allowed-origin-wildcards"
|
|
createBucket(hc, bktName)
|
|
|
|
cfg := &data.CORSConfiguration{
|
|
CORSRules: []data.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"*suffix.example"},
|
|
AllowedMethods: []string{"PUT"},
|
|
},
|
|
{
|
|
AllowedOrigins: []string{"https://*example"},
|
|
AllowedMethods: []string{"PUT"},
|
|
},
|
|
{
|
|
AllowedOrigins: []string{"prefix.example*"},
|
|
AllowedMethods: []string{"PUT"},
|
|
},
|
|
},
|
|
}
|
|
body, err := xml.Marshal(cfg)
|
|
require.NoError(t, err)
|
|
putBucketCORS(hc, bktName, string(body))
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
handler func(w http.ResponseWriter, r *http.Request)
|
|
requestHeaders map[string]string
|
|
expectedHeaders map[string]string
|
|
expectedStatus int
|
|
}{
|
|
{
|
|
name: "append cors headers, empty request cors headers",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, invalid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://origin.com",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, first rule, no symbols in place of wildcard",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "suffix.example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "suffix.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, first rule, valid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://suffix.example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "http://suffix.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, first rule, invalid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://suffix-example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, second rule, no symbols in place of wildcard",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, second rule, valid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://www.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, second rule, invalid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, third rule, no symbols in place of wildcard",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "prefix.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, third rule, valid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example.com",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "prefix.example.com",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, third rule, invalid origin",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "www.prefix.example",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, third rule, invalid request method in header",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example.com",
|
|
api.AccessControlRequestMethod: "GET",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
},
|
|
{
|
|
name: "append cors headers, third rule, valid request method in header",
|
|
handler: hc.Handler().AppendCORSHeaders,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example.com",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "prefix.example.com",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "preflight, empty request cors headers",
|
|
handler: hc.Handler().Preflight,
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
expectedStatus: http.StatusBadRequest,
|
|
},
|
|
{
|
|
name: "preflight, invalid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://origin.com",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "preflight, first rule, no symbols in place of wildcard",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "suffix.example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "suffix.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "prelight, first rule, valid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://suffix.example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "http://suffix.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "preflight, first rule, invalid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "http://suffix-example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "preflight, second rule, no symbols in place of wildcard",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "preflight, second rule, valid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://www.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "preflight, second rule, invalid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "preflight, third rule, no symbols in place of wildcard",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "prefix.example",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "preflight, third rule, valid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example.com",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "prefix.example.com",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
{
|
|
name: "preflight, third rule, invalid origin",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "www.prefix.example",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "preflight, third rule, invalid request method in header",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example.com",
|
|
api.AccessControlRequestMethod: "GET",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "preflight, third rule, valid request method in header",
|
|
handler: hc.Handler().Preflight,
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "prefix.example.com",
|
|
api.AccessControlRequestMethod: "PUT",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "prefix.example.com",
|
|
api.AccessControlAllowMethods: "PUT",
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
|
for k, v := range tc.requestHeaders {
|
|
r.Header.Set(k, v)
|
|
}
|
|
|
|
tc.handler(w, r)
|
|
|
|
expectedStatus := http.StatusOK
|
|
if tc.expectedStatus != 0 {
|
|
expectedStatus = tc.expectedStatus
|
|
}
|
|
require.Equal(t, expectedStatus, w.Code)
|
|
for k, v := range tc.expectedHeaders {
|
|
require.Equal(t, v, w.Header().Get(k))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAllowedHeaderWildcards(t *testing.T) {
|
|
hc := prepareHandlerContext(t)
|
|
bktName := "bucket-allowed-header-wildcards"
|
|
createBucket(hc, bktName)
|
|
|
|
cfg := &data.CORSConfiguration{
|
|
CORSRules: []data.CORSRule{
|
|
{
|
|
AllowedOrigins: []string{"https://www.example.com"},
|
|
AllowedMethods: []string{"HEAD"},
|
|
AllowedHeaders: []string{"*-suffix"},
|
|
},
|
|
{
|
|
AllowedOrigins: []string{"https://www.example.com"},
|
|
AllowedMethods: []string{"HEAD"},
|
|
AllowedHeaders: []string{"start-*-end"},
|
|
},
|
|
{
|
|
AllowedOrigins: []string{"https://www.example.com"},
|
|
AllowedMethods: []string{"HEAD"},
|
|
AllowedHeaders: []string{"X-Amz-*"},
|
|
},
|
|
},
|
|
}
|
|
body, err := xml.Marshal(cfg)
|
|
require.NoError(t, err)
|
|
putBucketCORS(hc, bktName, string(body))
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
requestHeaders map[string]string
|
|
expectedHeaders map[string]string
|
|
expectedStatus int
|
|
}{
|
|
{
|
|
name: "first rule, valid headers",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "header-suffix, -suffix",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://www.example.com",
|
|
api.AccessControlAllowMethods: "HEAD",
|
|
api.AccessControlAllowHeaders: "header-suffix, -suffix",
|
|
},
|
|
},
|
|
{
|
|
name: "first rule, invalid headers",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "header-suffix-*",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
api.AccessControlAllowHeaders: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "second rule, valid headers",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "start--end, start-header-end",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://www.example.com",
|
|
api.AccessControlAllowMethods: "HEAD",
|
|
api.AccessControlAllowHeaders: "start--end, start-header-end",
|
|
},
|
|
},
|
|
{
|
|
name: "second rule, invalid header ending",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "start-header-end-*",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
api.AccessControlAllowHeaders: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "second rule, invalid header beginning",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "*-start-header-end",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
api.AccessControlAllowHeaders: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
{
|
|
name: "third rule, valid headers",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "X-Amz-Date, X-Amz-Content-Sha256",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "https://www.example.com",
|
|
api.AccessControlAllowMethods: "HEAD",
|
|
api.AccessControlAllowHeaders: "X-Amz-Date, X-Amz-Content-Sha256",
|
|
},
|
|
},
|
|
{
|
|
name: "third rule, invalid headers",
|
|
requestHeaders: map[string]string{
|
|
api.Origin: "https://www.example.com",
|
|
api.AccessControlRequestMethod: "HEAD",
|
|
api.AccessControlRequestHeaders: "Authorization",
|
|
},
|
|
expectedHeaders: map[string]string{
|
|
api.AccessControlAllowOrigin: "",
|
|
api.AccessControlAllowMethods: "",
|
|
api.AccessControlAllowHeaders: "",
|
|
},
|
|
expectedStatus: http.StatusForbidden,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w, r := prepareTestRequest(hc, bktName, "", nil)
|
|
for k, v := range tc.requestHeaders {
|
|
r.Header.Set(k, v)
|
|
}
|
|
|
|
hc.Handler().Preflight(w, r)
|
|
|
|
expectedStatus := http.StatusOK
|
|
if tc.expectedStatus != 0 {
|
|
expectedStatus = tc.expectedStatus
|
|
}
|
|
require.Equal(t, expectedStatus, w.Code)
|
|
for k, v := range tc.expectedHeaders {
|
|
require.Equal(t, v, w.Header().Get(k))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func addCORSToTree(hc *handlerContext, cors string, bkt *data.BucketInfo, corsCnrID cid.ID) {
|
|
var addr oid.Address
|
|
addr.SetContainer(corsCnrID)
|
|
addr.SetObject(oidtest.ID())
|
|
|
|
var obj object.Object
|
|
obj.SetPayload([]byte(cors))
|
|
obj.SetPayloadSize(uint64(len(cors)))
|
|
|
|
hc.tp.SetObject(addr, &obj)
|
|
|
|
meta := make(map[string]string)
|
|
meta["FileName"] = "bucket-cors"
|
|
meta["OID"] = addr.Object().EncodeToString()
|
|
meta["CID"] = addr.Container().EncodeToString()
|
|
|
|
_, err := hc.treeMock.AddNode(hc.context, bkt, "system", 0, meta)
|
|
require.NoError(hc.t, err)
|
|
}
|
|
|
|
func TestPutBucketCORSCopiesNumbers(t *testing.T) {
|
|
hc := prepareHandlerContext(t)
|
|
|
|
bktName := "bucket-cors"
|
|
createBucket(hc, bktName)
|
|
|
|
cfg := &data.CORSConfiguration{
|
|
CORSRules: []data.CORSRule{{
|
|
AllowedHeaders: []string{"*"},
|
|
AllowedMethods: []string{"GET"},
|
|
AllowedOrigins: []string{"*"},
|
|
}},
|
|
}
|
|
|
|
hc.config.corsCopiesNumbers = []uint32{1}
|
|
hc.config.copiesNumbers = map[string][]uint32{"default": {2}}
|
|
|
|
putBucketCORSConfiguration(hc, bktName, cfg, map[string]string{"X-Amz-Meta-Frostfs-Copies-Number": "3"}, true)
|
|
|
|
objs := hc.tp.Objects()
|
|
require.Len(t, objs, 1)
|
|
require.EqualValues(t, hc.config.corsCopiesNumbers, hc.tp.CopiesNumbers(addrFromObject(objs[0]).EncodeToString()))
|
|
}
|
|
|
|
func requireEqualCORS(t *testing.T, expected string, actual string) {
|
|
expectedCORS := &data.CORSConfiguration{}
|
|
err := xml.NewDecoder(strings.NewReader(expected)).Decode(expectedCORS)
|
|
require.NoError(t, err)
|
|
|
|
actualCORS := &data.CORSConfiguration{}
|
|
err = xml.NewDecoder(strings.NewReader(actual)).Decode(actualCORS)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, expectedCORS, actualCORS)
|
|
}
|
|
|
|
func putBucketCORS(hc *handlerContext, bktName string, body string) {
|
|
w, r := prepareTestPayloadRequest(hc, bktName, "", strings.NewReader(body))
|
|
box, _ := createAccessBox(hc.t)
|
|
r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}))
|
|
hc.Handler().PutBucketCorsHandler(w, r)
|
|
assertStatus(hc.t, w, http.StatusOK)
|
|
}
|
|
|
|
func deleteBucketCORS(hc *handlerContext, bktName string) {
|
|
w, r := prepareTestPayloadRequest(hc, bktName, "", nil)
|
|
box, _ := createAccessBox(hc.t)
|
|
r = r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: box}))
|
|
hc.Handler().DeleteBucketCorsHandler(w, r)
|
|
assertStatus(hc.t, w, http.StatusNoContent)
|
|
}
|
|
|
|
func getBucketCORS(hc *handlerContext, bktName string) *httptest.ResponseRecorder {
|
|
w, r := prepareTestPayloadRequest(hc, bktName, "", nil)
|
|
hc.Handler().GetBucketCorsHandler(w, r)
|
|
assertStatus(hc.t, w, http.StatusOK)
|
|
return w
|
|
}
|
|
|
|
func putBucketCORSConfiguration(hc *handlerContext, bktName string, cfg *data.CORSConfiguration, headers map[string]string, addMD5 bool) {
|
|
w := putBucketCORSConfigurationBase(hc, bktName, cfg, headers, addMD5)
|
|
assertStatus(hc.t, w, http.StatusOK)
|
|
}
|
|
|
|
func putBucketCORSConfigurationBase(hc *handlerContext, bktName string, cfg *data.CORSConfiguration, headers map[string]string, addMD5 bool) *httptest.ResponseRecorder {
|
|
w, r := prepareTestRequest(hc, bktName, "", cfg)
|
|
|
|
for k, v := range headers {
|
|
r.Header.Set(k, v)
|
|
}
|
|
|
|
if addMD5 {
|
|
rawBody, err := xml.Marshal(cfg)
|
|
require.NoError(hc.t, err)
|
|
|
|
hash := md5.New()
|
|
hash.Write(rawBody)
|
|
r.Header.Set(api.ContentMD5, base64.StdEncoding.EncodeToString(hash.Sum(nil)))
|
|
}
|
|
|
|
hc.Handler().PutBucketCorsHandler(w, r)
|
|
return w
|
|
}
|