[#197] session: Refactor and document the package

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-04-07 19:09:15 +03:00 committed by LeL
parent 497053c785
commit 552c7875bf
32 changed files with 1622 additions and 1358 deletions

View file

@ -9,7 +9,6 @@ import (
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-api-go/v2/signature" "github.com/nspcc-dev/neofs-api-go/v2/signature"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status" apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/version" "github.com/nspcc-dev/neofs-sdk-go/version"
) )
@ -35,23 +34,6 @@ func (x statusRes) Status() apistatus.Status {
return x.st return x.st
} }
type prmSession struct {
tokenSessionSet bool
tokenSession session.Token
}
// SetSessionToken sets token of the session within which request should be sent.
func (x *prmSession) SetSessionToken(tok session.Token) {
x.tokenSession = tok
x.tokenSessionSet = true
}
func (x prmSession) writeToMetaHeader(meta *v2session.RequestMetaHeader) {
if x.tokenSessionSet {
meta.SetSessionToken(x.tokenSession.ToV2())
}
}
// groups meta parameters shared between all Client operations. // groups meta parameters shared between all Client operations.
type prmCommonMeta struct { type prmCommonMeta struct {
// NeoFS request X-Headers // NeoFS request X-Headers

View file

@ -107,9 +107,15 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
// form meta header // form meta header
var meta v2session.RequestMetaHeader var meta v2session.RequestMetaHeader
meta.SetSessionToken(prm.cnr.SessionToken().ToV2())
prm.prmCommonMeta.writeToMetaHeader(&meta) prm.prmCommonMeta.writeToMetaHeader(&meta)
if tok := prm.cnr.SessionToken(); tok != nil {
var tokv2 v2session.Token
tok.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
}
// form request // form request
var req v2container.PutRequest var req v2container.PutRequest
@ -240,9 +246,16 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
cnr := container.NewContainerFromV2(body.GetContainer()) cnr := container.NewContainerFromV2(body.GetContainer())
cnr.SetSessionToken( tokv2 := body.GetSessionToken()
session.NewTokenFromV2(body.GetSessionToken()), if tokv2 != nil {
) var tok session.Container
// FIXME: need to handle the error
err := tok.ReadFromV2(*tokv2)
if err == nil {
cnr.SetSessionToken(&tok)
}
}
var sig *neofscrypto.Signature var sig *neofscrypto.Signature
@ -368,10 +381,12 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC
// PrmContainerDelete groups parameters of ContainerDelete operation. // PrmContainerDelete groups parameters of ContainerDelete operation.
type PrmContainerDelete struct { type PrmContainerDelete struct {
prmCommonMeta prmCommonMeta
prmSession
idSet bool idSet bool
id cid.ID id cid.ID
tokSet bool
tok session.Container
} }
// SetContainer sets identifier of the NeoFS container to be removed. // SetContainer sets identifier of the NeoFS container to be removed.
@ -381,6 +396,17 @@ func (x *PrmContainerDelete) SetContainer(id cid.ID) {
x.idSet = true x.idSet = true
} }
// WithinSession specifies session within which container should be removed.
//
// Creator of the session acquires the authorship of the request.
// This may affect the execution of an operation (e.g. access control).
//
// Must be signed.
func (x *PrmContainerDelete) WithinSession(tok session.Container) {
x.tok = tok
x.tokSet = true
}
// ResContainerDelete groups resulting values of ContainerDelete operation. // ResContainerDelete groups resulting values of ContainerDelete operation.
type ResContainerDelete struct { type ResContainerDelete struct {
statusRes statusRes
@ -456,10 +482,15 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
// form meta header // form meta header
var meta v2session.RequestMetaHeader var meta v2session.RequestMetaHeader
prm.prmSession.writeToMetaHeader(&meta)
prm.prmCommonMeta.writeToMetaHeader(&meta) prm.prmCommonMeta.writeToMetaHeader(&meta)
if prm.tokSet {
var tokv2 v2session.Token
prm.tok.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
}
// form request // form request
var req v2container.DeleteRequest var req v2container.DeleteRequest
@ -577,9 +608,16 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC
table := eacl.NewTableFromV2(body.GetEACL()) table := eacl.NewTableFromV2(body.GetEACL())
table.SetSessionToken( tokv2 := body.GetSessionToken()
session.NewTokenFromV2(body.GetSessionToken()), if tokv2 != nil {
) var tok session.Container
// FIXME: need to handle the error
err := tok.ReadFromV2(*tokv2)
if err == nil {
table.SetSessionToken(&tok)
}
}
var sig *neofscrypto.Signature var sig *neofscrypto.Signature
@ -674,9 +712,15 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
// form meta header // form meta header
var meta v2session.RequestMetaHeader var meta v2session.RequestMetaHeader
meta.SetSessionToken(prm.table.SessionToken().ToV2())
prm.prmCommonMeta.writeToMetaHeader(&meta) prm.prmCommonMeta.writeToMetaHeader(&meta)
if tok := prm.table.SessionToken(); tok != nil {
var tokv2 v2session.Token
tok.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
}
// form request // form request
var req v2container.SetExtendedACLRequest var req v2container.SetExtendedACLRequest

View file

@ -34,8 +34,11 @@ type PrmObjectDelete struct {
// This may affect the execution of an operation (e.g. access control). // This may affect the execution of an operation (e.g. access control).
// //
// Must be signed. // Must be signed.
func (x *PrmObjectDelete) WithinSession(t session.Token) { func (x *PrmObjectDelete) WithinSession(t session.Object) {
x.meta.SetSessionToken(t.ToV2()) var tv2 v2session.Token
t.WriteToV2(&tv2)
x.meta.SetSessionToken(&tv2)
} }
// WithBearerToken attaches bearer token to be used for the operation. // WithBearerToken attaches bearer token to be used for the operation.

View file

@ -30,7 +30,7 @@ type prmObjectRead struct {
local bool local bool
sessionSet bool sessionSet bool
session session.Token session session.Object
bearerSet bool bearerSet bool
bearer bearer.Token bearer bearer.Token
@ -54,7 +54,10 @@ func (x prmObjectRead) writeToMetaHeader(h *v2session.RequestMetaHeader) {
} }
if x.sessionSet { if x.sessionSet {
h.SetSessionToken(x.session.ToV2()) var tokv2 v2session.Token
x.session.WriteToV2(&tokv2)
h.SetSessionToken(&tokv2)
} }
x.prmCommonMeta.writeToMetaHeader(h) x.prmCommonMeta.writeToMetaHeader(h)
@ -76,7 +79,7 @@ func (x *prmObjectRead) MarkLocal() {
// This may affect the execution of an operation (e.g. access control). // This may affect the execution of an operation (e.g. access control).
// //
// Must be signed. // Must be signed.
func (x *prmObjectRead) WithinSession(t session.Token) { func (x *prmObjectRead) WithinSession(t session.Object) {
x.session = t x.session = t
x.sessionSet = true x.sessionSet = true
} }

View file

@ -37,8 +37,11 @@ func (x *PrmObjectHash) MarkLocal() {
// This may affect the execution of an operation (e.g. access control). // This may affect the execution of an operation (e.g. access control).
// //
// Must be signed. // Must be signed.
func (x *PrmObjectHash) WithinSession(t session.Token) { func (x *PrmObjectHash) WithinSession(t session.Object) {
x.meta.SetSessionToken(t.ToV2()) var tv2 v2session.Token
t.WriteToV2(&tv2)
x.meta.SetSessionToken(&tv2)
} }
// WithBearerToken attaches bearer token to be used for the operation. // WithBearerToken attaches bearer token to be used for the operation.

View file

@ -80,8 +80,11 @@ func (x *ObjectWriter) WithBearerToken(t bearer.Token) {
// WithinSession specifies session within which object should be stored. // WithinSession specifies session within which object should be stored.
// Should be called once before any writing steps. // Should be called once before any writing steps.
func (x *ObjectWriter) WithinSession(t session.Token) { func (x *ObjectWriter) WithinSession(t session.Object) {
x.metaHdr.SetSessionToken(t.ToV2()) var tv2 v2session.Token
t.WriteToV2(&tv2)
x.metaHdr.SetSessionToken(&tv2)
} }
// MarkLocal tells the server to execute the operation locally. // MarkLocal tells the server to execute the operation locally.

View file

@ -28,7 +28,7 @@ type PrmObjectSearch struct {
local bool local bool
sessionSet bool sessionSet bool
session session.Token session session.Object
bearerSet bool bearerSet bool
bearer bearer.Token bearer bearer.Token
@ -50,7 +50,7 @@ func (x *PrmObjectSearch) MarkLocal() {
// This may affect the execution of an operation (e.g. access control). // This may affect the execution of an operation (e.g. access control).
// //
// Must be signed. // Must be signed.
func (x *PrmObjectSearch) WithinSession(t session.Token) { func (x *PrmObjectSearch) WithinSession(t session.Object) {
x.session = t x.session = t
x.sessionSet = true x.sessionSet = true
} }
@ -269,7 +269,10 @@ func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*Ob
} }
if prm.sessionSet { if prm.sessionSet {
meta.SetSessionToken(prm.session.ToV2()) var tokv2 v2session.Token
prm.session.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
} }
prm.prmCommonMeta.writeToMetaHeader(&meta) prm.prmCommonMeta.writeToMetaHeader(&meta)

View file

@ -18,7 +18,7 @@ import (
type Container struct { type Container struct {
v2 container.Container v2 container.Container
token *session.Token token *session.Container
sig *neofscrypto.Signature sig *neofscrypto.Signature
} }
@ -172,13 +172,13 @@ func (c *Container) SetPlacementPolicy(v *netmap.PlacementPolicy) {
// SessionToken returns token of the session within // SessionToken returns token of the session within
// which container was created. // which container was created.
func (c Container) SessionToken() *session.Token { func (c Container) SessionToken() *session.Container {
return c.token return c.token
} }
// SetSessionToken sets token of the session within // SetSessionToken sets token of the session within
// which container was created. // which container was created.
func (c *Container) SetSessionToken(t *session.Token) { func (c *Container) SetSessionToken(t *session.Container) {
c.token = t c.token = t
} }

View file

@ -76,7 +76,7 @@ func TestContainerEncoding(t *testing.T) {
} }
func TestContainer_SessionToken(t *testing.T) { func TestContainer_SessionToken(t *testing.T) {
tok := sessiontest.Token() tok := sessiontest.Container()
cnr := container.New() cnr := container.New()

View file

@ -19,7 +19,7 @@ import (
type Table struct { type Table struct {
version version.Version version version.Version
cid *cid.ID cid *cid.ID
token *session.Token token *session.Container
sig *neofscrypto.Signature sig *neofscrypto.Signature
records []Record records []Record
} }
@ -63,13 +63,13 @@ func (t *Table) AddRecord(r *Record) {
// SessionToken returns token of the session // SessionToken returns token of the session
// within which Table was set. // within which Table was set.
func (t Table) SessionToken() *session.Token { func (t Table) SessionToken() *session.Container {
return t.token return t.token
} }
// SetSessionToken sets token of the session // SetSessionToken sets token of the session
// within which Table was set. // within which Table was set.
func (t *Table) SetSessionToken(tok *session.Token) { func (t *Table) SetSessionToken(tok *session.Container) {
t.token = tok t.token = tok
} }

View file

@ -93,7 +93,7 @@ func TestTableEncoding(t *testing.T) {
} }
func TestTable_SessionToken(t *testing.T) { func TestTable_SessionToken(t *testing.T) {
tok := sessiontest.Token() tok := sessiontest.Container()
table := eacl.NewTable() table := eacl.NewTable()
table.SetSessionToken(tok) table.SetSessionToken(tok)

View file

@ -6,6 +6,7 @@ import (
"github.com/nspcc-dev/neofs-api-go/v2/object" "github.com/nspcc-dev/neofs-api-go/v2/object"
"github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/refs"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/checksum" "github.com/nspcc-dev/neofs-sdk-go/checksum"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto" neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
@ -519,19 +520,31 @@ func (o *Object) resetRelations() {
// SessionToken returns token of the session // SessionToken returns token of the session
// within which object was created. // within which object was created.
func (o *Object) SessionToken() *session.Token { func (o *Object) SessionToken() *session.Object {
return session.NewTokenFromV2( tokv2 := (*object.Object)(o).GetHeader().GetSessionToken()
(*object.Object)(o). if tokv2 == nil {
GetHeader(). return nil
GetSessionToken(), }
)
var res session.Object
fmt.Println(res.ReadFromV2(*tokv2))
return &res
} }
// SetSessionToken sets token of the session // SetSessionToken sets token of the session
// within which object was created. // within which object was created.
func (o *Object) SetSessionToken(v *session.Token) { func (o *Object) SetSessionToken(v *session.Object) {
o.setHeaderField(func(h *object.Header) { o.setHeaderField(func(h *object.Header) {
h.SetSessionToken(v.ToV2()) var tokv2 *v2session.Token
if v != nil {
tokv2 = new(v2session.Token)
v.WriteToV2(tokv2)
}
h.SetSessionToken(tokv2)
}) })
} }

View file

@ -213,7 +213,7 @@ func TestObject_ToV2(t *testing.T) {
func TestObject_SetSessionToken(t *testing.T) { func TestObject_SetSessionToken(t *testing.T) {
obj := New() obj := New()
tok := sessiontest.Token() tok := sessiontest.ObjectSigned()
obj.SetSessionToken(tok) obj.SetSessionToken(tok)

View file

@ -46,7 +46,7 @@ func generate(withParent bool) *object.Object {
ver := version.Current() ver := version.Current()
x.SetID(oidtest.ID()) x.SetID(oidtest.ID())
x.SetSessionToken(sessiontest.Token()) x.SetSessionToken(sessiontest.Object())
x.SetPayload([]byte{1, 2, 3}) x.SetPayload([]byte{1, 2, 3})
x.SetOwnerID(usertest.ID()) x.SetOwnerID(usertest.ID())
x.SetContainerID(cidtest.ID()) x.SetContainerID(cidtest.ID())

View file

@ -14,7 +14,7 @@ type sessionCache struct {
} }
type cacheValue struct { type cacheValue struct {
token *session.Token token session.Object
} }
func newCache() (*sessionCache, error) { func newCache() (*sessionCache, error) {
@ -29,28 +29,22 @@ func newCache() (*sessionCache, error) {
// Get returns a copy of the session token from the cache without signature // Get returns a copy of the session token from the cache without signature
// and context related fields. Returns nil if token is missing in the cache. // and context related fields. Returns nil if token is missing in the cache.
// It is safe to modify and re-sign returned session token. // It is safe to modify and re-sign returned session token.
func (c *sessionCache) Get(key string) *session.Token { func (c *sessionCache) Get(key string) (session.Object, bool) {
valueRaw, ok := c.cache.Get(key) valueRaw, ok := c.cache.Get(key)
if !ok { if !ok {
return nil return session.Object{}, false
} }
value := valueRaw.(*cacheValue) value := valueRaw.(*cacheValue)
if c.expired(value) { if c.expired(value) {
c.cache.Remove(key) c.cache.Remove(key)
return nil return session.Object{}, false
} }
if value.token == nil { return value.token, true
return nil
}
res := copySessionTokenWithoutSignatureAndContext(*value.token)
return &res
} }
func (c *sessionCache) Put(key string, token *session.Token) bool { func (c *sessionCache) Put(key string, token session.Object) bool {
return c.cache.Add(key, &cacheValue{ return c.cache.Add(key, &cacheValue{
token: token, token: token,
}) })
@ -73,5 +67,5 @@ func (c *sessionCache) updateEpoch(newEpoch uint64) {
func (c *sessionCache) expired(val *cacheValue) bool { func (c *sessionCache) expired(val *cacheValue) bool {
epoch := atomic.LoadUint64(&c.currentEpoch) epoch := atomic.LoadUint64(&c.currentEpoch)
return val.token.Exp() <= epoch return val.token.ExpiredAt(epoch)
} }

View file

@ -11,29 +11,27 @@ import (
func TestSessionCache_GetUnmodifiedToken(t *testing.T) { func TestSessionCache_GetUnmodifiedToken(t *testing.T) {
const key = "Foo" const key = "Foo"
target := sessiontest.Token() target := *sessiontest.Object()
pk, err := keys.NewPrivateKey() pk, err := keys.NewPrivateKey()
require.NoError(t, err) require.NoError(t, err)
check := func(t *testing.T, tok *session.Token, extra string) { check := func(t *testing.T, tok session.Object, extra string) {
require.False(t, tok.VerifySignature(), extra) require.False(t, tok.VerifySignature(), extra)
require.Nil(t, tok.Context(), extra)
} }
cache, err := newCache() cache, err := newCache()
require.NoError(t, err) require.NoError(t, err)
cache.Put(key, target) cache.Put(key, target)
value := cache.Get(key) value, ok := cache.Get(key)
require.True(t, ok)
check(t, value, "before sign") check(t, value, "before sign")
err = value.Sign(&pk.PrivateKey) err = value.Sign(pk.PrivateKey)
require.NoError(t, err) require.NoError(t, err)
octx := sessiontest.ObjectContext() value, ok = cache.Get(key)
value.SetContext(octx) require.True(t, ok)
value = cache.Get(key)
check(t, value, "after sign") check(t, value, "after sign")
} }

View file

@ -14,13 +14,14 @@ import (
"sync" "sync"
"time" "time"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/accounting" "github.com/nspcc-dev/neofs-sdk-go/accounting"
"github.com/nspcc-dev/neofs-sdk-go/bearer" "github.com/nspcc-dev/neofs-sdk-go/bearer"
sdkClient "github.com/nspcc-dev/neofs-sdk-go/client" sdkClient "github.com/nspcc-dev/neofs-sdk-go/client"
"github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/eacl" "github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/object"
@ -155,7 +156,7 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
var cliPrm sdkClient.PrmContainerDelete var cliPrm sdkClient.PrmContainerDelete
cliPrm.SetContainer(prm.cnrID) cliPrm.SetContainer(prm.cnrID)
if prm.stokenSet { if prm.stokenSet {
cliPrm.SetSessionToken(prm.stoken) cliPrm.WithinSession(prm.stoken)
} }
if _, err := c.client.ContainerDelete(ctx, cliPrm); err != nil { if _, err := c.client.ContainerDelete(ctx, cliPrm); err != nil {
@ -607,26 +608,26 @@ type clientPack struct {
type prmContext struct { type prmContext struct {
defaultSession bool defaultSession bool
verb sessionv2.ObjectSessionVerb verb session.ObjectVerb
addr *address.Address addr address.Address
} }
func (x *prmContext) useDefaultSession() { func (x *prmContext) useDefaultSession() {
x.defaultSession = true x.defaultSession = true
} }
func (x *prmContext) useAddress(addr *address.Address) { func (x *prmContext) useAddress(addr address.Address) {
x.addr = addr x.addr = addr
} }
func (x *prmContext) useVerb(verb sessionv2.ObjectSessionVerb) { func (x *prmContext) useVerb(verb session.ObjectVerb) {
x.verb = verb x.verb = verb
} }
type prmCommon struct { type prmCommon struct {
key *ecdsa.PrivateKey key *ecdsa.PrivateKey
btoken *bearer.Token btoken *bearer.Token
stoken *session.Token stoken *session.Object
} }
// UseKey specifies private key to sign the requests. // UseKey specifies private key to sign the requests.
@ -641,7 +642,7 @@ func (x *prmCommon) UseBearer(token bearer.Token) {
} }
// UseSession specifies session within which operation should be performed. // UseSession specifies session within which operation should be performed.
func (x *prmCommon) UseSession(token session.Token) { func (x *prmCommon) UseSession(token session.Object) {
x.stoken = &token x.stoken = &token
} }
@ -787,7 +788,7 @@ func (x *PrmContainerList) SetOwnerID(ownerID user.ID) {
type PrmContainerDelete struct { type PrmContainerDelete struct {
cnrID cid.ID cnrID cid.ID
stoken session.Token stoken session.Container
stokenSet bool stokenSet bool
waitParams WaitParams waitParams WaitParams
@ -800,7 +801,7 @@ func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) {
} }
// SetSessionToken specifies session within which operation should be performed. // SetSessionToken specifies session within which operation should be performed.
func (x *PrmContainerDelete) SetSessionToken(token session.Token) { func (x *PrmContainerDelete) SetSessionToken(token session.Container) {
x.stoken = token x.stoken = token
x.stokenSet = true x.stokenSet = true
} }
@ -990,7 +991,7 @@ func (p *Pool) Dial(ctx context.Context) error {
zap.Error(err)) zap.Error(err))
} else if err == nil { } else if err == nil {
healthy, atLeastOneHealthy = true, true healthy, atLeastOneHealthy = true, true
_ = p.cache.Put(formCacheKey(addr, p.key), st) _ = p.cache.Put(formCacheKey(addr, p.key), *st)
} }
clientPacks[j] = &clientPack{client: c, healthy: healthy, address: addr} clientPacks[j] = &clientPack{client: c, healthy: healthy, address: addr}
} }
@ -1229,7 +1230,7 @@ func (p *Pool) checkSessionTokenErr(err error, address string) bool {
return false return false
} }
func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.ID, dur uint64) (*session.Token, error) { func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.ID, dur uint64) (*session.Object, error) {
ni, err := c.networkInfo(ctx, prmNetworkInfo{}) ni, err := c.networkInfo(ctx, prmNetworkInfo{})
if err != nil { if err != nil {
return nil, err return nil, err
@ -1251,7 +1252,26 @@ func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.I
return nil, err return nil, err
} }
return sessionTokenForOwner(ownerID, res, exp), nil var id uuid.UUID
err = id.UnmarshalBinary(res.id)
if err != nil {
return nil, fmt.Errorf("invalid session token ID: %w", err)
}
var key neofsecdsa.PublicKey
err = key.Decode(res.sessionKey)
if err != nil {
return nil, fmt.Errorf("invalid public session key: %w", err)
}
var st session.Object
st.SetID(id)
st.SetAuthKey(&key)
st.SetExp(exp)
return &st, nil
} }
type callContext struct { type callContext struct {
@ -1268,8 +1288,9 @@ type callContext struct {
// flag to open default session if session token is missing // flag to open default session if session token is missing
sessionDefault bool sessionDefault bool
sessionTarget func(session.Token) sessionTarget func(session.Object)
sessionContext *session.ObjectContext sessionVerb session.ObjectVerb
sessionAddr address.Address
} }
func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContext) error { func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContext) error {
@ -1294,9 +1315,8 @@ func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContex
// note that we don't override session provided by the caller // note that we don't override session provided by the caller
ctx.sessionDefault = cfg.stoken == nil && prmCtx.defaultSession ctx.sessionDefault = cfg.stoken == nil && prmCtx.defaultSession
if ctx.sessionDefault { if ctx.sessionDefault {
ctx.sessionContext = session.NewObjectContext() ctx.sessionVerb = prmCtx.verb
ctx.sessionContext.ToV2().SetVerb(prmCtx.verb) ctx.sessionAddr = prmCtx.addr
ctx.sessionContext.ApplyTo(prmCtx.addr)
} }
return err return err
@ -1307,32 +1327,32 @@ func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContex
func (p *Pool) openDefaultSession(ctx *callContext) error { func (p *Pool) openDefaultSession(ctx *callContext) error {
cacheKey := formCacheKey(ctx.endpoint, ctx.key) cacheKey := formCacheKey(ctx.endpoint, ctx.key)
tok := p.cache.Get(cacheKey) tok, ok := p.cache.Get(cacheKey)
if tok == nil { if !ok {
var err error var err error
var sessionOwner user.ID var sessionOwner user.ID
user.IDFromKey(&sessionOwner, ctx.key.PublicKey) user.IDFromKey(&sessionOwner, ctx.key.PublicKey)
// open new session // open new session
tok, err = createSessionTokenForDuration(ctx, ctx.client, sessionOwner, p.stokenDuration) t, err := createSessionTokenForDuration(ctx, ctx.client, sessionOwner, p.stokenDuration)
if err != nil { if err != nil {
return fmt.Errorf("session API client: %w", err) return fmt.Errorf("session API client: %w", err)
} }
// cache the opened session // cache the opened session
p.cache.Put(cacheKey, tok) p.cache.Put(cacheKey, *t)
} }
tokToSign := *tok tok.ForVerb(ctx.sessionVerb)
tokToSign.SetContext(ctx.sessionContext) tok.ApplyTo(ctx.sessionAddr)
// sign the token // sign the token
if err := tokToSign.Sign(ctx.key); err != nil { if err := tok.Sign(*ctx.key); err != nil {
return fmt.Errorf("sign token of the opened session: %w", err) return fmt.Errorf("sign token of the opened session: %w", err)
} }
ctx.sessionTarget(tokToSign) ctx.sessionTarget(tok)
return nil return nil
} }
@ -1371,8 +1391,8 @@ func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (*oid.ID, error)
var prmCtx prmContext var prmCtx prmContext
prmCtx.useDefaultSession() prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbPut) prmCtx.useVerb(session.VerbObjectPut)
prmCtx.useAddress(newAddressFromCnrID(cIDp)) prmCtx.useAddress(*newAddressFromCnrID(cIDp))
p.fillAppropriateKey(&prm.prmCommon) p.fillAppropriateKey(&prm.prmCommon)
@ -1408,8 +1428,8 @@ func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (*oid.ID, error)
func (p *Pool) DeleteObject(ctx context.Context, prm PrmObjectDelete) error { func (p *Pool) DeleteObject(ctx context.Context, prm PrmObjectDelete) error {
var prmCtx prmContext var prmCtx prmContext
prmCtx.useDefaultSession() prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbDelete) prmCtx.useVerb(session.VerbObjectDelete)
prmCtx.useAddress(&prm.addr) prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon) p.fillAppropriateKey(&prm.prmCommon)
@ -1456,8 +1476,8 @@ type ResGetObject struct {
func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject, error) { func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject, error) {
var prmCtx prmContext var prmCtx prmContext
prmCtx.useDefaultSession() prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbGet) prmCtx.useVerb(session.VerbObjectGet)
prmCtx.useAddress(&prm.addr) prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon) p.fillAppropriateKey(&prm.prmCommon)
@ -1481,8 +1501,8 @@ func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject,
func (p *Pool) HeadObject(ctx context.Context, prm PrmObjectHead) (*object.Object, error) { func (p *Pool) HeadObject(ctx context.Context, prm PrmObjectHead) (*object.Object, error) {
var prmCtx prmContext var prmCtx prmContext
prmCtx.useDefaultSession() prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbHead) prmCtx.useVerb(session.VerbObjectHead)
prmCtx.useAddress(&prm.addr) prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon) p.fillAppropriateKey(&prm.prmCommon)
@ -1529,8 +1549,8 @@ func (x *ResObjectRange) Close() error {
func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (*ResObjectRange, error) { func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (*ResObjectRange, error) {
var prmCtx prmContext var prmCtx prmContext
prmCtx.useDefaultSession() prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbRange) prmCtx.useVerb(session.VerbObjectRange)
prmCtx.useAddress(&prm.addr) prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon) p.fillAppropriateKey(&prm.prmCommon)
@ -1595,8 +1615,8 @@ func (x *ResObjectSearch) Close() {
func (p *Pool) SearchObjects(ctx context.Context, prm PrmObjectSearch) (*ResObjectSearch, error) { func (p *Pool) SearchObjects(ctx context.Context, prm PrmObjectSearch) (*ResObjectSearch, error) {
var prmCtx prmContext var prmCtx prmContext
prmCtx.useDefaultSession() prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbSearch) prmCtx.useVerb(session.VerbObjectSearch)
prmCtx.useAddress(newAddressFromCnrID(&prm.cnrID)) prmCtx.useAddress(*newAddressFromCnrID(&prm.cnrID))
p.fillAppropriateKey(&prm.prmCommon) p.fillAppropriateKey(&prm.prmCommon)
@ -1788,17 +1808,6 @@ func (p *Pool) Close() {
<-p.closedCh <-p.closedCh
} }
// creates new session token with specified owner from SessionCreate call result.
func sessionTokenForOwner(id user.ID, cliRes *resCreateSession, exp uint64) *session.Token {
st := session.NewToken()
st.SetOwnerID(&id)
st.SetID(cliRes.id)
st.SetSessionKey(cliRes.sessionKey)
st.SetExp(exp)
return st
}
func newAddressFromCnrID(cnrID *cid.ID) *address.Address { func newAddressFromCnrID(cnrID *cid.ID) *address.Address {
addr := address.NewAddress() addr := address.NewAddress()
if cnrID != nil { if cnrID != nil {
@ -1806,22 +1815,3 @@ func newAddressFromCnrID(cnrID *cid.ID) *address.Address {
} }
return addr return addr
} }
func copySessionTokenWithoutSignatureAndContext(from session.Token) (to session.Token) {
to.SetIat(from.Iat())
to.SetExp(from.Exp())
to.SetNbf(from.Nbf())
sessionTokenID := make([]byte, len(from.ID()))
copy(sessionTokenID, from.ID())
to.SetID(sessionTokenID)
sessionTokenKey := make([]byte, len(from.SessionKey()))
copy(sessionTokenKey, from.SessionKey())
to.SetSessionKey(sessionTokenKey)
sessionTokenOwner := *from.OwnerID()
to.SetOwnerID(&sessionTokenOwner)
return to
}

View file

@ -14,6 +14,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-sdk-go/container" "github.com/nspcc-dev/neofs-sdk-go/container"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/netmap" "github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/object" "github.com/nspcc-dev/neofs-sdk-go/object"
"github.com/nspcc-dev/neofs-sdk-go/object/address" "github.com/nspcc-dev/neofs-sdk-go/object/address"
@ -73,20 +74,30 @@ func newPrivateKey(t *testing.T) *ecdsa.PrivateKey {
return &p.PrivateKey return &p.PrivateKey
} }
func newBinPublicKey(t *testing.T) []byte {
authKey := neofsecdsa.PublicKey(newPrivateKey(t).PublicKey)
bKey := make([]byte, authKey.MaxEncodedSize())
bKey = bKey[:authKey.Encode(bKey)]
return bKey
}
func TestBuildPoolOneNodeFailed(t *testing.T) { func TestBuildPoolOneNodeFailed(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
ctrl2 := gomock.NewController(t) ctrl2 := gomock.NewController(t)
var expectedToken *session.Token var expectedToken *session.Object
clientCount := -1 clientCount := -1
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
clientCount++ clientCount++
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t) tok := newToken(t)
id := tok.ID()
return &resCreateSession{ return &resCreateSession{
sessionKey: tok.SessionKey(), id: id[:],
id: tok.ID(), sessionKey: newBinPublicKey(t),
}, nil }, nil
}).AnyTimes() }).AnyTimes()
@ -96,9 +107,10 @@ func TestBuildPoolOneNodeFailed(t *testing.T) {
mockClient2 := NewMockClient(ctrl2) mockClient2 := NewMockClient(ctrl2)
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
expectedToken = newToken(t) expectedToken = newToken(t)
id := expectedToken.ID()
return &resCreateSession{ return &resCreateSession{
sessionKey: expectedToken.SessionKey(), id: id[:],
id: expectedToken.ID(), sessionKey: newBinPublicKey(t),
}, nil }, nil
}).AnyTimes() }).AnyTimes()
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
@ -134,8 +146,8 @@ func TestBuildPoolOneNodeFailed(t *testing.T) {
if err != nil { if err != nil {
return false return false
} }
st := clientPool.cache.Get(formCacheKey(cp.address, clientPool.key)) st, _ := clientPool.cache.Get(formCacheKey(cp.address, clientPool.key))
return areEqualTokens(st, expectedToken) return areEqualTokens(&st, expectedToken)
} }
require.Never(t, condition, 900*time.Millisecond, 100*time.Millisecond) require.Never(t, condition, 900*time.Millisecond, 100*time.Millisecond)
require.Eventually(t, condition, 3*time.Second, 300*time.Millisecond) require.Eventually(t, condition, 3*time.Second, 300*time.Millisecond)
@ -152,14 +164,14 @@ func TestBuildPoolZeroNodes(t *testing.T) {
func TestOneNode(t *testing.T) { func TestOneNode(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
tok := session.NewToken() uid := uuid.New()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err) var tok session.Object
tok.SetID(uid) tok.SetID(uid)
tokRes := &resCreateSession{ tokRes := &resCreateSession{
id: tok.ID(), id: uid[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
} }
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
@ -184,34 +196,34 @@ func TestOneNode(t *testing.T) {
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, areEqualTokens(tok, st)) require.True(t, areEqualTokens(&tok, &st))
} }
func areEqualTokens(t1, t2 *session.Token) bool { func areEqualTokens(t1, t2 *session.Object) bool {
if t1 == nil || t2 == nil { if t1 == nil || t2 == nil {
return false return false
} }
return bytes.Equal(t1.ID(), t2.ID()) &&
bytes.Equal(t1.SessionKey(), t2.SessionKey()) id1, id2 := t1.ID(), t2.ID()
return bytes.Equal(id1[:], id2[:])
} }
func TestTwoNodes(t *testing.T) { func TestTwoNodes(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
var tokens []*session.Token var tokens []*session.Object
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := session.NewToken() var tok session.Object
uid, err := uuid.New().MarshalBinary() uid := uuid.New()
require.NoError(t, err)
tok.SetID(uid) tok.SetID(uid)
tokens = append(tokens, tok) tokens = append(tokens, &tok)
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: uid[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, err }, nil
}) })
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes() mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes() mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
@ -235,11 +247,11 @@ func TestTwoNodes(t *testing.T) {
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st)) require.True(t, containsTokens(tokens, &st))
} }
func containsTokens(list []*session.Token, item *session.Token) bool { func containsTokens(list []*session.Object, item *session.Object) bool {
for _, tok := range list { for _, tok := range list {
if areEqualTokens(tok, item) { if areEqualTokens(tok, item) {
return true return true
@ -252,7 +264,7 @@ func TestOneOfTwoFailed(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
ctrl2 := gomock.NewController(t) ctrl2 := gomock.NewController(t)
var tokens []*session.Token var tokens []*session.Object
clientCount := -1 clientCount := -1
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
clientCount++ clientCount++
@ -260,9 +272,10 @@ func TestOneOfTwoFailed(t *testing.T) {
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t) tok := newToken(t)
tokens = append(tokens, tok) tokens = append(tokens, tok)
id := tok.ID()
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: id[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, nil }, nil
}).AnyTimes() }).AnyTimes()
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
@ -272,9 +285,10 @@ func TestOneOfTwoFailed(t *testing.T) {
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t) tok := newToken(t)
tokens = append(tokens, tok) tokens = append(tokens, tok)
id := tok.ID()
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: id[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, nil }, nil
}).AnyTimes() }).AnyTimes()
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).DoAndReturn(func(_ interface{}, _ ...interface{}) (*netmap.NodeInfo, error) { mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).DoAndReturn(func(_ interface{}, _ ...interface{}) (*netmap.NodeInfo, error) {
@ -313,8 +327,8 @@ func TestOneOfTwoFailed(t *testing.T) {
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, areEqualTokens(tokens[0], st)) require.True(t, areEqualTokens(tokens[0], &st))
} }
} }
@ -323,7 +337,10 @@ func TestTwoFailed(t *testing.T) {
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{}, nil).AnyTimes() mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{
id: uuid.Nil[:],
sessionKey: newBinPublicKey(t),
}, nil).AnyTimes()
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes() mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes() mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
return mockClient, nil return mockClient, nil
@ -356,19 +373,18 @@ func TestTwoFailed(t *testing.T) {
func TestSessionCache(t *testing.T) { func TestSessionCache(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
var tokens []*session.Token var tokens []*session.Object
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*resCreateSession, error) { mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*resCreateSession, error) {
tok := session.NewToken() var tok session.Object
uid, err := uuid.New().MarshalBinary() uid := uuid.New()
require.NoError(t, err)
tok.SetID(uid) tok.SetID(uid)
tokens = append(tokens, tok) tokens = append(tokens, &tok)
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: uid[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, err }, nil
}).MaxTimes(3) }).MaxTimes(3)
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes() mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
@ -399,12 +415,12 @@ func TestSessionCache(t *testing.T) {
// cache must contain session token // cache must contain session token
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st)) require.True(t, containsTokens(tokens, &st))
var prm PrmObjectGet var prm PrmObjectGet
prm.SetAddress(address.Address{}) prm.SetAddress(address.Address{})
prm.UseSession(*session.NewToken()) prm.UseSession(session.Object{})
_, err = pool.GetObject(ctx, prm) _, err = pool.GetObject(ctx, prm)
require.Error(t, err) require.Error(t, err)
@ -412,8 +428,8 @@ func TestSessionCache(t *testing.T) {
// cache must not contain session token // cache must not contain session token
cp, err = pool.connection() cp, err = pool.connection()
require.NoError(t, err) require.NoError(t, err)
st = pool.cache.Get(formCacheKey(cp.address, pool.key)) _, ok := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.Nil(t, st) require.False(t, ok)
var prm2 PrmObjectPut var prm2 PrmObjectPut
prm2.SetHeader(object.Object{}) prm2.SetHeader(object.Object{})
@ -424,23 +440,24 @@ func TestSessionCache(t *testing.T) {
// cache must contain session token // cache must contain session token
cp, err = pool.connection() cp, err = pool.connection()
require.NoError(t, err) require.NoError(t, err)
st = pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ = pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st)) require.True(t, containsTokens(tokens, &st))
} }
func TestPriority(t *testing.T) { func TestPriority(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
ctrl2 := gomock.NewController(t) ctrl2 := gomock.NewController(t)
tokens := make([]*session.Token, 2) tokens := make([]*session.Object, 2)
clientBuilder := func(endpoint string) (client, error) { clientBuilder := func(endpoint string) (client, error) {
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t) tok := newToken(t)
tokens[0] = tok tokens[0] = tok
id := tok.ID()
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: id[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, nil }, nil
}).AnyTimes() }).AnyTimes()
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes() mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
@ -450,9 +467,10 @@ func TestPriority(t *testing.T) {
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t) tok := newToken(t)
tokens[1] = tok tokens[1] = tok
id := tok.ID()
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: id[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, nil }, nil
}).AnyTimes() }).AnyTimes()
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes() mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
@ -486,14 +504,14 @@ func TestPriority(t *testing.T) {
firstNode := func() bool { firstNode := func() bool {
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
return areEqualTokens(st, tokens[0]) return areEqualTokens(&st, tokens[0])
} }
secondNode := func() bool { secondNode := func() bool {
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
return areEqualTokens(st, tokens[1]) return areEqualTokens(&st, tokens[1])
} }
require.Never(t, secondNode, time.Second, 200*time.Millisecond) require.Never(t, secondNode, time.Second, 200*time.Millisecond)
@ -504,19 +522,18 @@ func TestPriority(t *testing.T) {
func TestSessionCacheWithKey(t *testing.T) { func TestSessionCacheWithKey(t *testing.T) {
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
var tokens []*session.Token var tokens []*session.Object
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) { mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := session.NewToken() var tok session.Object
uid, err := uuid.New().MarshalBinary() uid := uuid.New()
require.NoError(t, err)
tok.SetID(uid) tok.SetID(uid)
tokens = append(tokens, tok) tokens = append(tokens, &tok)
return &resCreateSession{ return &resCreateSession{
id: tok.ID(), id: uid[:],
sessionKey: tok.SessionKey(), sessionKey: newBinPublicKey(t),
}, err }, nil
}).MaxTimes(2) }).MaxTimes(2)
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes() mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
@ -545,8 +562,8 @@ func TestSessionCacheWithKey(t *testing.T) {
// cache must contain session token // cache must contain session token
cp, err := pool.connection() cp, err := pool.connection()
require.NoError(t, err) require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key)) st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st)) require.True(t, containsTokens(tokens, &st))
var prm PrmObjectGet var prm PrmObjectGet
prm.SetAddress(address.Address{}) prm.SetAddress(address.Address{})
@ -557,20 +574,22 @@ func TestSessionCacheWithKey(t *testing.T) {
require.Len(t, tokens, 2) require.Len(t, tokens, 2)
} }
func newToken(t *testing.T) *session.Token { func newToken(t *testing.T) *session.Object {
tok := session.NewToken() var tok session.Object
uid, err := uuid.New().MarshalBinary() tok.SetID(uuid.New())
require.NoError(t, err)
tok.SetID(uid)
return tok return &tok
} }
func TestSessionTokenOwner(t *testing.T) { func TestSessionTokenOwner(t *testing.T) {
t.Skip() // neofs-sdk-go#???
ctrl := gomock.NewController(t) ctrl := gomock.NewController(t)
clientBuilder := func(_ string) (client, error) { clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl) mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{}, nil).AnyTimes() mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{
id: uuid.Nil[:],
sessionKey: newBinPublicKey(t),
}, nil).AnyTimes()
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes() mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes() mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
return mockClient, nil return mockClient, nil
@ -605,15 +624,15 @@ func TestSessionTokenOwner(t *testing.T) {
var cc callContext var cc callContext
cc.Context = ctx cc.Context = ctx
cc.sessionTarget = func(session.Token) {} cc.sessionTarget = func(session.Object) {}
err = p.initCallContext(&cc, prm, prmCtx) err = p.initCallContext(&cc, prm, prmCtx)
require.NoError(t, err) require.NoError(t, err)
err = p.openDefaultSession(&cc) err = p.openDefaultSession(&cc)
require.NoError(t, err) require.NoError(t, err)
tkn := p.cache.Get(formCacheKey("peer0", anonKey)) tkn, _ := p.cache.Get(formCacheKey("peer0", anonKey))
require.True(t, anonOwner.Equals(*tkn.OwnerID())) require.True(t, tkn.VerifySignature())
} }
func TestWaitPresence(t *testing.T) { func TestWaitPresence(t *testing.T) {
@ -661,29 +680,29 @@ func TestWaitPresence(t *testing.T) {
} }
func TestCopySessionTokenWithoutSignatureAndContext(t *testing.T) { func TestCopySessionTokenWithoutSignatureAndContext(t *testing.T) {
from := sessiontest.SignedToken() from := *sessiontest.Object()
to := copySessionTokenWithoutSignatureAndContext(*from)
require.Equal(t, from.Nbf(), to.Nbf()) const verb = session.VerbObjectHead
require.Equal(t, from.Exp(), to.Exp()) from.ForVerb(verb)
require.Equal(t, from.Iat(), to.Iat())
require.Equal(t, from.ID(), to.ID())
require.Equal(t, from.OwnerID().String(), to.OwnerID().String())
require.Equal(t, from.SessionKey(), to.SessionKey())
to := from
require.Equal(t, from, to)
require.False(t, from.VerifySignature())
require.False(t, to.VerifySignature()) require.False(t, to.VerifySignature())
t.Run("empty object context", func(t *testing.T) { require.True(t, from.AssertVerb(verb))
octx := sessiontest.ObjectContext() require.True(t, to.AssertVerb(verb))
from.SetContext(octx)
to = copySessionTokenWithoutSignatureAndContext(*from)
require.Nil(t, to.Context())
})
t.Run("empty container context", func(t *testing.T) { k, err := keys.NewPrivateKey()
cctx := sessiontest.ContainerContext() require.NoError(t, err)
from.SetContext(cctx)
to = copySessionTokenWithoutSignatureAndContext(*from) from.ForVerb(verb + 1)
require.Nil(t, to.Context()) require.NoError(t, from.Sign(k.PrivateKey))
})
require.True(t, from.VerifySignature())
require.False(t, to.VerifySignature())
require.True(t, from.AssertVerb(verb+1))
require.False(t, to.AssertVerb(verb+1))
} }

View file

@ -1,153 +1,378 @@
package session package session
import ( import (
"bytes"
"crypto/ecdsa"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-api-go/v2/refs" "github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-api-go/v2/session"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id" cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/user"
) )
// ContainerContext represents NeoFS API v2-compatible // Container represents token of the NeoFS Container session. A session is opened
// context of the container session. // between any two sides of the system, and implements a mechanism for transferring
// the power of attorney of actions to another network member. The session has a
// limited validity period, and applies to a strictly defined set of operations.
// See methods for details.
// //
// It is a wrapper over session.ContainerSessionContext // Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
// which allows to abstract from details of the message // message. See ReadFromV2 / WriteToV2 methods.
// structure.
type ContainerContext session.ContainerSessionContext
// NewContainerContext creates and returns blank ContainerSessionContext.
// //
// Defaults: // Instances can be created using built-in var declaration.
// - not bound to any operation; type Container struct {
// - applied to all containers. cnrSet bool
func NewContainerContext() *ContainerContext {
v2 := new(session.ContainerSessionContext)
v2.SetWildcard(true)
return NewContainerContextFromV2(v2) lt session.TokenLifetime
c session.ContainerSessionContext
body session.TokenBody
sig neofscrypto.Signature
} }
// NewContainerContextFromV2 wraps session.ContainerSessionContext // ReadFromV2 reads Container from the session.Token message.
// into ContainerContext.
func NewContainerContextFromV2(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. // See also WriteToV2.
func (x *ContainerContext) ApplyTo(id *cid.ID) { func (x *Container) ReadFromV2(m session.Token) error {
v2 := (*session.ContainerSessionContext)(x) b := m.GetBody()
if b == nil {
var cidV2 *refs.ContainerID return errors.New("missing body")
if id != nil {
var c refs.ContainerID
id.WriteToV2(&c)
cidV2 = &c
} }
v2.SetWildcard(id == nil) bID := b.GetID()
v2.SetContainerID(cidV2) var id uuid.UUID
}
// ApplyToAllContainers is a helper function that conveniently err := id.UnmarshalBinary(bID)
// applies ContainerContext to all containers. if err != nil {
func ApplyToAllContainers(c *ContainerContext) { return fmt.Errorf("invalid binary ID: %w", err)
c.ApplyTo(nil) } else if ver := id.Version(); ver != 4 {
} return fmt.Errorf("invalid UUID version %s", ver)
}
// Container returns identifier of the container c, ok := b.GetContext().(*session.ContainerSessionContext)
// to which the ContainerContext applies. if !ok {
// return fmt.Errorf("invalid context %T", b.GetContext())
// Returns nil if ContainerContext is applied to }
// all containers.
func (x *ContainerContext) Container() *cid.ID { cnr := c.ContainerID()
v2 := (*session.ContainerSessionContext)(x) x.cnrSet = !c.Wildcard()
if x.cnrSet && cnr == nil {
return errors.New("container is not specified with unset wildcard")
}
x.body = *b
if c != nil {
x.c = *c
} else {
x.c = session.ContainerSessionContext{}
}
lt := b.GetLifetime()
if lt != nil {
x.lt = *lt
} else {
x.lt = session.TokenLifetime{}
}
sig := m.GetSignature()
if sig != nil {
x.sig.ReadFromV2(*sig)
} else {
x.sig = neofscrypto.Signature{}
}
if v2.Wildcard() {
return nil return nil
}
// WriteToV2 writes Container to the session.Token message.
// The message must not be nil.
//
// See also ReadFromV2.
func (x Container) WriteToV2(m *session.Token) {
var sig refs.Signature
x.sig.WriteToV2(&sig)
m.SetBody(&x.body)
m.SetSignature(&sig)
}
// Marshal encodes Container into a binary format of the NeoFS API protocol
// (Protocol Buffers with direct field order).
//
// See also Unmarshal.
func (x Container) Marshal() []byte {
var m session.Token
x.WriteToV2(&m)
data, err := m.StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
} }
cidV2 := v2.ContainerID() return data
if cidV2 == nil { }
return nil
// Unmarshal decodes NeoFS API protocol binary format into the Container
// (Protocol Buffers with direct field order). Returns an error describing
// a format violation.
//
// See also Marshal.
func (x *Container) Unmarshal(data []byte) error {
var m session.Token
err := m.Unmarshal(data)
if err != nil {
return err
} }
var cID cid.ID return x.ReadFromV2(m)
_ = cID.ReadFromV2(*cidV2)
return &cID
} }
func (x *ContainerContext) forVerb(v session.ContainerSessionVerb) { // MarshalJSON encodes Container into a JSON format of the NeoFS API protocol
(*session.ContainerSessionContext)(x). // (Protocol Buffers JSON).
SetVerb(v) //
// See also UnmarshalJSON.
func (x Container) MarshalJSON() ([]byte, error) {
var m session.Token
x.WriteToV2(&m)
return m.MarshalJSON()
} }
func (x *ContainerContext) isForVerb(v session.ContainerSessionVerb) bool { // UnmarshalJSON decodes NeoFS API protocol JSON format into the Container
return (*session.ContainerSessionContext)(x). // (Protocol Buffers JSON). Returns an error describing a format violation.
Verb() == v //
// See also MarshalJSON.
func (x *Container) UnmarshalJSON(data []byte) error {
var m session.Token
err := m.UnmarshalJSON(data)
if err != nil {
return err
}
return x.ReadFromV2(m)
} }
// ForPut binds the ContainerContext to // Sign calculates and writes signature of the Container data.
// PUT operation. // Returns signature calculation errors.
func (x *ContainerContext) ForPut() { //
x.forVerb(session.ContainerVerbPut) // Zero Container is unsigned.
//
// Note that any Container mutation is likely to break the signature, so it is
// expected to be calculated as a final stage of Container formation.
//
// See also VerifySignature.
func (x *Container) Sign(key ecdsa.PrivateKey) error {
var idUser user.ID
user.IDFromKey(&idUser, key.PublicKey)
var idUserV2 refs.OwnerID
idUser.WriteToV2(&idUserV2)
x.c.SetWildcard(!x.cnrSet)
x.body.SetOwnerID(&idUserV2)
x.body.SetLifetime(&x.lt)
x.body.SetContext(&x.c)
data, err := x.body.StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
return x.sig.Calculate(neofsecdsa.Signer(key), data)
} }
// IsForPut checks if ContainerContext is bound to // VerifySignature checks if Container signature is presented and valid.
// PUT operation. //
func (x *ContainerContext) IsForPut() bool { // Zero Container fails the check.
return x.isForVerb(session.ContainerVerbPut) //
// See also Sign.
func (x Container) VerifySignature() bool {
// TODO: check owner<->key relation
data, err := x.body.StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
return x.sig.Verify(data)
} }
// ForDelete binds the ContainerContext to // ApplyOnlyTo limits session scope to a given author container.
// DELETE operation. //
func (x *ContainerContext) ForDelete() { // See also AppliedTo.
x.forVerb(session.ContainerVerbDelete) func (x *Container) ApplyOnlyTo(cnr cid.ID) {
var cnrv2 refs.ContainerID
cnr.WriteToV2(&cnrv2)
x.c.SetContainerID(&cnrv2)
x.cnrSet = true
} }
// IsForDelete checks if ContainerContext is bound to // AppliedTo checks if session scope is limited by a given container.
// DELETE operation. //
func (x *ContainerContext) IsForDelete() bool { // Zero Container is applied to all author's containers.
return x.isForVerb(session.ContainerVerbDelete) //
// See also ApplyOnlyTo.
func (x Container) AppliedTo(cnr cid.ID) bool {
if !x.cnrSet {
return true
}
var cnr2 cid.ID
if err := cnr2.ReadFromV2(*x.c.ContainerID()); err != nil {
// NPE and error must never happen
panic(fmt.Sprintf("unexpected error from cid.ReadFromV2: %v", err))
}
return cnr2.Equals(cnr)
} }
// ForSetEACL binds the ContainerContext to // ContainerVerb enumerates container operations.
// SETEACL operation. type ContainerVerb int8
func (x *ContainerContext) ForSetEACL() {
x.forVerb(session.ContainerVerbSetEACL) const (
_ ContainerVerb = iota
VerbContainerPut // Put rpc
VerbContainerDelete // Delete rpc
VerbContainerSetEACL // SetExtendedACL rpc
)
// ForVerb specifies the container operation of the session scope. Each
// Container is related to the single operation.
//
// See also AssertVerb.
func (x *Container) ForVerb(verb ContainerVerb) {
x.c.SetVerb(session.ContainerSessionVerb(verb))
} }
// IsForSetEACL checks if ContainerContext is bound to // AssertVerb checks if Container relates to the given container operation.
// SETEACL operation. //
func (x *ContainerContext) IsForSetEACL() bool { // Zero Container relates to zero (unspecified) verb.
return x.isForVerb(session.ContainerVerbSetEACL) //
// See also ForVerb.
func (x Container) AssertVerb(verb ContainerVerb) bool {
return verb == ContainerVerb(x.c.Verb())
} }
// Marshal marshals ContainerContext into a protobuf binary form. // SetExp sets "exp" (expiration time) claim which identifies the expiration time
func (x *ContainerContext) Marshal() ([]byte, error) { // (in NeoFS epochs) on or after which the Container MUST NOT be accepted for
return x.ToV2().StableMarshal(nil) // processing. The processing of the "exp" claim requires that the current
// epoch MUST be before the expiration epoch listed in the "exp" claim.
//
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
//
// See also ExpiredAt.
func (x *Container) SetExp(exp uint64) {
x.lt.SetExp(exp)
} }
// Unmarshal unmarshals protobuf binary representation of ContainerContext. // ExpiredAt asserts "exp" claim.
func (x *ContainerContext) Unmarshal(data []byte) error { //
return x.ToV2().Unmarshal(data) // Zero Container is expired in any epoch.
//
// See also SetExp.
func (x Container) ExpiredAt(epoch uint64) bool {
return x.lt.GetExp() <= epoch
} }
// MarshalJSON encodes ContainerContext to protobuf JSON format. // SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
func (x *ContainerContext) MarshalJSON() ([]byte, error) { // epochs) before which the Container MUST NOT be accepted for processing.
return x.ToV2().MarshalJSON() // The processing of the "nbf" claim requires that the current date/time MUST be
// after or equal to the not-before date/time listed in the "nbf" claim.
//
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5.
//
// See also InvalidAt.
func (x *Container) SetNbf(nbf uint64) {
x.lt.SetNbf(nbf)
} }
// UnmarshalJSON decodes ContainerContext from protobuf JSON format. // SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
func (x *ContainerContext) UnmarshalJSON(data []byte) error { // epochs) at which the Container was issued. This claim can be used to
return x.ToV2().UnmarshalJSON(data) // determine the age of the Container.
//
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6.
//
// See also InvalidAt.
func (x *Container) SetIat(iat uint64) {
x.lt.SetIat(iat)
}
// InvalidAt asserts "exp", "nbf" and "iat" claims.
//
// Zero Container is invalid in any epoch.
//
// See also SetExp, SetNbf, SetIat.
func (x Container) InvalidAt(epoch uint64) bool {
return x.lt.GetNbf() > epoch || x.lt.GetIat() > epoch || x.ExpiredAt(epoch)
}
// SetID sets a unique identifier for the session. The identifier value MUST be
// assigned in a manner that ensures that there is a negligible probability
// that the same value will be accidentally assigned to a different session.
//
// ID format MUST be UUID version 4 (random). uuid.New can be used to generate
// a new ID. See https://datatracker.ietf.org/doc/html/rfc4122 and
// github.com/google/uuid package docs for details.
//
// See also ID.
func (x *Container) SetID(id uuid.UUID) {
x.body.SetID(id[:])
}
// ID returns a unique identifier for the session.
//
// Zero Container has empty UUID (all zeros, see uuid.Nil) which is legitimate
// but most likely not suitable.
//
// See also SetID.
func (x Container) ID() uuid.UUID {
data := x.body.GetID()
if data == nil {
return uuid.Nil
}
var id uuid.UUID
err := id.UnmarshalBinary(x.body.GetID())
if err != nil {
panic(fmt.Sprintf("unexpected error from UUID.UnmarshalBinary: %v", err))
}
return id
}
// SetAuthKey public key corresponding to the private key bound to the session.
//
// See also AssertAuthKey.
func (x *Container) SetAuthKey(key neofscrypto.PublicKey) {
bKey := make([]byte, key.MaxEncodedSize())
bKey = bKey[:key.Encode(bKey)]
x.body.SetSessionKey(bKey)
}
// AssertAuthKey asserts public key bound to the session.
//
// Zero Container fails the check.
//
// See also SetAuthKey.
func (x Container) AssertAuthKey(key neofscrypto.PublicKey) bool {
bKey := make([]byte, key.MaxEncodedSize())
bKey = bKey[:key.Encode(bKey)]
return bytes.Equal(bKey, x.body.GetSessionKey())
} }

