package handler import ( "net/http" "testing" "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" ) func TestConditionalHead(t *testing.T) { tc := prepareHandlerContext(t) bktName, objName := "bucket-for-conditional", "object" _, objInfo := createBucketAndObject(tc, bktName, objName) w, r := prepareTestRequest(tc, bktName, objName, nil) tc.Handler().HeadObjectHandler(w, r) assertStatus(t, w, http.StatusOK) etag := w.Result().Header.Get(api.ETag) etagQuoted := "\"" + etag + "\"" headers := map[string]string{api.IfMatch: etag} headObject(t, tc, bktName, objName, headers, http.StatusOK) headers = map[string]string{api.IfMatch: etagQuoted} headObject(t, tc, bktName, objName, headers, http.StatusOK) headers = map[string]string{api.IfMatch: "etag"} headObject(t, tc, bktName, objName, headers, http.StatusPreconditionFailed) headers = map[string]string{api.IfUnmodifiedSince: objInfo.Created.Add(time.Minute).Format(http.TimeFormat)} headObject(t, tc, bktName, objName, headers, http.StatusOK) var zeroTime time.Time headers = map[string]string{api.IfUnmodifiedSince: zeroTime.UTC().Format(http.TimeFormat)} headObject(t, tc, bktName, objName, headers, http.StatusPreconditionFailed) headers = map[string]string{ api.IfMatch: etag, api.IfUnmodifiedSince: zeroTime.UTC().Format(http.TimeFormat), } headObject(t, tc, bktName, objName, headers, http.StatusOK) headers = map[string]string{api.IfNoneMatch: etag} headObject(t, tc, bktName, objName, headers, http.StatusNotModified) headers = map[string]string{api.IfNoneMatch: etagQuoted} headObject(t, tc, bktName, objName, headers, http.StatusNotModified) headers = map[string]string{api.IfNoneMatch: "etag"} headObject(t, tc, bktName, objName, headers, http.StatusOK) headers = map[string]string{api.IfModifiedSince: zeroTime.UTC().Format(http.TimeFormat)} headObject(t, tc, bktName, objName, headers, http.StatusOK) headers = map[string]string{api.IfModifiedSince: time.Now().Add(time.Minute).UTC().Format(http.TimeFormat)} headObject(t, tc, bktName, objName, headers, http.StatusNotModified) headers = map[string]string{ api.IfNoneMatch: etag, api.IfModifiedSince: zeroTime.UTC().Format(http.TimeFormat), } headObject(t, tc, bktName, objName, headers, http.StatusNotModified) } func headObject(t *testing.T, tc *handlerContext, bktName, objName string, headers map[string]string, status int) { w, r := prepareTestRequest(tc, bktName, objName, nil) for key, val := range headers { r.Header.Set(key, val) } tc.Handler().HeadObjectHandler(w, r) assertStatus(t, w, status) } func TestInvalidAccessThroughCache(t *testing.T) { hc := prepareHandlerContext(t) bktName, objName := "bucket-for-cache", "obj-for-cache" bktInfo, _ := createBucketAndObject(hc, bktName, objName) setContainerEACL(hc, bktInfo.CID) headObject(t, hc, bktName, objName, nil, http.StatusOK) w, r := prepareTestRequest(hc, bktName, objName, nil) hc.Handler().HeadObjectHandler(w, r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: newTestAccessBox(t, nil)}))) assertStatus(t, w, http.StatusForbidden) } func setContainerEACL(hc *handlerContext, cnrID cid.ID) { table := eacl.NewTable() table.SetCID(cnrID) for _, op := range fullOps { table.AddRecord(getOthersRecord(op, eacl.ActionDeny)) } err := hc.MockedPool().SetContainerEACL(hc.Context(), *table, nil) require.NoError(hc.t, err) } func TestHeadObject(t *testing.T) { hc := prepareHandlerContextWithMinCache(t) bktName, objName := "bucket", "obj" bktInfo, objInfo := createVersionedBucketAndObject(hc.t, hc, bktName, objName) putObject(hc, bktName, objName) checkFound(hc.t, hc, bktName, objName, objInfo.VersionID()) checkFound(hc.t, hc, bktName, objName, emptyVersion) addr := getAddressOfLastVersion(hc, bktInfo, objName) hc.tp.SetObjectError(addr, &apistatus.ObjectNotFound{}) hc.tp.SetObjectError(objInfo.Address(), &apistatus.ObjectNotFound{}) headObjectAssertS3Error(hc, bktName, objName, objInfo.VersionID(), s3errors.ErrNoSuchVersion) headObjectAssertS3Error(hc, bktName, objName, emptyVersion, s3errors.ErrNoSuchKey) } func TestIsAvailableToResolve(t *testing.T) { list := []string{"container", "s3"} for i, testCase := range [...]struct { isAllowList bool list []string zone string expected bool }{ {isAllowList: true, list: list, zone: "container", expected: true}, {isAllowList: true, list: list, zone: "sftp", expected: false}, {isAllowList: false, list: list, zone: "s3", expected: false}, {isAllowList: false, list: list, zone: "system", expected: true}, {isAllowList: true, list: list, zone: "", expected: false}, } { result := isAvailableToResolve(testCase.zone, testCase.list, testCase.isAllowList) require.Equal(t, testCase.expected, result, "case %d", i+1) } } func newTestAccessBox(t *testing.T, key *keys.PrivateKey) *accessbox.Box { var err error if key == nil { key, err = keys.NewPrivateKey() require.NoError(t, err) } var btoken bearer.Token btoken.SetEACLTable(*eacl.NewTable()) err = btoken.Sign(key.PrivateKey) require.NoError(t, err) return &accessbox.Box{ Gate: &accessbox.GateData{ BearerToken: &btoken, }, } }