[#53] session: move package from neofs-api-go

Signed-off-by: Evgenii Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
Evgenii Stratonikov 2021-10-27 13:00:15 +03:00 committed by Alex Vanin
parent 1feafcbcbf
commit ee47683848
6 changed files with 825 additions and 0 deletions

139
session/container.go Normal file
View file

@ -0,0 +1,139 @@
package session
import (
"github.com/nspcc-dev/neofs-api-go/v2/session"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
)
// ContainerContext represents NeoFS API v2-compatible
// context of the container session.
//
// It is a wrapper over session.ContainerSessionContext
// which allows to abstract from details of the message
// structure.
type ContainerContext session.ContainerSessionContext
// NewContainerContext creates and returns blank ContainerSessionContext.
//
// Defaults:
// - not bound to any operation;
// - applied to all containers.
func NewContainerContext() *ContainerContext {
v2 := new(session.ContainerSessionContext)
v2.SetWildcard(true)
return ContainerContextFromV2(v2)
}
// ContainerContextFromV2 wraps session.ContainerSessionContext
// into ContainerContext.
func ContainerContextFromV2(v *session.ContainerSessionContext) *ContainerContext {
return (*ContainerContext)(v)
}
// ToV2 converts ContainerContext to session.ContainerSessionContext
// message structure.
func (x *ContainerContext) ToV2() *session.ContainerSessionContext {
return (*session.ContainerSessionContext)(x)
}
// ApplyTo specifies which container the ContainerContext applies to.
//
// If id is nil, ContainerContext is applied to all containers.
func (x *ContainerContext) ApplyTo(id *cid.ID) {
v2 := (*session.ContainerSessionContext)(x)
v2.SetWildcard(id == nil)
v2.SetContainerID(id.ToV2())
}
// ActOnAllContainers is a helper function that conveniently
// applies ContainerContext to all containers.
func ApplyToAllContainers(c *ContainerContext) {
c.ApplyTo(nil)
}
// Container returns identifier of the container
// to which the ContainerContext applies.
//
// Returns nil if ContainerContext is applied to
// all containers.
func (x *ContainerContext) Container() *cid.ID {
v2 := (*session.ContainerSessionContext)(x)
if v2.Wildcard() {
return nil
}
return cid.NewFromV2(v2.ContainerID())
}
func (x *ContainerContext) forVerb(v session.ContainerSessionVerb) {
(*session.ContainerSessionContext)(x).
SetVerb(v)
}
func (x *ContainerContext) isForVerb(v session.ContainerSessionVerb) bool {
return (*session.ContainerSessionContext)(x).
Verb() == v
}
// ForPut binds the ContainerContext to
// PUT operation.
func (x *ContainerContext) ForPut() {
x.forVerb(session.ContainerVerbPut)
}
// IsForPut checks if ContainerContext is bound to
// PUT operation.
func (x *ContainerContext) IsForPut() bool {
return x.isForVerb(session.ContainerVerbPut)
}
// ForDelete binds the ContainerContext to
// DELETE operation.
func (x *ContainerContext) ForDelete() {
x.forVerb(session.ContainerVerbDelete)
}
// IsForDelete checks if ContainerContext is bound to
// DELETE operation.
func (x *ContainerContext) IsForDelete() bool {
return x.isForVerb(session.ContainerVerbDelete)
}
// ForSetEACL binds the ContainerContext to
// SETEACL operation.
func (x *ContainerContext) ForSetEACL() {
x.forVerb(session.ContainerVerbSetEACL)
}
// IsForSetEACL checks if ContainerContext is bound to
// SETEACL operation.
func (x *ContainerContext) IsForSetEACL() bool {
return x.isForVerb(session.ContainerVerbSetEACL)
}
// Marshal marshals ContainerContext into a protobuf binary form.
func (x *ContainerContext) Marshal(bs ...[]byte) ([]byte, error) {
var buf []byte
if len(bs) > 0 {
buf = bs[0]
}
return x.ToV2().StableMarshal(buf)
}
// Unmarshal unmarshals protobuf binary representation of ContainerContext.
func (x *ContainerContext) Unmarshal(data []byte) error {
return x.ToV2().Unmarshal(data)
}
// MarshalJSON encodes ContainerContext to protobuf JSON format.
func (x *ContainerContext) MarshalJSON() ([]byte, error) {
return x.ToV2().MarshalJSON()
}
// UnmarshalJSON decodes ContainerContext from protobuf JSON format.
func (x *ContainerContext) UnmarshalJSON(data []byte) error {
return x.ToV2().UnmarshalJSON(data)
}

112
session/container_test.go Normal file
View file

@ -0,0 +1,112 @@
package session_test
import (
"testing"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/session"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/stretchr/testify/require"
)
func TestContainerContextVerbs(t *testing.T) {
c := session.NewContainerContext()
assert := func(setter func(), getter func() bool, verb v2session.ContainerSessionVerb) {
setter()
require.True(t, getter())
require.Equal(t, verb, c.ToV2().Verb())
}
t.Run("PUT", func(t *testing.T) {
assert(c.ForPut, c.IsForPut, v2session.ContainerVerbPut)
})
t.Run("DELETE", func(t *testing.T) {
assert(c.ForDelete, c.IsForDelete, v2session.ContainerVerbDelete)
})
t.Run("SETEACL", func(t *testing.T) {
assert(c.ForSetEACL, c.IsForSetEACL, v2session.ContainerVerbSetEACL)
})
}
func TestContainerContext_ApplyTo(t *testing.T) {
c := session.NewContainerContext()
id := cidtest.GenerateID()
t.Run("method", func(t *testing.T) {
c.ApplyTo(id)
require.Equal(t, id, c.Container())
c.ApplyTo(nil)
require.Nil(t, c.Container())
})
t.Run("helper functions", func(t *testing.T) {
c.ApplyTo(id)
session.ApplyToAllContainers(c)
require.Nil(t, c.Container())
})
}
func TestFilter_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *session.ContainerContext
require.Nil(t, x.ToV2())
})
t.Run("default values", func(t *testing.T) {
c := session.NewContainerContext()
// check initial values
require.Nil(t, c.Container())
for _, op := range []func() bool{
c.IsForPut,
c.IsForDelete,
c.IsForSetEACL,
} {
require.False(t, op())
}
// convert to v2 message
cV2 := c.ToV2()
require.Equal(t, v2session.ContainerVerbUnknown, cV2.Verb())
require.True(t, cV2.Wildcard())
require.Nil(t, cV2.ContainerID())
})
}
func TestContainerContextEncoding(t *testing.T) {
c := sessiontest.ContainerContext()
t.Run("binary", func(t *testing.T) {
data, err := c.Marshal()
require.NoError(t, err)
c2 := session.NewContainerContext()
require.NoError(t, c2.Unmarshal(data))
require.Equal(t, c, c2)
})
t.Run("json", func(t *testing.T) {
data, err := c.MarshalJSON()
require.NoError(t, err)
c2 := session.NewContainerContext()
require.NoError(t, c2.UnmarshalJSON(data))
require.Equal(t, c, c2)
})
}