View file

@ -1,8 +1,12 @@
package session_test package session_test
import ( import (
"math"
"math/rand"
"testing" "testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test" cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/session"
@ -10,103 +14,274 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestContainerContextVerbs(t *testing.T) { func TestContainer_ReadFromV2(t *testing.T) {
c := session.NewContainerContext() var x session.Container
var m v2session.Token
var b v2session.TokenBody
var c v2session.ContainerSessionContext
id := uuid.New()
assert := func(setter func(), getter func() bool, verb v2session.ContainerSessionVerb) { t.Run("protocol violation", func(t *testing.T) {
setter() require.Error(t, x.ReadFromV2(m))
require.True(t, getter()) m.SetBody(&b)
require.Equal(t, verb, c.ToV2().Verb()) require.Error(t, x.ReadFromV2(m))
}
t.Run("PUT", func(t *testing.T) { b.SetID(id[:])
assert(c.ForPut, c.IsForPut, v2session.ContainerVerbPut)
require.Error(t, x.ReadFromV2(m))
b.SetContext(&c)
require.Error(t, x.ReadFromV2(m))
c.SetWildcard(true)
require.NoError(t, x.ReadFromV2(m))
}) })
t.Run("DELETE", func(t *testing.T) { m.SetBody(&b)
assert(c.ForDelete, c.IsForDelete, v2session.ContainerVerbDelete) b.SetContext(&c)
b.SetID(id[:])
c.SetWildcard(true)
t.Run("container", func(t *testing.T) {
cnr1 := cidtest.ID()
cnr2 := cidtest.ID()
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AppliedTo(cnr1))
require.True(t, x.AppliedTo(cnr2))
var cnrv2 refs.ContainerID
cnr1.WriteToV2(&cnrv2)
c.SetContainerID(&cnrv2)
c.SetWildcard(false)
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AppliedTo(cnr1))
require.False(t, x.AppliedTo(cnr2))
}) })
t.Run("SETEACL", func(t *testing.T) { t.Run("verb", func(t *testing.T) {
assert(c.ForSetEACL, c.IsForSetEACL, v2session.ContainerVerbSetEACL) require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AssertVerb(0))
verb := v2session.ContainerSessionVerb(rand.Uint32())
c.SetVerb(verb)
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AssertVerb(session.ContainerVerb(verb)))
})
t.Run("id", func(t *testing.T) {
id := uuid.New()
bID := id[:]
b.SetID(bID)
require.NoError(t, x.ReadFromV2(m))
require.Equal(t, id, x.ID())
})
t.Run("lifetime", func(t *testing.T) {
const nbf, iat, exp = 11, 22, 33
var lt v2session.TokenLifetime
lt.SetNbf(nbf)
lt.SetIat(iat)
lt.SetExp(exp)
b.SetLifetime(&lt)
require.NoError(t, x.ReadFromV2(m))
require.False(t, x.ExpiredAt(exp-1))
require.True(t, x.ExpiredAt(exp))
require.True(t, x.ExpiredAt(exp+1))
require.True(t, x.InvalidAt(nbf-1))
require.True(t, x.InvalidAt(iat-1))
require.False(t, x.InvalidAt(iat))
require.False(t, x.InvalidAt(exp-1))
require.True(t, x.InvalidAt(exp))
require.True(t, x.InvalidAt(exp+1))
})
t.Run("session key", func(t *testing.T) {
key := randPublicKey()
bKey := make([]byte, key.MaxEncodedSize())
bKey = bKey[:key.Encode(bKey)]
b.SetSessionKey(bKey)
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AssertAuthKey(key))
}) })
} }
func TestContainerContext_ApplyTo(t *testing.T) { func TestEncodingContainer(t *testing.T) {
c := session.NewContainerContext() tok := *sessiontest.ContainerSigned()
id := cidtest.ID()
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 TestContextFilter_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) { t.Run("binary", func(t *testing.T) {
data, err := c.Marshal() data := tok.Marshal()
require.NoError(t, err)
c2 := session.NewContainerContext() var tok2 session.Container
require.NoError(t, c2.Unmarshal(data)) require.NoError(t, tok2.Unmarshal(data))
require.Equal(t, c, c2) require.Equal(t, tok, tok2)
}) })
t.Run("json", func(t *testing.T) { t.Run("json", func(t *testing.T) {
data, err := c.MarshalJSON() data, err := tok.MarshalJSON()
require.NoError(t, err) require.NoError(t, err)
c2 := session.NewContainerContext() var tok2 session.Container
require.NoError(t, c2.UnmarshalJSON(data)) require.NoError(t, tok2.UnmarshalJSON(data))
require.Equal(t, c, c2) require.Equal(t, tok, tok2)
}) })
} }
func TestContainerAppliedTo(t *testing.T) {
var x session.Container
cnr1 := cidtest.ID()
cnr2 := cidtest.ID()
require.True(t, x.AppliedTo(cnr1))
require.True(t, x.AppliedTo(cnr2))
x.ApplyOnlyTo(cnr1)
require.True(t, x.AppliedTo(cnr1))
require.False(t, x.AppliedTo(cnr2))
}
func TestContainerExp(t *testing.T) {
var x session.Container
exp := rand.Uint64()
require.True(t, x.ExpiredAt(exp))
x.SetExp(exp)
require.False(t, x.ExpiredAt(exp-1))
require.True(t, x.ExpiredAt(exp))
require.True(t, x.ExpiredAt(exp+1))
}
func TestContainerLifetime(t *testing.T) {
var x session.Container
nbf := rand.Uint64()
if nbf == math.MaxUint64 {
nbf--
}
iat := nbf
exp := iat + 1
x.SetNbf(nbf)
x.SetIat(iat)
x.SetExp(exp)
require.True(t, x.InvalidAt(nbf-1))
require.True(t, x.InvalidAt(iat-1))
require.False(t, x.InvalidAt(iat))
require.True(t, x.InvalidAt(exp))
}
func TestContainerID(t *testing.T) {
var x session.Container
require.Zero(t, x.ID())
id := uuid.New()
x.SetID(id)
require.Equal(t, id, x.ID())
}
func TestContainerAuthKey(t *testing.T) {
var x session.Container
key := randPublicKey()
require.False(t, x.AssertAuthKey(key))
x.SetAuthKey(key)
require.True(t, x.AssertAuthKey(key))
}
func TestContainerVerb(t *testing.T) {
var x session.Container
const v1, v2 = session.VerbContainerPut, session.VerbContainerDelete
require.False(t, x.AssertVerb(v1))
require.False(t, x.AssertVerb(v2))
x.ForVerb(v1)
require.True(t, x.AssertVerb(v1))
require.False(t, x.AssertVerb(v2))
}
func TestContainerSignature(t *testing.T) {
var x session.Container
const nbf = 11
const iat = 22
const exp = 33
id := uuid.New()
key := randPublicKey()
cnr := cidtest.ID()
verb := session.VerbContainerPut
signer := randSigner()
fs := []func(){
func() { x.SetNbf(nbf) },
func() { x.SetNbf(nbf + 1) },
func() { x.SetIat(iat) },
func() { x.SetIat(iat + 1) },
func() { x.SetExp(exp) },
func() { x.SetExp(exp + 1) },
func() { x.SetID(id) },
func() {
idcp := id
idcp[0]++
x.SetID(idcp)
},
func() { x.SetAuthKey(key) },
func() { x.SetAuthKey(randPublicKey()) },
func() { x.ApplyOnlyTo(cnr) },
func() { x.ApplyOnlyTo(cidtest.ID()) },
func() { x.ForVerb(verb) },
func() { x.ForVerb(verb + 1) },
}
for i := 0; i < len(fs); i += 2 {
fs[i]()
require.NoError(t, x.Sign(signer))
require.True(t, x.VerifySignature())
fs[i+1]()
require.False(t, x.VerifySignature())
fs[i]()
require.True(t, x.VerifySignature())
}
}

