forked from TrueCloudLab/frostfs-s3-gw
[#387] api: Add tests for middleware
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
parent
f4d174e740
commit
21dbe3ea8e
2 changed files with 140 additions and 13 deletions
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -32,12 +33,22 @@ func (p *poolStatisticMock) Statistic() pool.Statistic {
|
||||||
}
|
}
|
||||||
|
|
||||||
type centerMock struct {
|
type centerMock struct {
|
||||||
t *testing.T
|
t *testing.T
|
||||||
anon bool
|
anon bool
|
||||||
attrs []object.Attribute
|
noAuthHeader bool
|
||||||
|
isError bool
|
||||||
|
attrs []object.Attribute
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
|
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
|
var token *bearer.Token
|
||||||
|
|
||||||
if !c.anon {
|
if !c.anon {
|
||||||
|
@ -86,14 +97,23 @@ func (r *middlewareSettingsMock) ACLEnabled() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
type frostFSIDMock struct {
|
type frostFSIDMock struct {
|
||||||
tags map[string]string
|
tags map[string]string
|
||||||
|
validateError bool
|
||||||
|
userGroupsError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error {
|
func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error {
|
||||||
|
if f.validateError {
|
||||||
|
return fmt.Errorf("some error")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) {
|
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
|
return []string{}, f.tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,17 +125,21 @@ func (m *xmlMock) NewXMLDecoder(r io.Reader) *xml.Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
type resourceTaggingMock struct {
|
type resourceTaggingMock struct {
|
||||||
bucketTags map[string]string
|
bucketTags map[string]string
|
||||||
objectTags map[string]string
|
objectTags map[string]string
|
||||||
noSuchKey bool
|
noSuchObjectKey bool
|
||||||
|
noSuchBucketKey bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resourceTaggingMock) GetBucketTagging(context.Context, *data.BucketInfo) (map[string]string, error) {
|
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
|
return m.bucketTags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resourceTaggingMock) GetObjectTagging(context.Context, *data.GetObjectTaggingParams) (string, map[string]string, error) {
|
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 "", nil, apiErrors.GetAPIError(apiErrors.ErrNoSuchKey)
|
||||||
}
|
}
|
||||||
return "", m.objectTags, nil
|
return "", m.objectTags, nil
|
||||||
|
@ -215,9 +239,13 @@ func (h *handlerMock) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.writeResponse(w, res)
|
h.writeResponse(w, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handlerMock) DeleteObjectHandler(http.ResponseWriter, *http.Request) {
|
func (h *handlerMock) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
//TODO implement me
|
res := &handlerResult{
|
||||||
panic("implement me")
|
Method: middleware.DeleteObjectOperation,
|
||||||
|
ReqInfo: middleware.GetReqInfo(r.Context()),
|
||||||
|
}
|
||||||
|
|
||||||
|
h.writeResponse(w, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handlerMock) GetBucketLocationHandler(http.ResponseWriter, *http.Request) {
|
func (h *handlerMock) GetBucketLocationHandler(http.ResponseWriter, *http.Request) {
|
||||||
|
|
|
@ -43,7 +43,15 @@ func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
m.router.ServeHTTP(w, r)
|
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{}
|
middlewareSettings := &middlewareSettingsMock{}
|
||||||
policyChecker := inmemory.NewInMemoryLocalOverrides()
|
policyChecker := inmemory.NewInMemoryLocalOverrides()
|
||||||
|
|
||||||
|
@ -72,6 +80,11 @@ func prepareRouter(t *testing.T) *routerMock {
|
||||||
XMLDecoder: &xmlMock{},
|
XMLDecoder: &xmlMock{},
|
||||||
Tagging: &resourceTaggingMock{},
|
Tagging: &resourceTaggingMock{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, o := range opts {
|
||||||
|
o(&cfg)
|
||||||
|
}
|
||||||
|
|
||||||
return &routerMock{
|
return &routerMock{
|
||||||
t: t,
|
t: t,
|
||||||
router: NewRouter(cfg),
|
router: NewRouter(cfg),
|
||||||
|
@ -191,12 +204,25 @@ func TestPolicyChecker(t *testing.T) {
|
||||||
|
|
||||||
// check we can access 'bucket' in default namespace
|
// check we can access 'bucket' in default namespace
|
||||||
putObject(chiRouter, ns1, bktName1, objName1, nil)
|
putObject(chiRouter, ns1, bktName1, objName1, nil)
|
||||||
|
deleteObject(chiRouter, ns1, bktName1, objName1, nil)
|
||||||
|
|
||||||
// check we can access 'other-bucket' in custom namespace
|
// check we can access 'other-bucket' in custom namespace
|
||||||
putObject(chiRouter, ns2, bktName2, objName2, nil)
|
putObject(chiRouter, ns2, bktName2, objName2, nil)
|
||||||
|
deleteObject(chiRouter, ns2, bktName2, objName2, nil)
|
||||||
|
|
||||||
// check we cannot access 'bucket' in custom namespace
|
// check we cannot access 'bucket' in custom namespace
|
||||||
putObjectErr(chiRouter, ns2, bktName1, objName2, nil, apiErrors.ErrAccessDenied)
|
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) {
|
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}}})
|
tagging, err = xml.Marshal(data.Tagging{TagSet: []data.Tag{{Key: "key", Value: tagValue}}})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
putBucketTaggingErr(router, ns, bktName, tagging, apiErrors.ErrAccessDenied)
|
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) {
|
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)
|
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)
|
createBucket(router, ns, bktName)
|
||||||
getObjectErr(router, ns, bktName, objName, apiErrors.ErrNoSuchKey)
|
getObjectErr(router, ns, bktName, objName, apiErrors.ErrNoSuchKey)
|
||||||
})
|
})
|
||||||
|
@ -739,6 +772,18 @@ func listBucketsBase(router *routerMock, namespace string) *httptest.ResponseRec
|
||||||
return w
|
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 {
|
func putObject(router *routerMock, namespace, bktName, objName string, tag *data.Tag) handlerResult {
|
||||||
w := putObjectBase(router, namespace, bktName, objName, tag)
|
w := putObjectBase(router, namespace, bktName, objName, tag)
|
||||||
resp := readResponse(router.t, w)
|
resp := readResponse(router.t, w)
|
||||||
|
@ -764,6 +809,31 @@ func putObjectBase(router *routerMock, namespace, bktName, objName string, tag *
|
||||||
return w
|
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 {
|
func putBucketTagging(router *routerMock, namespace, bktName string, tagging []byte) handlerResult {
|
||||||
w := putBucketTaggingBase(router, namespace, bktName, tagging)
|
w := putBucketTaggingBase(router, namespace, bktName, tagging)
|
||||||
resp := readResponse(router.t, w)
|
resp := readResponse(router.t, w)
|
||||||
|
@ -873,6 +943,35 @@ func TestBillingMetrics(t *testing.T) {
|
||||||
require.Equal(t, "anon", dump.Requests[0].User)
|
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 {
|
func readResponse(t *testing.T, w *httptest.ResponseRecorder) handlerResult {
|
||||||
var res handlerResult
|
var res handlerResult
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue