[#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"
"github.com/nspcc-dev/neofs-api-go/v2/signature"
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"
)
@ -35,23 +34,6 @@ func (x statusRes) Status() apistatus.Status {
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.
type prmCommonMeta struct {
// NeoFS request X-Headers

View file

@ -107,9 +107,15 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
// form meta header
var meta v2session.RequestMetaHeader
meta.SetSessionToken(prm.cnr.SessionToken().ToV2())
prm.prmCommonMeta.writeToMetaHeader(&meta)
if tok := prm.cnr.SessionToken(); tok != nil {
var tokv2 v2session.Token
tok.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
}
// form request
var req v2container.PutRequest
@ -240,9 +246,16 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
cnr := container.NewContainerFromV2(body.GetContainer())
cnr.SetSessionToken(
session.NewTokenFromV2(body.GetSessionToken()),
)
tokv2 := 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
@ -368,10 +381,12 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC
// PrmContainerDelete groups parameters of ContainerDelete operation.
type PrmContainerDelete struct {
prmCommonMeta
prmSession
idSet bool
id cid.ID
tokSet bool
tok session.Container
}
// SetContainer sets identifier of the NeoFS container to be removed.
@ -381,6 +396,17 @@ func (x *PrmContainerDelete) SetContainer(id cid.ID) {
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.
type ResContainerDelete struct {
statusRes
@ -456,10 +482,15 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
// form meta header
var meta v2session.RequestMetaHeader
prm.prmSession.writeToMetaHeader(&meta)
prm.prmCommonMeta.writeToMetaHeader(&meta)
if prm.tokSet {
var tokv2 v2session.Token
prm.tok.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
}
// form request
var req v2container.DeleteRequest
@ -577,9 +608,16 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC
table := eacl.NewTableFromV2(body.GetEACL())
table.SetSessionToken(
session.NewTokenFromV2(body.GetSessionToken()),
)
tokv2 := 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
@ -674,9 +712,15 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
// form meta header
var meta v2session.RequestMetaHeader
meta.SetSessionToken(prm.table.SessionToken().ToV2())
prm.prmCommonMeta.writeToMetaHeader(&meta)
if tok := prm.table.SessionToken(); tok != nil {
var tokv2 v2session.Token
tok.WriteToV2(&tokv2)
meta.SetSessionToken(&tokv2)
}
// form request
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).
//
// Must be signed.
func (x *PrmObjectDelete) WithinSession(t session.Token) {
x.meta.SetSessionToken(t.ToV2())
func (x *PrmObjectDelete) WithinSession(t session.Object) {
var tv2 v2session.Token
t.WriteToV2(&tv2)
x.meta.SetSessionToken(&tv2)
}
// WithBearerToken attaches bearer token to be used for the operation.

View file

@ -30,7 +30,7 @@ type prmObjectRead struct {
local bool
sessionSet bool
session session.Token
session session.Object
bearerSet bool
bearer bearer.Token
@ -54,7 +54,10 @@ func (x prmObjectRead) writeToMetaHeader(h *v2session.RequestMetaHeader) {
}
if x.sessionSet {
h.SetSessionToken(x.session.ToV2())
var tokv2 v2session.Token
x.session.WriteToV2(&tokv2)
h.SetSessionToken(&tokv2)
}
x.prmCommonMeta.writeToMetaHeader(h)
@ -76,7 +79,7 @@ func (x *prmObjectRead) MarkLocal() {
// This may affect the execution of an operation (e.g. access control).
//
// Must be signed.
func (x *prmObjectRead) WithinSession(t session.Token) {
func (x *prmObjectRead) WithinSession(t session.Object) {
x.session = t
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).
//
// Must be signed.
func (x *PrmObjectHash) WithinSession(t session.Token) {
x.meta.SetSessionToken(t.ToV2())
func (x *PrmObjectHash) WithinSession(t session.Object) {
var tv2 v2session.Token
t.WriteToV2(&tv2)
x.meta.SetSessionToken(&tv2)
}
// 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.
// Should be called once before any writing steps.
func (x *ObjectWriter) WithinSession(t session.Token) {
x.metaHdr.SetSessionToken(t.ToV2())
func (x *ObjectWriter) WithinSession(t session.Object) {
var tv2 v2session.Token
t.WriteToV2(&tv2)
x.metaHdr.SetSessionToken(&tv2)
}
// MarkLocal tells the server to execute the operation locally.

View file

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

View file

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

View file

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

View file

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

View file

@ -93,7 +93,7 @@ func TestTableEncoding(t *testing.T) {
}
func TestTable_SessionToken(t *testing.T) {
tok := sessiontest.Token()
tok := sessiontest.Container()
table := eacl.NewTable()
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/refs"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
"github.com/nspcc-dev/neofs-sdk-go/checksum"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
@ -519,19 +520,31 @@ func (o *Object) resetRelations() {
// SessionToken returns token of the session
// within which object was created.
func (o *Object) SessionToken() *session.Token {
return session.NewTokenFromV2(
(*object.Object)(o).
GetHeader().
GetSessionToken(),
)
func (o *Object) SessionToken() *session.Object {
tokv2 := (*object.Object)(o).GetHeader().GetSessionToken()
if tokv2 == nil {
return nil
}
var res session.Object
fmt.Println(res.ReadFromV2(*tokv2))
return &res
}
// SetSessionToken sets token of the session
// 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) {
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) {
obj := New()
tok := sessiontest.Token()
tok := sessiontest.ObjectSigned()
obj.SetSessionToken(tok)

View file

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

View file

@ -14,7 +14,7 @@ type sessionCache struct {
}
type cacheValue struct {
token *session.Token
token session.Object
}
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
// and context related fields. Returns nil if token is missing in the cache.
// 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)
if !ok {
return nil
return session.Object{}, false
}
value := valueRaw.(*cacheValue)
if c.expired(value) {
c.cache.Remove(key)
return nil
return session.Object{}, false
}
if value.token == nil {
return nil
}
res := copySessionTokenWithoutSignatureAndContext(*value.token)
return &res
return value.token, true
}
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{
token: token,
})
@ -73,5 +67,5 @@ func (c *sessionCache) updateEpoch(newEpoch uint64) {
func (c *sessionCache) expired(val *cacheValue) bool {
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) {
const key = "Foo"
target := sessiontest.Token()
target := *sessiontest.Object()
pk, err := keys.NewPrivateKey()
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.Nil(t, tok.Context(), extra)
}
cache, err := newCache()
require.NoError(t, err)
cache.Put(key, target)
value := cache.Get(key)
value, ok := cache.Get(key)
require.True(t, ok)
check(t, value, "before sign")
err = value.Sign(&pk.PrivateKey)
err = value.Sign(pk.PrivateKey)
require.NoError(t, err)
octx := sessiontest.ObjectContext()
value.SetContext(octx)
value = cache.Get(key)
value, ok = cache.Get(key)
require.True(t, ok)
check(t, value, "after sign")
}

View file

@ -14,13 +14,14 @@ import (
"sync"
"time"
"github.com/google/uuid"
"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/bearer"
sdkClient "github.com/nspcc-dev/neofs-sdk-go/client"
"github.com/nspcc-dev/neofs-sdk-go/container"
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/netmap"
"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
cliPrm.SetContainer(prm.cnrID)
if prm.stokenSet {
cliPrm.SetSessionToken(prm.stoken)
cliPrm.WithinSession(prm.stoken)
}
if _, err := c.client.ContainerDelete(ctx, cliPrm); err != nil {
@ -607,26 +608,26 @@ type clientPack struct {
type prmContext struct {
defaultSession bool
verb sessionv2.ObjectSessionVerb
addr *address.Address
verb session.ObjectVerb
addr address.Address
}
func (x *prmContext) useDefaultSession() {
x.defaultSession = true
}
func (x *prmContext) useAddress(addr *address.Address) {
func (x *prmContext) useAddress(addr address.Address) {
x.addr = addr
}
func (x *prmContext) useVerb(verb sessionv2.ObjectSessionVerb) {
func (x *prmContext) useVerb(verb session.ObjectVerb) {
x.verb = verb
}
type prmCommon struct {
key *ecdsa.PrivateKey
btoken *bearer.Token
stoken *session.Token
stoken *session.Object
}
// 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.
func (x *prmCommon) UseSession(token session.Token) {
func (x *prmCommon) UseSession(token session.Object) {
x.stoken = &token
}
@ -787,7 +788,7 @@ func (x *PrmContainerList) SetOwnerID(ownerID user.ID) {
type PrmContainerDelete struct {
cnrID cid.ID
stoken session.Token
stoken session.Container
stokenSet bool
waitParams WaitParams
@ -800,7 +801,7 @@ func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) {
}
// 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.stokenSet = true
}
@ -990,7 +991,7 @@ func (p *Pool) Dial(ctx context.Context) error {
zap.Error(err))
} else if err == nil {
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}
}
@ -1229,7 +1230,7 @@ func (p *Pool) checkSessionTokenErr(err error, address string) bool {
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{})
if err != nil {
return nil, err
@ -1251,7 +1252,26 @@ func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.I
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 {
@ -1268,8 +1288,9 @@ type callContext struct {
// flag to open default session if session token is missing
sessionDefault bool
sessionTarget func(session.Token)
sessionContext *session.ObjectContext
sessionTarget func(session.Object)
sessionVerb session.ObjectVerb
sessionAddr address.Address
}
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
ctx.sessionDefault = cfg.stoken == nil && prmCtx.defaultSession
if ctx.sessionDefault {
ctx.sessionContext = session.NewObjectContext()
ctx.sessionContext.ToV2().SetVerb(prmCtx.verb)
ctx.sessionContext.ApplyTo(prmCtx.addr)
ctx.sessionVerb = prmCtx.verb
ctx.sessionAddr = prmCtx.addr
}
return err
@ -1307,32 +1327,32 @@ func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContex
func (p *Pool) openDefaultSession(ctx *callContext) error {
cacheKey := formCacheKey(ctx.endpoint, ctx.key)
tok := p.cache.Get(cacheKey)
if tok == nil {
tok, ok := p.cache.Get(cacheKey)
if !ok {
var err error
var sessionOwner user.ID
user.IDFromKey(&sessionOwner, ctx.key.PublicKey)
// open new session
tok, err = createSessionTokenForDuration(ctx, ctx.client, sessionOwner, p.stokenDuration)
t, err := createSessionTokenForDuration(ctx, ctx.client, sessionOwner, p.stokenDuration)
if err != nil {
return fmt.Errorf("session API client: %w", err)
}
// cache the opened session
p.cache.Put(cacheKey, tok)
p.cache.Put(cacheKey, *t)
}
tokToSign := *tok
tokToSign.SetContext(ctx.sessionContext)
tok.ForVerb(ctx.sessionVerb)
tok.ApplyTo(ctx.sessionAddr)
// 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)
}
ctx.sessionTarget(tokToSign)
ctx.sessionTarget(tok)
return nil
}
@ -1371,8 +1391,8 @@ func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (*oid.ID, error)
var prmCtx prmContext
prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbPut)
prmCtx.useAddress(newAddressFromCnrID(cIDp))
prmCtx.useVerb(session.VerbObjectPut)
prmCtx.useAddress(*newAddressFromCnrID(cIDp))
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 {
var prmCtx prmContext
prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbDelete)
prmCtx.useAddress(&prm.addr)
prmCtx.useVerb(session.VerbObjectDelete)
prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon)
@ -1456,8 +1476,8 @@ type ResGetObject struct {
func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject, error) {
var prmCtx prmContext
prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbGet)
prmCtx.useAddress(&prm.addr)
prmCtx.useVerb(session.VerbObjectGet)
prmCtx.useAddress(prm.addr)
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) {
var prmCtx prmContext
prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbHead)
prmCtx.useAddress(&prm.addr)
prmCtx.useVerb(session.VerbObjectHead)
prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon)
@ -1529,8 +1549,8 @@ func (x *ResObjectRange) Close() error {
func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (*ResObjectRange, error) {
var prmCtx prmContext
prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbRange)
prmCtx.useAddress(&prm.addr)
prmCtx.useVerb(session.VerbObjectRange)
prmCtx.useAddress(prm.addr)
p.fillAppropriateKey(&prm.prmCommon)
@ -1595,8 +1615,8 @@ func (x *ResObjectSearch) Close() {
func (p *Pool) SearchObjects(ctx context.Context, prm PrmObjectSearch) (*ResObjectSearch, error) {
var prmCtx prmContext
prmCtx.useDefaultSession()
prmCtx.useVerb(sessionv2.ObjectVerbSearch)
prmCtx.useAddress(newAddressFromCnrID(&prm.cnrID))
prmCtx.useVerb(session.VerbObjectSearch)
prmCtx.useAddress(*newAddressFromCnrID(&prm.cnrID))
p.fillAppropriateKey(&prm.prmCommon)
@ -1788,17 +1808,6 @@ func (p *Pool) Close() {
<-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 {
addr := address.NewAddress()
if cnrID != nil {
@ -1806,22 +1815,3 @@ func newAddressFromCnrID(cnrID *cid.ID) *address.Address {
}
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/nspcc-dev/neo-go/pkg/crypto/keys"
"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/object"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
@ -73,20 +74,30 @@ func newPrivateKey(t *testing.T) *ecdsa.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) {
ctrl := gomock.NewController(t)
ctrl2 := gomock.NewController(t)
var expectedToken *session.Token
var expectedToken *session.Object
clientCount := -1
clientBuilder := func(_ string) (client, error) {
clientCount++
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t)
id := tok.ID()
return &resCreateSession{
sessionKey: tok.SessionKey(),
id: tok.ID(),
id: id[:],
sessionKey: newBinPublicKey(t),
}, nil
}).AnyTimes()
@ -96,9 +107,10 @@ func TestBuildPoolOneNodeFailed(t *testing.T) {
mockClient2 := NewMockClient(ctrl2)
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
expectedToken = newToken(t)
id := expectedToken.ID()
return &resCreateSession{
sessionKey: expectedToken.SessionKey(),
id: expectedToken.ID(),
id: id[:],
sessionKey: newBinPublicKey(t),
}, 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 {
return false
}
st := clientPool.cache.Get(formCacheKey(cp.address, clientPool.key))
return areEqualTokens(st, expectedToken)
st, _ := clientPool.cache.Get(formCacheKey(cp.address, clientPool.key))
return areEqualTokens(&st, expectedToken)
}
require.Never(t, condition, 900*time.Millisecond, 100*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) {
ctrl := gomock.NewController(t)
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
uid := uuid.New()
var tok session.Object
tok.SetID(uid)
tokRes := &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
id: uid[:],
sessionKey: newBinPublicKey(t),
}
clientBuilder := func(_ string) (client, error) {
@ -184,34 +196,34 @@ func TestOneNode(t *testing.T) {
cp, err := pool.connection()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, areEqualTokens(tok, st))
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
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 {
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) {
ctrl := gomock.NewController(t)
var tokens []*session.Token
var tokens []*session.Object
clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
var tok session.Object
uid := uuid.New()
tok.SetID(uid)
tokens = append(tokens, tok)
tokens = append(tokens, &tok)
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
}, err
id: uid[:],
sessionKey: newBinPublicKey(t),
}, nil
})
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, 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()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st))
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
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 {
if areEqualTokens(tok, item) {
return true
@ -252,7 +264,7 @@ func TestOneOfTwoFailed(t *testing.T) {
ctrl := gomock.NewController(t)
ctrl2 := gomock.NewController(t)
var tokens []*session.Token
var tokens []*session.Object
clientCount := -1
clientBuilder := func(_ string) (client, error) {
clientCount++
@ -260,9 +272,10 @@ func TestOneOfTwoFailed(t *testing.T) {
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t)
tokens = append(tokens, tok)
id := tok.ID()
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
id: id[:],
sessionKey: newBinPublicKey(t),
}, 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) {
tok := newToken(t)
tokens = append(tokens, tok)
id := tok.ID()
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
id: id[:],
sessionKey: newBinPublicKey(t),
}, nil
}).AnyTimes()
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++ {
cp, err := pool.connection()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, areEqualTokens(tokens[0], st))
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, areEqualTokens(tokens[0], &st))
}
}
@ -323,7 +337,10 @@ func TestTwoFailed(t *testing.T) {
clientBuilder := func(_ string) (client, error) {
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().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
return mockClient, nil
@ -356,19 +373,18 @@ func TestTwoFailed(t *testing.T) {
func TestSessionCache(t *testing.T) {
ctrl := gomock.NewController(t)
var tokens []*session.Token
var tokens []*session.Object
clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*resCreateSession, error) {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
var tok session.Object
uid := uuid.New()
tok.SetID(uid)
tokens = append(tokens, tok)
tokens = append(tokens, &tok)
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
}, err
id: uid[:],
sessionKey: newBinPublicKey(t),
}, nil
}).MaxTimes(3)
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
cp, err := pool.connection()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st))
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, &st))
var prm PrmObjectGet
prm.SetAddress(address.Address{})
prm.UseSession(*session.NewToken())
prm.UseSession(session.Object{})
_, err = pool.GetObject(ctx, prm)
require.Error(t, err)
@ -412,8 +428,8 @@ func TestSessionCache(t *testing.T) {
// cache must not contain session token
cp, err = pool.connection()
require.NoError(t, err)
st = pool.cache.Get(formCacheKey(cp.address, pool.key))
require.Nil(t, st)
_, ok := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.False(t, ok)
var prm2 PrmObjectPut
prm2.SetHeader(object.Object{})
@ -424,23 +440,24 @@ func TestSessionCache(t *testing.T) {
// cache must contain session token
cp, err = pool.connection()
require.NoError(t, err)
st = pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st))
st, _ = pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, &st))
}
func TestPriority(t *testing.T) {
ctrl := gomock.NewController(t)
ctrl2 := gomock.NewController(t)
tokens := make([]*session.Token, 2)
tokens := make([]*session.Object, 2)
clientBuilder := func(endpoint string) (client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := newToken(t)
tokens[0] = tok
id := tok.ID()
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
id: id[:],
sessionKey: newBinPublicKey(t),
}, nil
}).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) {
tok := newToken(t)
tokens[1] = tok
id := tok.ID()
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
id: id[:],
sessionKey: newBinPublicKey(t),
}, 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 {
cp, err := pool.connection()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
return areEqualTokens(st, tokens[0])
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
return areEqualTokens(&st, tokens[0])
}
secondNode := func() bool {
cp, err := pool.connection()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
return areEqualTokens(st, tokens[1])
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
return areEqualTokens(&st, tokens[1])
}
require.Never(t, secondNode, time.Second, 200*time.Millisecond)
@ -504,19 +522,18 @@ func TestPriority(t *testing.T) {
func TestSessionCacheWithKey(t *testing.T) {
ctrl := gomock.NewController(t)
var tokens []*session.Token
var tokens []*session.Object
clientBuilder := func(_ string) (client, error) {
mockClient := NewMockClient(ctrl)
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
var tok session.Object
uid := uuid.New()
tok.SetID(uid)
tokens = append(tokens, tok)
tokens = append(tokens, &tok)
return &resCreateSession{
id: tok.ID(),
sessionKey: tok.SessionKey(),
}, err
id: uid[:],
sessionKey: newBinPublicKey(t),
}, nil
}).MaxTimes(2)
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
cp, err := pool.connection()
require.NoError(t, err)
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, st))
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
require.True(t, containsTokens(tokens, &st))
var prm PrmObjectGet
prm.SetAddress(address.Address{})
@ -557,20 +574,22 @@ func TestSessionCacheWithKey(t *testing.T) {
require.Len(t, tokens, 2)
}
func newToken(t *testing.T) *session.Token {
tok := session.NewToken()
uid, err := uuid.New().MarshalBinary()
require.NoError(t, err)
tok.SetID(uid)
func newToken(t *testing.T) *session.Object {
var tok session.Object
tok.SetID(uuid.New())
return tok
return &tok
}
func TestSessionTokenOwner(t *testing.T) {
t.Skip() // neofs-sdk-go#???
ctrl := gomock.NewController(t)
clientBuilder := func(_ string) (client, error) {
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().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
return mockClient, nil
@ -605,15 +624,15 @@ func TestSessionTokenOwner(t *testing.T) {
var cc callContext
cc.Context = ctx
cc.sessionTarget = func(session.Token) {}
cc.sessionTarget = func(session.Object) {}
err = p.initCallContext(&cc, prm, prmCtx)
require.NoError(t, err)
err = p.openDefaultSession(&cc)
require.NoError(t, err)
tkn := p.cache.Get(formCacheKey("peer0", anonKey))
require.True(t, anonOwner.Equals(*tkn.OwnerID()))
tkn, _ := p.cache.Get(formCacheKey("peer0", anonKey))
require.True(t, tkn.VerifySignature())
}
func TestWaitPresence(t *testing.T) {
@ -661,29 +680,29 @@ func TestWaitPresence(t *testing.T) {
}
func TestCopySessionTokenWithoutSignatureAndContext(t *testing.T) {
from := sessiontest.SignedToken()
to := copySessionTokenWithoutSignatureAndContext(*from)
from := *sessiontest.Object()
require.Equal(t, from.Nbf(), to.Nbf())
require.Equal(t, from.Exp(), to.Exp())
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())
const verb = session.VerbObjectHead
from.ForVerb(verb)
to := from
require.Equal(t, from, to)
require.False(t, from.VerifySignature())
require.False(t, to.VerifySignature())
t.Run("empty object context", func(t *testing.T) {
octx := sessiontest.ObjectContext()
from.SetContext(octx)
to = copySessionTokenWithoutSignatureAndContext(*from)
require.Nil(t, to.Context())
})
require.True(t, from.AssertVerb(verb))
require.True(t, to.AssertVerb(verb))
t.Run("empty container context", func(t *testing.T) {
cctx := sessiontest.ContainerContext()
from.SetContext(cctx)
to = copySessionTokenWithoutSignatureAndContext(*from)
require.Nil(t, to.Context())
})
k, err := keys.NewPrivateKey()
require.NoError(t, err)
from.ForVerb(verb + 1)
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
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"
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
// context of the container session.
// Container represents token of the NeoFS Container session. A session is opened
// 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
// which allows to abstract from details of the message
// structure.
type ContainerContext session.ContainerSessionContext
// NewContainerContext creates and returns blank ContainerSessionContext.
// Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
// message. See ReadFromV2 / WriteToV2 methods.
//
// Defaults:
// - not bound to any operation;
// - applied to all containers.
func NewContainerContext() *ContainerContext {
v2 := new(session.ContainerSessionContext)
v2.SetWildcard(true)
// Instances can be created using built-in var declaration.
type Container struct {
cnrSet bool
return NewContainerContextFromV2(v2)
lt session.TokenLifetime
c session.ContainerSessionContext
body session.TokenBody
sig neofscrypto.Signature
}
// NewContainerContextFromV2 wraps session.ContainerSessionContext
// 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.
// ReadFromV2 reads Container from the session.Token message.
//
// If id is nil, ContainerContext is applied to all containers.
func (x *ContainerContext) ApplyTo(id *cid.ID) {
v2 := (*session.ContainerSessionContext)(x)
var cidV2 *refs.ContainerID
if id != nil {
var c refs.ContainerID
id.WriteToV2(&c)
cidV2 = &c
// See also WriteToV2.
func (x *Container) ReadFromV2(m session.Token) error {
b := m.GetBody()
if b == nil {
return errors.New("missing body")
}
v2.SetWildcard(id == nil)
v2.SetContainerID(cidV2)
}
bID := b.GetID()
var id uuid.UUID
// ApplyToAllContainers is a helper function that conveniently
// applies ContainerContext to all containers.
func ApplyToAllContainers(c *ContainerContext) {
c.ApplyTo(nil)
}
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)
}
// Container returns identifier of the container
// to which the ContainerContext applies.
//
// Returns nil if ContainerContext is applied to
// all containers.
func (x *ContainerContext) Container() *cid.ID {
v2 := (*session.ContainerSessionContext)(x)
c, ok := b.GetContext().(*session.ContainerSessionContext)
if !ok {
return fmt.Errorf("invalid context %T", b.GetContext())
}
cnr := c.ContainerID()
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
}
// 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()
if cidV2 == nil {
return nil
return data
}
// 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
_ = cID.ReadFromV2(*cidV2)
return &cID
return x.ReadFromV2(m)
}
func (x *ContainerContext) forVerb(v session.ContainerSessionVerb) {
(*session.ContainerSessionContext)(x).
SetVerb(v)
// MarshalJSON encodes Container into a JSON format of the NeoFS API protocol
// (Protocol Buffers JSON).
//
// 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 {
return (*session.ContainerSessionContext)(x).
Verb() == v
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Container
// (Protocol Buffers JSON). Returns an error describing a format violation.
//
// 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
// PUT operation.
func (x *ContainerContext) ForPut() {
x.forVerb(session.ContainerVerbPut)
// Sign calculates and writes signature of the Container data.
// Returns signature calculation errors.
//
// 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
// PUT operation.
func (x *ContainerContext) IsForPut() bool {
return x.isForVerb(session.ContainerVerbPut)
// VerifySignature checks if Container signature is presented and valid.
//
// Zero Container fails the check.
//
// 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
// DELETE operation.
func (x *ContainerContext) ForDelete() {
x.forVerb(session.ContainerVerbDelete)
// ApplyOnlyTo limits session scope to a given author container.
//
// See also AppliedTo.
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
// DELETE operation.
func (x *ContainerContext) IsForDelete() bool {
return x.isForVerb(session.ContainerVerbDelete)
// AppliedTo checks if session scope is limited by a given container.
//
// Zero Container is applied to all author's containers.
//
// 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
// SETEACL operation.
func (x *ContainerContext) ForSetEACL() {
x.forVerb(session.ContainerVerbSetEACL)
// ContainerVerb enumerates container operations.
type ContainerVerb int8
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
// SETEACL operation.
func (x *ContainerContext) IsForSetEACL() bool {
return x.isForVerb(session.ContainerVerbSetEACL)
// AssertVerb checks if Container relates to the given container operation.
//
// Zero Container relates to zero (unspecified) verb.
//
// See also ForVerb.
func (x Container) AssertVerb(verb ContainerVerb) bool {
return verb == ContainerVerb(x.c.Verb())
}
// Marshal marshals ContainerContext into a protobuf binary form.
func (x *ContainerContext) Marshal() ([]byte, error) {
return x.ToV2().StableMarshal(nil)
// SetExp sets "exp" (expiration time) claim which identifies the expiration time
// (in NeoFS epochs) on or after which the Container MUST NOT be accepted for
// 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.
func (x *ContainerContext) Unmarshal(data []byte) error {
return x.ToV2().Unmarshal(data)
// ExpiredAt asserts "exp" claim.
//
// 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.
func (x *ContainerContext) MarshalJSON() ([]byte, error) {
return x.ToV2().MarshalJSON()
// SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
// epochs) before which the Container MUST NOT be accepted for processing.
// 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.
func (x *ContainerContext) UnmarshalJSON(data []byte) error {
return x.ToV2().UnmarshalJSON(data)
// SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
// epochs) at which the Container was issued. This claim can be used to
// 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
import (
"math"
"math/rand"
"testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
"github.com/nspcc-dev/neofs-sdk-go/session"
@ -10,103 +14,274 @@ import (
"github.com/stretchr/testify/require"
)
func TestContainerContextVerbs(t *testing.T) {
c := session.NewContainerContext()
func TestContainer_ReadFromV2(t *testing.T) {
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) {
setter()
t.Run("protocol violation", func(t *testing.T) {
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) {
assert(c.ForPut, c.IsForPut, v2session.ContainerVerbPut)
b.SetID(id[:])
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) {
assert(c.ForDelete, c.IsForDelete, v2session.ContainerVerbDelete)
m.SetBody(&b)
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) {
assert(c.ForSetEACL, c.IsForSetEACL, v2session.ContainerVerbSetEACL)
t.Run("verb", func(t *testing.T) {
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) {
c := session.NewContainerContext()
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()
func TestEncodingContainer(t *testing.T) {
tok := *sessiontest.ContainerSigned()
t.Run("binary", func(t *testing.T) {
data, err := c.Marshal()
require.NoError(t, err)
data := tok.Marshal()
c2 := session.NewContainerContext()
require.NoError(t, c2.Unmarshal(data))
var tok2 session.Container
require.NoError(t, tok2.Unmarshal(data))
require.Equal(t, c, c2)
require.Equal(t, tok, tok2)
})
t.Run("json", func(t *testing.T) {
data, err := c.MarshalJSON()
data, err := tok.MarshalJSON()
require.NoError(t, err)
c2 := session.NewContainerContext()
require.NoError(t, c2.UnmarshalJSON(data))
var tok2 session.Container
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
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"
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/user"
)
// ObjectContext represents NeoFS API v2-compatible
// context of the object session.
// Object represents token of the NeoFS Object session. A session is opened
// 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
// which allows abstracting from details of the message
// structure.
type ObjectContext session.ObjectSessionContext
// NewObjectContext creates and returns blank ObjectContext.
// Object is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
// message. See ReadFromV2 / WriteToV2 methods.
//
// Defaults:
// - not bound to any operation;
// - nil object address.
func NewObjectContext() *ObjectContext {
v2 := new(session.ObjectSessionContext)
// Instances can be created using built-in var declaration.
type Object struct {
lt session.TokenLifetime
return NewObjectContextFromV2(v2)
obj refs.Address
c session.ObjectSessionContext
body session.TokenBody
sig neofscrypto.Signature
}
// NewObjectContextFromV2 wraps session.ObjectSessionContext
// into ObjectContext.
func NewObjectContextFromV2(v *session.ObjectSessionContext) *ObjectContext {
return (*ObjectContext)(v)
// ReadFromV2 reads Object from the session.Token message.
//
// See also WriteToV2.
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
// message structure.
func (x *ObjectContext) ToV2() *session.ObjectSessionContext {
return (*session.ObjectSessionContext)(x)
// WriteToV2 writes Object to the session.Token message.
// The message must not be nil.
//
// 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.
func (x *ObjectContext) ApplyTo(id *address.Address) {
v2 := (*session.ObjectSessionContext)(x)
// Marshal encodes Object into a binary format of the NeoFS API protocol
// (Protocol Buffers with direct field order).
//
// 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
// to which the ObjectContext applies.
func (x *ObjectContext) Address() *address.Address {
v2 := (*session.ObjectSessionContext)(x)
// Unmarshal decodes NeoFS API protocol binary format into the Object
// (Protocol Buffers with direct field order). Returns an error describing
// a format violation.
//
// 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) {
(*session.ObjectSessionContext)(x).
SetVerb(v)
// MarshalJSON encodes Object into a JSON format of the NeoFS API protocol
// (Protocol Buffers JSON).
//
// 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 {
return (*session.ObjectSessionContext)(x).
GetVerb() == v
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Object
// (Protocol Buffers JSON). Returns an error describing a format violation.
//
// 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
// PUT operation.
func (x *ObjectContext) ForPut() {
x.forVerb(session.ObjectVerbPut)
// Sign calculates and writes signature of the Object data.
// Returns signature calculation errors.
//
// 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
// PUT operation.
func (x *ObjectContext) IsForPut() bool {
return x.isForVerb(session.ObjectVerbPut)
// VerifySignature checks if Object signature is presented and valid.
//
// Zero Object fails the check.
//
// 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
// DELETE operation.
func (x *ObjectContext) ForDelete() {
x.forVerb(session.ObjectVerbDelete)
// ApplyTo limits session scope to a given author object.
//
// See also AppliedTo.
func (x *Object) ApplyTo(a address.Address) {
x.obj = *a.ToV2()
}
// IsForDelete checks if ObjectContext is bound to
// DELETE operation.
func (x *ObjectContext) IsForDelete() bool {
return x.isForVerb(session.ObjectVerbDelete)
// AppliedTo checks if session scope is limited by a given object.
//
// Zero Object isn't applied to any author's object.
//
// 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
// GET operation.
func (x *ObjectContext) ForGet() {
x.forVerb(session.ObjectVerbGet)
// ObjectVerb enumerates object operations.
type ObjectVerb int8
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
// GET operation.
func (x *ObjectContext) IsForGet() bool {
return x.isForVerb(session.ObjectVerbGet)
// AssertVerb checks if Object relates to one of the given object operations.
//
// Zero Object relates to zero (unspecified) verb.
//
// 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
// HEAD operation.
func (x *ObjectContext) ForHead() {
x.forVerb(session.ObjectVerbHead)
// SetExp sets "exp" (expiration time) claim which identifies the expiration time
// (in NeoFS epochs) on or after which the Object MUST NOT be accepted for
// 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 *Object) SetExp(exp uint64) {
x.lt.SetExp(exp)
}
// IsForHead checks if ObjectContext is bound to
// HEAD operation.
func (x *ObjectContext) IsForHead() bool {
return x.isForVerb(session.ObjectVerbHead)
// ExpiredAt asserts "exp" claim.
//
// Zero Object is expired in any epoch.
//
// See also SetExp.
func (x Object) ExpiredAt(epoch uint64) bool {
return x.lt.GetExp() <= epoch
}
// ForSearch binds the ObjectContext to
// SEARCH operation.
func (x *ObjectContext) ForSearch() {
x.forVerb(session.ObjectVerbSearch)
// SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
// epochs) before which the Object MUST NOT be accepted for processing.
// 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 *Object) SetNbf(nbf uint64) {
x.lt.SetNbf(nbf)
}
// IsForSearch checks if ObjectContext is bound to
// SEARCH operation.
func (x *ObjectContext) IsForSearch() bool {
return x.isForVerb(session.ObjectVerbSearch)
// SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
// epochs) at which the Object was issued. This claim can be used to
// determine the age of the Object.
//
// 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
// RANGE operation.
func (x *ObjectContext) ForRange() {
x.forVerb(session.ObjectVerbRange)
// InvalidAt asserts "exp", "nbf" and "iat" claims.
//
// Zero Object is invalid in any epoch.
//
// 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
// RANGE operation.
func (x *ObjectContext) IsForRange() bool {
return x.isForVerb(session.ObjectVerbRange)
// 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 *Object) SetID(id uuid.UUID) {
x.body.SetID(id[:])
}
// ForRangeHash binds the ObjectContext to
// RANGEHASH operation.
func (x *ObjectContext) ForRangeHash() {
x.forVerb(session.ObjectVerbRangeHash)
// ID returns a unique identifier for the session.
//
// Zero Object has empty UUID (all zeros, see uuid.Nil) which is legitimate
// 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
// RANGEHASH operation.
func (x *ObjectContext) IsForRangeHash() bool {
return x.isForVerb(session.ObjectVerbRangeHash)
// SetAuthKey public key corresponding to the private key bound to the session.
//
// See also AssertAuthKey.
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.
func (x *ObjectContext) Marshal() ([]byte, error) {
return x.ToV2().StableMarshal(nil)
}
// AssertAuthKey asserts public key bound to the session.
//
// 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.
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)
return bytes.Equal(bKey, x.body.GetSessionKey())
}

View file

@ -1,123 +1,296 @@
package session_test
import (
"crypto/ecdsa"
"fmt"
"math"
"math/rand"
"testing"
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
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"
"github.com/nspcc-dev/neofs-sdk-go/session"
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
"github.com/stretchr/testify/require"
)
func TestObjectContextVerbs(t *testing.T) {
c := session.NewObjectContext()
assert := func(setter func(), getter func() bool, verb v2session.ObjectSessionVerb) {
setter()
require.True(t, getter())
require.Equal(t, verb, c.ToV2().GetVerb())
func randSigner() ecdsa.PrivateKey {
k, err := keys.NewPrivateKey()
if err != nil {
panic(fmt.Sprintf("generate private key: %v", err))
}
t.Run("PUT", func(t *testing.T) {
assert(c.ForPut, c.IsForPut, v2session.ObjectVerbPut)
return k.PrivateKey
}
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) {
assert(c.ForDelete, c.IsForDelete, v2session.ObjectVerbDelete)
m.SetBody(&b)
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) {
assert(c.ForGet, c.IsForGet, v2session.ObjectVerbGet)
t.Run("verb", func(t *testing.T) {
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) {
assert(c.ForSearch, c.IsForSearch, v2session.ObjectVerbSearch)
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("RANGE", func(t *testing.T) {
assert(c.ForRange, c.IsForRange, v2session.ObjectVerbRange)
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("RANGEHASH", func(t *testing.T) {
assert(c.ForRangeHash, c.IsForRangeHash, v2session.ObjectVerbRangeHash)
})
t.Run("session key", func(t *testing.T) {
key := randPublicKey()
t.Run("HEAD", func(t *testing.T) {
assert(c.ForHead, c.IsForHead, v2session.ObjectVerbHead)
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 TestObjectContext_ApplyTo(t *testing.T) {
c := session.NewObjectContext()
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()
func TestEncodingObject(t *testing.T) {
tok := *sessiontest.ObjectSigned()
t.Run("binary", func(t *testing.T) {
data, err := c.Marshal()
require.NoError(t, err)
data := tok.Marshal()
c2 := session.NewObjectContext()
require.NoError(t, c2.Unmarshal(data))
var tok2 session.Object
require.NoError(t, tok2.Unmarshal(data))
require.Equal(t, c, c2)
require.Equal(t, tok, tok2)
})
t.Run("json", func(t *testing.T) {
data, err := c.MarshalJSON()
data, err := tok.MarshalJSON()
require.NoError(t, err)
c2 := session.NewObjectContext()
require.NoError(t, c2.UnmarshalJSON(data))
var tok2 session.Object
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())
})
}