48
session/doc.go Normal file
View file

@ -0,0 +1,48 @@
/*
Package session collects functionality of the NeoFS sessions.
Sessions are used in NeoFS as a mechanism for transferring the power of attorney
of actions to another network member.
Session tokens represent proof of trust. Each session has a limited lifetime and
scope related to some NeoFS service: Object, Container, etc.
Both parties agree on a secret (private session key), the possession of which
will be authenticated by a trusted person. The principal confirms his trust by
signing the public part of the secret (public session key).
var tok Container
tok.ForVerb(VerbContainerDelete)
tok.SetAuthKey(trustedKey)
// ...
err := tok.Sign(principalKey)
// ...
// transfer the token to a trusted party
The trusted member can perform operations on behalf of the trustee.
Instances can be also used to process NeoFS API V2 protocol messages
(see neo.fs.v2.accounting package in https://github.com/nspcc-dev/neofs-api).
On client side:
import "github.com/nspcc-dev/neofs-api-go/v2/session"
var msg session.Token
tok.WriteToV2(&msg)
// send msg
On server side:
// recv msg
var tok session.Container
tok.ReadFromV2(msg)
// process cnr
Using package types in an application is recommended to potentially work with
different protocol versions with which these types are compatible.
*/
package session

View file

@ -1,166 +1,379 @@
package session package session
import ( import (
"bytes"
"crypto/ecdsa"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session" "github.com/nspcc-dev/neofs-api-go/v2/session"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/object/address" "github.com/nspcc-dev/neofs-sdk-go/object/address"
"github.com/nspcc-dev/neofs-sdk-go/user"
) )
// ObjectContext represents NeoFS API v2-compatible // Object represents token of the NeoFS Object session. A session is opened
// context of the object session. // between any two sides of the system, and implements a mechanism for transferring
// the power of attorney of actions to another network member. The session has a
// limited validity period, and applies to a strictly defined set of operations.
// See methods for details.
// //
// It is a wrapper over session.ObjectSessionContext // Object is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
// which allows abstracting from details of the message // message. See ReadFromV2 / WriteToV2 methods.
// structure.
type ObjectContext session.ObjectSessionContext
// NewObjectContext creates and returns blank ObjectContext.
// //
// Defaults: // Instances can be created using built-in var declaration.
// - not bound to any operation; type Object struct {
// - nil object address. lt session.TokenLifetime
func NewObjectContext() *ObjectContext {
v2 := new(session.ObjectSessionContext)
return NewObjectContextFromV2(v2) obj refs.Address
c session.ObjectSessionContext
body session.TokenBody
sig neofscrypto.Signature
} }
// NewObjectContextFromV2 wraps session.ObjectSessionContext // ReadFromV2 reads Object from the session.Token message.
// into ObjectContext. //
func NewObjectContextFromV2(v *session.ObjectSessionContext) *ObjectContext { // See also WriteToV2.
return (*ObjectContext)(v) func (x *Object) ReadFromV2(m session.Token) error {
b := m.GetBody()
if b == nil {
return errors.New("missing body")
}
bID := b.GetID()
var id uuid.UUID
err := id.UnmarshalBinary(bID)
if err != nil {
return fmt.Errorf("invalid binary ID: %w", err)
} else if ver := id.Version(); ver != 4 {
return fmt.Errorf("invalid UUID version %s", ver)
}
c, ok := b.GetContext().(*session.ObjectSessionContext)
if !ok {
return fmt.Errorf("invalid context %T", b.GetContext())
}
x.body = *b
if c != nil {
x.c = *c
obj := c.GetAddress()
if obj != nil {
x.obj = *obj
} else {
x.obj = refs.Address{}
}
} else {
x.c = session.ObjectSessionContext{}
x.obj = refs.Address{}
}
lt := b.GetLifetime()
if lt != nil {
x.lt = *lt
} else {
x.lt = session.TokenLifetime{}
}
sig := m.GetSignature()
if sig != nil {
x.sig.ReadFromV2(*sig)
} else {
x.sig = neofscrypto.Signature{}
}
return nil
} }
// ToV2 converts ObjectContext to session.ObjectSessionContext // WriteToV2 writes Object to the session.Token message.
// message structure. // The message must not be nil.
func (x *ObjectContext) ToV2() *session.ObjectSessionContext { //
return (*session.ObjectSessionContext)(x) // See also ReadFromV2.
func (x Object) WriteToV2(m *session.Token) {
var sig refs.Signature
x.sig.WriteToV2(&sig)
m.SetBody(&x.body)
m.SetSignature(&sig)
} }
// ApplyTo specifies which object the ObjectContext applies to. // Marshal encodes Object into a binary format of the NeoFS API protocol
func (x *ObjectContext) ApplyTo(id *address.Address) { // (Protocol Buffers with direct field order).
v2 := (*session.ObjectSessionContext)(x) //
// See also Unmarshal.
func (x Object) Marshal() []byte {
var m session.Token
x.WriteToV2(&m)
v2.SetAddress(id.ToV2()) data, err := m.StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
return data
} }
// Address returns identifier of the object // Unmarshal decodes NeoFS API protocol binary format into the Object
// to which the ObjectContext applies. // (Protocol Buffers with direct field order). Returns an error describing
func (x *ObjectContext) Address() *address.Address { // a format violation.
v2 := (*session.ObjectSessionContext)(x) //
// See also Marshal.
func (x *Object) Unmarshal(data []byte) error {
var m session.Token
return address.NewAddressFromV2(v2.GetAddress()) err := m.Unmarshal(data)
if err != nil {
return err
}
return x.ReadFromV2(m)
} }
func (x *ObjectContext) forVerb(v session.ObjectSessionVerb) { // MarshalJSON encodes Object into a JSON format of the NeoFS API protocol
(*session.ObjectSessionContext)(x). // (Protocol Buffers JSON).
SetVerb(v) //
// See also UnmarshalJSON.
func (x Object) MarshalJSON() ([]byte, error) {
var m session.Token
x.WriteToV2(&m)
return m.MarshalJSON()
} }
func (x *ObjectContext) isForVerb(v session.ObjectSessionVerb) bool { // UnmarshalJSON decodes NeoFS API protocol JSON format into the Object
return (*session.ObjectSessionContext)(x). // (Protocol Buffers JSON). Returns an error describing a format violation.
GetVerb() == v //
// See also MarshalJSON.
func (x *Object) UnmarshalJSON(data []byte) error {
var m session.Token
err := m.UnmarshalJSON(data)
if err != nil {
return err
}
return x.ReadFromV2(m)
} }
// ForPut binds the ObjectContext to // Sign calculates and writes signature of the Object data.
// PUT operation. // Returns signature calculation errors.
func (x *ObjectContext) ForPut() { //
x.forVerb(session.ObjectVerbPut) // Zero Object is unsigned.
//
// Note that any Object mutation is likely to break the signature, so it is
// expected to be calculated as a final stage of Object formation.
//
// See also VerifySignature.
func (x *Object) Sign(key ecdsa.PrivateKey) error {
var idUser user.ID
user.IDFromKey(&idUser, key.PublicKey)
var idUserV2 refs.OwnerID
idUser.WriteToV2(&idUserV2)
x.c.SetAddress(&x.obj)
x.body.SetOwnerID(&idUserV2)
x.body.SetLifetime(&x.lt)
x.body.SetContext(&x.c)
data, err := x.body.StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
return x.sig.Calculate(neofsecdsa.Signer(key), data)
} }
// IsForPut checks if ObjectContext is bound to // VerifySignature checks if Object signature is presented and valid.
// PUT operation. //
func (x *ObjectContext) IsForPut() bool { // Zero Object fails the check.
return x.isForVerb(session.ObjectVerbPut) //
// See also Sign.
func (x Object) VerifySignature() bool {
// TODO: check owner<->key relation
data, err := x.body.StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
return x.sig.Verify(data)
} }
// ForDelete binds the ObjectContext to // ApplyTo limits session scope to a given author object.
// DELETE operation. //
func (x *ObjectContext) ForDelete() { // See also AppliedTo.
x.forVerb(session.ObjectVerbDelete) func (x *Object) ApplyTo(a address.Address) {
x.obj = *a.ToV2()
} }
// IsForDelete checks if ObjectContext is bound to // AppliedTo checks if session scope is limited by a given object.
// DELETE operation. //
func (x *ObjectContext) IsForDelete() bool { // Zero Object isn't applied to any author's object.
return x.isForVerb(session.ObjectVerbDelete) //
// See also ApplyTo.
func (x Object) AppliedTo(obj address.Address) bool {
objv2 := *address.NewAddressFromV2(&x.obj)
// FIXME: use Equals method
return obj.String() == objv2.String()
} }
// ForGet binds the ObjectContext to // ObjectVerb enumerates object operations.
// GET operation. type ObjectVerb int8
func (x *ObjectContext) ForGet() {
x.forVerb(session.ObjectVerbGet) const (
_ ObjectVerb = iota
VerbObjectPut // Put rpc
VerbObjectGet // Get rpc
VerbObjectHead // Head rpc
VerbObjectSearch // Search rpc
VerbObjectDelete // Delete rpc
VerbObjectRange // GetRange rpc
VerbObjectRangeHash // GetRangeHash rpc
)
// ForVerb specifies the object operation of the session scope. Each
// Object is related to the single operation.
//
// See also AssertVerb.
func (x *Object) ForVerb(verb ObjectVerb) {
x.c.SetVerb(session.ObjectSessionVerb(verb))
} }
// IsForGet checks if ObjectContext is bound to // AssertVerb checks if Object relates to one of the given object operations.
// GET operation. //
func (x *ObjectContext) IsForGet() bool { // Zero Object relates to zero (unspecified) verb.
return x.isForVerb(session.ObjectVerbGet) //
// See also ForVerb.
func (x Object) AssertVerb(verbs ...ObjectVerb) bool {
verb := ObjectVerb(x.c.GetVerb())
for i := range verbs {
if verbs[i] == verb {
return true
}
}
return false
} }
// ForHead binds the ObjectContext to // SetExp sets "exp" (expiration time) claim which identifies the expiration time
// HEAD operation. // (in NeoFS epochs) on or after which the Object MUST NOT be accepted for
func (x *ObjectContext) ForHead() { // processing. The processing of the "exp" claim requires that the current
x.forVerb(session.ObjectVerbHead) // epoch MUST be before the expiration epoch listed in the "exp" claim.
//
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
//
// See also ExpiredAt.
func (x *Object) SetExp(exp uint64) {
x.lt.SetExp(exp)
} }
// IsForHead checks if ObjectContext is bound to // ExpiredAt asserts "exp" claim.
// HEAD operation. //
func (x *ObjectContext) IsForHead() bool { // Zero Object is expired in any epoch.
return x.isForVerb(session.ObjectVerbHead) //
// See also SetExp.
func (x Object) ExpiredAt(epoch uint64) bool {
return x.lt.GetExp() <= epoch
} }
// ForSearch binds the ObjectContext to // SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
// SEARCH operation. // epochs) before which the Object MUST NOT be accepted for processing.
func (x *ObjectContext) ForSearch() { // The processing of the "nbf" claim requires that the current date/time MUST be
x.forVerb(session.ObjectVerbSearch) // after or equal to the not-before date/time listed in the "nbf" claim.
//
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5.
//
// See also InvalidAt.
func (x *Object) SetNbf(nbf uint64) {
x.lt.SetNbf(nbf)
} }
// IsForSearch checks if ObjectContext is bound to // SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
// SEARCH operation. // epochs) at which the Object was issued. This claim can be used to
func (x *ObjectContext) IsForSearch() bool { // determine the age of the Object.
return x.isForVerb(session.ObjectVerbSearch) //
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6.
//
// See also InvalidAt.
func (x *Object) SetIat(iat uint64) {
x.lt.SetIat(iat)
} }
// ForRange binds the ObjectContext to // InvalidAt asserts "exp", "nbf" and "iat" claims.
// RANGE operation. //
func (x *ObjectContext) ForRange() { // Zero Object is invalid in any epoch.
x.forVerb(session.ObjectVerbRange) //
// See also SetExp, SetNbf, SetIat.
func (x Object) InvalidAt(epoch uint64) bool {
return x.lt.GetNbf() > epoch || x.lt.GetIat() > epoch || x.ExpiredAt(epoch)
} }
// IsForRange checks if ObjectContext is bound to // SetID sets a unique identifier for the session. The identifier value MUST be
// RANGE operation. // assigned in a manner that ensures that there is a negligible probability
func (x *ObjectContext) IsForRange() bool { // that the same value will be accidentally assigned to a different session.
return x.isForVerb(session.ObjectVerbRange) //
// ID format MUST be UUID version 4 (random). uuid.New can be used to generate
// a new ID. See https://datatracker.ietf.org/doc/html/rfc4122 and
// github.com/google/uuid package docs for details.
//
// See also ID.
func (x *Object) SetID(id uuid.UUID) {
x.body.SetID(id[:])
} }
// ForRangeHash binds the ObjectContext to // ID returns a unique identifier for the session.
// RANGEHASH operation. //
func (x *ObjectContext) ForRangeHash() { // Zero Object has empty UUID (all zeros, see uuid.Nil) which is legitimate
x.forVerb(session.ObjectVerbRangeHash) // but most likely not suitable.
//
// See also SetID.
func (x Object) ID() uuid.UUID {
data := x.body.GetID()
if data == nil {
return uuid.Nil
}
var id uuid.UUID
err := id.UnmarshalBinary(x.body.GetID())
if err != nil {
panic(fmt.Sprintf("unexpected error from UUID.UnmarshalBinary: %v", err))
}
return id
} }
// IsForRangeHash checks if ObjectContext is bound to // SetAuthKey public key corresponding to the private key bound to the session.
// RANGEHASH operation. //
func (x *ObjectContext) IsForRangeHash() bool { // See also AssertAuthKey.
return x.isForVerb(session.ObjectVerbRangeHash) func (x *Object) SetAuthKey(key neofscrypto.PublicKey) {
bKey := make([]byte, key.MaxEncodedSize())
bKey = bKey[:key.Encode(bKey)]
x.body.SetSessionKey(bKey)
} }
// Marshal marshals ObjectContext into a protobuf binary form. // AssertAuthKey asserts public key bound to the session.
func (x *ObjectContext) Marshal() ([]byte, error) { //
return x.ToV2().StableMarshal(nil) // Zero Object fails the check.
} //
// See also SetAuthKey.
func (x Object) AssertAuthKey(key neofscrypto.PublicKey) bool {
bKey := make([]byte, key.MaxEncodedSize())
bKey = bKey[:key.Encode(bKey)]
// Unmarshal unmarshals protobuf binary representation of ObjectContext. return bytes.Equal(bKey, x.body.GetSessionKey())
func (x *ObjectContext) Unmarshal(data []byte) error {
return x.ToV2().Unmarshal(data)
}
// MarshalJSON encodes ObjectContext to protobuf JSON format.
func (x *ObjectContext) MarshalJSON() ([]byte, error) {
return x.ToV2().MarshalJSON()
}
// UnmarshalJSON decodes ObjectContext from protobuf JSON format.
func (x *ObjectContext) UnmarshalJSON(data []byte) error {
return x.ToV2().UnmarshalJSON(data)
} }