282
session/session.go Normal file
View file

@ -0,0 +1,282 @@
package session
import (
"crypto/ecdsa"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session"
v2signature "github.com/nspcc-dev/neofs-api-go/v2/signature"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/signature"
sigutil "github.com/nspcc-dev/neofs-sdk-go/util/signature"
)
// Token represents NeoFS API v2-compatible
// session token.
type Token session.SessionToken
// NewTokenFromV2 wraps session.SessionToken message structure
// into Token.
//
// Nil session.SessionToken converts to nil.
func NewTokenFromV2(tV2 *session.SessionToken) *Token {
return (*Token)(tV2)
}
// NewToken creates and returns blank Token.
//
// Defaults:
// - body: nil;
// - id: nil;
// - ownerId: nil;
// - sessionKey: nil;
// - exp: 0;
// - iat: 0;
// - nbf: 0;
func NewToken() *Token {
return NewTokenFromV2(new(session.SessionToken))
}
// ToV2 converts Token to session.SessionToken message structure.
//
// Nil Token converts to nil.
func (t *Token) ToV2() *session.SessionToken {
return (*session.SessionToken)(t)
}
func (t *Token) setBodyField(setter func(*session.SessionTokenBody)) {
token := (*session.SessionToken)(t)
body := token.GetBody()
if body == nil {
body = new(session.SessionTokenBody)
token.SetBody(body)
}
setter(body)
}
// ID returns Token identifier.
func (t *Token) ID() []byte {
return (*session.SessionToken)(t).
GetBody().
GetID()
}
// SetID sets Token identifier.
func (t *Token) SetID(v []byte) {
t.setBodyField(func(body *session.SessionTokenBody) {
body.SetID(v)
})
}
// OwnerID returns Token's owner identifier.
func (t *Token) OwnerID() *owner.ID {
return owner.NewIDFromV2(
(*session.SessionToken)(t).
GetBody().
GetOwnerID(),
)
}
// SetOwnerID sets Token's owner identifier.
func (t *Token) SetOwnerID(v *owner.ID) {
t.setBodyField(func(body *session.SessionTokenBody) {
body.SetOwnerID(v.ToV2())
})
}
// SessionKey returns public key of the session
// in a binary format.
func (t *Token) SessionKey() []byte {
return (*session.SessionToken)(t).
GetBody().
GetSessionKey()
}
// SetSessionKey sets public key of the session
// in a binary format.
func (t *Token) SetSessionKey(v []byte) {
t.setBodyField(func(body *session.SessionTokenBody) {
body.SetSessionKey(v)
})
}
func (t *Token) setLifetimeField(f func(*session.TokenLifetime)) {
t.setBodyField(func(body *session.SessionTokenBody) {
lt := body.GetLifetime()
if lt == nil {
lt = new(session.TokenLifetime)
body.SetLifetime(lt)
}
f(lt)
})
}
// Exp returns epoch number of the token expiration.
func (t *Token) Exp() uint64 {
return (*session.SessionToken)(t).
GetBody().
GetLifetime().
GetExp()
}
// SetExp sets epoch number of the token expiration.
func (t *Token) SetExp(exp uint64) {
t.setLifetimeField(func(lt *session.TokenLifetime) {
lt.SetExp(exp)
})
}
// Nbf returns starting epoch number of the token.
func (t *Token) Nbf() uint64 {
return (*session.SessionToken)(t).
GetBody().
GetLifetime().
GetNbf()
}
// SetNbf sets starting epoch number of the token.
func (t *Token) SetNbf(nbf uint64) {
t.setLifetimeField(func(lt *session.TokenLifetime) {
lt.SetNbf(nbf)
})
}
// Iat returns starting epoch number of the token.
func (t *Token) Iat() uint64 {
return (*session.SessionToken)(t).
GetBody().
GetLifetime().
GetIat()
}
// SetIat sets the number of the epoch in which the token was issued.
func (t *Token) SetIat(iat uint64) {
t.setLifetimeField(func(lt *session.TokenLifetime) {
lt.SetIat(iat)
})
}
// Sign calculates and writes signature of the Token data.
//
// Returns signature calculation errors.
func (t *Token) Sign(key *ecdsa.PrivateKey) error {
tV2 := (*session.SessionToken)(t)
signedData := v2signature.StableMarshalerWrapper{
SM: tV2.GetBody(),
}
return sigutil.SignDataWithHandler(key, signedData, func(key, sig []byte) {
tSig := tV2.GetSignature()
if tSig == nil {
tSig = new(refs.Signature)
}
tSig.SetKey(key)
tSig.SetSign(sig)
tV2.SetSignature(tSig)
})
}
// VerifySignature checks if token signature is
// presented and valid.
func (t *Token) VerifySignature() bool {
tV2 := (*session.SessionToken)(t)
signedData := v2signature.StableMarshalerWrapper{
SM: tV2.GetBody(),
}
return sigutil.VerifyDataWithSource(signedData, func() (key, sig []byte) {
tSig := tV2.GetSignature()
return tSig.GetKey(), tSig.GetSign()
}) == nil
}
// Signature returns Token signature.
func (t *Token) Signature() *signature.Signature {
return signature.NewFromV2(
(*session.SessionToken)(t).
GetSignature(),
)
}
// SetContext sets context of the Token.
//
// Supported contexts:
// - *ContainerContext.
//
// Resets context if it is not supported.
func (t *Token) SetContext(v interface{}) {
var cV2 session.SessionTokenContext
switch c := v.(type) {
case *ContainerContext:
cV2 = c.ToV2()
}
t.setBodyField(func(body *session.SessionTokenBody) {
body.SetContext(cV2)
})
}
// Context returns context of the Token.
//
// Supports same contexts as SetContext.
//
// Returns nil if context is not supported.
func (t *Token) Context() interface{} {
switch v := (*session.SessionToken)(t).
GetBody().
GetContext(); c := v.(type) {
default:
return nil
case *session.ContainerSessionContext:
return ContainerContextFromV2(c)
}
}
// GetContainerContext is a helper function that casts
// Token context to ContainerContext.
//
// Returns nil if context is not a ContainerContext.
func GetContainerContext(t *Token) *ContainerContext {
c, _ := t.Context().(*ContainerContext)
return c
}
// Marshal marshals Token into a protobuf binary form.
//
// Buffer is allocated when the argument is empty.
// Otherwise, the first buffer is used.
func (t *Token) Marshal(bs ...[]byte) ([]byte, error) {
var buf []byte
if len(bs) > 0 {
buf = bs[0]
}
return (*session.SessionToken)(t).
StableMarshal(buf)
}
// Unmarshal unmarshals protobuf binary representation of Token.
func (t *Token) Unmarshal(data []byte) error {
return (*session.SessionToken)(t).
Unmarshal(data)
}
// MarshalJSON encodes Token to protobuf JSON format.
func (t *Token) MarshalJSON() ([]byte, error) {
return (*session.SessionToken)(t).
MarshalJSON()
}
// UnmarshalJSON decodes Token from protobuf JSON format.
func (t *Token) UnmarshalJSON(data []byte) error {
return (*session.SessionToken)(t).
UnmarshalJSON(data)
}

