package object import ( "context" "crypto/ecdsa" "errors" "testing" "github.com/nspcc-dev/neofs-api-go/acl" "github.com/nspcc-dev/neofs-api-go/bootstrap" "github.com/nspcc-dev/neofs-api-go/container" "github.com/nspcc-dev/neofs-api-go/object" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" crypto "github.com/nspcc-dev/neofs-crypto" libacl "github.com/nspcc-dev/neofs-node/lib/acl" "github.com/nspcc-dev/neofs-node/lib/implementations" "github.com/nspcc-dev/neofs-node/lib/ir" "github.com/nspcc-dev/neofs-node/lib/test" "github.com/stretchr/testify/require" ) type ( testACLEntity struct { // Set of interfaces which testCommonEntity must implement, but some methods from those does not call. serviceRequest RequestTargeter implementations.ACLHelper implementations.ContainerNodesLister implementations.ContainerOwnerChecker acl.ExtendedACLTable libacl.RequestInfo // Argument interceptor. Used for ascertain of correct parameter passage between components. f func(...interface{}) // Mocked result of any interface. res interface{} // Mocked error of any interface. err error } ) type testBasicChecker struct { libacl.BasicChecker actionErr error action bool sticky bool extended bool bearer bool } func (t *testACLEntity) calculateRequestAction(context.Context, requestActionParams) acl.ExtendedACLAction { return t.res.(acl.ExtendedACLAction) } func (t *testACLEntity) buildRequestInfo(req serviceRequest, target acl.Target) (libacl.RequestInfo, error) { if t.f != nil { t.f(req, target) } if t.err != nil { return nil, t.err } return t.res.(libacl.RequestInfo), nil } func (t *testACLEntity) Action(table acl.ExtendedACLTable, req libacl.RequestInfo) acl.ExtendedACLAction { if t.f != nil { t.f(table, req) } return t.res.(acl.ExtendedACLAction) } func (t *testACLEntity) GetExtendedACLTable(_ context.Context, cid CID) (acl.ExtendedACLTable, error) { if t.f != nil { t.f(cid) } if t.err != nil { return nil, t.err } return t.res.(acl.ExtendedACLTable), nil } func (s *testBasicChecker) Extended(uint32) bool { return s.extended } func (s *testBasicChecker) Sticky(uint32) bool { return s.sticky } func (s *testBasicChecker) Bearer(uint32, object.RequestType) (bool, error) { return s.bearer, nil } func (s *testBasicChecker) Action(uint32, object.RequestType, acl.Target) (bool, error) { return s.action, s.actionErr } func (t *testACLEntity) GetBasicACL(context.Context, CID) (uint32, error) { if t.err != nil { return 0, t.err } return t.res.(uint32), nil } func (t *testACLEntity) Target(context.Context, serviceRequest) acl.Target { return t.res.(acl.Target) } func (t *testACLEntity) CID() CID { return CID{} } func (t *testACLEntity) Type() object.RequestType { return t.res.(object.RequestType) } func (t *testACLEntity) GetBearerToken() service.BearerToken { return nil } func (t *testACLEntity) GetOwner() (*ecdsa.PublicKey, error) { if t.err != nil { return nil, t.err } return t.res.(*ecdsa.PublicKey), nil } func (t testACLEntity) GetIRInfo(ir.GetInfoParams) (*ir.GetInfoResult, error) { if t.err != nil { return nil, t.err } res := new(ir.GetInfoResult) res.SetInfo(*t.res.(*ir.Info)) return res, nil } func (t *testACLEntity) ContainerNodesInfo(ctx context.Context, cid CID, prev int) ([]bootstrap.NodeInfo, error) { if t.err != nil { return nil, t.err } return t.res.([][]bootstrap.NodeInfo)[prev], nil } func (t *testACLEntity) IsContainerOwner(_ context.Context, cid CID, owner OwnerID) (bool, error) { if t.f != nil { t.f(cid, owner) } if t.err != nil { return false, t.err } return t.res.(bool), nil } func (t testACLEntity) GetSignKeyPairs() []service.SignKeyPair { if t.res == nil { return nil } return t.res.([]service.SignKeyPair) } func TestPreprocessor(t *testing.T) { ctx := context.TODO() t.Run("empty request", func(t *testing.T) { require.PanicsWithValue(t, pmEmptyServiceRequest, func() { _ = new(aclPreProcessor).preProcess(ctx, nil) }) }) t.Run("everything is okay", func(t *testing.T) { rule := uint32(0x00000003) // set F-bit rule |= 1 << 28 checker := new(libacl.BasicACLChecker) preprocessor := aclPreProcessor{ log: test.NewTestLogger(false), aclInfoReceiver: aclInfoReceiver{ basicACLGetter: &testACLEntity{res: rule}, basicChecker: checker, targetFinder: &testACLEntity{res: acl.Target_Others}, }, basicChecker: checker, } require.NoError(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: acl.Target_System} require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) preprocessor.aclInfoReceiver.targetFinder = &testACLEntity{res: acl.Target_User} require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) }) t.Run("can't fetch container", func(t *testing.T) { preprocessor := aclPreProcessor{ log: test.NewTestLogger(false), aclInfoReceiver: aclInfoReceiver{ basicACLGetter: &testACLEntity{err: container.ErrNotFound}, targetFinder: &testACLEntity{res: acl.Target_Others}, }, } require.Error(t, preprocessor.preProcess(ctx, &testACLEntity{res: object.RequestGet})) }) t.Run("sticky bit", func(t *testing.T) { checker := &testBasicChecker{ actionErr: nil, action: true, sticky: true, } s := &aclPreProcessor{ log: test.NewTestLogger(false), aclInfoReceiver: aclInfoReceiver{ basicACLGetter: &testACLEntity{ res: uint32(0), }, basicChecker: checker, targetFinder: &testACLEntity{ res: acl.Target_User, }, }, basicChecker: checker, } ownerKey := &test.DecodeKey(0).PublicKey ownerID, err := refs.NewOwnerID(ownerKey) require.NoError(t, err) okItems := []func() []serviceRequest{ // Read requests func() []serviceRequest { return []serviceRequest{ new(object.GetRequest), new(object.HeadRequest), new(object.SearchRequest), new(GetRangeRequest), new(object.GetRangeHashRequest), } }, // PutRequest / DeleteRequest (w/o token) func() []serviceRequest { req := object.MakePutRequestHeader(&Object{ SystemHeader: SystemHeader{ OwnerID: ownerID, }, }) req.AddSignKey(nil, ownerKey) putReq := &putRequest{ PutRequest: req, } delReq := new(object.DeleteRequest) delReq.OwnerID = ownerID delReq.AddSignKey(nil, ownerKey) return []serviceRequest{putReq, delReq} }, // PutRequest / DeleteRequest (w/ token) func() []serviceRequest { token := new(service.Token) token.SetOwnerID(ownerID) token.SetOwnerKey(crypto.MarshalPublicKey(ownerKey)) req := object.MakePutRequestHeader(&Object{ SystemHeader: SystemHeader{ OwnerID: ownerID, }, }) req.SetToken(token) putReq := &putRequest{ PutRequest: req, } delReq := new(object.DeleteRequest) delReq.OwnerID = ownerID delReq.SetToken(token) return []serviceRequest{putReq, delReq} }, } failItems := []func() []serviceRequest{ // PutRequest / DeleteRequest (w/o token and wrong owner) func() []serviceRequest { otherOwner := ownerID otherOwner[0]++ req := object.MakePutRequestHeader(&Object{ SystemHeader: SystemHeader{ OwnerID: otherOwner, }, }) req.AddSignKey(nil, ownerKey) putReq := &putRequest{ PutRequest: req, } delReq := new(object.DeleteRequest) delReq.OwnerID = otherOwner delReq.AddSignKey(nil, ownerKey) return []serviceRequest{putReq, delReq} }, // PutRequest / DeleteRequest (w/ token w/ wrong owner) func() []serviceRequest { otherOwner := ownerID otherOwner[0]++ token := new(service.Token) token.SetOwnerID(ownerID) token.SetOwnerKey(crypto.MarshalPublicKey(ownerKey)) req := object.MakePutRequestHeader(&Object{ SystemHeader: SystemHeader{ OwnerID: otherOwner, }, }) req.SetToken(token) putReq := &putRequest{ PutRequest: req, } delReq := new(object.DeleteRequest) delReq.OwnerID = otherOwner delReq.SetToken(token) return []serviceRequest{putReq, delReq} }, } for _, ok := range okItems { for _, req := range ok() { require.NoError(t, s.preProcess(ctx, req)) } } for _, fail := range failItems { for _, req := range fail() { require.Error(t, s.preProcess(ctx, req)) } } }) t.Run("extended ACL", func(t *testing.T) { target := acl.Target_Others req := &testACLEntity{ res: object.RequestGet, } actCalc := new(testACLEntity) checker := &testBasicChecker{ action: true, extended: true, } s := &aclPreProcessor{ log: test.NewTestLogger(false), aclInfoReceiver: aclInfoReceiver{ basicACLGetter: &testACLEntity{ res: uint32(1), }, basicChecker: checker, targetFinder: &testACLEntity{ res: target, }, }, basicChecker: checker, reqActionCalc: actCalc, } // force to return non-ActionAllow actCalc.res = acl.ActionAllow + 1 require.EqualError(t, s.preProcess(ctx, req), errAccessDenied.Error()) // force to return ActionAllow actCalc.res = acl.ActionAllow require.NoError(t, s.preProcess(ctx, req)) }) } func TestTargetFinder(t *testing.T) { ctx := context.TODO() irKey := test.DecodeKey(2) containerKey := test.DecodeKey(3) prevContainerKey := test.DecodeKey(4) irInfo := new(ir.Info) irNode := ir.Node{} irNode.SetKey(crypto.MarshalPublicKey(&irKey.PublicKey)) irInfo.SetNodes([]ir.Node{irNode}) finder := &targetFinder{ log: test.NewTestLogger(false), irStorage: &testACLEntity{ res: irInfo, }, cnrLister: &testACLEntity{res: [][]bootstrap.NodeInfo{ {{PubKey: crypto.MarshalPublicKey(&containerKey.PublicKey)}}, {{PubKey: crypto.MarshalPublicKey(&prevContainerKey.PublicKey)}}, }}, } t.Run("trusted node", func(t *testing.T) { pk := &test.DecodeKey(0).PublicKey ownerKey := &test.DecodeKey(1).PublicKey owner, err := refs.NewOwnerID(ownerKey) require.NoError(t, err) token := new(service.Token) token.SetSessionKey(crypto.MarshalPublicKey(pk)) token.SetOwnerKey(crypto.MarshalPublicKey(ownerKey)) token.SetOwnerID(owner) req := new(object.SearchRequest) req.ContainerID = CID{1, 2, 3} req.SetToken(token) req.AddSignKey(nil, pk) finder.cnrOwnerChecker = &testACLEntity{ f: func(items ...interface{}) { require.Equal(t, req.CID(), items[0]) require.Equal(t, owner, items[1]) }, res: true, } require.Equal(t, acl.Target_User, finder.Target(ctx, req)) }) t.Run("container owner", func(t *testing.T) { finder.cnrOwnerChecker = &testACLEntity{res: true} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) require.Equal(t, acl.Target_User, finder.Target(ctx, req)) }) t.Run("system owner", func(t *testing.T) { finder.cnrOwnerChecker = &testACLEntity{res: false} req := new(object.SearchRequest) req.AddSignKey(nil, &irKey.PublicKey) require.Equal(t, acl.Target_System, finder.Target(ctx, req)) req = new(object.SearchRequest) req.AddSignKey(nil, &containerKey.PublicKey) require.Equal(t, acl.Target_System, finder.Target(ctx, req)) req = new(object.SearchRequest) req.AddSignKey(nil, &prevContainerKey.PublicKey) require.Equal(t, acl.Target_System, finder.Target(ctx, req)) }) t.Run("other owner", func(t *testing.T) { finder.cnrOwnerChecker = &testACLEntity{res: false} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) require.Equal(t, acl.Target_Others, finder.Target(ctx, req)) }) t.Run("can't fetch request owner", func(t *testing.T) { req := new(object.SearchRequest) require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) }) t.Run("can't fetch container", func(t *testing.T) { finder.cnrOwnerChecker = &testACLEntity{err: container.ErrNotFound} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) }) t.Run("can't fetch ir list", func(t *testing.T) { finder.cnrOwnerChecker = &testACLEntity{res: false} finder.irStorage = &testACLEntity{err: errors.New("blockchain is busy")} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) }) t.Run("can't fetch container list", func(t *testing.T) { finder.cnrOwnerChecker = &testACLEntity{res: false} finder.cnrLister = &testACLEntity{err: container.ErrNotFound} req := new(object.SearchRequest) req.AddSignKey(nil, &test.DecodeKey(0).PublicKey) require.Equal(t, acl.Target_Unknown, finder.Target(ctx, req)) }) }