View file

@ -1,123 +1,296 @@
package session_test package session_test
import ( import (
"crypto/ecdsa"
"fmt"
"math"
"math/rand"
"testing" "testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session" v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
addresstest "github.com/nspcc-dev/neofs-sdk-go/object/address/test" addresstest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
"github.com/nspcc-dev/neofs-sdk-go/session" "github.com/nspcc-dev/neofs-sdk-go/session"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test" sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestObjectContextVerbs(t *testing.T) { func randSigner() ecdsa.PrivateKey {
c := session.NewObjectContext() k, err := keys.NewPrivateKey()
if err != nil {
assert := func(setter func(), getter func() bool, verb v2session.ObjectSessionVerb) { panic(fmt.Sprintf("generate private key: %v", err))
setter()
require.True(t, getter())
require.Equal(t, verb, c.ToV2().GetVerb())
} }
t.Run("PUT", func(t *testing.T) { return k.PrivateKey
assert(c.ForPut, c.IsForPut, v2session.ObjectVerbPut) }
func randPublicKey() neofscrypto.PublicKey {
k := randSigner().PublicKey
return (*neofsecdsa.PublicKey)(&k)
}
func TestObject_ReadFromV2(t *testing.T) {
var x session.Object
var m v2session.Token
var b v2session.TokenBody
var c v2session.ObjectSessionContext
id := uuid.New()
t.Run("protocol violation", func(t *testing.T) {
require.Error(t, x.ReadFromV2(m))
m.SetBody(&b)
require.Error(t, x.ReadFromV2(m))
b.SetID(id[:])
require.Error(t, x.ReadFromV2(m))
b.SetContext(&c)
require.NoError(t, x.ReadFromV2(m))
}) })
t.Run("DELETE", func(t *testing.T) { m.SetBody(&b)
assert(c.ForDelete, c.IsForDelete, v2session.ObjectVerbDelete) b.SetContext(&c)
b.SetID(id[:])
t.Run("object", func(t *testing.T) {
var obj address.Address
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AppliedTo(obj))
obj = *addresstest.Address()
objv2 := *obj.ToV2()
c.SetAddress(&objv2)
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AppliedTo(obj))
}) })
t.Run("GET", func(t *testing.T) { t.Run("verb", func(t *testing.T) {
assert(c.ForGet, c.IsForGet, v2session.ObjectVerbGet) require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AssertVerb(0))
verb := v2session.ObjectSessionVerb(rand.Uint32())
c.SetVerb(verb)
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AssertVerb(session.ObjectVerb(verb)))
}) })
t.Run("SEARCH", func(t *testing.T) { t.Run("id", func(t *testing.T) {
assert(c.ForSearch, c.IsForSearch, v2session.ObjectVerbSearch) id := uuid.New()
bID := id[:]
b.SetID(bID)
require.NoError(t, x.ReadFromV2(m))
require.Equal(t, id, x.ID())
}) })
t.Run("RANGE", func(t *testing.T) { t.Run("lifetime", func(t *testing.T) {
assert(c.ForRange, c.IsForRange, v2session.ObjectVerbRange) const nbf, iat, exp = 11, 22, 33
var lt v2session.TokenLifetime
lt.SetNbf(nbf)
lt.SetIat(iat)
lt.SetExp(exp)
b.SetLifetime(&lt)
require.NoError(t, x.ReadFromV2(m))
require.False(t, x.ExpiredAt(exp-1))
require.True(t, x.ExpiredAt(exp))
require.True(t, x.ExpiredAt(exp+1))
require.True(t, x.InvalidAt(nbf-1))
require.True(t, x.InvalidAt(iat-1))
require.False(t, x.InvalidAt(iat))
require.False(t, x.InvalidAt(exp-1))
require.True(t, x.InvalidAt(exp))
require.True(t, x.InvalidAt(exp+1))
}) })
t.Run("RANGEHASH", func(t *testing.T) { t.Run("session key", func(t *testing.T) {
assert(c.ForRangeHash, c.IsForRangeHash, v2session.ObjectVerbRangeHash) key := randPublicKey()
})
t.Run("HEAD", func(t *testing.T) { bKey := make([]byte, key.MaxEncodedSize())
assert(c.ForHead, c.IsForHead, v2session.ObjectVerbHead) bKey = bKey[:key.Encode(bKey)]
b.SetSessionKey(bKey)
require.NoError(t, x.ReadFromV2(m))
require.True(t, x.AssertAuthKey(key))
}) })
} }
func TestObjectContext_ApplyTo(t *testing.T) { func TestEncodingObject(t *testing.T) {
c := session.NewObjectContext() tok := *sessiontest.ObjectSigned()
id := addresstest.Address()
t.Run("method", func(t *testing.T) {
c.ApplyTo(id)
require.Equal(t, id, c.Address())
c.ApplyTo(nil)
require.Nil(t, c.Address())
})
}
func TestObjectFilter_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *session.ObjectContext
require.Nil(t, x.ToV2())
})
t.Run("default values", func(t *testing.T) {
c := session.NewObjectContext()
// check initial values
require.Nil(t, c.Address())
for _, op := range []func() bool{
c.IsForPut,
c.IsForDelete,
c.IsForGet,
c.IsForHead,
c.IsForRange,
c.IsForRangeHash,
c.IsForSearch,
} {
require.False(t, op())
}
// convert to v2 message
cV2 := c.ToV2()
require.Equal(t, v2session.ObjectVerbUnknown, cV2.GetVerb())
require.Nil(t, cV2.GetAddress())
})
}
func TestObjectContextEncoding(t *testing.T) {
c := sessiontest.ObjectContext()
t.Run("binary", func(t *testing.T) { t.Run("binary", func(t *testing.T) {
data, err := c.Marshal() data := tok.Marshal()
require.NoError(t, err)
c2 := session.NewObjectContext() var tok2 session.Object
require.NoError(t, c2.Unmarshal(data)) require.NoError(t, tok2.Unmarshal(data))
require.Equal(t, c, c2) require.Equal(t, tok, tok2)
}) })
t.Run("json", func(t *testing.T) { t.Run("json", func(t *testing.T) {
data, err := c.MarshalJSON() data, err := tok.MarshalJSON()
require.NoError(t, err) require.NoError(t, err)
c2 := session.NewObjectContext() var tok2 session.Object
require.NoError(t, c2.UnmarshalJSON(data)) require.NoError(t, tok2.UnmarshalJSON(data))
require.Equal(t, c, c2) require.Equal(t, tok, tok2)
}) })
} }
func TestObjectAppliedTo(t *testing.T) {
var x session.Object
a := *addresstest.Address()
require.False(t, x.AppliedTo(a))
x.ApplyTo(a)
require.True(t, x.AppliedTo(a))
}
func TestObjectExp(t *testing.T) {
var x session.Object
exp := rand.Uint64()
require.True(t, x.ExpiredAt(exp))
x.SetExp(exp)
require.False(t, x.ExpiredAt(exp-1))
require.True(t, x.ExpiredAt(exp))
require.True(t, x.ExpiredAt(exp+1))
}
func TestObjectLifetime(t *testing.T) {
var x session.Object
nbf := rand.Uint64()
if nbf == math.MaxUint64 {
nbf--
}
iat := nbf
exp := iat + 1
x.SetNbf(nbf)
x.SetIat(iat)
x.SetExp(exp)
require.True(t, x.InvalidAt(nbf-1))
require.True(t, x.InvalidAt(iat-1))
require.False(t, x.InvalidAt(iat))
require.True(t, x.InvalidAt(exp))
}
func TestObjectID(t *testing.T) {
var x session.Object
require.Zero(t, x.ID())
id := uuid.New()
x.SetID(id)
require.Equal(t, id, x.ID())
}
func TestObjectAuthKey(t *testing.T) {
var x session.Object
key := randPublicKey()
require.False(t, x.AssertAuthKey(key))
x.SetAuthKey(key)
require.True(t, x.AssertAuthKey(key))
}
func TestObjectVerb(t *testing.T) {
var x session.Object
const v1, v2 = session.VerbObjectGet, session.VerbObjectPut
require.False(t, x.AssertVerb(v1, v2))
x.ForVerb(v1)
require.True(t, x.AssertVerb(v1))
require.False(t, x.AssertVerb(v2))
require.True(t, x.AssertVerb(v1, v2))
require.True(t, x.AssertVerb(v2, v1))
}
func TestObjectSignature(t *testing.T) {
var x session.Object
const nbf = 11
const iat = 22
const exp = 33
id := uuid.New()
key := randPublicKey()
obj := *addresstest.Address()
verb := session.VerbObjectDelete
signer := randSigner()
fs := []func(){
func() { x.SetNbf(nbf) },
func() { x.SetNbf(nbf + 1) },
func() { x.SetIat(iat) },
func() { x.SetIat(iat + 1) },
func() { x.SetExp(exp) },
func() { x.SetExp(exp + 1) },
func() { x.SetID(id) },
func() {
idcp := id
idcp[0]++
x.SetID(idcp)
},
func() { x.SetAuthKey(key) },
func() { x.SetAuthKey(randPublicKey()) },
func() { x.ApplyTo(obj) },
func() { x.ApplyTo(*addresstest.Address()) },
func() { x.ForVerb(verb) },
func() { x.ForVerb(verb + 1) },
}
for i := 0; i < len(fs); i += 2 {
fs[i]()
require.NoError(t, x.Sign(signer))
require.True(t, x.VerifySignature())
fs[i+1]()
require.False(t, x.VerifySignature())
fs[i]()
require.True(t, x.VerifySignature())
}
}

