diff --git a/client/accounting.go b/client/accounting.go index e48239ee..785b740b 100644 --- a/client/accounting.go +++ b/client/accounting.go @@ -45,12 +45,17 @@ func (x ResBalanceGet) Amount() accounting.Decimal { // // Return errors: // - [ErrMissingAccount] +// - [ErrMissingSigner] func (c *Client) BalanceGet(ctx context.Context, prm PrmBalanceGet) (*ResBalanceGet, error) { switch { case !prm.accountSet: return nil, ErrMissingAccount } + if c.prm.signer == nil { + return nil, ErrMissingSigner + } + // form request body var accountV2 refs.OwnerID prm.account.WriteToV2(&accountV2) diff --git a/client/accounting_test.go b/client/accounting_test.go new file mode 100644 index 00000000..625a3367 --- /dev/null +++ b/client/accounting_test.go @@ -0,0 +1,29 @@ +package client + +import ( + "context" + "testing" + + "github.com/nspcc-dev/neofs-sdk-go/user" + "github.com/stretchr/testify/require" +) + +func TestClient_BalanceGet(t *testing.T) { + c := newClient(t, nil, nil) + ctx := context.Background() + + t.Run("missing", func(t *testing.T) { + t.Run("account", func(t *testing.T) { + _, err := c.BalanceGet(ctx, PrmBalanceGet{}) + require.ErrorIs(t, err, ErrMissingAccount) + }) + + t.Run("signer", func(t *testing.T) { + var prm PrmBalanceGet + prm.SetAccount(user.ID{}) + + _, err := c.BalanceGet(ctx, prm) + require.ErrorIs(t, err, ErrMissingSigner) + }) + }) +} diff --git a/client/client.go b/client/client.go index 2e8c5281..79f4c2fb 100644 --- a/client/client.go +++ b/client/client.go @@ -139,6 +139,22 @@ func (c *Client) setNeoFSAPIServer(server neoFSAPIServer) { c.server = server } +// getSigner returns a signer for requests. Provided signer fromPrm (if any) is prioritized, otherwise +// Client's default is used. +// Returns [ErrMissingSigner] if no signer is provided at all. +func (c *Client) getSigner(fromPrm neofscrypto.Signer) (neofscrypto.Signer, error) { + signer := fromPrm + if signer == nil { + signer = c.prm.signer + } + + if signer == nil { + return nil, ErrMissingSigner + } + + return signer, nil +} + // Close closes underlying connection to the NeoFS server. Implements io.Closer. // MUST NOT be called before successful Dial. Can be called concurrently // with server operations processing on running goroutines: in this case diff --git a/client/container.go b/client/container.go index a3c9ed46..b0bb35ea 100644 --- a/client/container.go +++ b/client/container.go @@ -70,10 +70,6 @@ func (x ResContainerPut) ID() cid.ID { return x.id } -func (c *Client) defaultSigner() neofscrypto.Signer { - return c.prm.signer -} - // ContainerPut sends request to save container in NeoFS. // // Any errors (local or remote, including returned status codes) are returned as Go errors, @@ -88,6 +84,7 @@ func (c *Client) defaultSigner() neofscrypto.Signer { // // Return errors: // - [ErrMissingContainer] +// - [ErrMissingSigner] func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResContainerPut, error) { // check parameters switch { @@ -95,18 +92,18 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon return nil, ErrMissingContainer } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + // TODO: check private signer is set before forming the request // sign container var cnr v2container.Container prm.cnr.WriteToV2(&cnr) var sig neofscrypto.Signature - signer := prm.signer - if signer == nil { - signer = c.defaultSigner() - } - - err := container.CalculateSignature(&sig, prm.cnr, signer) + err = container.CalculateSignature(&sig, prm.cnr, signer) if err != nil { return nil, fmt.Errorf("calculate container signature: %w", err) } @@ -210,12 +207,17 @@ func (x ResContainerGet) Container() container.Container { // // Return errors: // - [ErrMissingContainer] +// - [ErrMissingSigner] func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResContainerGet, error) { switch { case !prm.idSet: return nil, ErrMissingContainer } + if c.prm.signer == nil { + return nil, ErrMissingSigner + } + var cidV2 refs.ContainerID prm.id.WriteToV2(&cidV2) @@ -300,6 +302,7 @@ func (x ResContainerList) Containers() []cid.ID { // // Return errors: // - [ErrMissingAccount] +// - [ErrMissingSigner] func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResContainerList, error) { // check parameters switch { @@ -307,6 +310,10 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC return nil, ErrMissingAccount } + if c.prm.signer == nil { + return nil, ErrMissingSigner + } + // form request body var ownerV2 refs.OwnerID prm.ownerID.WriteToV2(&ownerV2) @@ -406,6 +413,7 @@ func (x *PrmContainerDelete) WithinSession(tok session.Container) { // // Return errors: // - [ErrMissingContainer] +// - [ErrMissingSigner] // - [neofscrypto.ErrIncorrectSigner] // // Reflects all internal errors in second return value (transport problems, response processing, etc.). @@ -416,6 +424,15 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) er return ErrMissingContainer } + signer, err := c.getSigner(prm.signer) + if err != nil { + return err + } + + if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 { + return errNonNeoSigner + } + // sign container ID var cidV2 refs.ContainerID prm.id.WriteToV2(&cidV2) @@ -425,15 +442,7 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) er data := cidV2.GetValue() var sig neofscrypto.Signature - signer := prm.signer - if signer == nil { - signer = c.defaultSigner() - } - - if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 { - return errNonNeoSigner - } - err := sig.Calculate(signer, data) + err = sig.Calculate(signer, data) if err != nil { return fmt.Errorf("calculate signature: %w", err) } @@ -518,6 +527,7 @@ func (x ResContainerEACL) Table() eacl.Table { // // Return errors: // - [ErrMissingContainer] +// - [ErrMissingSigner] func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResContainerEACL, error) { // check parameters switch { @@ -525,6 +535,10 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC return nil, ErrMissingContainer } + if c.prm.signer == nil { + return nil, ErrMissingSigner + } + var cidV2 refs.ContainerID prm.id.WriteToV2(&cidV2) @@ -627,6 +641,7 @@ func (x *PrmContainerSetEACL) WithinSession(s session.Container) { // - [ErrMissingEACL] // - [ErrMissingEACLContainer] // - [neofscrypto.ErrIncorrectSigner] +// - [ErrMissingSigner] // // Context is required and must not be nil. It is used for network communication. func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) error { @@ -636,6 +651,15 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) return ErrMissingEACL } + signer, err := c.getSigner(prm.signer) + if err != nil { + return err + } + + if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 { + return errNonNeoSigner + } + _, isCIDSet := prm.table.CID() if !isCIDSet { return ErrMissingEACLContainer @@ -645,16 +669,7 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL) eaclV2 := prm.table.ToV2() var sig neofscrypto.Signature - signer := prm.signer - if signer == nil { - signer = c.defaultSigner() - } - - if signer.Scheme() != neofscrypto.ECDSA_DETERMINISTIC_SHA256 { - return errNonNeoSigner - } - - err := sig.Calculate(signer, eaclV2.StableMarshal(nil)) + err = sig.Calculate(signer, eaclV2.StableMarshal(nil)) if err != nil { return fmt.Errorf("calculate signature: %w", err) } @@ -734,6 +749,7 @@ func (x *PrmAnnounceSpace) SetValues(vs []container.SizeEstimation) { // // Return errors: // - [ErrMissingAnnouncements] +// - [ErrMissingSigner] func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounceSpace) error { // check parameters switch { @@ -741,6 +757,10 @@ func (c *Client) ContainerAnnounceUsedSpace(ctx context.Context, prm PrmAnnounce return ErrMissingAnnouncements } + if c.prm.signer == nil { + return ErrMissingSigner + } + // convert list of SDK announcement structures into NeoFS-API v2 list v2announce := make([]v2container.UsedSpaceAnnouncement, len(prm.announcements)) for i := range prm.announcements { diff --git a/client/container_test.go b/client/container_test.go new file mode 100644 index 00000000..da8a17b2 --- /dev/null +++ b/client/container_test.go @@ -0,0 +1,74 @@ +package client + +import ( + "context" + "testing" + + "github.com/nspcc-dev/neofs-sdk-go/container" + "github.com/stretchr/testify/require" +) + +func TestClient_Container(t *testing.T) { + c := newClient(t, nil, nil) + ctx := context.Background() + + t.Run("missing signer", func(t *testing.T) { + tt := []struct { + name string + methodCall func() error + }{ + { + "put", + func() error { + _, err := c.ContainerPut(ctx, PrmContainerPut{cnrSet: true}) + return err + }, + }, + { + "get", + func() error { + _, err := c.ContainerGet(ctx, PrmContainerGet{idSet: true}) + return err + }, + }, + { + "list", + func() error { + _, err := c.ContainerList(ctx, PrmContainerList{ownerSet: true}) + return err + }, + }, + { + "delete", + func() error { + return c.ContainerDelete(ctx, PrmContainerDelete{idSet: true}) + }, + }, + { + "eacl", + func() error { + _, err := c.ContainerEACL(ctx, PrmContainerEACL{idSet: true}) + return err + }, + }, + { + "set_eacl", + func() error { + return c.ContainerSetEACL(ctx, PrmContainerSetEACL{tableSet: true}) + }, + }, + { + "announce_space", + func() error { + return c.ContainerAnnounceUsedSpace(ctx, PrmAnnounceSpace{announcements: make([]container.SizeEstimation, 1)}) + }, + }, + } + + for _, test := range tt { + t.Run(test.name, func(t *testing.T) { + require.ErrorIs(t, test.methodCall(), ErrMissingSigner) + }) + } + }) +} diff --git a/client/errors.go b/client/errors.go index 367f31fb..5880a4df 100644 --- a/client/errors.go +++ b/client/errors.go @@ -17,6 +17,8 @@ var ( ErrMissingObject = errors.New("missing object") // ErrMissingAccount is returned when account/owner is not provided. ErrMissingAccount = errors.New("missing account") + // ErrMissingSigner is returned when signer is not provided. + ErrMissingSigner = errors.New("missing signer") // ErrMissingEACL is returned when eACL table is not provided. ErrMissingEACL = errors.New("missing eACL table") // ErrMissingEACLContainer is returned when container info is not provided in eACL table. diff --git a/client/netmap.go b/client/netmap.go index 90b248c2..0988cb16 100644 --- a/client/netmap.go +++ b/client/netmap.go @@ -45,7 +45,14 @@ func (x ResEndpointInfo) NodeInfo() netmap.NodeInfo { // // Exactly one return value is non-nil. Server status return is returned in ResEndpointInfo. // Reflects all internal errors in second return value (transport problems, response processing, etc.). +// +// Returns errors: +// - [ErrMissingSigner] func (c *Client) EndpointInfo(ctx context.Context, prm PrmEndpointInfo) (*ResEndpointInfo, error) { + if c.prm.signer == nil { + return nil, ErrMissingSigner + } + // form request var req v2netmap.LocalNodeInfoRequest @@ -194,7 +201,14 @@ func (x ResNetMapSnapshot) NetMap() netmap.NetMap { // // Exactly one return value is non-nil. Server status return is returned in ResNetMapSnapshot. // Reflects all internal errors in second return value (transport problems, response processing, etc.). +// +// Returns errors: +// - [ErrMissingSigner] func (c *Client) NetMapSnapshot(ctx context.Context, _ PrmNetMapSnapshot) (*ResNetMapSnapshot, error) { + if c.prm.signer == nil { + return nil, ErrMissingSigner + } + // form request body var body v2netmap.SnapshotRequestBody diff --git a/client/netmap_test.go b/client/netmap_test.go index 7003db04..4d06f19b 100644 --- a/client/netmap_test.go +++ b/client/netmap_test.go @@ -141,3 +141,12 @@ func TestClient_NetMapSnapshot(t *testing.T) { require.NoError(t, err) require.Equal(t, netMap, res.NetMap()) } + +func TestClient_NetMapSnapshot_MissingSigner(t *testing.T) { + c := newClient(t, nil, nil) + + t.Run("missing signer", func(t *testing.T) { + _, err := c.NetMapSnapshot(context.Background(), PrmNetMapSnapshot{}) + require.ErrorIs(t, err, ErrMissingSigner) + }) +} diff --git a/client/object_delete.go b/client/object_delete.go index 72f9a383..399ff8a9 100644 --- a/client/object_delete.go +++ b/client/object_delete.go @@ -118,12 +118,13 @@ func (x ResObjectDelete) Tombstone() oid.ID { // // Return errors: // - global (see Client docs) -// - [ErrMissingContainer]; -// - [ErrMissingObject]; -// - [apistatus.ErrContainerNotFound]; -// - [apistatus.ErrObjectAccessDenied]; -// - [apistatus.ErrObjectLocked]; -// - [apistatus.ErrSessionTokenExpired]. +// - [ErrMissingContainer] +// - [ErrMissingObject] +// - [ErrMissingSigner] +// - [apistatus.ErrContainerNotFound] +// - [apistatus.ErrObjectAccessDenied] +// - [apistatus.ErrObjectLocked] +// - [apistatus.ErrSessionTokenExpired] func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObjectDelete, error) { switch { case prm.addr.GetContainerID() == nil: @@ -132,6 +133,11 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj return nil, ErrMissingObject } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + // form request body prm.body.SetAddress(&prm.addr) @@ -140,12 +146,7 @@ func (c *Client) ObjectDelete(ctx context.Context, prm PrmObjectDelete) (*ResObj req.SetBody(&prm.body) c.prepareRequest(&req, &prm.meta) - signer := prm.signer - if signer == nil { - signer = c.prm.signer - } - - err := signServiceMessage(signer, &req) + err = signServiceMessage(signer, &req) if err != nil { return nil, fmt.Errorf("sign request: %w", err) } diff --git a/client/object_delete_test.go b/client/object_delete_test.go index 00b1bb1c..2b395e57 100644 --- a/client/object_delete_test.go +++ b/client/object_delete_test.go @@ -2,6 +2,7 @@ package client import ( "bytes" + "context" "crypto/rand" "crypto/sha256" "testing" @@ -71,3 +72,16 @@ func TestPrmObjectDelete_ByAddress(t *testing.T) { require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue())) }) } + +func TestClient_ObjectDelete(t *testing.T) { + t.Run("missing signer", func(t *testing.T) { + c := newClient(t, nil, nil) + + var nonilAddr v2refs.Address + nonilAddr.SetObjectID(new(v2refs.ObjectID)) + nonilAddr.SetContainerID(new(v2refs.ContainerID)) + + _, err := c.ObjectDelete(context.Background(), PrmObjectDelete{addr: nonilAddr}) + require.ErrorIs(t, err, ErrMissingSigner) + }) +} diff --git a/client/object_get.go b/client/object_get.go index 4a506d72..5cf4d03d 100644 --- a/client/object_get.go +++ b/client/object_get.go @@ -252,13 +252,13 @@ func (x *ObjectReader) close(ignoreEOF bool) error { // codes are returned as error. // // Return errors: -// - global (see Client docs); -// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectGet.MakeRaw). -// - [apistatus.ErrContainerNotFound]; -// - [apistatus.ErrObjectNotFound]; -// - [apistatus.ErrObjectAccessDenied]; -// - [apistatus.ErrObjectAlreadyRemoved]; -// - [apistatus.ErrSessionTokenExpired]. +// - global (see Client docs) +// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectGet.MakeRaw) +// - [apistatus.ErrContainerNotFound] +// - [apistatus.ErrObjectNotFound] +// - [apistatus.ErrObjectAccessDenied] +// - [apistatus.ErrObjectAlreadyRemoved] +// - [apistatus.ErrSessionTokenExpired] func (x *ObjectReader) Close() error { return x.close(true) } @@ -294,6 +294,7 @@ func (x *ObjectReader) Read(p []byte) (int, error) { // Return errors: // - [ErrMissingContainer] // - [ErrMissingObject] +// - [ErrMissingSigner] func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectReader, error) { // check parameters switch { @@ -303,6 +304,11 @@ func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectRe return nil, ErrMissingObject } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + // form request body var body v2object.GetRequestBody @@ -315,12 +321,7 @@ func (c *Client) ObjectGetInit(ctx context.Context, prm PrmObjectGet) (*ObjectRe req.SetBody(&body) c.prepareRequest(&req, &prm.meta) - signer := prm.signer - if signer == nil { - signer = c.prm.signer - } - - err := signServiceMessage(signer, &req) + err = signServiceMessage(signer, &req) if err != nil { return nil, fmt.Errorf("sign request: %w", err) } @@ -391,15 +392,16 @@ func (x *ResObjectHead) ReadHeader(dst *object.Object) bool { // Context is required and must not be nil. It is used for network communication. // // Return errors: -// - global (see Client docs); -// - [ErrMissingContainer]; -// - [ErrMissingObject]; -// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectHead.MakeRaw). -// - [apistatus.ErrContainerNotFound]; -// - [apistatus.ErrObjectNotFound]; -// - [apistatus.ErrObjectAccessDenied]; -// - [apistatus.ErrObjectAlreadyRemoved]; -// - [apistatus.ErrSessionTokenExpired]. +// - global (see Client docs) +// - [ErrMissingContainer] +// - [ErrMissingObject] +// - [ErrMissingSigner] +// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectHead.MakeRaw) +// - [apistatus.ErrContainerNotFound] +// - [apistatus.ErrObjectNotFound] +// - [apistatus.ErrObjectAccessDenied] +// - [apistatus.ErrObjectAlreadyRemoved] +// - [apistatus.ErrSessionTokenExpired] func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectHead, error) { switch { case prm.addr.GetContainerID() == nil: @@ -408,6 +410,11 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH return nil, ErrMissingObject } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + var body v2object.HeadRequestBody body.SetRaw(prm.raw) body.SetAddress(&prm.addr) @@ -416,13 +423,8 @@ func (c *Client) ObjectHead(ctx context.Context, prm PrmObjectHead) (*ResObjectH req.SetBody(&body) c.prepareRequest(&req, &prm.meta) - signer := prm.signer - if signer == nil { - signer = c.prm.signer - } - // sign the request - err := signServiceMessage(signer, &req) + err = signServiceMessage(signer, &req) if err != nil { return nil, fmt.Errorf("sign request: %w", err) } @@ -598,14 +600,14 @@ func (x *ObjectRangeReader) close(ignoreEOF bool) error { // codes are returned as error. // // Return errors: -// - global (see Client docs); -// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectRange.MakeRaw). -// - [apistatus.ErrContainerNotFound]; -// - [apistatus.ErrObjectNotFound]; -// - [apistatus.ErrObjectAccessDenied]; -// - [apistatus.ErrObjectAlreadyRemoved]; -// - [apistatus.ErrObjectOutOfRange]; -// - [apistatus.ErrSessionTokenExpired]. +// - global (see Client docs) +// - *[object.SplitInfoError] (returned on virtual objects with PrmObjectRange.MakeRaw) +// - [apistatus.ErrContainerNotFound] +// - [apistatus.ErrObjectNotFound] +// - [apistatus.ErrObjectAccessDenied] +// - [apistatus.ErrObjectAlreadyRemoved] +// - [apistatus.ErrObjectOutOfRange] +// - [apistatus.ErrSessionTokenExpired] func (x *ObjectRangeReader) Close() error { return x.close(true) } @@ -643,6 +645,7 @@ func (x *ObjectRangeReader) Read(p []byte) (int, error) { // Return errors: // - [ErrMissingContainer] // - [ErrMissingObject] +// - [ErrMissingSigner] // - [ErrZeroRangeLength] func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*ObjectRangeReader, error) { // check parameters @@ -655,6 +658,11 @@ func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*Obje return nil, ErrZeroRangeLength } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + // form request body var body v2object.GetRangeRequestBody @@ -668,12 +676,7 @@ func (c *Client) ObjectRangeInit(ctx context.Context, prm PrmObjectRange) (*Obje req.SetBody(&body) c.prepareRequest(&req, &prm.meta) - signer := prm.signer - if signer == nil { - signer = c.prm.signer - } - - err := signServiceMessage(signer, &req) + err = signServiceMessage(signer, &req) if err != nil { return nil, fmt.Errorf("sign request: %w", err) } diff --git a/client/object_get_test.go b/client/object_get_test.go index d967658c..d6e8e244 100644 --- a/client/object_get_test.go +++ b/client/object_get_test.go @@ -2,9 +2,11 @@ package client import ( "bytes" + "context" "math/rand" "testing" + v2object "github.com/nspcc-dev/neofs-api-go/v2/object" v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" "github.com/nspcc-dev/neofs-sdk-go/object" @@ -84,3 +86,50 @@ func TestPrmObjectRange_SetRange(t *testing.T) { require.Equal(t, off, tmp.ToV2().GetOffset()) }) } + +func TestClient_Get(t *testing.T) { + t.Run("missing signer", func(t *testing.T) { + c := newClient(t, nil, nil) + ctx := context.Background() + + var nonilAddr v2refs.Address + nonilAddr.SetObjectID(new(v2refs.ObjectID)) + nonilAddr.SetContainerID(new(v2refs.ContainerID)) + + tt := []struct { + name string + methodCall func() error + }{ + { + "get", + func() error { + _, err := c.ObjectGetInit(ctx, PrmObjectGet{prmObjectRead: prmObjectRead{addr: nonilAddr}}) + return err + }, + }, + { + "get_range", + func() error { + var rng v2object.Range + rng.SetLength(1) + + _, err := c.ObjectRangeInit(ctx, PrmObjectRange{prmObjectRead: prmObjectRead{addr: nonilAddr}, rng: rng}) + return err + }, + }, + { + "get_head", + func() error { + _, err := c.ObjectHead(ctx, PrmObjectHead{prmObjectRead: prmObjectRead{addr: nonilAddr}}) + return err + }, + }, + } + + for _, test := range tt { + t.Run(test.name, func(t *testing.T) { + require.ErrorIs(t, test.methodCall(), ErrMissingSigner) + }) + } + }) +} diff --git a/client/object_hash.go b/client/object_hash.go index f87e6313..7ddbb232 100644 --- a/client/object_hash.go +++ b/client/object_hash.go @@ -157,6 +157,7 @@ func (x ResObjectHash) Checksums() [][]byte { // Return errors: // - [ErrMissingContainer] // - [ErrMissingObject] +// - [ErrMissingSigner] // - [ErrMissingRanges] func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectHash, error) { switch { @@ -168,6 +169,11 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH return nil, ErrMissingRanges } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + prm.body.SetAddress(&prm.addr) if prm.csAlgo == v2refs.UnknownChecksum { prm.body.SetType(v2refs.SHA256) @@ -179,12 +185,7 @@ func (c *Client) ObjectHash(ctx context.Context, prm PrmObjectHash) (*ResObjectH c.prepareRequest(&req, &prm.meta) req.SetBody(&prm.body) - signer := prm.signer - if signer == nil { - signer = c.prm.signer - } - - err := signServiceMessage(signer, &req) + err = signServiceMessage(signer, &req) if err != nil { return nil, fmt.Errorf("sign request: %w", err) } diff --git a/client/object_hash_test.go b/client/object_hash_test.go index 695bbd89..e0c0dc0a 100644 --- a/client/object_hash_test.go +++ b/client/object_hash_test.go @@ -2,8 +2,10 @@ package client import ( "bytes" + "context" "testing" + v2object "github.com/nspcc-dev/neofs-api-go/v2/object" v2refs "github.com/nspcc-dev/neofs-api-go/v2/refs" cid "github.com/nspcc-dev/neofs-sdk-go/container/id" oid "github.com/nspcc-dev/neofs-sdk-go/object/id" @@ -48,3 +50,23 @@ func TestPrmObjectHash_ByAddress(t *testing.T) { require.True(t, bytes.Equal(cidV2.GetValue(), prm.addr.GetContainerID().GetValue())) }) } + +func TestClient_ObjectHash(t *testing.T) { + c := newClient(t, nil, nil) + + t.Run("missing signer", func(t *testing.T) { + var nonilAddr v2refs.Address + nonilAddr.SetObjectID(new(v2refs.ObjectID)) + nonilAddr.SetContainerID(new(v2refs.ContainerID)) + + var reqBody v2object.GetRangeHashRequestBody + reqBody.SetRanges(make([]v2object.Range, 1)) + + _, err := c.ObjectHash(context.Background(), PrmObjectHash{ + addr: nonilAddr, + body: reqBody, + }) + + require.ErrorIs(t, err, ErrMissingSigner) + }) +} diff --git a/client/object_put.go b/client/object_put.go index 806b5022..9da9c4e3 100644 --- a/client/object_put.go +++ b/client/object_put.go @@ -184,13 +184,13 @@ func (x *ObjectWriter) WritePayloadChunk(chunk []byte) bool { // codes are returned as error. // // Return errors: -// - global (see Client docs); -// - [apistatus.ErrContainerNotFound]; -// - [apistatus.ErrObjectAccessDenied]; -// - [apistatus.ErrObjectLocked]; -// - [apistatus.ErrLockNonRegularObject]; -// - [apistatus.ErrSessionTokenNotFound]; -// - [apistatus.ErrSessionTokenExpired]. +// - global (see Client docs) +// - [apistatus.ErrContainerNotFound] +// - [apistatus.ErrObjectAccessDenied] +// - [apistatus.ErrObjectLocked] +// - [apistatus.ErrLockNonRegularObject] +// - [apistatus.ErrSessionTokenNotFound] +// - [apistatus.ErrSessionTokenExpired] func (x *ObjectWriter) Close() (*ResObjectPut, error) { defer x.cancelCtxStream() @@ -230,9 +230,17 @@ func (x *ObjectWriter) Close() (*ResObjectPut, error) { // Exactly one return value is non-nil. Resulting writer must be finally closed. // // Context is required and must not be nil. It is used for network communication. +// +// Returns errors: +// - [ErrMissingSigner] func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*ObjectWriter, error) { var w ObjectWriter + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + ctx, cancel := context.WithCancel(ctx) stream, err := rpcapi.PutObject(&c.c, &w.respV2, client.WithContext(ctx)) if err != nil { @@ -240,10 +248,7 @@ func (c *Client) ObjectPutInit(ctx context.Context, prm PrmObjectPutInit) (*Obje return nil, fmt.Errorf("open stream: %w", err) } - w.signer = prm.signer - if w.signer == nil { - w.signer = c.prm.signer - } + w.signer = signer w.cancelCtxStream = cancel w.client = c w.stream = stream diff --git a/client/object_search.go b/client/object_search.go index 3f8b9c4a..1260b395 100644 --- a/client/object_search.go +++ b/client/object_search.go @@ -185,10 +185,10 @@ func (x *ObjectListReader) Iterate(f func(oid.ID) bool) error { // codes are returned as error. // // Return errors: -// - global (see Client docs); -// - [apistatus.ErrContainerNotFound]; -// - [apistatus.ErrObjectAccessDenied]; -// - [apistatus.ErrSessionTokenExpired]. +// - global (see Client docs) +// - [apistatus.ErrContainerNotFound] +// - [apistatus.ErrObjectAccessDenied] +// - [apistatus.ErrSessionTokenExpired] func (x *ObjectListReader) Close() error { defer x.cancelCtxStream() @@ -209,6 +209,7 @@ func (x *ObjectListReader) Close() error { // // Return errors: // - [ErrMissingContainer] +// - [ErrMissingSigner] func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*ObjectListReader, error) { // check parameters switch { @@ -216,6 +217,11 @@ func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*Ob return nil, ErrMissingContainer } + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + var cidV2 v2refs.ContainerID prm.cnrID.WriteToV2(&cidV2) @@ -229,12 +235,7 @@ func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*Ob req.SetBody(&body) c.prepareRequest(&req, &prm.meta) - signer := prm.signer - if signer == nil { - signer = c.prm.signer - } - - err := signServiceMessage(signer, &req) + err = signServiceMessage(signer, &req) if err != nil { return nil, fmt.Errorf("sign request: %w", err) } diff --git a/client/object_search_test.go b/client/object_search_test.go index 70b2af51..b900fb4b 100644 --- a/client/object_search_test.go +++ b/client/object_search_test.go @@ -1,6 +1,7 @@ package client import ( + "context" "errors" "io" "testing" @@ -107,6 +108,15 @@ func TestObjectIterate(t *testing.T) { }) } +func TestClient_ObjectSearch(t *testing.T) { + c := newClient(t, nil, nil) + + t.Run("missing signer", func(t *testing.T) { + _, err := c.ObjectSearchInit(context.Background(), PrmObjectSearch{cnrSet: true}) + require.ErrorIs(t, err, ErrMissingSigner) + }) +} + func testListReaderResponse(t *testing.T) (neofscrypto.Signer, *ObjectListReader) { return test.RandomSigner(t), &ObjectListReader{ cancelCtxStream: func() {}, diff --git a/client/reputation.go b/client/reputation.go index 747a09dd..b8683fc7 100644 --- a/client/reputation.go +++ b/client/reputation.go @@ -42,6 +42,7 @@ func (x *PrmAnnounceLocalTrust) SetValues(trusts []reputation.Trust) { // Return errors: // - [ErrZeroEpoch] // - [ErrMissingTrusts] +// - [ErrMissingSigner] func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTrust) error { // check parameters switch { @@ -51,6 +52,10 @@ func (c *Client) AnnounceLocalTrust(ctx context.Context, prm PrmAnnounceLocalTru return ErrMissingTrusts } + if c.prm.signer == nil { + return ErrMissingSigner + } + // form request body reqBody := new(v2reputation.AnnounceLocalTrustRequestBody) reqBody.SetEpoch(prm.epoch) @@ -131,6 +136,7 @@ func (x *PrmAnnounceIntermediateTrust) SetCurrentValue(trust reputation.PeerToPe // Return errors: // - [ErrZeroEpoch] // - [ErrMissingTrust] +// - [ErrMissingSigner] func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceIntermediateTrust) error { // check parameters switch { @@ -140,6 +146,10 @@ func (c *Client) AnnounceIntermediateTrust(ctx context.Context, prm PrmAnnounceI return ErrMissingTrust } + if c.prm.signer == nil { + return ErrMissingSigner + } + var trust v2reputation.PeerToPeerTrust prm.trust.WriteToV2(&trust) diff --git a/client/reputation_test.go b/client/reputation_test.go new file mode 100644 index 00000000..8a76cd7e --- /dev/null +++ b/client/reputation_test.go @@ -0,0 +1,26 @@ +package client + +import ( + "context" + "testing" + + "github.com/nspcc-dev/neofs-sdk-go/reputation" + "github.com/stretchr/testify/require" +) + +func TestClient_Reputation(t *testing.T) { + c := newClient(t, nil, nil) + ctx := context.Background() + + t.Run("missing signer", func(t *testing.T) { + t.Run("local", func(t *testing.T) { + err := c.AnnounceLocalTrust(ctx, PrmAnnounceLocalTrust{epoch: 1, trusts: make([]reputation.Trust, 1)}) + require.ErrorIs(t, err, ErrMissingSigner) + }) + + t.Run("intermediate", func(t *testing.T) { + err := c.AnnounceIntermediateTrust(ctx, PrmAnnounceIntermediateTrust{epoch: 1, trustSet: true}) + require.ErrorIs(t, err, ErrMissingSigner) + }) + }) +} diff --git a/client/session.go b/client/session.go index b7492020..ac36814b 100644 --- a/client/session.go +++ b/client/session.go @@ -66,9 +66,17 @@ func (x ResSessionCreate) PublicKey() []byte { // see [apistatus] package for NeoFS-specific error types. // // Context is required and must not be nil. It is used for network communication. +// +// Return errors: +// - [ErrMissingSigner] func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResSessionCreate, error) { + signer, err := c.getSigner(prm.signer) + if err != nil { + return nil, err + } + var ownerID user.ID - if err := user.IDFromSigner(&ownerID, prm.signer); err != nil { + if err = user.IDFromSigner(&ownerID, signer); err != nil { return nil, fmt.Errorf("IDFromSigner: %w", err) } @@ -93,11 +101,7 @@ func (c *Client) SessionCreate(ctx context.Context, prm PrmSessionCreate) (*ResS ) c.initCallContext(&cc) - cc.signer = prm.signer - if cc.signer == nil { - cc.signer = c.prm.signer - } - + cc.signer = signer cc.meta = prm.prmCommonMeta cc.req = &req cc.call = func() (responseV2, error) { diff --git a/client/session_test.go b/client/session_test.go index 623ea671..454ba798 100644 --- a/client/session_test.go +++ b/client/session_test.go @@ -66,4 +66,11 @@ func TestClient_SessionCreate(t *testing.T) { require.ErrorIs(t, err, ErrMissingResponseField) require.Equal(t, "missing session key field in the response", err.Error()) }) + + t.Run("missing signer", func(t *testing.T) { + c := newClient(t, nil, nil) + + _, err := c.SessionCreate(ctx, PrmSessionCreate{}) + require.ErrorIs(t, err, ErrMissingSigner) + }) }