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 := ` GET * ` bodyNoXmlns := ` GET * ` 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 := ` GET http://www.example.com Authorization x-amz-request-id X-Amz-Request-Id 600 ` 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 := ` GET PUT * * ` 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 := ` GET PUT * ` 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 := ` GET PUT * * ` body := ` DELETE * * ` 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 := ` GET PUT * * ` newBody := ` HEAD * * ` 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 := ` GET PUT * * ` 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 }