View file

@ -1,294 +0,0 @@
package session
import (
"crypto/ecdsa"
"errors"
"fmt"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
"github.com/nspcc-dev/neofs-api-go/v2/session"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
"github.com/nspcc-dev/neofs-sdk-go/user"
)
// Token represents NeoFS API v2-compatible
// session token.
type Token session.Token
// NewTokenFromV2 wraps session.Token message structure
// into Token.
//
// Nil session.Token converts to nil.
func NewTokenFromV2(tV2 *session.Token) *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.Token))
}
// ToV2 converts Token to session.Token message structure.
//
// Nil Token converts to nil.
func (t *Token) ToV2() *session.Token {
return (*session.Token)(t)
}
func (t *Token) setBodyField(setter func(*session.TokenBody)) {
token := (*session.Token)(t)
body := token.GetBody()
if body == nil {
body = new(session.TokenBody)
token.SetBody(body)
}
setter(body)
}
// ID returns Token identifier.
func (t *Token) ID() []byte {
return (*session.Token)(t).
GetBody().
GetID()
}
// SetID sets Token identifier.
func (t *Token) SetID(v []byte) {
t.setBodyField(func(body *session.TokenBody) {
body.SetID(v)
})
}
// OwnerID returns Token's owner identifier.
func (t *Token) OwnerID() *user.ID {
m := (*session.Token)(t).GetBody().GetOwnerID()
if m == nil {
return nil
}
var res user.ID
_ = res.ReadFromV2(*m)
return &res
}
// SetOwnerID sets Token's owner identifier.
func (t *Token) SetOwnerID(v *user.ID) {
t.setBodyField(func(body *session.TokenBody) {
var m refs.OwnerID
v.WriteToV2(&m)
body.SetOwnerID(&m)
})
}
// SessionKey returns public key of the session
// in a binary format.
func (t *Token) SessionKey() []byte {
return (*session.Token)(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.TokenBody) {
body.SetSessionKey(v)
})
}
func (t *Token) setLifetimeField(f func(*session.TokenLifetime)) {
t.setBodyField(func(body *session.TokenBody) {
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.Token)(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.Token)(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.Token)(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 {
if key == nil {
return errors.New("nil private key")
}
tV2 := (*session.Token)(t)
digest, err := tV2.GetBody().StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
var sig neofscrypto.Signature
err = sig.Calculate(neofsecdsa.Signer(*key), digest)
if err != nil {
return err
}
var sigV2 refs.Signature
sig.WriteToV2(&sigV2)
tV2.SetSignature(&sigV2)
return nil
}
// VerifySignature checks if token signature is
// presented and valid.
func (t *Token) VerifySignature() bool {
tV2 := (*session.Token)(t)
digest, err := tV2.GetBody().StableMarshal(nil)
if err != nil {
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
}
sigV2 := tV2.GetSignature()
if sigV2 == nil {
return false
}
var sig neofscrypto.Signature
sig.ReadFromV2(*sigV2)
return sig.Verify(digest)
}
// SetContext sets context of the Token.
//
// Supported contexts:
// - *ContainerContext,
// - *ObjectContext.
//
// Resets context if it is not supported.
func (t *Token) SetContext(v interface{}) {
var cV2 session.TokenContext
switch c := v.(type) {
case *ContainerContext:
cV2 = c.ToV2()
case *ObjectContext:
cV2 = c.ToV2()
}
t.setBodyField(func(body *session.TokenBody) {
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.Token)(t).
GetBody().
GetContext(); c := v.(type) {
default:
return nil
case *session.ContainerSessionContext:
return NewContainerContextFromV2(c)
case *session.ObjectSessionContext:
return NewObjectContextFromV2(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.
func (t *Token) Marshal() ([]byte, error) {
return (*session.Token)(t).
StableMarshal(nil)
}
// Unmarshal unmarshals protobuf binary representation of Token.
func (t *Token) Unmarshal(data []byte) error {
return (*session.Token)(t).
Unmarshal(data)
}
// MarshalJSON encodes Token to protobuf JSON format.
func (t *Token) MarshalJSON() ([]byte, error) {
return (*session.Token)(t).
MarshalJSON()
}
// UnmarshalJSON decodes Token from protobuf JSON format.
func (t *Token) UnmarshalJSON(data []byte) error {
return (*session.Token)(t).
UnmarshalJSON(data)
}

View file

@ -1,202 +0,0 @@
package session_test
import (
"testing"
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/session"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
usertest "github.com/nspcc-dev/neofs-sdk-go/user/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 := usertest.ID()
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.Token()
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.Token()
require.False(t, tok.VerifySignature())
})
t.Run("signed", func(t *testing.T) {
tok := sessiontest.SignedToken()
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.Token
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.False(t, token.VerifySignature())
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())
})
}

View file

@ -1,27 +0,0 @@
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))]()
cID := cidtest.ID()
c.ApplyTo(&cID)
return c
}

