From b079a7604f19ef5aa5fa4abed0cb7a394fb14d34 Mon Sep 17 00:00:00 2001 From: Leonard Lyubich Date: Fri, 8 May 2020 12:34:16 +0300 Subject: [PATCH] session: implement gRPC session creator --- refs/types.go | 7 ++- service/types.go | 18 +++++-- session/create.go | 67 +++++++++++++++++++++++++++ session/create_test.go | 103 +++++++++++++++++++++++++++++++++++++++++ session/request.go | 4 ++ session/types.go | 25 ++++++++++ 6 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 session/create.go create mode 100644 session/create_test.go diff --git a/refs/types.go b/refs/types.go index a29424e..417eec3 100644 --- a/refs/types.go +++ b/refs/types.go @@ -37,9 +37,14 @@ type ( OwnerID chain.WalletAddress ) +// OwnerIDSource is an interface of the container of an OwnerID value with read access. +type OwnerIDSource interface { + GetOwnerID() OwnerID +} + // OwnerIDContainer is an interface of the container of an OwnerID value. type OwnerIDContainer interface { - GetOwnerID() OwnerID + OwnerIDSource SetOwnerID(OwnerID) } diff --git a/service/types.go b/service/types.go index c3148a0..52d68d2 100644 --- a/service/types.go +++ b/service/types.go @@ -124,6 +124,18 @@ type ExpirationEpochContainer interface { SetExpirationEpoch(uint64) } +// LifetimeSource is an interface of the container of creation-expiration epoch pair with read access. +type LifetimeSource interface { + CreationEpochSource + ExpirationEpochSource +} + +// LifetimeSource is an interface of the container of creation-expiration epoch pair. +type LifetimeContainer interface { + CreationEpochContainer + ExpirationEpochContainer +} + // SessionKeySource is an interface of the container of session key bytes with read access. type SessionKeySource interface { GetSessionKey() []byte @@ -157,16 +169,14 @@ type SessionTokenSource interface { // - ID of the token's owner; // - verb of the session; // - address of the session object; -// - creation epoch number of the token; -// - expiration epoch number of the token; +// - token lifetime; // - public session key bytes. type SessionTokenInfo interface { TokenIDContainer OwnerIDContainer VerbContainer AddressContainer - CreationEpochContainer - ExpirationEpochContainer + LifetimeContainer SessionKeyContainer } diff --git a/session/create.go b/session/create.go new file mode 100644 index 0000000..a2d2b99 --- /dev/null +++ b/session/create.go @@ -0,0 +1,67 @@ +package session + +import ( + "context" + "crypto/ecdsa" + + "github.com/nspcc-dev/neofs-api-go/internal" + "github.com/nspcc-dev/neofs-api-go/service" + crypto "github.com/nspcc-dev/neofs-crypto" + "google.golang.org/grpc" +) + +type gRPCCreator struct { + conn *grpc.ClientConn + + key *ecdsa.PrivateKey + + clientFunc func(*grpc.ClientConn) SessionClient +} + +const ErrNilCreateParamsSource = internal.Error("create params source is nil") + +const ErrNilGPRCClientConn = internal.Error("gRPC client connection is nil") + +// NewGRPCCreator unites virtual gRPC client with private ket and returns Creator interface. +// +// If passed ClientConn is nil, ErrNilGPRCClientConn returns. +// If passed private key is nil, crypto.ErrEmptyPrivateKey returns. +func NewGRPCCreator(conn *grpc.ClientConn, key *ecdsa.PrivateKey) (Creator, error) { + if conn == nil { + return nil, ErrNilGPRCClientConn + } else if key == nil { + return nil, crypto.ErrEmptyPrivateKey + } + + return &gRPCCreator{ + conn: conn, + + key: key, + + clientFunc: NewSessionClient, + }, nil +} + +// Create constructs message, signs it with private key and sends it to a gRPC client. +// +// If passed CreateParamsSource is nil, ErrNilCreateParamsSource returns. +// If message could not be signed, an error returns. +func (s gRPCCreator) Create(ctx context.Context, p CreateParamsSource) (CreateResult, error) { + if p == nil { + return nil, ErrNilCreateParamsSource + } + + // create and fill a message + req := new(CreateRequest) + req.SetOwnerID(p.GetOwnerID()) + req.SetCreationEpoch(p.CreationEpoch()) + req.SetExpirationEpoch(p.ExpirationEpoch()) + + // sign with private key + if err := service.SignDataWithSessionToken(s.key, req); err != nil { + return nil, err + } + + // make gRPC call + return s.clientFunc(s.conn).Create(ctx, req) +} diff --git a/session/create_test.go b/session/create_test.go new file mode 100644 index 0000000..732d4fd --- /dev/null +++ b/session/create_test.go @@ -0,0 +1,103 @@ +package session + +import ( + "context" + "crypto/ecdsa" + "testing" + + "github.com/nspcc-dev/neofs-api-go/service" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/nspcc-dev/neofs-crypto/test" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" +) + +type testSessionClient struct { + fn func(*CreateRequest) + resp *CreateResponse + err error +} + +func (s testSessionClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateResponse, error) { + if s.fn != nil { + s.fn(in) + } + + return s.resp, s.err +} + +func TestNewGRPCCreator(t *testing.T) { + var ( + err error + conn = new(grpc.ClientConn) + sk = new(ecdsa.PrivateKey) + ) + + // nil client connection + _, err = NewGRPCCreator(nil, sk) + require.EqualError(t, err, ErrNilGPRCClientConn.Error()) + + // nil private key + _, err = NewGRPCCreator(conn, nil) + require.EqualError(t, err, crypto.ErrEmptyPrivateKey.Error()) + + // valid params + res, err := NewGRPCCreator(conn, sk) + require.NoError(t, err) + + v := res.(*gRPCCreator) + require.Equal(t, conn, v.conn) + require.Equal(t, sk, v.key) + require.NotNil(t, v.clientFunc) +} + +func TestGRPCCreator_Create(t *testing.T) { + ctx := context.TODO() + s := new(gRPCCreator) + + // nil CreateParamsSource + _, err := s.Create(ctx, nil) + require.EqualError(t, err, ErrNilCreateParamsSource.Error()) + + var ( + ownerID = OwnerID{1, 2, 3} + created = uint64(2) + expired = uint64(4) + ) + + p := NewParams() + p.SetOwnerID(ownerID) + p.SetCreationEpoch(created) + p.SetExpirationEpoch(expired) + + // nil private key + _, err = s.Create(ctx, p) + require.Error(t, err) + + // create test private key + s.key = test.DecodeKey(0) + + // create test client + c := &testSessionClient{ + fn: func(req *CreateRequest) { + require.Equal(t, ownerID, req.GetOwnerID()) + require.Equal(t, created, req.CreationEpoch()) + require.Equal(t, expired, req.ExpirationEpoch()) + require.NoError(t, service.VerifyAccumulatedSignaturesWithToken(req)) + }, + resp: &CreateResponse{ + ID: TokenID{1, 2, 3}, + SessionKey: []byte{1, 2, 3}, + }, + err: errors.New("test error"), + } + + s.clientFunc = func(*grpc.ClientConn) SessionClient { + return c + } + + res, err := s.Create(ctx, p) + require.EqualError(t, err, c.err.Error()) + require.Equal(t, c.resp, res) +} diff --git a/session/request.go b/session/request.go index 85f563f..161edca 100644 --- a/session/request.go +++ b/session/request.go @@ -14,6 +14,10 @@ const signedRequestDataSize = 0 + var requestEndianness = binary.BigEndian +func NewParams() CreateParamsContainer { + return new(CreateRequest) +} + // GetOwnerID is an OwnerID field getter. func (m CreateRequest) GetOwnerID() OwnerID { return m.OwnerID diff --git a/session/types.go b/session/types.go index c890aaf..9e8db48 100644 --- a/session/types.go +++ b/session/types.go @@ -5,6 +5,8 @@ import ( "crypto/ecdsa" "github.com/nspcc-dev/neofs-api-go/internal" + "github.com/nspcc-dev/neofs-api-go/refs" + "github.com/nspcc-dev/neofs-api-go/service" ) // PrivateToken is an interface of session private part. @@ -55,5 +57,28 @@ type KeyStore interface { Get(context.Context, OwnerID) ([]*ecdsa.PublicKey, error) } +// CreateParamsSource is an interface of the container of session parameters with read access. +type CreateParamsSource interface { + refs.OwnerIDSource + service.LifetimeSource +} + +// CreateParamsContainer is an interface of the container of session parameters. +type CreateParamsContainer interface { + refs.OwnerIDContainer + service.LifetimeContainer +} + +// CreateResult is an interface of the container of an opened session info with read access. +type CreateResult interface { + service.TokenIDSource + service.SessionKeySource +} + +// Creator is an interface of the tool for a session opening. +type Creator interface { + Create(context.Context, CreateParamsSource) (CreateResult, error) +} + // ErrPrivateTokenNotFound is raised when addressed private token was not found in storage. const ErrPrivateTokenNotFound = internal.Error("private token not found")