202
session/session_test.go Normal file
View file

@ -0,0 +1,202 @@
package session_test
import (
"testing"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
ownertest "github.com/nspcc-dev/neofs-sdk-go/owner/test"
"github.com/nspcc-dev/neofs-sdk-go/session"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/stretchr/testify/require"
)
func TestSessionToken_SetID(t *testing.T) {
token := session.NewToken()
id := []byte{1, 2, 3}
token.SetID(id)
require.Equal(t, id, token.ID())
}
func TestSessionToken_SetOwnerID(t *testing.T) {
token := session.NewToken()
ownerID := ownertest.GenerateID()
token.SetOwnerID(ownerID)
require.Equal(t, ownerID, token.OwnerID())
}
func TestSessionToken_SetSessionKey(t *testing.T) {
token := session.NewToken()
key := []byte{1, 2, 3}
token.SetSessionKey(key)
require.Equal(t, key, token.SessionKey())
}
func TestSessionTokenEncoding(t *testing.T) {
tok := sessiontest.Generate()
t.Run("binary", func(t *testing.T) {
data, err := tok.Marshal()
require.NoError(t, err)
tok2 := session.NewToken()
require.NoError(t, tok2.Unmarshal(data))
require.Equal(t, tok, tok2)
})
t.Run("json", func(t *testing.T) {
data, err := tok.MarshalJSON()
require.NoError(t, err)
tok2 := session.NewToken()
require.NoError(t, tok2.UnmarshalJSON(data))
require.Equal(t, tok, tok2)
})
}
func TestToken_VerifySignature(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var tok *session.Token
require.False(t, tok.VerifySignature())
})
t.Run("unsigned", func(t *testing.T) {
tok := sessiontest.Generate()
require.False(t, tok.VerifySignature())
})
t.Run("signed", func(t *testing.T) {
tok := sessiontest.GenerateSigned()
require.True(t, tok.VerifySignature())
})
}
var unsupportedContexts = []interface{}{
123,
true,
session.NewToken(),
}
var nonContainerContexts = unsupportedContexts
func TestToken_Context(t *testing.T) {
tok := session.NewToken()
for _, item := range []struct {
ctx interface{}
v2assert func(interface{})
}{
{
ctx: sessiontest.ContainerContext(),
v2assert: func(c interface{}) {
require.Equal(t, c.(*session.ContainerContext).ToV2(), tok.ToV2().GetBody().GetContext())
},
},
} {
tok.SetContext(item.ctx)
require.Equal(t, item.ctx, tok.Context())
item.v2assert(item.ctx)
}
for _, c := range unsupportedContexts {
tok.SetContext(c)
require.Nil(t, tok.Context())
}
}
func TestGetContainerContext(t *testing.T) {
tok := session.NewToken()
c := sessiontest.ContainerContext()
tok.SetContext(c)
require.Equal(t, c, session.GetContainerContext(tok))
for _, c := range nonContainerContexts {
tok.SetContext(c)
require.Nil(t, session.GetContainerContext(tok))
}
}
func TestToken_Exp(t *testing.T) {
tok := session.NewToken()
const exp = 11
tok.SetExp(exp)
require.EqualValues(t, exp, tok.Exp())
}
func TestToken_Nbf(t *testing.T) {
tok := session.NewToken()
const nbf = 22
tok.SetNbf(nbf)
require.EqualValues(t, nbf, tok.Nbf())
}
func TestToken_Iat(t *testing.T) {
tok := session.NewToken()
const iat = 33
tok.SetIat(iat)
require.EqualValues(t, iat, tok.Iat())
}
func TestNewTokenFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *sessionv2.SessionToken
require.Nil(t, session.NewTokenFromV2(x))
})
}
func TestToken_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *session.Token
require.Nil(t, x.ToV2())
})
}
func TestNewToken(t *testing.T) {
t.Run("default values", func(t *testing.T) {
token := session.NewToken()
// check initial values
require.Nil(t, token.Signature())
require.Nil(t, token.OwnerID())
require.Nil(t, token.SessionKey())
require.Nil(t, token.ID())
require.Zero(t, token.Exp())
require.Zero(t, token.Iat())
require.Zero(t, token.Nbf())
// convert to v2 message
tokenV2 := token.ToV2()
require.Nil(t, tokenV2.GetSignature())
require.Nil(t, tokenV2.GetBody())
})
}