13
session/test/doc.go Normal file
View file

@ -0,0 +1,13 @@
/*
Package sessiontest provides functions for convenient testing of session package API.
Note that importing the package into source files is highly discouraged.
Random instance generation functions can be useful when testing expects any value, e.g.:
import sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
val := sessiontest.Container()
// test the value
*/
package sessiontest

View file

@ -1,30 +0,0 @@
package sessiontest
import (
"math/rand"
addresstest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
// ObjectContext returns session.ObjectContext
// which applies to random operation on a random object.
func ObjectContext() *session.ObjectContext {
c := session.NewObjectContext()
setters := []func(){
c.ForPut,
c.ForDelete,
c.ForHead,
c.ForRange,
c.ForRangeHash,
c.ForSearch,
c.ForGet,
}
setters[rand.Uint32()%uint32(len(setters))]()
c.ApplyTo(addresstest.Address())
return c
}

97
session/test/session.go Normal file
View file

@ -0,0 +1,97 @@
package sessiontest
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
addresstest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
"github.com/nspcc-dev/neofs-sdk-go/session"
)
var p ecdsa.PrivateKey
func init() {
k, err := keys.NewPrivateKey()
if err != nil {
panic(err)
}
p = k.PrivateKey
}
// Container returns random session.Container.
//
// Resulting token is unsigned.
func Container() *session.Container {
var tok session.Container
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
tok.ForVerb(session.VerbContainerPut)
tok.ApplyOnlyTo(cidtest.ID())
tok.SetID(uuid.New())
tok.SetAuthKey((*neofsecdsa.PublicKey)(&priv.PublicKey))
tok.SetExp(11)
tok.SetNbf(22)
tok.SetIat(33)
return &tok
}
// ContainerSigned returns signed random session.Container.
//
// Panics if token could not be signed (actually unexpected).
func ContainerSigned() *session.Container {
tok := Container()
err := tok.Sign(p)
if err != nil {
panic(err)
}
return tok
}
// Object returns random session.Object.
//
// Resulting token is unsigned.
func Object() *session.Object {
var tok session.Object
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
tok.ForVerb(session.VerbObjectPut)
tok.ApplyTo(*addresstest.Address())
tok.SetID(uuid.New())
tok.SetAuthKey((*neofsecdsa.PublicKey)(&priv.PublicKey))
tok.SetExp(11)
tok.SetNbf(22)
tok.SetIat(33)
return &tok
}
// ObjectSigned returns signed random session.Object.
//
// Panics if token could not be signed (actually unexpected).
func ObjectSigned() *session.Object {
tok := Object()
err := tok.Sign(p)
if err != nil {
panic(err)
}
return tok
}

