diff --git a/api/router_mock_test.go b/api/router_mock_test.go index bd67f27..0b72813 100644 --- a/api/router_mock_test.go +++ b/api/router_mock_test.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "encoding/xml" + "fmt" "io" "net/http" "testing" @@ -32,12 +33,22 @@ func (p *poolStatisticMock) Statistic() pool.Statistic { } type centerMock struct { - t *testing.T - anon bool - attrs []object.Attribute + t *testing.T + anon bool + noAuthHeader bool + isError bool + attrs []object.Attribute } func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) { + if c.noAuthHeader { + return nil, middleware.ErrNoAuthorizationHeader + } + + if c.isError { + return nil, fmt.Errorf("some error") + } + var token *bearer.Token if !c.anon { @@ -86,14 +97,23 @@ func (r *middlewareSettingsMock) ACLEnabled() bool { } type frostFSIDMock struct { - tags map[string]string + tags map[string]string + validateError bool + userGroupsError bool } func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error { + if f.validateError { + return fmt.Errorf("some error") + } + return nil } func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) { + if f.userGroupsError { + return nil, nil, fmt.Errorf("some error") + } return []string{}, f.tags, nil } @@ -105,17 +125,21 @@ func (m *xmlMock) NewXMLDecoder(r io.Reader) *xml.Decoder { } type resourceTaggingMock struct { - bucketTags map[string]string - objectTags map[string]string - noSuchKey bool + bucketTags map[string]string + objectTags map[string]string + noSuchObjectKey bool + noSuchBucketKey bool } func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) { + if m.noSuchBucketKey { + return nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey) + } return m.bucketTags, nil } func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) { - if m.noSuchKey { + if m.noSuchObjectKey { return "", nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey) } return "", m.objectTags, nil @@ -215,9 +239,13 @@ func (h *handlerMock) PutObjectHandler(w http.ResponseWriter, r *http.Request) { h.writeResponse(w, res) } -func (h *handlerMock) DeleteObjectHandler(http.ResponseWriter, *http.Request) { - //TODO implement me - panic("implement me") +func (h *handlerMock) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) { + res := &handlerResult{ + Method: middleware.DeleteObjectOperation, + ReqInfo: middleware.GetReqInfo(r.Context()), + } + + h.writeResponse(w, res) } func (h *handlerMock) GetBucketLocationHandler(http.ResponseWriter, *http.Request) { diff --git a/api/router_test.go b/api/router_test.go index c181c53..8628606 100644 --- a/api/router_test.go +++ b/api/router_test.go @@ -43,7 +43,15 @@ func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) { m.router.ServeHTTP(w, r) } -func prepareRouter(t *testing.T) *routerMock { +type option func(*Config) + +func frostFSIDValidation(flag bool) option { + return func(cfg *Config) { + cfg.FrostFSIDValidation = flag + } +} + +func prepareRouter(t *testing.T, opts ...option) *routerMock { middlewareSettings := &middlewareSettingsMock{} policyChecker := inmemory.NewInMemoryLocalOverrides() @@ -72,6 +80,11 @@ func prepareRouter(t *testing.T) *routerMock { XMLDecoder: &xmlMock{}, Tagging: &resourceTaggingMock{}, } + + for _, o := range opts { + o(&cfg) + } + return &routerMock{ t: t, router: NewRouter(cfg), @@ -191,12 +204,25 @@ func TestPolicyChecker(t *testing.T) { // check we can access 'bucket' in default namespace putObject(chiRouter, ns1, bktName1, objName1, nil) + deleteObject(chiRouter, ns1, bktName1, objName1, nil) // check we can access 'other-bucket' in custom namespace putObject(chiRouter, ns2, bktName2, objName2, nil) + deleteObject(chiRouter, ns2, bktName2, objName2, nil) // check we cannot access 'bucket' in custom namespace putObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied) + deleteObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied) +} + +func TestPolicyCheckerError(t *testing.T) { + chiRouter := prepareRouter(t) + ns1, bktName1, objName1 := "", "bucket", "object" + putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apiErrors.ErrNoSuchBucket) + + chiRouter = prepareRouter(t) + chiRouter.cfg.FrostfsID.(*frostFSIDMock).userGroupsError = true + putObjectErr(chiRouter, ns1, bktName1, objName1, nil, apiErrors.ErrInternalError) } func TestPolicyCheckerReqTypeDetermination(t *testing.T) { @@ -507,6 +533,9 @@ func TestRequestTagsCheck(t *testing.T) { tagging, err = xml.Marshal(data.Tagging{TagSet: []data.Tag{{Key: "key", Value: tagValue}}}) require.NoError(t, err) putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrAccessDenied) + + tagging = nil + putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrMalformedXML) }) t.Run("put object with tag", func(t *testing.T) { @@ -588,7 +617,11 @@ func TestResourceTagsCheck(t *testing.T) { listObjectsV1Err(router, ns, bktName, "", "", "", apiErrors.ErrNoSuchBucket) - router.cfg.Tagging.(*resourceTaggingMock).noSuchKey = true + router.cfg.Tagging.(*resourceTaggingMock).noSuchBucketKey = true + createBucket(router, ns, bktName) + getBucketErr(router, ns, bktName, apiErrors.ErrNoSuchKey) + + router.cfg.Tagging.(*resourceTaggingMock).noSuchObjectKey = true createBucket(router, ns, bktName) getObjectErr(router, ns, bktName, objName, apiErrors.ErrNoSuchKey) }) @@ -719,6 +752,18 @@ func listBucketsBase(router *routerMock, namespace string) *httptest.ResponseRec return w } +func getBucketErr(router *routerMock, namespace, bktName string, errCode apiErrors.ErrorCode) { + w := getBucketBase(router, namespace, bktName) + assertAPIError(router.t, w, errCode) +} + +func getBucketBase(router *routerMock, namespace, bktName string) *httptest.ResponseRecorder { + w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/"+bktName, nil) + r.Header.Set(FrostfsNamespaceHeader, namespace) + router.ServeHTTP(w, r) + return w +} + func putObject(router *routerMock, namespace, bktName, objName string, tag *data.Tag) handlerResult { w := putObjectBase(router, namespace, bktName, objName, tag) resp := readResponse(router.t, w) @@ -744,6 +789,31 @@ func putObjectBase(router *routerMock, namespace, bktName, objName string, tag * return w } +func deleteObject(router *routerMock, namespace, bktName, objName string, tag *data.Tag) handlerResult { + w := deleteObjectBase(router, namespace, bktName, objName, tag) + resp := readResponse(router.t, w) + require.Equal(router.t, s3middleware.DeleteObjectOperation, resp.Method) + return resp +} + +func deleteObjectErr(router *routerMock, namespace, bktName, objName string, tag *data.Tag, errCode apiErrors.ErrorCode) { + w := deleteObjectBase(router, namespace, bktName, objName, tag) + assertAPIError(router.t, w, errCode) +} + +func deleteObjectBase(router *routerMock, namespace, bktName, objName string, tag *data.Tag) *httptest.ResponseRecorder { + w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodDelete, "/"+bktName+"/"+objName, nil) + if tag != nil { + queries := url.Values{ + tag.Key: []string{tag.Value}, + } + r.Header.Set(AmzTagging, queries.Encode()) + } + r.Header.Set(FrostfsNamespaceHeader, namespace) + router.ServeHTTP(w, r) + return w +} + func putBucketTagging(router *routerMock, namespace, bktName string, tagging []byte) handlerResult { w := putBucketTaggingBase(router, namespace, bktName, tagging) resp := readResponse(router.t, w) @@ -853,6 +923,35 @@ func TestBillingMetrics(t *testing.T) { require.Equal(t, "anon", dump.Requests[0].User) } +func TestAuthenticate(t *testing.T) { + chiRouter := prepareRouter(t) + createBucket(chiRouter, "", "bkt-1") + + chiRouter = prepareRouter(t) + chiRouter.cfg.Center.(*centerMock).noAuthHeader = true + createBucket(chiRouter, "", "bkt-2") + + chiRouter = prepareRouter(t) + chiRouter.cfg.Center.(*centerMock).isError = true + createBucketErr(chiRouter, "", "bkt-3", nil, apiErrors.ErrAccessDenied) +} + +func TestFrostFSIDValidation(t *testing.T) { + // successful frostFSID validation + chiRouter := prepareRouter(t, frostFSIDValidation(true)) + createBucket(chiRouter, "", "bkt-1") + + // anon request, skip frostFSID validation + chiRouter = prepareRouter(t, frostFSIDValidation(true)) + chiRouter.cfg.Center.(*centerMock).anon = true + createBucket(chiRouter, "", "bkt-2") + + // frostFSID validation failed + chiRouter = prepareRouter(t, frostFSIDValidation(true)) + chiRouter.cfg.FrostfsID.(*frostFSIDMock).validateError = true + createBucketErr(chiRouter, "", "bkt-3", nil, apiErrors.ErrInternalError) +} + func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult { var res handlerResult