26
session/test/container.go Normal file
View file

@ -0,0 +1,26 @@
package sessiontest
import (
"math/rand"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
// ContainerContext returns session.ContainerContext
// which applies to random operation on a random container.
func ContainerContext() *session.ContainerContext {
c := session.NewContainerContext()
setters := []func(){
c.ForPut,
c.ForDelete,
c.ForSetEACL,
}
setters[rand.Uint32()%uint32(len(setters))]()
c.ApplyTo(cidtest.GenerateID())
return c
}

64
session/test/token.go Normal file
View file

@ -0,0 +1,64 @@
package sessiontest
import (
"math/rand"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
var p *keys.PrivateKey
func init() {
var err error
p, err = keys.NewPrivateKey()
if err != nil {
panic(err)
}
}
// Generate returns random session.Token.
//
// Resulting token is unsigned.
func Generate() *session.Token {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
if err != nil {
panic(err)
}
w := new(owner.NEO3Wallet)
rand.Read(w.Bytes())
ownerID := owner.NewID()
ownerID.SetNeo3Wallet(w)
keyBin := p.PublicKey().Bytes()
tok.SetID(uid)
tok.SetOwnerID(ownerID)
tok.SetSessionKey(keyBin)
tok.SetExp(11)
tok.SetNbf(22)
tok.SetIat(33)
return tok
}
// GenerateSigned returns signed random session.Token.
//
// Panics if token could not be signed (actually unexpected).
func GenerateSigned() *session.Token {
tok := Generate()
err := tok.Sign(&p.PrivateKey)
if err != nil {
panic(err)
}
return tok
}