View file

@ -1,69 +0,0 @@
package sessiontest
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/user"
)
var p *keys.PrivateKey
func init() {
var err error
p, err = keys.NewPrivateKey()
if err != nil {
panic(err)
}
}
// Token returns random session.Token.
//
// Resulting token is unsigned.
func Token() *session.Token {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
if err != nil {
panic(err)
}
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
panic(err)
}
var ownerID user.ID
user.IDFromKey(&ownerID, priv.PublicKey)
keyBin := p.PublicKey().Bytes()
tok.SetID(uid)
tok.SetOwnerID(&ownerID)
tok.SetSessionKey(keyBin)
tok.SetExp(11)
tok.SetNbf(22)
tok.SetIat(33)
return tok
}
// SignedToken returns signed random session.Token.
//
// Panics if token could not be signed (actually unexpected).
func SignedToken() *session.Token {
tok := Token()
err := tok.Sign(&p.PrivateKey)
if err != nil {
panic(err)
}
return tok
}

View file

@ -1,55 +0,0 @@
package session
import (
"github.com/nspcc-dev/neofs-api-go/v2/session"
)
// XHeader represents v2-compatible XHeader.
type XHeader session.XHeader
// NewXHeaderFromV2 wraps v2 XHeader message to XHeader.
//
// Nil session.XHeader converts to nil.
func NewXHeaderFromV2(v *session.XHeader) *XHeader {
return (*XHeader)(v)
}
// NewXHeader creates, initializes and returns blank XHeader instance.
//
// Defaults:
// - key: "";
// - value: "".
func NewXHeader() *XHeader {
return NewXHeaderFromV2(new(session.XHeader))
}
// ToV2 converts XHeader to v2 XHeader message.
//
// Nil XHeader converts to nil.
func (x *XHeader) ToV2() *session.XHeader {
return (*session.XHeader)(x)
}
// Key returns key to X-Header.
func (x *XHeader) Key() string {
return (*session.XHeader)(x).
GetKey()
}
// SetKey sets key to X-Header.
func (x *XHeader) SetKey(k string) {
(*session.XHeader)(x).
SetKey(k)
}
// Value returns value of X-Header.
func (x *XHeader) Value() string {
return (*session.XHeader)(x).
GetValue()
}
// SetValue sets value of X-Header.
func (x *XHeader) SetValue(k string) {
(*session.XHeader)(x).
SetValue(k)
}

View file

@ -1,58 +0,0 @@
package session
import (
"testing"
"github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/stretchr/testify/require"
)
func TestXHeader(t *testing.T) {
x := NewXHeader()
key := "some key"
val := "some value"
x.SetKey(key)
x.SetValue(val)
require.Equal(t, key, x.Key())
require.Equal(t, val, x.Value())
xV2 := x.ToV2()
require.Equal(t, key, xV2.GetKey())
require.Equal(t, val, xV2.GetValue())
}
func TestNewXHeaderFromV2(t *testing.T) {
t.Run("from nil", func(t *testing.T) {
var x *session.XHeader
require.Nil(t, NewXHeaderFromV2(x))
})
}
func TestXHeader_ToV2(t *testing.T) {
t.Run("nil", func(t *testing.T) {
var x *XHeader
require.Nil(t, x.ToV2())
})
}
func TestNewXHeader(t *testing.T) {
t.Run("default values", func(t *testing.T) {
xh := NewXHeader()
// check initial values
require.Empty(t, xh.Value())
require.Empty(t, xh.Key())
// convert to v2 message
xhV2 := xh.ToV2()
require.Empty(t, xhV2.GetValue())
require.Empty(t, xhV2.GetKey())
})
}