[#197] session: Refactor and document the package
Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
parent
497053c785
commit
552c7875bf
32 changed files with 1622 additions and 1358 deletions
|
@ -9,7 +9,6 @@ import (
|
||||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
"github.com/nspcc-dev/neofs-api-go/v2/signature"
|
||||||
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/version"
|
"github.com/nspcc-dev/neofs-sdk-go/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,23 +34,6 @@ func (x statusRes) Status() apistatus.Status {
|
||||||
return x.st
|
return x.st
|
||||||
}
|
}
|
||||||
|
|
||||||
type prmSession struct {
|
|
||||||
tokenSessionSet bool
|
|
||||||
tokenSession session.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSessionToken sets token of the session within which request should be sent.
|
|
||||||
func (x *prmSession) SetSessionToken(tok session.Token) {
|
|
||||||
x.tokenSession = tok
|
|
||||||
x.tokenSessionSet = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x prmSession) writeToMetaHeader(meta *v2session.RequestMetaHeader) {
|
|
||||||
if x.tokenSessionSet {
|
|
||||||
meta.SetSessionToken(x.tokenSession.ToV2())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// groups meta parameters shared between all Client operations.
|
// groups meta parameters shared between all Client operations.
|
||||||
type prmCommonMeta struct {
|
type prmCommonMeta struct {
|
||||||
// NeoFS request X-Headers
|
// NeoFS request X-Headers
|
||||||
|
|
|
@ -107,9 +107,15 @@ func (c *Client) ContainerPut(ctx context.Context, prm PrmContainerPut) (*ResCon
|
||||||
|
|
||||||
// form meta header
|
// form meta header
|
||||||
var meta v2session.RequestMetaHeader
|
var meta v2session.RequestMetaHeader
|
||||||
meta.SetSessionToken(prm.cnr.SessionToken().ToV2())
|
|
||||||
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
||||||
|
|
||||||
|
if tok := prm.cnr.SessionToken(); tok != nil {
|
||||||
|
var tokv2 v2session.Token
|
||||||
|
tok.WriteToV2(&tokv2)
|
||||||
|
|
||||||
|
meta.SetSessionToken(&tokv2)
|
||||||
|
}
|
||||||
|
|
||||||
// form request
|
// form request
|
||||||
var req v2container.PutRequest
|
var req v2container.PutRequest
|
||||||
|
|
||||||
|
@ -240,9 +246,16 @@ func (c *Client) ContainerGet(ctx context.Context, prm PrmContainerGet) (*ResCon
|
||||||
|
|
||||||
cnr := container.NewContainerFromV2(body.GetContainer())
|
cnr := container.NewContainerFromV2(body.GetContainer())
|
||||||
|
|
||||||
cnr.SetSessionToken(
|
tokv2 := body.GetSessionToken()
|
||||||
session.NewTokenFromV2(body.GetSessionToken()),
|
if tokv2 != nil {
|
||||||
)
|
var tok session.Container
|
||||||
|
|
||||||
|
// FIXME: need to handle the error
|
||||||
|
err := tok.ReadFromV2(*tokv2)
|
||||||
|
if err == nil {
|
||||||
|
cnr.SetSessionToken(&tok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sig *neofscrypto.Signature
|
var sig *neofscrypto.Signature
|
||||||
|
|
||||||
|
@ -368,10 +381,12 @@ func (c *Client) ContainerList(ctx context.Context, prm PrmContainerList) (*ResC
|
||||||
// PrmContainerDelete groups parameters of ContainerDelete operation.
|
// PrmContainerDelete groups parameters of ContainerDelete operation.
|
||||||
type PrmContainerDelete struct {
|
type PrmContainerDelete struct {
|
||||||
prmCommonMeta
|
prmCommonMeta
|
||||||
prmSession
|
|
||||||
|
|
||||||
idSet bool
|
idSet bool
|
||||||
id cid.ID
|
id cid.ID
|
||||||
|
|
||||||
|
tokSet bool
|
||||||
|
tok session.Container
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetContainer sets identifier of the NeoFS container to be removed.
|
// SetContainer sets identifier of the NeoFS container to be removed.
|
||||||
|
@ -381,6 +396,17 @@ func (x *PrmContainerDelete) SetContainer(id cid.ID) {
|
||||||
x.idSet = true
|
x.idSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithinSession specifies session within which container should be removed.
|
||||||
|
//
|
||||||
|
// Creator of the session acquires the authorship of the request.
|
||||||
|
// This may affect the execution of an operation (e.g. access control).
|
||||||
|
//
|
||||||
|
// Must be signed.
|
||||||
|
func (x *PrmContainerDelete) WithinSession(tok session.Container) {
|
||||||
|
x.tok = tok
|
||||||
|
x.tokSet = true
|
||||||
|
}
|
||||||
|
|
||||||
// ResContainerDelete groups resulting values of ContainerDelete operation.
|
// ResContainerDelete groups resulting values of ContainerDelete operation.
|
||||||
type ResContainerDelete struct {
|
type ResContainerDelete struct {
|
||||||
statusRes
|
statusRes
|
||||||
|
@ -456,10 +482,15 @@ func (c *Client) ContainerDelete(ctx context.Context, prm PrmContainerDelete) (*
|
||||||
|
|
||||||
// form meta header
|
// form meta header
|
||||||
var meta v2session.RequestMetaHeader
|
var meta v2session.RequestMetaHeader
|
||||||
|
|
||||||
prm.prmSession.writeToMetaHeader(&meta)
|
|
||||||
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
||||||
|
|
||||||
|
if prm.tokSet {
|
||||||
|
var tokv2 v2session.Token
|
||||||
|
prm.tok.WriteToV2(&tokv2)
|
||||||
|
|
||||||
|
meta.SetSessionToken(&tokv2)
|
||||||
|
}
|
||||||
|
|
||||||
// form request
|
// form request
|
||||||
var req v2container.DeleteRequest
|
var req v2container.DeleteRequest
|
||||||
|
|
||||||
|
@ -577,9 +608,16 @@ func (c *Client) ContainerEACL(ctx context.Context, prm PrmContainerEACL) (*ResC
|
||||||
|
|
||||||
table := eacl.NewTableFromV2(body.GetEACL())
|
table := eacl.NewTableFromV2(body.GetEACL())
|
||||||
|
|
||||||
table.SetSessionToken(
|
tokv2 := body.GetSessionToken()
|
||||||
session.NewTokenFromV2(body.GetSessionToken()),
|
if tokv2 != nil {
|
||||||
)
|
var tok session.Container
|
||||||
|
|
||||||
|
// FIXME: need to handle the error
|
||||||
|
err := tok.ReadFromV2(*tokv2)
|
||||||
|
if err == nil {
|
||||||
|
table.SetSessionToken(&tok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var sig *neofscrypto.Signature
|
var sig *neofscrypto.Signature
|
||||||
|
|
||||||
|
@ -674,9 +712,15 @@ func (c *Client) ContainerSetEACL(ctx context.Context, prm PrmContainerSetEACL)
|
||||||
|
|
||||||
// form meta header
|
// form meta header
|
||||||
var meta v2session.RequestMetaHeader
|
var meta v2session.RequestMetaHeader
|
||||||
meta.SetSessionToken(prm.table.SessionToken().ToV2())
|
|
||||||
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
||||||
|
|
||||||
|
if tok := prm.table.SessionToken(); tok != nil {
|
||||||
|
var tokv2 v2session.Token
|
||||||
|
tok.WriteToV2(&tokv2)
|
||||||
|
|
||||||
|
meta.SetSessionToken(&tokv2)
|
||||||
|
}
|
||||||
|
|
||||||
// form request
|
// form request
|
||||||
var req v2container.SetExtendedACLRequest
|
var req v2container.SetExtendedACLRequest
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,11 @@ type PrmObjectDelete struct {
|
||||||
// This may affect the execution of an operation (e.g. access control).
|
// This may affect the execution of an operation (e.g. access control).
|
||||||
//
|
//
|
||||||
// Must be signed.
|
// Must be signed.
|
||||||
func (x *PrmObjectDelete) WithinSession(t session.Token) {
|
func (x *PrmObjectDelete) WithinSession(t session.Object) {
|
||||||
x.meta.SetSessionToken(t.ToV2())
|
var tv2 v2session.Token
|
||||||
|
t.WriteToV2(&tv2)
|
||||||
|
|
||||||
|
x.meta.SetSessionToken(&tv2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithBearerToken attaches bearer token to be used for the operation.
|
// WithBearerToken attaches bearer token to be used for the operation.
|
||||||
|
|
|
@ -30,7 +30,7 @@ type prmObjectRead struct {
|
||||||
local bool
|
local bool
|
||||||
|
|
||||||
sessionSet bool
|
sessionSet bool
|
||||||
session session.Token
|
session session.Object
|
||||||
|
|
||||||
bearerSet bool
|
bearerSet bool
|
||||||
bearer bearer.Token
|
bearer bearer.Token
|
||||||
|
@ -54,7 +54,10 @@ func (x prmObjectRead) writeToMetaHeader(h *v2session.RequestMetaHeader) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if x.sessionSet {
|
if x.sessionSet {
|
||||||
h.SetSessionToken(x.session.ToV2())
|
var tokv2 v2session.Token
|
||||||
|
x.session.WriteToV2(&tokv2)
|
||||||
|
|
||||||
|
h.SetSessionToken(&tokv2)
|
||||||
}
|
}
|
||||||
|
|
||||||
x.prmCommonMeta.writeToMetaHeader(h)
|
x.prmCommonMeta.writeToMetaHeader(h)
|
||||||
|
@ -76,7 +79,7 @@ func (x *prmObjectRead) MarkLocal() {
|
||||||
// This may affect the execution of an operation (e.g. access control).
|
// This may affect the execution of an operation (e.g. access control).
|
||||||
//
|
//
|
||||||
// Must be signed.
|
// Must be signed.
|
||||||
func (x *prmObjectRead) WithinSession(t session.Token) {
|
func (x *prmObjectRead) WithinSession(t session.Object) {
|
||||||
x.session = t
|
x.session = t
|
||||||
x.sessionSet = true
|
x.sessionSet = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,11 @@ func (x *PrmObjectHash) MarkLocal() {
|
||||||
// This may affect the execution of an operation (e.g. access control).
|
// This may affect the execution of an operation (e.g. access control).
|
||||||
//
|
//
|
||||||
// Must be signed.
|
// Must be signed.
|
||||||
func (x *PrmObjectHash) WithinSession(t session.Token) {
|
func (x *PrmObjectHash) WithinSession(t session.Object) {
|
||||||
x.meta.SetSessionToken(t.ToV2())
|
var tv2 v2session.Token
|
||||||
|
t.WriteToV2(&tv2)
|
||||||
|
|
||||||
|
x.meta.SetSessionToken(&tv2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithBearerToken attaches bearer token to be used for the operation.
|
// WithBearerToken attaches bearer token to be used for the operation.
|
||||||
|
|
|
@ -80,8 +80,11 @@ func (x *ObjectWriter) WithBearerToken(t bearer.Token) {
|
||||||
|
|
||||||
// WithinSession specifies session within which object should be stored.
|
// WithinSession specifies session within which object should be stored.
|
||||||
// Should be called once before any writing steps.
|
// Should be called once before any writing steps.
|
||||||
func (x *ObjectWriter) WithinSession(t session.Token) {
|
func (x *ObjectWriter) WithinSession(t session.Object) {
|
||||||
x.metaHdr.SetSessionToken(t.ToV2())
|
var tv2 v2session.Token
|
||||||
|
t.WriteToV2(&tv2)
|
||||||
|
|
||||||
|
x.metaHdr.SetSessionToken(&tv2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkLocal tells the server to execute the operation locally.
|
// MarkLocal tells the server to execute the operation locally.
|
||||||
|
|
|
@ -28,7 +28,7 @@ type PrmObjectSearch struct {
|
||||||
local bool
|
local bool
|
||||||
|
|
||||||
sessionSet bool
|
sessionSet bool
|
||||||
session session.Token
|
session session.Object
|
||||||
|
|
||||||
bearerSet bool
|
bearerSet bool
|
||||||
bearer bearer.Token
|
bearer bearer.Token
|
||||||
|
@ -50,7 +50,7 @@ func (x *PrmObjectSearch) MarkLocal() {
|
||||||
// This may affect the execution of an operation (e.g. access control).
|
// This may affect the execution of an operation (e.g. access control).
|
||||||
//
|
//
|
||||||
// Must be signed.
|
// Must be signed.
|
||||||
func (x *PrmObjectSearch) WithinSession(t session.Token) {
|
func (x *PrmObjectSearch) WithinSession(t session.Object) {
|
||||||
x.session = t
|
x.session = t
|
||||||
x.sessionSet = true
|
x.sessionSet = true
|
||||||
}
|
}
|
||||||
|
@ -269,7 +269,10 @@ func (c *Client) ObjectSearchInit(ctx context.Context, prm PrmObjectSearch) (*Ob
|
||||||
}
|
}
|
||||||
|
|
||||||
if prm.sessionSet {
|
if prm.sessionSet {
|
||||||
meta.SetSessionToken(prm.session.ToV2())
|
var tokv2 v2session.Token
|
||||||
|
prm.session.WriteToV2(&tokv2)
|
||||||
|
|
||||||
|
meta.SetSessionToken(&tokv2)
|
||||||
}
|
}
|
||||||
|
|
||||||
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
prm.prmCommonMeta.writeToMetaHeader(&meta)
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
type Container struct {
|
type Container struct {
|
||||||
v2 container.Container
|
v2 container.Container
|
||||||
|
|
||||||
token *session.Token
|
token *session.Container
|
||||||
|
|
||||||
sig *neofscrypto.Signature
|
sig *neofscrypto.Signature
|
||||||
}
|
}
|
||||||
|
@ -172,13 +172,13 @@ func (c *Container) SetPlacementPolicy(v *netmap.PlacementPolicy) {
|
||||||
|
|
||||||
// SessionToken returns token of the session within
|
// SessionToken returns token of the session within
|
||||||
// which container was created.
|
// which container was created.
|
||||||
func (c Container) SessionToken() *session.Token {
|
func (c Container) SessionToken() *session.Container {
|
||||||
return c.token
|
return c.token
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSessionToken sets token of the session within
|
// SetSessionToken sets token of the session within
|
||||||
// which container was created.
|
// which container was created.
|
||||||
func (c *Container) SetSessionToken(t *session.Token) {
|
func (c *Container) SetSessionToken(t *session.Container) {
|
||||||
c.token = t
|
c.token = t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ func TestContainerEncoding(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainer_SessionToken(t *testing.T) {
|
func TestContainer_SessionToken(t *testing.T) {
|
||||||
tok := sessiontest.Token()
|
tok := sessiontest.Container()
|
||||||
|
|
||||||
cnr := container.New()
|
cnr := container.New()
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import (
|
||||||
type Table struct {
|
type Table struct {
|
||||||
version version.Version
|
version version.Version
|
||||||
cid *cid.ID
|
cid *cid.ID
|
||||||
token *session.Token
|
token *session.Container
|
||||||
sig *neofscrypto.Signature
|
sig *neofscrypto.Signature
|
||||||
records []Record
|
records []Record
|
||||||
}
|
}
|
||||||
|
@ -63,13 +63,13 @@ func (t *Table) AddRecord(r *Record) {
|
||||||
|
|
||||||
// SessionToken returns token of the session
|
// SessionToken returns token of the session
|
||||||
// within which Table was set.
|
// within which Table was set.
|
||||||
func (t Table) SessionToken() *session.Token {
|
func (t Table) SessionToken() *session.Container {
|
||||||
return t.token
|
return t.token
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSessionToken sets token of the session
|
// SetSessionToken sets token of the session
|
||||||
// within which Table was set.
|
// within which Table was set.
|
||||||
func (t *Table) SetSessionToken(tok *session.Token) {
|
func (t *Table) SetSessionToken(tok *session.Container) {
|
||||||
t.token = tok
|
t.token = tok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,7 @@ func TestTableEncoding(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTable_SessionToken(t *testing.T) {
|
func TestTable_SessionToken(t *testing.T) {
|
||||||
tok := sessiontest.Token()
|
tok := sessiontest.Container()
|
||||||
|
|
||||||
table := eacl.NewTable()
|
table := eacl.NewTable()
|
||||||
table.SetSessionToken(tok)
|
table.SetSessionToken(tok)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
"github.com/nspcc-dev/neofs-api-go/v2/object"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
"github.com/nspcc-dev/neofs-sdk-go/checksum"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||||
|
@ -519,19 +520,31 @@ func (o *Object) resetRelations() {
|
||||||
|
|
||||||
// SessionToken returns token of the session
|
// SessionToken returns token of the session
|
||||||
// within which object was created.
|
// within which object was created.
|
||||||
func (o *Object) SessionToken() *session.Token {
|
func (o *Object) SessionToken() *session.Object {
|
||||||
return session.NewTokenFromV2(
|
tokv2 := (*object.Object)(o).GetHeader().GetSessionToken()
|
||||||
(*object.Object)(o).
|
if tokv2 == nil {
|
||||||
GetHeader().
|
return nil
|
||||||
GetSessionToken(),
|
}
|
||||||
)
|
|
||||||
|
var res session.Object
|
||||||
|
|
||||||
|
fmt.Println(res.ReadFromV2(*tokv2))
|
||||||
|
|
||||||
|
return &res
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSessionToken sets token of the session
|
// SetSessionToken sets token of the session
|
||||||
// within which object was created.
|
// within which object was created.
|
||||||
func (o *Object) SetSessionToken(v *session.Token) {
|
func (o *Object) SetSessionToken(v *session.Object) {
|
||||||
o.setHeaderField(func(h *object.Header) {
|
o.setHeaderField(func(h *object.Header) {
|
||||||
h.SetSessionToken(v.ToV2())
|
var tokv2 *v2session.Token
|
||||||
|
|
||||||
|
if v != nil {
|
||||||
|
tokv2 = new(v2session.Token)
|
||||||
|
v.WriteToV2(tokv2)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.SetSessionToken(tokv2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -213,7 +213,7 @@ func TestObject_ToV2(t *testing.T) {
|
||||||
func TestObject_SetSessionToken(t *testing.T) {
|
func TestObject_SetSessionToken(t *testing.T) {
|
||||||
obj := New()
|
obj := New()
|
||||||
|
|
||||||
tok := sessiontest.Token()
|
tok := sessiontest.ObjectSigned()
|
||||||
|
|
||||||
obj.SetSessionToken(tok)
|
obj.SetSessionToken(tok)
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ func generate(withParent bool) *object.Object {
|
||||||
ver := version.Current()
|
ver := version.Current()
|
||||||
|
|
||||||
x.SetID(oidtest.ID())
|
x.SetID(oidtest.ID())
|
||||||
x.SetSessionToken(sessiontest.Token())
|
x.SetSessionToken(sessiontest.Object())
|
||||||
x.SetPayload([]byte{1, 2, 3})
|
x.SetPayload([]byte{1, 2, 3})
|
||||||
x.SetOwnerID(usertest.ID())
|
x.SetOwnerID(usertest.ID())
|
||||||
x.SetContainerID(cidtest.ID())
|
x.SetContainerID(cidtest.ID())
|
||||||
|
|
|
@ -14,7 +14,7 @@ type sessionCache struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type cacheValue struct {
|
type cacheValue struct {
|
||||||
token *session.Token
|
token session.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCache() (*sessionCache, error) {
|
func newCache() (*sessionCache, error) {
|
||||||
|
@ -29,28 +29,22 @@ func newCache() (*sessionCache, error) {
|
||||||
// Get returns a copy of the session token from the cache without signature
|
// Get returns a copy of the session token from the cache without signature
|
||||||
// and context related fields. Returns nil if token is missing in the cache.
|
// and context related fields. Returns nil if token is missing in the cache.
|
||||||
// It is safe to modify and re-sign returned session token.
|
// It is safe to modify and re-sign returned session token.
|
||||||
func (c *sessionCache) Get(key string) *session.Token {
|
func (c *sessionCache) Get(key string) (session.Object, bool) {
|
||||||
valueRaw, ok := c.cache.Get(key)
|
valueRaw, ok := c.cache.Get(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil
|
return session.Object{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
value := valueRaw.(*cacheValue)
|
value := valueRaw.(*cacheValue)
|
||||||
if c.expired(value) {
|
if c.expired(value) {
|
||||||
c.cache.Remove(key)
|
c.cache.Remove(key)
|
||||||
return nil
|
return session.Object{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if value.token == nil {
|
return value.token, true
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := copySessionTokenWithoutSignatureAndContext(*value.token)
|
|
||||||
|
|
||||||
return &res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *sessionCache) Put(key string, token *session.Token) bool {
|
func (c *sessionCache) Put(key string, token session.Object) bool {
|
||||||
return c.cache.Add(key, &cacheValue{
|
return c.cache.Add(key, &cacheValue{
|
||||||
token: token,
|
token: token,
|
||||||
})
|
})
|
||||||
|
@ -73,5 +67,5 @@ func (c *sessionCache) updateEpoch(newEpoch uint64) {
|
||||||
|
|
||||||
func (c *sessionCache) expired(val *cacheValue) bool {
|
func (c *sessionCache) expired(val *cacheValue) bool {
|
||||||
epoch := atomic.LoadUint64(&c.currentEpoch)
|
epoch := atomic.LoadUint64(&c.currentEpoch)
|
||||||
return val.token.Exp() <= epoch
|
return val.token.ExpiredAt(epoch)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,29 +11,27 @@ import (
|
||||||
|
|
||||||
func TestSessionCache_GetUnmodifiedToken(t *testing.T) {
|
func TestSessionCache_GetUnmodifiedToken(t *testing.T) {
|
||||||
const key = "Foo"
|
const key = "Foo"
|
||||||
target := sessiontest.Token()
|
target := *sessiontest.Object()
|
||||||
|
|
||||||
pk, err := keys.NewPrivateKey()
|
pk, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
check := func(t *testing.T, tok *session.Token, extra string) {
|
check := func(t *testing.T, tok session.Object, extra string) {
|
||||||
require.False(t, tok.VerifySignature(), extra)
|
require.False(t, tok.VerifySignature(), extra)
|
||||||
require.Nil(t, tok.Context(), extra)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cache, err := newCache()
|
cache, err := newCache()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
cache.Put(key, target)
|
cache.Put(key, target)
|
||||||
value := cache.Get(key)
|
value, ok := cache.Get(key)
|
||||||
|
require.True(t, ok)
|
||||||
check(t, value, "before sign")
|
check(t, value, "before sign")
|
||||||
|
|
||||||
err = value.Sign(&pk.PrivateKey)
|
err = value.Sign(pk.PrivateKey)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
octx := sessiontest.ObjectContext()
|
value, ok = cache.Get(key)
|
||||||
value.SetContext(octx)
|
require.True(t, ok)
|
||||||
|
|
||||||
value = cache.Get(key)
|
|
||||||
check(t, value, "after sign")
|
check(t, value, "after sign")
|
||||||
}
|
}
|
||||||
|
|
126
pool/pool.go
126
pool/pool.go
|
@ -14,13 +14,14 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
sessionv2 "github.com/nspcc-dev/neofs-api-go/v2/session"
|
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
"github.com/nspcc-dev/neofs-sdk-go/accounting"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
"github.com/nspcc-dev/neofs-sdk-go/bearer"
|
||||||
sdkClient "github.com/nspcc-dev/neofs-sdk-go/client"
|
sdkClient "github.com/nspcc-dev/neofs-sdk-go/client"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||||
|
@ -155,7 +156,7 @@ func (c *clientWrapper) containerDelete(ctx context.Context, prm PrmContainerDel
|
||||||
var cliPrm sdkClient.PrmContainerDelete
|
var cliPrm sdkClient.PrmContainerDelete
|
||||||
cliPrm.SetContainer(prm.cnrID)
|
cliPrm.SetContainer(prm.cnrID)
|
||||||
if prm.stokenSet {
|
if prm.stokenSet {
|
||||||
cliPrm.SetSessionToken(prm.stoken)
|
cliPrm.WithinSession(prm.stoken)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.client.ContainerDelete(ctx, cliPrm); err != nil {
|
if _, err := c.client.ContainerDelete(ctx, cliPrm); err != nil {
|
||||||
|
@ -607,26 +608,26 @@ type clientPack struct {
|
||||||
|
|
||||||
type prmContext struct {
|
type prmContext struct {
|
||||||
defaultSession bool
|
defaultSession bool
|
||||||
verb sessionv2.ObjectSessionVerb
|
verb session.ObjectVerb
|
||||||
addr *address.Address
|
addr address.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *prmContext) useDefaultSession() {
|
func (x *prmContext) useDefaultSession() {
|
||||||
x.defaultSession = true
|
x.defaultSession = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *prmContext) useAddress(addr *address.Address) {
|
func (x *prmContext) useAddress(addr address.Address) {
|
||||||
x.addr = addr
|
x.addr = addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *prmContext) useVerb(verb sessionv2.ObjectSessionVerb) {
|
func (x *prmContext) useVerb(verb session.ObjectVerb) {
|
||||||
x.verb = verb
|
x.verb = verb
|
||||||
}
|
}
|
||||||
|
|
||||||
type prmCommon struct {
|
type prmCommon struct {
|
||||||
key *ecdsa.PrivateKey
|
key *ecdsa.PrivateKey
|
||||||
btoken *bearer.Token
|
btoken *bearer.Token
|
||||||
stoken *session.Token
|
stoken *session.Object
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseKey specifies private key to sign the requests.
|
// UseKey specifies private key to sign the requests.
|
||||||
|
@ -641,7 +642,7 @@ func (x *prmCommon) UseBearer(token bearer.Token) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseSession specifies session within which operation should be performed.
|
// UseSession specifies session within which operation should be performed.
|
||||||
func (x *prmCommon) UseSession(token session.Token) {
|
func (x *prmCommon) UseSession(token session.Object) {
|
||||||
x.stoken = &token
|
x.stoken = &token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,7 +788,7 @@ func (x *PrmContainerList) SetOwnerID(ownerID user.ID) {
|
||||||
type PrmContainerDelete struct {
|
type PrmContainerDelete struct {
|
||||||
cnrID cid.ID
|
cnrID cid.ID
|
||||||
|
|
||||||
stoken session.Token
|
stoken session.Container
|
||||||
stokenSet bool
|
stokenSet bool
|
||||||
|
|
||||||
waitParams WaitParams
|
waitParams WaitParams
|
||||||
|
@ -800,7 +801,7 @@ func (x *PrmContainerDelete) SetContainerID(cnrID cid.ID) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetSessionToken specifies session within which operation should be performed.
|
// SetSessionToken specifies session within which operation should be performed.
|
||||||
func (x *PrmContainerDelete) SetSessionToken(token session.Token) {
|
func (x *PrmContainerDelete) SetSessionToken(token session.Container) {
|
||||||
x.stoken = token
|
x.stoken = token
|
||||||
x.stokenSet = true
|
x.stokenSet = true
|
||||||
}
|
}
|
||||||
|
@ -990,7 +991,7 @@ func (p *Pool) Dial(ctx context.Context) error {
|
||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
healthy, atLeastOneHealthy = true, true
|
healthy, atLeastOneHealthy = true, true
|
||||||
_ = p.cache.Put(formCacheKey(addr, p.key), st)
|
_ = p.cache.Put(formCacheKey(addr, p.key), *st)
|
||||||
}
|
}
|
||||||
clientPacks[j] = &clientPack{client: c, healthy: healthy, address: addr}
|
clientPacks[j] = &clientPack{client: c, healthy: healthy, address: addr}
|
||||||
}
|
}
|
||||||
|
@ -1229,7 +1230,7 @@ func (p *Pool) checkSessionTokenErr(err error, address string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.ID, dur uint64) (*session.Token, error) {
|
func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.ID, dur uint64) (*session.Object, error) {
|
||||||
ni, err := c.networkInfo(ctx, prmNetworkInfo{})
|
ni, err := c.networkInfo(ctx, prmNetworkInfo{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1251,7 +1252,26 @@ func createSessionTokenForDuration(ctx context.Context, c client, ownerID user.I
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionTokenForOwner(ownerID, res, exp), nil
|
var id uuid.UUID
|
||||||
|
|
||||||
|
err = id.UnmarshalBinary(res.id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid session token ID: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var key neofsecdsa.PublicKey
|
||||||
|
|
||||||
|
err = key.Decode(res.sessionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid public session key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var st session.Object
|
||||||
|
st.SetID(id)
|
||||||
|
st.SetAuthKey(&key)
|
||||||
|
st.SetExp(exp)
|
||||||
|
|
||||||
|
return &st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type callContext struct {
|
type callContext struct {
|
||||||
|
@ -1268,8 +1288,9 @@ type callContext struct {
|
||||||
|
|
||||||
// flag to open default session if session token is missing
|
// flag to open default session if session token is missing
|
||||||
sessionDefault bool
|
sessionDefault bool
|
||||||
sessionTarget func(session.Token)
|
sessionTarget func(session.Object)
|
||||||
sessionContext *session.ObjectContext
|
sessionVerb session.ObjectVerb
|
||||||
|
sessionAddr address.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContext) error {
|
func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContext) error {
|
||||||
|
@ -1294,9 +1315,8 @@ func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContex
|
||||||
// note that we don't override session provided by the caller
|
// note that we don't override session provided by the caller
|
||||||
ctx.sessionDefault = cfg.stoken == nil && prmCtx.defaultSession
|
ctx.sessionDefault = cfg.stoken == nil && prmCtx.defaultSession
|
||||||
if ctx.sessionDefault {
|
if ctx.sessionDefault {
|
||||||
ctx.sessionContext = session.NewObjectContext()
|
ctx.sessionVerb = prmCtx.verb
|
||||||
ctx.sessionContext.ToV2().SetVerb(prmCtx.verb)
|
ctx.sessionAddr = prmCtx.addr
|
||||||
ctx.sessionContext.ApplyTo(prmCtx.addr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -1307,32 +1327,32 @@ func (p *Pool) initCallContext(ctx *callContext, cfg prmCommon, prmCtx prmContex
|
||||||
func (p *Pool) openDefaultSession(ctx *callContext) error {
|
func (p *Pool) openDefaultSession(ctx *callContext) error {
|
||||||
cacheKey := formCacheKey(ctx.endpoint, ctx.key)
|
cacheKey := formCacheKey(ctx.endpoint, ctx.key)
|
||||||
|
|
||||||
tok := p.cache.Get(cacheKey)
|
tok, ok := p.cache.Get(cacheKey)
|
||||||
if tok == nil {
|
if !ok {
|
||||||
var err error
|
var err error
|
||||||
var sessionOwner user.ID
|
var sessionOwner user.ID
|
||||||
|
|
||||||
user.IDFromKey(&sessionOwner, ctx.key.PublicKey)
|
user.IDFromKey(&sessionOwner, ctx.key.PublicKey)
|
||||||
|
|
||||||
// open new session
|
// open new session
|
||||||
tok, err = createSessionTokenForDuration(ctx, ctx.client, sessionOwner, p.stokenDuration)
|
t, err := createSessionTokenForDuration(ctx, ctx.client, sessionOwner, p.stokenDuration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("session API client: %w", err)
|
return fmt.Errorf("session API client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// cache the opened session
|
// cache the opened session
|
||||||
p.cache.Put(cacheKey, tok)
|
p.cache.Put(cacheKey, *t)
|
||||||
}
|
}
|
||||||
|
|
||||||
tokToSign := *tok
|
tok.ForVerb(ctx.sessionVerb)
|
||||||
tokToSign.SetContext(ctx.sessionContext)
|
tok.ApplyTo(ctx.sessionAddr)
|
||||||
|
|
||||||
// sign the token
|
// sign the token
|
||||||
if err := tokToSign.Sign(ctx.key); err != nil {
|
if err := tok.Sign(*ctx.key); err != nil {
|
||||||
return fmt.Errorf("sign token of the opened session: %w", err)
|
return fmt.Errorf("sign token of the opened session: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.sessionTarget(tokToSign)
|
ctx.sessionTarget(tok)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1371,8 +1391,8 @@ func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (*oid.ID, error)
|
||||||
|
|
||||||
var prmCtx prmContext
|
var prmCtx prmContext
|
||||||
prmCtx.useDefaultSession()
|
prmCtx.useDefaultSession()
|
||||||
prmCtx.useVerb(sessionv2.ObjectVerbPut)
|
prmCtx.useVerb(session.VerbObjectPut)
|
||||||
prmCtx.useAddress(newAddressFromCnrID(cIDp))
|
prmCtx.useAddress(*newAddressFromCnrID(cIDp))
|
||||||
|
|
||||||
p.fillAppropriateKey(&prm.prmCommon)
|
p.fillAppropriateKey(&prm.prmCommon)
|
||||||
|
|
||||||
|
@ -1408,8 +1428,8 @@ func (p *Pool) PutObject(ctx context.Context, prm PrmObjectPut) (*oid.ID, error)
|
||||||
func (p *Pool) DeleteObject(ctx context.Context, prm PrmObjectDelete) error {
|
func (p *Pool) DeleteObject(ctx context.Context, prm PrmObjectDelete) error {
|
||||||
var prmCtx prmContext
|
var prmCtx prmContext
|
||||||
prmCtx.useDefaultSession()
|
prmCtx.useDefaultSession()
|
||||||
prmCtx.useVerb(sessionv2.ObjectVerbDelete)
|
prmCtx.useVerb(session.VerbObjectDelete)
|
||||||
prmCtx.useAddress(&prm.addr)
|
prmCtx.useAddress(prm.addr)
|
||||||
|
|
||||||
p.fillAppropriateKey(&prm.prmCommon)
|
p.fillAppropriateKey(&prm.prmCommon)
|
||||||
|
|
||||||
|
@ -1456,8 +1476,8 @@ type ResGetObject struct {
|
||||||
func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject, error) {
|
func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject, error) {
|
||||||
var prmCtx prmContext
|
var prmCtx prmContext
|
||||||
prmCtx.useDefaultSession()
|
prmCtx.useDefaultSession()
|
||||||
prmCtx.useVerb(sessionv2.ObjectVerbGet)
|
prmCtx.useVerb(session.VerbObjectGet)
|
||||||
prmCtx.useAddress(&prm.addr)
|
prmCtx.useAddress(prm.addr)
|
||||||
|
|
||||||
p.fillAppropriateKey(&prm.prmCommon)
|
p.fillAppropriateKey(&prm.prmCommon)
|
||||||
|
|
||||||
|
@ -1481,8 +1501,8 @@ func (p *Pool) GetObject(ctx context.Context, prm PrmObjectGet) (*ResGetObject,
|
||||||
func (p *Pool) HeadObject(ctx context.Context, prm PrmObjectHead) (*object.Object, error) {
|
func (p *Pool) HeadObject(ctx context.Context, prm PrmObjectHead) (*object.Object, error) {
|
||||||
var prmCtx prmContext
|
var prmCtx prmContext
|
||||||
prmCtx.useDefaultSession()
|
prmCtx.useDefaultSession()
|
||||||
prmCtx.useVerb(sessionv2.ObjectVerbHead)
|
prmCtx.useVerb(session.VerbObjectHead)
|
||||||
prmCtx.useAddress(&prm.addr)
|
prmCtx.useAddress(prm.addr)
|
||||||
|
|
||||||
p.fillAppropriateKey(&prm.prmCommon)
|
p.fillAppropriateKey(&prm.prmCommon)
|
||||||
|
|
||||||
|
@ -1529,8 +1549,8 @@ func (x *ResObjectRange) Close() error {
|
||||||
func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (*ResObjectRange, error) {
|
func (p *Pool) ObjectRange(ctx context.Context, prm PrmObjectRange) (*ResObjectRange, error) {
|
||||||
var prmCtx prmContext
|
var prmCtx prmContext
|
||||||
prmCtx.useDefaultSession()
|
prmCtx.useDefaultSession()
|
||||||
prmCtx.useVerb(sessionv2.ObjectVerbRange)
|
prmCtx.useVerb(session.VerbObjectRange)
|
||||||
prmCtx.useAddress(&prm.addr)
|
prmCtx.useAddress(prm.addr)
|
||||||
|
|
||||||
p.fillAppropriateKey(&prm.prmCommon)
|
p.fillAppropriateKey(&prm.prmCommon)
|
||||||
|
|
||||||
|
@ -1595,8 +1615,8 @@ func (x *ResObjectSearch) Close() {
|
||||||
func (p *Pool) SearchObjects(ctx context.Context, prm PrmObjectSearch) (*ResObjectSearch, error) {
|
func (p *Pool) SearchObjects(ctx context.Context, prm PrmObjectSearch) (*ResObjectSearch, error) {
|
||||||
var prmCtx prmContext
|
var prmCtx prmContext
|
||||||
prmCtx.useDefaultSession()
|
prmCtx.useDefaultSession()
|
||||||
prmCtx.useVerb(sessionv2.ObjectVerbSearch)
|
prmCtx.useVerb(session.VerbObjectSearch)
|
||||||
prmCtx.useAddress(newAddressFromCnrID(&prm.cnrID))
|
prmCtx.useAddress(*newAddressFromCnrID(&prm.cnrID))
|
||||||
|
|
||||||
p.fillAppropriateKey(&prm.prmCommon)
|
p.fillAppropriateKey(&prm.prmCommon)
|
||||||
|
|
||||||
|
@ -1788,17 +1808,6 @@ func (p *Pool) Close() {
|
||||||
<-p.closedCh
|
<-p.closedCh
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates new session token with specified owner from SessionCreate call result.
|
|
||||||
func sessionTokenForOwner(id user.ID, cliRes *resCreateSession, exp uint64) *session.Token {
|
|
||||||
st := session.NewToken()
|
|
||||||
st.SetOwnerID(&id)
|
|
||||||
st.SetID(cliRes.id)
|
|
||||||
st.SetSessionKey(cliRes.sessionKey)
|
|
||||||
st.SetExp(exp)
|
|
||||||
|
|
||||||
return st
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAddressFromCnrID(cnrID *cid.ID) *address.Address {
|
func newAddressFromCnrID(cnrID *cid.ID) *address.Address {
|
||||||
addr := address.NewAddress()
|
addr := address.NewAddress()
|
||||||
if cnrID != nil {
|
if cnrID != nil {
|
||||||
|
@ -1806,22 +1815,3 @@ func newAddressFromCnrID(cnrID *cid.ID) *address.Address {
|
||||||
}
|
}
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func copySessionTokenWithoutSignatureAndContext(from session.Token) (to session.Token) {
|
|
||||||
to.SetIat(from.Iat())
|
|
||||||
to.SetExp(from.Exp())
|
|
||||||
to.SetNbf(from.Nbf())
|
|
||||||
|
|
||||||
sessionTokenID := make([]byte, len(from.ID()))
|
|
||||||
copy(sessionTokenID, from.ID())
|
|
||||||
to.SetID(sessionTokenID)
|
|
||||||
|
|
||||||
sessionTokenKey := make([]byte, len(from.SessionKey()))
|
|
||||||
copy(sessionTokenKey, from.SessionKey())
|
|
||||||
to.SetSessionKey(sessionTokenKey)
|
|
||||||
|
|
||||||
sessionTokenOwner := *from.OwnerID()
|
|
||||||
to.SetOwnerID(&sessionTokenOwner)
|
|
||||||
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
"github.com/nspcc-dev/neofs-sdk-go/netmap"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||||
|
@ -73,20 +74,30 @@ func newPrivateKey(t *testing.T) *ecdsa.PrivateKey {
|
||||||
return &p.PrivateKey
|
return &p.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newBinPublicKey(t *testing.T) []byte {
|
||||||
|
authKey := neofsecdsa.PublicKey(newPrivateKey(t).PublicKey)
|
||||||
|
|
||||||
|
bKey := make([]byte, authKey.MaxEncodedSize())
|
||||||
|
bKey = bKey[:authKey.Encode(bKey)]
|
||||||
|
|
||||||
|
return bKey
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildPoolOneNodeFailed(t *testing.T) {
|
func TestBuildPoolOneNodeFailed(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
ctrl2 := gomock.NewController(t)
|
ctrl2 := gomock.NewController(t)
|
||||||
|
|
||||||
var expectedToken *session.Token
|
var expectedToken *session.Object
|
||||||
clientCount := -1
|
clientCount := -1
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
clientCount++
|
clientCount++
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := newToken(t)
|
tok := newToken(t)
|
||||||
|
id := tok.ID()
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
sessionKey: tok.SessionKey(),
|
id: id[:],
|
||||||
id: tok.ID(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, nil
|
}, nil
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
|
|
||||||
|
@ -96,9 +107,10 @@ func TestBuildPoolOneNodeFailed(t *testing.T) {
|
||||||
mockClient2 := NewMockClient(ctrl2)
|
mockClient2 := NewMockClient(ctrl2)
|
||||||
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
expectedToken = newToken(t)
|
expectedToken = newToken(t)
|
||||||
|
id := expectedToken.ID()
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
sessionKey: expectedToken.SessionKey(),
|
id: id[:],
|
||||||
id: expectedToken.ID(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, nil
|
}, nil
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
|
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
|
||||||
|
@ -134,8 +146,8 @@ func TestBuildPoolOneNodeFailed(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
st := clientPool.cache.Get(formCacheKey(cp.address, clientPool.key))
|
st, _ := clientPool.cache.Get(formCacheKey(cp.address, clientPool.key))
|
||||||
return areEqualTokens(st, expectedToken)
|
return areEqualTokens(&st, expectedToken)
|
||||||
}
|
}
|
||||||
require.Never(t, condition, 900*time.Millisecond, 100*time.Millisecond)
|
require.Never(t, condition, 900*time.Millisecond, 100*time.Millisecond)
|
||||||
require.Eventually(t, condition, 3*time.Second, 300*time.Millisecond)
|
require.Eventually(t, condition, 3*time.Second, 300*time.Millisecond)
|
||||||
|
@ -152,14 +164,14 @@ func TestBuildPoolZeroNodes(t *testing.T) {
|
||||||
func TestOneNode(t *testing.T) {
|
func TestOneNode(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
tok := session.NewToken()
|
uid := uuid.New()
|
||||||
uid, err := uuid.New().MarshalBinary()
|
|
||||||
require.NoError(t, err)
|
var tok session.Object
|
||||||
tok.SetID(uid)
|
tok.SetID(uid)
|
||||||
|
|
||||||
tokRes := &resCreateSession{
|
tokRes := &resCreateSession{
|
||||||
id: tok.ID(),
|
id: uid[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}
|
}
|
||||||
|
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
|
@ -184,34 +196,34 @@ func TestOneNode(t *testing.T) {
|
||||||
|
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.True(t, areEqualTokens(tok, st))
|
require.True(t, areEqualTokens(&tok, &st))
|
||||||
}
|
}
|
||||||
|
|
||||||
func areEqualTokens(t1, t2 *session.Token) bool {
|
func areEqualTokens(t1, t2 *session.Object) bool {
|
||||||
if t1 == nil || t2 == nil {
|
if t1 == nil || t2 == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return bytes.Equal(t1.ID(), t2.ID()) &&
|
|
||||||
bytes.Equal(t1.SessionKey(), t2.SessionKey())
|
id1, id2 := t1.ID(), t2.ID()
|
||||||
|
return bytes.Equal(id1[:], id2[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTwoNodes(t *testing.T) {
|
func TestTwoNodes(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
var tokens []*session.Token
|
var tokens []*session.Object
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := session.NewToken()
|
var tok session.Object
|
||||||
uid, err := uuid.New().MarshalBinary()
|
uid := uuid.New()
|
||||||
require.NoError(t, err)
|
|
||||||
tok.SetID(uid)
|
tok.SetID(uid)
|
||||||
tokens = append(tokens, tok)
|
tokens = append(tokens, &tok)
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: uid[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, err
|
}, nil
|
||||||
})
|
})
|
||||||
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
|
||||||
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
||||||
|
@ -235,11 +247,11 @@ func TestTwoNodes(t *testing.T) {
|
||||||
|
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.True(t, containsTokens(tokens, st))
|
require.True(t, containsTokens(tokens, &st))
|
||||||
}
|
}
|
||||||
|
|
||||||
func containsTokens(list []*session.Token, item *session.Token) bool {
|
func containsTokens(list []*session.Object, item *session.Object) bool {
|
||||||
for _, tok := range list {
|
for _, tok := range list {
|
||||||
if areEqualTokens(tok, item) {
|
if areEqualTokens(tok, item) {
|
||||||
return true
|
return true
|
||||||
|
@ -252,7 +264,7 @@ func TestOneOfTwoFailed(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
ctrl2 := gomock.NewController(t)
|
ctrl2 := gomock.NewController(t)
|
||||||
|
|
||||||
var tokens []*session.Token
|
var tokens []*session.Object
|
||||||
clientCount := -1
|
clientCount := -1
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
clientCount++
|
clientCount++
|
||||||
|
@ -260,9 +272,10 @@ func TestOneOfTwoFailed(t *testing.T) {
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := newToken(t)
|
tok := newToken(t)
|
||||||
tokens = append(tokens, tok)
|
tokens = append(tokens, tok)
|
||||||
|
id := tok.ID()
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: id[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, nil
|
}, nil
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
|
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes()
|
||||||
|
@ -272,9 +285,10 @@ func TestOneOfTwoFailed(t *testing.T) {
|
||||||
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := newToken(t)
|
tok := newToken(t)
|
||||||
tokens = append(tokens, tok)
|
tokens = append(tokens, tok)
|
||||||
|
id := tok.ID()
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: id[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, nil
|
}, nil
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).DoAndReturn(func(_ interface{}, _ ...interface{}) (*netmap.NodeInfo, error) {
|
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).DoAndReturn(func(_ interface{}, _ ...interface{}) (*netmap.NodeInfo, error) {
|
||||||
|
@ -313,8 +327,8 @@ func TestOneOfTwoFailed(t *testing.T) {
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.True(t, areEqualTokens(tokens[0], st))
|
require.True(t, areEqualTokens(tokens[0], &st))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -323,7 +337,10 @@ func TestTwoFailed(t *testing.T) {
|
||||||
|
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{}, nil).AnyTimes()
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{
|
||||||
|
id: uuid.Nil[:],
|
||||||
|
sessionKey: newBinPublicKey(t),
|
||||||
|
}, nil).AnyTimes()
|
||||||
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
|
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
|
||||||
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
||||||
return mockClient, nil
|
return mockClient, nil
|
||||||
|
@ -356,19 +373,18 @@ func TestTwoFailed(t *testing.T) {
|
||||||
func TestSessionCache(t *testing.T) {
|
func TestSessionCache(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
var tokens []*session.Token
|
var tokens []*session.Object
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*resCreateSession, error) {
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}, _ ...interface{}) (*resCreateSession, error) {
|
||||||
tok := session.NewToken()
|
var tok session.Object
|
||||||
uid, err := uuid.New().MarshalBinary()
|
uid := uuid.New()
|
||||||
require.NoError(t, err)
|
|
||||||
tok.SetID(uid)
|
tok.SetID(uid)
|
||||||
tokens = append(tokens, tok)
|
tokens = append(tokens, &tok)
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: uid[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, err
|
}, nil
|
||||||
}).MaxTimes(3)
|
}).MaxTimes(3)
|
||||||
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
||||||
|
|
||||||
|
@ -399,12 +415,12 @@ func TestSessionCache(t *testing.T) {
|
||||||
// cache must contain session token
|
// cache must contain session token
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.True(t, containsTokens(tokens, st))
|
require.True(t, containsTokens(tokens, &st))
|
||||||
|
|
||||||
var prm PrmObjectGet
|
var prm PrmObjectGet
|
||||||
prm.SetAddress(address.Address{})
|
prm.SetAddress(address.Address{})
|
||||||
prm.UseSession(*session.NewToken())
|
prm.UseSession(session.Object{})
|
||||||
|
|
||||||
_, err = pool.GetObject(ctx, prm)
|
_, err = pool.GetObject(ctx, prm)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
@ -412,8 +428,8 @@ func TestSessionCache(t *testing.T) {
|
||||||
// cache must not contain session token
|
// cache must not contain session token
|
||||||
cp, err = pool.connection()
|
cp, err = pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st = pool.cache.Get(formCacheKey(cp.address, pool.key))
|
_, ok := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.Nil(t, st)
|
require.False(t, ok)
|
||||||
|
|
||||||
var prm2 PrmObjectPut
|
var prm2 PrmObjectPut
|
||||||
prm2.SetHeader(object.Object{})
|
prm2.SetHeader(object.Object{})
|
||||||
|
@ -424,23 +440,24 @@ func TestSessionCache(t *testing.T) {
|
||||||
// cache must contain session token
|
// cache must contain session token
|
||||||
cp, err = pool.connection()
|
cp, err = pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st = pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ = pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.True(t, containsTokens(tokens, st))
|
require.True(t, containsTokens(tokens, &st))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPriority(t *testing.T) {
|
func TestPriority(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
ctrl2 := gomock.NewController(t)
|
ctrl2 := gomock.NewController(t)
|
||||||
|
|
||||||
tokens := make([]*session.Token, 2)
|
tokens := make([]*session.Object, 2)
|
||||||
clientBuilder := func(endpoint string) (client, error) {
|
clientBuilder := func(endpoint string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := newToken(t)
|
tok := newToken(t)
|
||||||
tokens[0] = tok
|
tokens[0] = tok
|
||||||
|
id := tok.ID()
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: id[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, nil
|
}, nil
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
|
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(nil, fmt.Errorf("error")).AnyTimes()
|
||||||
|
@ -450,9 +467,10 @@ func TestPriority(t *testing.T) {
|
||||||
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient2.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := newToken(t)
|
tok := newToken(t)
|
||||||
tokens[1] = tok
|
tokens[1] = tok
|
||||||
|
id := tok.ID()
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: id[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, nil
|
}, nil
|
||||||
}).AnyTimes()
|
}).AnyTimes()
|
||||||
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
|
mockClient2.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
|
||||||
|
@ -486,14 +504,14 @@ func TestPriority(t *testing.T) {
|
||||||
firstNode := func() bool {
|
firstNode := func() bool {
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
return areEqualTokens(st, tokens[0])
|
return areEqualTokens(&st, tokens[0])
|
||||||
}
|
}
|
||||||
secondNode := func() bool {
|
secondNode := func() bool {
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
return areEqualTokens(st, tokens[1])
|
return areEqualTokens(&st, tokens[1])
|
||||||
}
|
}
|
||||||
require.Never(t, secondNode, time.Second, 200*time.Millisecond)
|
require.Never(t, secondNode, time.Second, 200*time.Millisecond)
|
||||||
|
|
||||||
|
@ -504,19 +522,18 @@ func TestPriority(t *testing.T) {
|
||||||
func TestSessionCacheWithKey(t *testing.T) {
|
func TestSessionCacheWithKey(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
|
|
||||||
var tokens []*session.Token
|
var tokens []*session.Object
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).DoAndReturn(func(_, _ interface{}) (*resCreateSession, error) {
|
||||||
tok := session.NewToken()
|
var tok session.Object
|
||||||
uid, err := uuid.New().MarshalBinary()
|
uid := uuid.New()
|
||||||
require.NoError(t, err)
|
|
||||||
tok.SetID(uid)
|
tok.SetID(uid)
|
||||||
tokens = append(tokens, tok)
|
tokens = append(tokens, &tok)
|
||||||
return &resCreateSession{
|
return &resCreateSession{
|
||||||
id: tok.ID(),
|
id: uid[:],
|
||||||
sessionKey: tok.SessionKey(),
|
sessionKey: newBinPublicKey(t),
|
||||||
}, err
|
}, nil
|
||||||
}).MaxTimes(2)
|
}).MaxTimes(2)
|
||||||
|
|
||||||
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
||||||
|
@ -545,8 +562,8 @@ func TestSessionCacheWithKey(t *testing.T) {
|
||||||
// cache must contain session token
|
// cache must contain session token
|
||||||
cp, err := pool.connection()
|
cp, err := pool.connection()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
st := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
st, _ := pool.cache.Get(formCacheKey(cp.address, pool.key))
|
||||||
require.True(t, containsTokens(tokens, st))
|
require.True(t, containsTokens(tokens, &st))
|
||||||
|
|
||||||
var prm PrmObjectGet
|
var prm PrmObjectGet
|
||||||
prm.SetAddress(address.Address{})
|
prm.SetAddress(address.Address{})
|
||||||
|
@ -557,20 +574,22 @@ func TestSessionCacheWithKey(t *testing.T) {
|
||||||
require.Len(t, tokens, 2)
|
require.Len(t, tokens, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newToken(t *testing.T) *session.Token {
|
func newToken(t *testing.T) *session.Object {
|
||||||
tok := session.NewToken()
|
var tok session.Object
|
||||||
uid, err := uuid.New().MarshalBinary()
|
tok.SetID(uuid.New())
|
||||||
require.NoError(t, err)
|
|
||||||
tok.SetID(uid)
|
|
||||||
|
|
||||||
return tok
|
return &tok
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionTokenOwner(t *testing.T) {
|
func TestSessionTokenOwner(t *testing.T) {
|
||||||
|
t.Skip() // neofs-sdk-go#???
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
clientBuilder := func(_ string) (client, error) {
|
clientBuilder := func(_ string) (client, error) {
|
||||||
mockClient := NewMockClient(ctrl)
|
mockClient := NewMockClient(ctrl)
|
||||||
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{}, nil).AnyTimes()
|
mockClient.EXPECT().sessionCreate(gomock.Any(), gomock.Any()).Return(&resCreateSession{
|
||||||
|
id: uuid.Nil[:],
|
||||||
|
sessionKey: newBinPublicKey(t),
|
||||||
|
}, nil).AnyTimes()
|
||||||
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().endpointInfo(gomock.Any(), gomock.Any()).Return(&netmap.NodeInfo{}, nil).AnyTimes()
|
||||||
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
mockClient.EXPECT().networkInfo(gomock.Any(), gomock.Any()).Return(&netmap.NetworkInfo{}, nil).AnyTimes()
|
||||||
return mockClient, nil
|
return mockClient, nil
|
||||||
|
@ -605,15 +624,15 @@ func TestSessionTokenOwner(t *testing.T) {
|
||||||
|
|
||||||
var cc callContext
|
var cc callContext
|
||||||
cc.Context = ctx
|
cc.Context = ctx
|
||||||
cc.sessionTarget = func(session.Token) {}
|
cc.sessionTarget = func(session.Object) {}
|
||||||
err = p.initCallContext(&cc, prm, prmCtx)
|
err = p.initCallContext(&cc, prm, prmCtx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = p.openDefaultSession(&cc)
|
err = p.openDefaultSession(&cc)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tkn := p.cache.Get(formCacheKey("peer0", anonKey))
|
tkn, _ := p.cache.Get(formCacheKey("peer0", anonKey))
|
||||||
require.True(t, anonOwner.Equals(*tkn.OwnerID()))
|
require.True(t, tkn.VerifySignature())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWaitPresence(t *testing.T) {
|
func TestWaitPresence(t *testing.T) {
|
||||||
|
@ -661,29 +680,29 @@ func TestWaitPresence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCopySessionTokenWithoutSignatureAndContext(t *testing.T) {
|
func TestCopySessionTokenWithoutSignatureAndContext(t *testing.T) {
|
||||||
from := sessiontest.SignedToken()
|
from := *sessiontest.Object()
|
||||||
to := copySessionTokenWithoutSignatureAndContext(*from)
|
|
||||||
|
|
||||||
require.Equal(t, from.Nbf(), to.Nbf())
|
const verb = session.VerbObjectHead
|
||||||
require.Equal(t, from.Exp(), to.Exp())
|
from.ForVerb(verb)
|
||||||
require.Equal(t, from.Iat(), to.Iat())
|
|
||||||
require.Equal(t, from.ID(), to.ID())
|
|
||||||
require.Equal(t, from.OwnerID().String(), to.OwnerID().String())
|
|
||||||
require.Equal(t, from.SessionKey(), to.SessionKey())
|
|
||||||
|
|
||||||
|
to := from
|
||||||
|
|
||||||
|
require.Equal(t, from, to)
|
||||||
|
|
||||||
|
require.False(t, from.VerifySignature())
|
||||||
require.False(t, to.VerifySignature())
|
require.False(t, to.VerifySignature())
|
||||||
|
|
||||||
t.Run("empty object context", func(t *testing.T) {
|
require.True(t, from.AssertVerb(verb))
|
||||||
octx := sessiontest.ObjectContext()
|
require.True(t, to.AssertVerb(verb))
|
||||||
from.SetContext(octx)
|
|
||||||
to = copySessionTokenWithoutSignatureAndContext(*from)
|
|
||||||
require.Nil(t, to.Context())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("empty container context", func(t *testing.T) {
|
k, err := keys.NewPrivateKey()
|
||||||
cctx := sessiontest.ContainerContext()
|
require.NoError(t, err)
|
||||||
from.SetContext(cctx)
|
|
||||||
to = copySessionTokenWithoutSignatureAndContext(*from)
|
from.ForVerb(verb + 1)
|
||||||
require.Nil(t, to.Context())
|
require.NoError(t, from.Sign(k.PrivateKey))
|
||||||
})
|
|
||||||
|
require.True(t, from.VerifySignature())
|
||||||
|
require.False(t, to.VerifySignature())
|
||||||
|
require.True(t, from.AssertVerb(verb+1))
|
||||||
|
require.False(t, to.AssertVerb(verb+1))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,153 +1,378 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||||
|
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContainerContext represents NeoFS API v2-compatible
|
// Container represents token of the NeoFS Container session. A session is opened
|
||||||
// context of the container session.
|
// between any two sides of the system, and implements a mechanism for transferring
|
||||||
|
// the power of attorney of actions to another network member. The session has a
|
||||||
|
// limited validity period, and applies to a strictly defined set of operations.
|
||||||
|
// See methods for details.
|
||||||
//
|
//
|
||||||
// It is a wrapper over session.ContainerSessionContext
|
// Container is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
|
||||||
// which allows to abstract from details of the message
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
// structure.
|
|
||||||
type ContainerContext session.ContainerSessionContext
|
|
||||||
|
|
||||||
// NewContainerContext creates and returns blank ContainerSessionContext.
|
|
||||||
//
|
//
|
||||||
// Defaults:
|
// Instances can be created using built-in var declaration.
|
||||||
// - not bound to any operation;
|
type Container struct {
|
||||||
// - applied to all containers.
|
cnrSet bool
|
||||||
func NewContainerContext() *ContainerContext {
|
|
||||||
v2 := new(session.ContainerSessionContext)
|
|
||||||
v2.SetWildcard(true)
|
|
||||||
|
|
||||||
return NewContainerContextFromV2(v2)
|
lt session.TokenLifetime
|
||||||
|
|
||||||
|
c session.ContainerSessionContext
|
||||||
|
|
||||||
|
body session.TokenBody
|
||||||
|
|
||||||
|
sig neofscrypto.Signature
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContainerContextFromV2 wraps session.ContainerSessionContext
|
// ReadFromV2 reads Container from the session.Token message.
|
||||||
// into ContainerContext.
|
|
||||||
func NewContainerContextFromV2(v *session.ContainerSessionContext) *ContainerContext {
|
|
||||||
return (*ContainerContext)(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToV2 converts ContainerContext to session.ContainerSessionContext
|
|
||||||
// message structure.
|
|
||||||
func (x *ContainerContext) ToV2() *session.ContainerSessionContext {
|
|
||||||
return (*session.ContainerSessionContext)(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApplyTo specifies which container the ContainerContext applies to.
|
|
||||||
//
|
//
|
||||||
// If id is nil, ContainerContext is applied to all containers.
|
// See also WriteToV2.
|
||||||
func (x *ContainerContext) ApplyTo(id *cid.ID) {
|
func (x *Container) ReadFromV2(m session.Token) error {
|
||||||
v2 := (*session.ContainerSessionContext)(x)
|
b := m.GetBody()
|
||||||
|
if b == nil {
|
||||||
var cidV2 *refs.ContainerID
|
return errors.New("missing body")
|
||||||
|
|
||||||
if id != nil {
|
|
||||||
var c refs.ContainerID
|
|
||||||
id.WriteToV2(&c)
|
|
||||||
|
|
||||||
cidV2 = &c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v2.SetWildcard(id == nil)
|
bID := b.GetID()
|
||||||
v2.SetContainerID(cidV2)
|
var id uuid.UUID
|
||||||
|
|
||||||
|
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.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{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyToAllContainers is a helper function that conveniently
|
// WriteToV2 writes Container to the session.Token message.
|
||||||
// applies ContainerContext to all containers.
|
// The message must not be nil.
|
||||||
func ApplyToAllContainers(c *ContainerContext) {
|
|
||||||
c.ApplyTo(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container returns identifier of the container
|
|
||||||
// to which the ContainerContext applies.
|
|
||||||
//
|
//
|
||||||
// Returns nil if ContainerContext is applied to
|
// See also ReadFromV2.
|
||||||
// all containers.
|
func (x Container) WriteToV2(m *session.Token) {
|
||||||
func (x *ContainerContext) Container() *cid.ID {
|
var sig refs.Signature
|
||||||
v2 := (*session.ContainerSessionContext)(x)
|
x.sig.WriteToV2(&sig)
|
||||||
|
|
||||||
if v2.Wildcard() {
|
m.SetBody(&x.body)
|
||||||
return nil
|
m.SetSignature(&sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal encodes Container into a binary format of the NeoFS API protocol
|
||||||
|
// (Protocol Buffers with direct field order).
|
||||||
|
//
|
||||||
|
// See also Unmarshal.
|
||||||
|
func (x Container) Marshal() []byte {
|
||||||
|
var m session.Token
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
data, err := m.StableMarshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
cidV2 := v2.ContainerID()
|
return data
|
||||||
if cidV2 == nil {
|
}
|
||||||
return nil
|
|
||||||
|
// Unmarshal decodes NeoFS API protocol binary format into the Container
|
||||||
|
// (Protocol Buffers with direct field order). Returns an error describing
|
||||||
|
// a format violation.
|
||||||
|
//
|
||||||
|
// See also Marshal.
|
||||||
|
func (x *Container) Unmarshal(data []byte) error {
|
||||||
|
var m session.Token
|
||||||
|
|
||||||
|
err := m.Unmarshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var cID cid.ID
|
return x.ReadFromV2(m)
|
||||||
_ = cID.ReadFromV2(*cidV2)
|
|
||||||
|
|
||||||
return &cID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ContainerContext) forVerb(v session.ContainerSessionVerb) {
|
// MarshalJSON encodes Container into a JSON format of the NeoFS API protocol
|
||||||
(*session.ContainerSessionContext)(x).
|
// (Protocol Buffers JSON).
|
||||||
SetVerb(v)
|
//
|
||||||
|
// See also UnmarshalJSON.
|
||||||
|
func (x Container) MarshalJSON() ([]byte, error) {
|
||||||
|
var m session.Token
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
return m.MarshalJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ContainerContext) isForVerb(v session.ContainerSessionVerb) bool {
|
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Container
|
||||||
return (*session.ContainerSessionContext)(x).
|
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
||||||
Verb() == v
|
//
|
||||||
|
// See also MarshalJSON.
|
||||||
|
func (x *Container) UnmarshalJSON(data []byte) error {
|
||||||
|
var m session.Token
|
||||||
|
|
||||||
|
err := m.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.ReadFromV2(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForPut binds the ContainerContext to
|
// Sign calculates and writes signature of the Container data.
|
||||||
// PUT operation.
|
// Returns signature calculation errors.
|
||||||
func (x *ContainerContext) ForPut() {
|
//
|
||||||
x.forVerb(session.ContainerVerbPut)
|
// Zero Container is unsigned.
|
||||||
|
//
|
||||||
|
// Note that any Container mutation is likely to break the signature, so it is
|
||||||
|
// expected to be calculated as a final stage of Container formation.
|
||||||
|
//
|
||||||
|
// See also VerifySignature.
|
||||||
|
func (x *Container) Sign(key ecdsa.PrivateKey) error {
|
||||||
|
var idUser user.ID
|
||||||
|
user.IDFromKey(&idUser, key.PublicKey)
|
||||||
|
|
||||||
|
var idUserV2 refs.OwnerID
|
||||||
|
idUser.WriteToV2(&idUserV2)
|
||||||
|
|
||||||
|
x.c.SetWildcard(!x.cnrSet)
|
||||||
|
|
||||||
|
x.body.SetOwnerID(&idUserV2)
|
||||||
|
x.body.SetLifetime(&x.lt)
|
||||||
|
x.body.SetContext(&x.c)
|
||||||
|
|
||||||
|
data, err := x.body.StableMarshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.sig.Calculate(neofsecdsa.Signer(key), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForPut checks if ContainerContext is bound to
|
// VerifySignature checks if Container signature is presented and valid.
|
||||||
// PUT operation.
|
//
|
||||||
func (x *ContainerContext) IsForPut() bool {
|
// Zero Container fails the check.
|
||||||
return x.isForVerb(session.ContainerVerbPut)
|
//
|
||||||
|
// See also Sign.
|
||||||
|
func (x Container) VerifySignature() bool {
|
||||||
|
// TODO: check owner<->key relation
|
||||||
|
data, err := x.body.StableMarshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.sig.Verify(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForDelete binds the ContainerContext to
|
// ApplyOnlyTo limits session scope to a given author container.
|
||||||
// DELETE operation.
|
//
|
||||||
func (x *ContainerContext) ForDelete() {
|
// See also AppliedTo.
|
||||||
x.forVerb(session.ContainerVerbDelete)
|
func (x *Container) ApplyOnlyTo(cnr cid.ID) {
|
||||||
|
var cnrv2 refs.ContainerID
|
||||||
|
cnr.WriteToV2(&cnrv2)
|
||||||
|
|
||||||
|
x.c.SetContainerID(&cnrv2)
|
||||||
|
x.cnrSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForDelete checks if ContainerContext is bound to
|
// AppliedTo checks if session scope is limited by a given container.
|
||||||
// DELETE operation.
|
//
|
||||||
func (x *ContainerContext) IsForDelete() bool {
|
// Zero Container is applied to all author's containers.
|
||||||
return x.isForVerb(session.ContainerVerbDelete)
|
//
|
||||||
|
// See also ApplyOnlyTo.
|
||||||
|
func (x Container) AppliedTo(cnr cid.ID) bool {
|
||||||
|
if !x.cnrSet {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var cnr2 cid.ID
|
||||||
|
|
||||||
|
if err := cnr2.ReadFromV2(*x.c.ContainerID()); err != nil {
|
||||||
|
// NPE and error must never happen
|
||||||
|
panic(fmt.Sprintf("unexpected error from cid.ReadFromV2: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cnr2.Equals(cnr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForSetEACL binds the ContainerContext to
|
// ContainerVerb enumerates container operations.
|
||||||
// SETEACL operation.
|
type ContainerVerb int8
|
||||||
func (x *ContainerContext) ForSetEACL() {
|
|
||||||
x.forVerb(session.ContainerVerbSetEACL)
|
const (
|
||||||
|
_ ContainerVerb = iota
|
||||||
|
|
||||||
|
VerbContainerPut // Put rpc
|
||||||
|
VerbContainerDelete // Delete rpc
|
||||||
|
VerbContainerSetEACL // SetExtendedACL rpc
|
||||||
|
)
|
||||||
|
|
||||||
|
// ForVerb specifies the container operation of the session scope. Each
|
||||||
|
// Container is related to the single operation.
|
||||||
|
//
|
||||||
|
// See also AssertVerb.
|
||||||
|
func (x *Container) ForVerb(verb ContainerVerb) {
|
||||||
|
x.c.SetVerb(session.ContainerSessionVerb(verb))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForSetEACL checks if ContainerContext is bound to
|
// AssertVerb checks if Container relates to the given container operation.
|
||||||
// SETEACL operation.
|
//
|
||||||
func (x *ContainerContext) IsForSetEACL() bool {
|
// Zero Container relates to zero (unspecified) verb.
|
||||||
return x.isForVerb(session.ContainerVerbSetEACL)
|
//
|
||||||
|
// See also ForVerb.
|
||||||
|
func (x Container) AssertVerb(verb ContainerVerb) bool {
|
||||||
|
return verb == ContainerVerb(x.c.Verb())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal marshals ContainerContext into a protobuf binary form.
|
// SetExp sets "exp" (expiration time) claim which identifies the expiration time
|
||||||
func (x *ContainerContext) Marshal() ([]byte, error) {
|
// (in NeoFS epochs) on or after which the Container MUST NOT be accepted for
|
||||||
return x.ToV2().StableMarshal(nil)
|
// processing. The processing of the "exp" claim requires that the current
|
||||||
|
// epoch MUST be before the expiration epoch listed in the "exp" claim.
|
||||||
|
//
|
||||||
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
|
||||||
|
//
|
||||||
|
// See also ExpiredAt.
|
||||||
|
func (x *Container) SetExp(exp uint64) {
|
||||||
|
x.lt.SetExp(exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of ContainerContext.
|
// ExpiredAt asserts "exp" claim.
|
||||||
func (x *ContainerContext) Unmarshal(data []byte) error {
|
//
|
||||||
return x.ToV2().Unmarshal(data)
|
// Zero Container is expired in any epoch.
|
||||||
|
//
|
||||||
|
// See also SetExp.
|
||||||
|
func (x Container) ExpiredAt(epoch uint64) bool {
|
||||||
|
return x.lt.GetExp() <= epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON encodes ContainerContext to protobuf JSON format.
|
// SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
|
||||||
func (x *ContainerContext) MarshalJSON() ([]byte, error) {
|
// epochs) before which the Container MUST NOT be accepted for processing.
|
||||||
return x.ToV2().MarshalJSON()
|
// The processing of the "nbf" claim requires that the current date/time MUST be
|
||||||
|
// after or equal to the not-before date/time listed in the "nbf" claim.
|
||||||
|
//
|
||||||
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5.
|
||||||
|
//
|
||||||
|
// See also InvalidAt.
|
||||||
|
func (x *Container) SetNbf(nbf uint64) {
|
||||||
|
x.lt.SetNbf(nbf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalJSON decodes ContainerContext from protobuf JSON format.
|
// SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
|
||||||
func (x *ContainerContext) UnmarshalJSON(data []byte) error {
|
// epochs) at which the Container was issued. This claim can be used to
|
||||||
return x.ToV2().UnmarshalJSON(data)
|
// determine the age of the Container.
|
||||||
|
//
|
||||||
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6.
|
||||||
|
//
|
||||||
|
// See also InvalidAt.
|
||||||
|
func (x *Container) SetIat(iat uint64) {
|
||||||
|
x.lt.SetIat(iat)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InvalidAt asserts "exp", "nbf" and "iat" claims.
|
||||||
|
//
|
||||||
|
// Zero Container is invalid in any epoch.
|
||||||
|
//
|
||||||
|
// See also SetExp, SetNbf, SetIat.
|
||||||
|
func (x Container) InvalidAt(epoch uint64) bool {
|
||||||
|
return x.lt.GetNbf() > epoch || x.lt.GetIat() > epoch || x.ExpiredAt(epoch)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetID sets a unique identifier for the session. The identifier value MUST be
|
||||||
|
// assigned in a manner that ensures that there is a negligible probability
|
||||||
|
// that the same value will be accidentally assigned to a different session.
|
||||||
|
//
|
||||||
|
// ID format MUST be UUID version 4 (random). uuid.New can be used to generate
|
||||||
|
// a new ID. See https://datatracker.ietf.org/doc/html/rfc4122 and
|
||||||
|
// github.com/google/uuid package docs for details.
|
||||||
|
//
|
||||||
|
// See also ID.
|
||||||
|
func (x *Container) SetID(id uuid.UUID) {
|
||||||
|
x.body.SetID(id[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns a unique identifier for the session.
|
||||||
|
//
|
||||||
|
// Zero Container has empty UUID (all zeros, see uuid.Nil) which is legitimate
|
||||||
|
// but most likely not suitable.
|
||||||
|
//
|
||||||
|
// See also SetID.
|
||||||
|
func (x Container) ID() uuid.UUID {
|
||||||
|
data := x.body.GetID()
|
||||||
|
if data == nil {
|
||||||
|
return uuid.Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var id uuid.UUID
|
||||||
|
|
||||||
|
err := id.UnmarshalBinary(x.body.GetID())
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from UUID.UnmarshalBinary: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthKey public key corresponding to the private key bound to the session.
|
||||||
|
//
|
||||||
|
// See also AssertAuthKey.
|
||||||
|
func (x *Container) SetAuthKey(key neofscrypto.PublicKey) {
|
||||||
|
bKey := make([]byte, key.MaxEncodedSize())
|
||||||
|
bKey = bKey[:key.Encode(bKey)]
|
||||||
|
|
||||||
|
x.body.SetSessionKey(bKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertAuthKey asserts public key bound to the session.
|
||||||
|
//
|
||||||
|
// Zero Container fails the check.
|
||||||
|
//
|
||||||
|
// See also SetAuthKey.
|
||||||
|
func (x Container) AssertAuthKey(key neofscrypto.PublicKey) bool {
|
||||||
|
bKey := make([]byte, key.MaxEncodedSize())
|
||||||
|
bKey = bKey[:key.Encode(bKey)]
|
||||||
|
|
||||||
|
return bytes.Equal(bKey, x.body.GetSessionKey())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
package session_test
|
package session_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
cidtest "github.com/nspcc-dev/neofs-sdk-go/container/id/test"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
|
@ -10,103 +14,274 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContainerContextVerbs(t *testing.T) {
|
func TestContainer_ReadFromV2(t *testing.T) {
|
||||||
c := session.NewContainerContext()
|
var x session.Container
|
||||||
|
var m v2session.Token
|
||||||
|
var b v2session.TokenBody
|
||||||
|
var c v2session.ContainerSessionContext
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
assert := func(setter func(), getter func() bool, verb v2session.ContainerSessionVerb) {
|
t.Run("protocol violation", func(t *testing.T) {
|
||||||
setter()
|
require.Error(t, x.ReadFromV2(m))
|
||||||
|
|
||||||
require.True(t, getter())
|
m.SetBody(&b)
|
||||||
|
|
||||||
require.Equal(t, verb, c.ToV2().Verb())
|
require.Error(t, x.ReadFromV2(m))
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("PUT", func(t *testing.T) {
|
b.SetID(id[:])
|
||||||
assert(c.ForPut, c.IsForPut, v2session.ContainerVerbPut)
|
|
||||||
|
require.Error(t, x.ReadFromV2(m))
|
||||||
|
|
||||||
|
b.SetContext(&c)
|
||||||
|
|
||||||
|
require.Error(t, x.ReadFromV2(m))
|
||||||
|
|
||||||
|
c.SetWildcard(true)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("DELETE", func(t *testing.T) {
|
m.SetBody(&b)
|
||||||
assert(c.ForDelete, c.IsForDelete, v2session.ContainerVerbDelete)
|
b.SetContext(&c)
|
||||||
|
b.SetID(id[:])
|
||||||
|
c.SetWildcard(true)
|
||||||
|
|
||||||
|
t.Run("container", func(t *testing.T) {
|
||||||
|
cnr1 := cidtest.ID()
|
||||||
|
cnr2 := cidtest.ID()
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AppliedTo(cnr1))
|
||||||
|
require.True(t, x.AppliedTo(cnr2))
|
||||||
|
|
||||||
|
var cnrv2 refs.ContainerID
|
||||||
|
cnr1.WriteToV2(&cnrv2)
|
||||||
|
|
||||||
|
c.SetContainerID(&cnrv2)
|
||||||
|
c.SetWildcard(false)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AppliedTo(cnr1))
|
||||||
|
require.False(t, x.AppliedTo(cnr2))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("SETEACL", func(t *testing.T) {
|
t.Run("verb", func(t *testing.T) {
|
||||||
assert(c.ForSetEACL, c.IsForSetEACL, v2session.ContainerVerbSetEACL)
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AssertVerb(0))
|
||||||
|
|
||||||
|
verb := v2session.ContainerSessionVerb(rand.Uint32())
|
||||||
|
|
||||||
|
c.SetVerb(verb)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AssertVerb(session.ContainerVerb(verb)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("id", func(t *testing.T) {
|
||||||
|
id := uuid.New()
|
||||||
|
bID := id[:]
|
||||||
|
|
||||||
|
b.SetID(bID)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.Equal(t, id, x.ID())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("lifetime", func(t *testing.T) {
|
||||||
|
const nbf, iat, exp = 11, 22, 33
|
||||||
|
|
||||||
|
var lt v2session.TokenLifetime
|
||||||
|
lt.SetNbf(nbf)
|
||||||
|
lt.SetIat(iat)
|
||||||
|
lt.SetExp(exp)
|
||||||
|
|
||||||
|
b.SetLifetime(<)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.False(t, x.ExpiredAt(exp-1))
|
||||||
|
require.True(t, x.ExpiredAt(exp))
|
||||||
|
require.True(t, x.ExpiredAt(exp+1))
|
||||||
|
require.True(t, x.InvalidAt(nbf-1))
|
||||||
|
require.True(t, x.InvalidAt(iat-1))
|
||||||
|
require.False(t, x.InvalidAt(iat))
|
||||||
|
require.False(t, x.InvalidAt(exp-1))
|
||||||
|
require.True(t, x.InvalidAt(exp))
|
||||||
|
require.True(t, x.InvalidAt(exp+1))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("session key", func(t *testing.T) {
|
||||||
|
key := randPublicKey()
|
||||||
|
|
||||||
|
bKey := make([]byte, key.MaxEncodedSize())
|
||||||
|
bKey = bKey[:key.Encode(bKey)]
|
||||||
|
|
||||||
|
b.SetSessionKey(bKey)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AssertAuthKey(key))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerContext_ApplyTo(t *testing.T) {
|
func TestEncodingContainer(t *testing.T) {
|
||||||
c := session.NewContainerContext()
|
tok := *sessiontest.ContainerSigned()
|
||||||
id := cidtest.ID()
|
|
||||||
|
|
||||||
t.Run("method", func(t *testing.T) {
|
|
||||||
c.ApplyTo(&id)
|
|
||||||
|
|
||||||
require.Equal(t, id, *c.Container())
|
|
||||||
|
|
||||||
c.ApplyTo(nil)
|
|
||||||
|
|
||||||
require.Nil(t, c.Container())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("helper functions", func(t *testing.T) {
|
|
||||||
c.ApplyTo(&id)
|
|
||||||
|
|
||||||
session.ApplyToAllContainers(c)
|
|
||||||
|
|
||||||
require.Nil(t, c.Container())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContextFilter_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *session.ContainerContext
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
c := session.NewContainerContext()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Nil(t, c.Container())
|
|
||||||
|
|
||||||
for _, op := range []func() bool{
|
|
||||||
c.IsForPut,
|
|
||||||
c.IsForDelete,
|
|
||||||
c.IsForSetEACL,
|
|
||||||
} {
|
|
||||||
require.False(t, op())
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
cV2 := c.ToV2()
|
|
||||||
|
|
||||||
require.Equal(t, v2session.ContainerVerbUnknown, cV2.Verb())
|
|
||||||
require.True(t, cV2.Wildcard())
|
|
||||||
require.Nil(t, cV2.ContainerID())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestContainerContextEncoding(t *testing.T) {
|
|
||||||
c := sessiontest.ContainerContext()
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
t.Run("binary", func(t *testing.T) {
|
||||||
data, err := c.Marshal()
|
data := tok.Marshal()
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
c2 := session.NewContainerContext()
|
var tok2 session.Container
|
||||||
require.NoError(t, c2.Unmarshal(data))
|
require.NoError(t, tok2.Unmarshal(data))
|
||||||
|
|
||||||
require.Equal(t, c, c2)
|
require.Equal(t, tok, tok2)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
t.Run("json", func(t *testing.T) {
|
||||||
data, err := c.MarshalJSON()
|
data, err := tok.MarshalJSON()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c2 := session.NewContainerContext()
|
var tok2 session.Container
|
||||||
require.NoError(t, c2.UnmarshalJSON(data))
|
require.NoError(t, tok2.UnmarshalJSON(data))
|
||||||
|
|
||||||
require.Equal(t, c, c2)
|
require.Equal(t, tok, tok2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainerAppliedTo(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
cnr1 := cidtest.ID()
|
||||||
|
cnr2 := cidtest.ID()
|
||||||
|
|
||||||
|
require.True(t, x.AppliedTo(cnr1))
|
||||||
|
require.True(t, x.AppliedTo(cnr2))
|
||||||
|
|
||||||
|
x.ApplyOnlyTo(cnr1)
|
||||||
|
|
||||||
|
require.True(t, x.AppliedTo(cnr1))
|
||||||
|
require.False(t, x.AppliedTo(cnr2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerExp(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
exp := rand.Uint64()
|
||||||
|
|
||||||
|
require.True(t, x.ExpiredAt(exp))
|
||||||
|
|
||||||
|
x.SetExp(exp)
|
||||||
|
|
||||||
|
require.False(t, x.ExpiredAt(exp-1))
|
||||||
|
require.True(t, x.ExpiredAt(exp))
|
||||||
|
require.True(t, x.ExpiredAt(exp+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerLifetime(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
nbf := rand.Uint64()
|
||||||
|
if nbf == math.MaxUint64 {
|
||||||
|
nbf--
|
||||||
|
}
|
||||||
|
|
||||||
|
iat := nbf
|
||||||
|
exp := iat + 1
|
||||||
|
|
||||||
|
x.SetNbf(nbf)
|
||||||
|
x.SetIat(iat)
|
||||||
|
x.SetExp(exp)
|
||||||
|
|
||||||
|
require.True(t, x.InvalidAt(nbf-1))
|
||||||
|
require.True(t, x.InvalidAt(iat-1))
|
||||||
|
require.False(t, x.InvalidAt(iat))
|
||||||
|
require.True(t, x.InvalidAt(exp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerID(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
require.Zero(t, x.ID())
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
x.SetID(id)
|
||||||
|
|
||||||
|
require.Equal(t, id, x.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerAuthKey(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
key := randPublicKey()
|
||||||
|
|
||||||
|
require.False(t, x.AssertAuthKey(key))
|
||||||
|
|
||||||
|
x.SetAuthKey(key)
|
||||||
|
|
||||||
|
require.True(t, x.AssertAuthKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerVerb(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
const v1, v2 = session.VerbContainerPut, session.VerbContainerDelete
|
||||||
|
|
||||||
|
require.False(t, x.AssertVerb(v1))
|
||||||
|
require.False(t, x.AssertVerb(v2))
|
||||||
|
|
||||||
|
x.ForVerb(v1)
|
||||||
|
require.True(t, x.AssertVerb(v1))
|
||||||
|
require.False(t, x.AssertVerb(v2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContainerSignature(t *testing.T) {
|
||||||
|
var x session.Container
|
||||||
|
|
||||||
|
const nbf = 11
|
||||||
|
const iat = 22
|
||||||
|
const exp = 33
|
||||||
|
id := uuid.New()
|
||||||
|
key := randPublicKey()
|
||||||
|
cnr := cidtest.ID()
|
||||||
|
verb := session.VerbContainerPut
|
||||||
|
|
||||||
|
signer := randSigner()
|
||||||
|
|
||||||
|
fs := []func(){
|
||||||
|
func() { x.SetNbf(nbf) },
|
||||||
|
func() { x.SetNbf(nbf + 1) },
|
||||||
|
|
||||||
|
func() { x.SetIat(iat) },
|
||||||
|
func() { x.SetIat(iat + 1) },
|
||||||
|
|
||||||
|
func() { x.SetExp(exp) },
|
||||||
|
func() { x.SetExp(exp + 1) },
|
||||||
|
|
||||||
|
func() { x.SetID(id) },
|
||||||
|
func() {
|
||||||
|
idcp := id
|
||||||
|
idcp[0]++
|
||||||
|
x.SetID(idcp)
|
||||||
|
},
|
||||||
|
|
||||||
|
func() { x.SetAuthKey(key) },
|
||||||
|
func() { x.SetAuthKey(randPublicKey()) },
|
||||||
|
|
||||||
|
func() { x.ApplyOnlyTo(cnr) },
|
||||||
|
func() { x.ApplyOnlyTo(cidtest.ID()) },
|
||||||
|
|
||||||
|
func() { x.ForVerb(verb) },
|
||||||
|
func() { x.ForVerb(verb + 1) },
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(fs); i += 2 {
|
||||||
|
fs[i]()
|
||||||
|
|
||||||
|
require.NoError(t, x.Sign(signer))
|
||||||
|
require.True(t, x.VerifySignature())
|
||||||
|
|
||||||
|
fs[i+1]()
|
||||||
|
require.False(t, x.VerifySignature())
|
||||||
|
|
||||||
|
fs[i]()
|
||||||
|
require.True(t, x.VerifySignature())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
48
session/doc.go
Normal file
48
session/doc.go
Normal 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
|
|
@ -1,166 +1,379 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/refs"
|
||||||
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
"github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ObjectContext represents NeoFS API v2-compatible
|
// Object represents token of the NeoFS Object session. A session is opened
|
||||||
// context of the object session.
|
// between any two sides of the system, and implements a mechanism for transferring
|
||||||
|
// the power of attorney of actions to another network member. The session has a
|
||||||
|
// limited validity period, and applies to a strictly defined set of operations.
|
||||||
|
// See methods for details.
|
||||||
//
|
//
|
||||||
// It is a wrapper over session.ObjectSessionContext
|
// Object is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/session.Token
|
||||||
// which allows abstracting from details of the message
|
// message. See ReadFromV2 / WriteToV2 methods.
|
||||||
// structure.
|
|
||||||
type ObjectContext session.ObjectSessionContext
|
|
||||||
|
|
||||||
// NewObjectContext creates and returns blank ObjectContext.
|
|
||||||
//
|
//
|
||||||
// Defaults:
|
// Instances can be created using built-in var declaration.
|
||||||
// - not bound to any operation;
|
type Object struct {
|
||||||
// - nil object address.
|
lt session.TokenLifetime
|
||||||
func NewObjectContext() *ObjectContext {
|
|
||||||
v2 := new(session.ObjectSessionContext)
|
|
||||||
|
|
||||||
return NewObjectContextFromV2(v2)
|
obj refs.Address
|
||||||
|
|
||||||
|
c session.ObjectSessionContext
|
||||||
|
|
||||||
|
body session.TokenBody
|
||||||
|
|
||||||
|
sig neofscrypto.Signature
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewObjectContextFromV2 wraps session.ObjectSessionContext
|
// ReadFromV2 reads Object from the session.Token message.
|
||||||
// into ObjectContext.
|
//
|
||||||
func NewObjectContextFromV2(v *session.ObjectSessionContext) *ObjectContext {
|
// See also WriteToV2.
|
||||||
return (*ObjectContext)(v)
|
func (x *Object) ReadFromV2(m session.Token) error {
|
||||||
|
b := m.GetBody()
|
||||||
|
if b == nil {
|
||||||
|
return errors.New("missing body")
|
||||||
|
}
|
||||||
|
|
||||||
|
bID := b.GetID()
|
||||||
|
var id uuid.UUID
|
||||||
|
|
||||||
|
err := id.UnmarshalBinary(bID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid binary ID: %w", err)
|
||||||
|
} else if ver := id.Version(); ver != 4 {
|
||||||
|
return fmt.Errorf("invalid UUID version %s", ver)
|
||||||
|
}
|
||||||
|
|
||||||
|
c, ok := b.GetContext().(*session.ObjectSessionContext)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid context %T", b.GetContext())
|
||||||
|
}
|
||||||
|
|
||||||
|
x.body = *b
|
||||||
|
|
||||||
|
if c != nil {
|
||||||
|
x.c = *c
|
||||||
|
|
||||||
|
obj := c.GetAddress()
|
||||||
|
if obj != nil {
|
||||||
|
x.obj = *obj
|
||||||
|
} else {
|
||||||
|
x.obj = refs.Address{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
x.c = session.ObjectSessionContext{}
|
||||||
|
x.obj = refs.Address{}
|
||||||
|
}
|
||||||
|
|
||||||
|
lt := b.GetLifetime()
|
||||||
|
if lt != nil {
|
||||||
|
x.lt = *lt
|
||||||
|
} else {
|
||||||
|
x.lt = session.TokenLifetime{}
|
||||||
|
}
|
||||||
|
|
||||||
|
sig := m.GetSignature()
|
||||||
|
if sig != nil {
|
||||||
|
x.sig.ReadFromV2(*sig)
|
||||||
|
} else {
|
||||||
|
x.sig = neofscrypto.Signature{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToV2 converts ObjectContext to session.ObjectSessionContext
|
// WriteToV2 writes Object to the session.Token message.
|
||||||
// message structure.
|
// The message must not be nil.
|
||||||
func (x *ObjectContext) ToV2() *session.ObjectSessionContext {
|
//
|
||||||
return (*session.ObjectSessionContext)(x)
|
// See also ReadFromV2.
|
||||||
|
func (x Object) WriteToV2(m *session.Token) {
|
||||||
|
var sig refs.Signature
|
||||||
|
x.sig.WriteToV2(&sig)
|
||||||
|
|
||||||
|
m.SetBody(&x.body)
|
||||||
|
m.SetSignature(&sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyTo specifies which object the ObjectContext applies to.
|
// Marshal encodes Object into a binary format of the NeoFS API protocol
|
||||||
func (x *ObjectContext) ApplyTo(id *address.Address) {
|
// (Protocol Buffers with direct field order).
|
||||||
v2 := (*session.ObjectSessionContext)(x)
|
//
|
||||||
|
// See also Unmarshal.
|
||||||
|
func (x Object) Marshal() []byte {
|
||||||
|
var m session.Token
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
v2.SetAddress(id.ToV2())
|
data, err := m.StableMarshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address returns identifier of the object
|
// Unmarshal decodes NeoFS API protocol binary format into the Object
|
||||||
// to which the ObjectContext applies.
|
// (Protocol Buffers with direct field order). Returns an error describing
|
||||||
func (x *ObjectContext) Address() *address.Address {
|
// a format violation.
|
||||||
v2 := (*session.ObjectSessionContext)(x)
|
//
|
||||||
|
// See also Marshal.
|
||||||
|
func (x *Object) Unmarshal(data []byte) error {
|
||||||
|
var m session.Token
|
||||||
|
|
||||||
return address.NewAddressFromV2(v2.GetAddress())
|
err := m.Unmarshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.ReadFromV2(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ObjectContext) forVerb(v session.ObjectSessionVerb) {
|
// MarshalJSON encodes Object into a JSON format of the NeoFS API protocol
|
||||||
(*session.ObjectSessionContext)(x).
|
// (Protocol Buffers JSON).
|
||||||
SetVerb(v)
|
//
|
||||||
|
// See also UnmarshalJSON.
|
||||||
|
func (x Object) MarshalJSON() ([]byte, error) {
|
||||||
|
var m session.Token
|
||||||
|
x.WriteToV2(&m)
|
||||||
|
|
||||||
|
return m.MarshalJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ObjectContext) isForVerb(v session.ObjectSessionVerb) bool {
|
// UnmarshalJSON decodes NeoFS API protocol JSON format into the Object
|
||||||
return (*session.ObjectSessionContext)(x).
|
// (Protocol Buffers JSON). Returns an error describing a format violation.
|
||||||
GetVerb() == v
|
//
|
||||||
|
// See also MarshalJSON.
|
||||||
|
func (x *Object) UnmarshalJSON(data []byte) error {
|
||||||
|
var m session.Token
|
||||||
|
|
||||||
|
err := m.UnmarshalJSON(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.ReadFromV2(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForPut binds the ObjectContext to
|
// Sign calculates and writes signature of the Object data.
|
||||||
// PUT operation.
|
// Returns signature calculation errors.
|
||||||
func (x *ObjectContext) ForPut() {
|
//
|
||||||
x.forVerb(session.ObjectVerbPut)
|
// Zero Object is unsigned.
|
||||||
|
//
|
||||||
|
// Note that any Object mutation is likely to break the signature, so it is
|
||||||
|
// expected to be calculated as a final stage of Object formation.
|
||||||
|
//
|
||||||
|
// See also VerifySignature.
|
||||||
|
func (x *Object) Sign(key ecdsa.PrivateKey) error {
|
||||||
|
var idUser user.ID
|
||||||
|
user.IDFromKey(&idUser, key.PublicKey)
|
||||||
|
|
||||||
|
var idUserV2 refs.OwnerID
|
||||||
|
idUser.WriteToV2(&idUserV2)
|
||||||
|
|
||||||
|
x.c.SetAddress(&x.obj)
|
||||||
|
|
||||||
|
x.body.SetOwnerID(&idUserV2)
|
||||||
|
x.body.SetLifetime(&x.lt)
|
||||||
|
x.body.SetContext(&x.c)
|
||||||
|
|
||||||
|
data, err := x.body.StableMarshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.sig.Calculate(neofsecdsa.Signer(key), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForPut checks if ObjectContext is bound to
|
// VerifySignature checks if Object signature is presented and valid.
|
||||||
// PUT operation.
|
//
|
||||||
func (x *ObjectContext) IsForPut() bool {
|
// Zero Object fails the check.
|
||||||
return x.isForVerb(session.ObjectVerbPut)
|
//
|
||||||
|
// See also Sign.
|
||||||
|
func (x Object) VerifySignature() bool {
|
||||||
|
// TODO: check owner<->key relation
|
||||||
|
data, err := x.body.StableMarshal(nil)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from Token.StableMarshal: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.sig.Verify(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForDelete binds the ObjectContext to
|
// ApplyTo limits session scope to a given author object.
|
||||||
// DELETE operation.
|
//
|
||||||
func (x *ObjectContext) ForDelete() {
|
// See also AppliedTo.
|
||||||
x.forVerb(session.ObjectVerbDelete)
|
func (x *Object) ApplyTo(a address.Address) {
|
||||||
|
x.obj = *a.ToV2()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForDelete checks if ObjectContext is bound to
|
// AppliedTo checks if session scope is limited by a given object.
|
||||||
// DELETE operation.
|
//
|
||||||
func (x *ObjectContext) IsForDelete() bool {
|
// Zero Object isn't applied to any author's object.
|
||||||
return x.isForVerb(session.ObjectVerbDelete)
|
//
|
||||||
|
// See also ApplyTo.
|
||||||
|
func (x Object) AppliedTo(obj address.Address) bool {
|
||||||
|
objv2 := *address.NewAddressFromV2(&x.obj)
|
||||||
|
|
||||||
|
// FIXME: use Equals method
|
||||||
|
return obj.String() == objv2.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForGet binds the ObjectContext to
|
// ObjectVerb enumerates object operations.
|
||||||
// GET operation.
|
type ObjectVerb int8
|
||||||
func (x *ObjectContext) ForGet() {
|
|
||||||
x.forVerb(session.ObjectVerbGet)
|
const (
|
||||||
|
_ ObjectVerb = iota
|
||||||
|
|
||||||
|
VerbObjectPut // Put rpc
|
||||||
|
VerbObjectGet // Get rpc
|
||||||
|
VerbObjectHead // Head rpc
|
||||||
|
VerbObjectSearch // Search rpc
|
||||||
|
VerbObjectDelete // Delete rpc
|
||||||
|
VerbObjectRange // GetRange rpc
|
||||||
|
VerbObjectRangeHash // GetRangeHash rpc
|
||||||
|
)
|
||||||
|
|
||||||
|
// ForVerb specifies the object operation of the session scope. Each
|
||||||
|
// Object is related to the single operation.
|
||||||
|
//
|
||||||
|
// See also AssertVerb.
|
||||||
|
func (x *Object) ForVerb(verb ObjectVerb) {
|
||||||
|
x.c.SetVerb(session.ObjectSessionVerb(verb))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForGet checks if ObjectContext is bound to
|
// AssertVerb checks if Object relates to one of the given object operations.
|
||||||
// GET operation.
|
//
|
||||||
func (x *ObjectContext) IsForGet() bool {
|
// Zero Object relates to zero (unspecified) verb.
|
||||||
return x.isForVerb(session.ObjectVerbGet)
|
//
|
||||||
|
// See also ForVerb.
|
||||||
|
func (x Object) AssertVerb(verbs ...ObjectVerb) bool {
|
||||||
|
verb := ObjectVerb(x.c.GetVerb())
|
||||||
|
|
||||||
|
for i := range verbs {
|
||||||
|
if verbs[i] == verb {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForHead binds the ObjectContext to
|
// SetExp sets "exp" (expiration time) claim which identifies the expiration time
|
||||||
// HEAD operation.
|
// (in NeoFS epochs) on or after which the Object MUST NOT be accepted for
|
||||||
func (x *ObjectContext) ForHead() {
|
// processing. The processing of the "exp" claim requires that the current
|
||||||
x.forVerb(session.ObjectVerbHead)
|
// epoch MUST be before the expiration epoch listed in the "exp" claim.
|
||||||
|
//
|
||||||
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.4.
|
||||||
|
//
|
||||||
|
// See also ExpiredAt.
|
||||||
|
func (x *Object) SetExp(exp uint64) {
|
||||||
|
x.lt.SetExp(exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForHead checks if ObjectContext is bound to
|
// ExpiredAt asserts "exp" claim.
|
||||||
// HEAD operation.
|
//
|
||||||
func (x *ObjectContext) IsForHead() bool {
|
// Zero Object is expired in any epoch.
|
||||||
return x.isForVerb(session.ObjectVerbHead)
|
//
|
||||||
|
// See also SetExp.
|
||||||
|
func (x Object) ExpiredAt(epoch uint64) bool {
|
||||||
|
return x.lt.GetExp() <= epoch
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForSearch binds the ObjectContext to
|
// SetNbf sets "nbf" (not before) claim which identifies the time (in NeoFS
|
||||||
// SEARCH operation.
|
// epochs) before which the Object MUST NOT be accepted for processing.
|
||||||
func (x *ObjectContext) ForSearch() {
|
// The processing of the "nbf" claim requires that the current date/time MUST be
|
||||||
x.forVerb(session.ObjectVerbSearch)
|
// after or equal to the not-before date/time listed in the "nbf" claim.
|
||||||
|
//
|
||||||
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.5.
|
||||||
|
//
|
||||||
|
// See also InvalidAt.
|
||||||
|
func (x *Object) SetNbf(nbf uint64) {
|
||||||
|
x.lt.SetNbf(nbf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForSearch checks if ObjectContext is bound to
|
// SetIat sets "iat" (issued at) claim which identifies the time (in NeoFS
|
||||||
// SEARCH operation.
|
// epochs) at which the Object was issued. This claim can be used to
|
||||||
func (x *ObjectContext) IsForSearch() bool {
|
// determine the age of the Object.
|
||||||
return x.isForVerb(session.ObjectVerbSearch)
|
//
|
||||||
|
// Naming is inspired by https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.6.
|
||||||
|
//
|
||||||
|
// See also InvalidAt.
|
||||||
|
func (x *Object) SetIat(iat uint64) {
|
||||||
|
x.lt.SetIat(iat)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForRange binds the ObjectContext to
|
// InvalidAt asserts "exp", "nbf" and "iat" claims.
|
||||||
// RANGE operation.
|
//
|
||||||
func (x *ObjectContext) ForRange() {
|
// Zero Object is invalid in any epoch.
|
||||||
x.forVerb(session.ObjectVerbRange)
|
//
|
||||||
|
// See also SetExp, SetNbf, SetIat.
|
||||||
|
func (x Object) InvalidAt(epoch uint64) bool {
|
||||||
|
return x.lt.GetNbf() > epoch || x.lt.GetIat() > epoch || x.ExpiredAt(epoch)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForRange checks if ObjectContext is bound to
|
// SetID sets a unique identifier for the session. The identifier value MUST be
|
||||||
// RANGE operation.
|
// assigned in a manner that ensures that there is a negligible probability
|
||||||
func (x *ObjectContext) IsForRange() bool {
|
// that the same value will be accidentally assigned to a different session.
|
||||||
return x.isForVerb(session.ObjectVerbRange)
|
//
|
||||||
|
// ID format MUST be UUID version 4 (random). uuid.New can be used to generate
|
||||||
|
// a new ID. See https://datatracker.ietf.org/doc/html/rfc4122 and
|
||||||
|
// github.com/google/uuid package docs for details.
|
||||||
|
//
|
||||||
|
// See also ID.
|
||||||
|
func (x *Object) SetID(id uuid.UUID) {
|
||||||
|
x.body.SetID(id[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForRangeHash binds the ObjectContext to
|
// ID returns a unique identifier for the session.
|
||||||
// RANGEHASH operation.
|
//
|
||||||
func (x *ObjectContext) ForRangeHash() {
|
// Zero Object has empty UUID (all zeros, see uuid.Nil) which is legitimate
|
||||||
x.forVerb(session.ObjectVerbRangeHash)
|
// but most likely not suitable.
|
||||||
|
//
|
||||||
|
// See also SetID.
|
||||||
|
func (x Object) ID() uuid.UUID {
|
||||||
|
data := x.body.GetID()
|
||||||
|
if data == nil {
|
||||||
|
return uuid.Nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var id uuid.UUID
|
||||||
|
|
||||||
|
err := id.UnmarshalBinary(x.body.GetID())
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("unexpected error from UUID.UnmarshalBinary: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsForRangeHash checks if ObjectContext is bound to
|
// SetAuthKey public key corresponding to the private key bound to the session.
|
||||||
// RANGEHASH operation.
|
//
|
||||||
func (x *ObjectContext) IsForRangeHash() bool {
|
// See also AssertAuthKey.
|
||||||
return x.isForVerb(session.ObjectVerbRangeHash)
|
func (x *Object) SetAuthKey(key neofscrypto.PublicKey) {
|
||||||
|
bKey := make([]byte, key.MaxEncodedSize())
|
||||||
|
bKey = bKey[:key.Encode(bKey)]
|
||||||
|
|
||||||
|
x.body.SetSessionKey(bKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal marshals ObjectContext into a protobuf binary form.
|
// AssertAuthKey asserts public key bound to the session.
|
||||||
func (x *ObjectContext) Marshal() ([]byte, error) {
|
//
|
||||||
return x.ToV2().StableMarshal(nil)
|
// Zero Object fails the check.
|
||||||
}
|
//
|
||||||
|
// See also SetAuthKey.
|
||||||
|
func (x Object) AssertAuthKey(key neofscrypto.PublicKey) bool {
|
||||||
|
bKey := make([]byte, key.MaxEncodedSize())
|
||||||
|
bKey = bKey[:key.Encode(bKey)]
|
||||||
|
|
||||||
// Unmarshal unmarshals protobuf binary representation of ObjectContext.
|
return bytes.Equal(bKey, x.body.GetSessionKey())
|
||||||
func (x *ObjectContext) Unmarshal(data []byte) error {
|
|
||||||
return x.ToV2().Unmarshal(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON encodes ObjectContext to protobuf JSON format.
|
|
||||||
func (x *ObjectContext) MarshalJSON() ([]byte, error) {
|
|
||||||
return x.ToV2().MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON decodes ObjectContext from protobuf JSON format.
|
|
||||||
func (x *ObjectContext) UnmarshalJSON(data []byte) error {
|
|
||||||
return x.ToV2().UnmarshalJSON(data)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,123 +1,296 @@
|
||||||
package session_test
|
package session_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
v2session "github.com/nspcc-dev/neofs-api-go/v2/session"
|
||||||
|
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
|
||||||
|
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
|
||||||
|
"github.com/nspcc-dev/neofs-sdk-go/object/address"
|
||||||
addresstest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
|
addresstest "github.com/nspcc-dev/neofs-sdk-go/object/address/test"
|
||||||
"github.com/nspcc-dev/neofs-sdk-go/session"
|
"github.com/nspcc-dev/neofs-sdk-go/session"
|
||||||
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
|
sessiontest "github.com/nspcc-dev/neofs-sdk-go/session/test"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestObjectContextVerbs(t *testing.T) {
|
func randSigner() ecdsa.PrivateKey {
|
||||||
c := session.NewObjectContext()
|
k, err := keys.NewPrivateKey()
|
||||||
|
if err != nil {
|
||||||
assert := func(setter func(), getter func() bool, verb v2session.ObjectSessionVerb) {
|
panic(fmt.Sprintf("generate private key: %v", err))
|
||||||
setter()
|
|
||||||
|
|
||||||
require.True(t, getter())
|
|
||||||
|
|
||||||
require.Equal(t, verb, c.ToV2().GetVerb())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("PUT", func(t *testing.T) {
|
return k.PrivateKey
|
||||||
assert(c.ForPut, c.IsForPut, v2session.ObjectVerbPut)
|
}
|
||||||
|
|
||||||
|
func randPublicKey() neofscrypto.PublicKey {
|
||||||
|
k := randSigner().PublicKey
|
||||||
|
return (*neofsecdsa.PublicKey)(&k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObject_ReadFromV2(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
var m v2session.Token
|
||||||
|
var b v2session.TokenBody
|
||||||
|
var c v2session.ObjectSessionContext
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
t.Run("protocol violation", func(t *testing.T) {
|
||||||
|
require.Error(t, x.ReadFromV2(m))
|
||||||
|
|
||||||
|
m.SetBody(&b)
|
||||||
|
|
||||||
|
require.Error(t, x.ReadFromV2(m))
|
||||||
|
|
||||||
|
b.SetID(id[:])
|
||||||
|
|
||||||
|
require.Error(t, x.ReadFromV2(m))
|
||||||
|
|
||||||
|
b.SetContext(&c)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("DELETE", func(t *testing.T) {
|
m.SetBody(&b)
|
||||||
assert(c.ForDelete, c.IsForDelete, v2session.ObjectVerbDelete)
|
b.SetContext(&c)
|
||||||
|
b.SetID(id[:])
|
||||||
|
|
||||||
|
t.Run("object", func(t *testing.T) {
|
||||||
|
var obj address.Address
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AppliedTo(obj))
|
||||||
|
|
||||||
|
obj = *addresstest.Address()
|
||||||
|
|
||||||
|
objv2 := *obj.ToV2()
|
||||||
|
|
||||||
|
c.SetAddress(&objv2)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AppliedTo(obj))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("GET", func(t *testing.T) {
|
t.Run("verb", func(t *testing.T) {
|
||||||
assert(c.ForGet, c.IsForGet, v2session.ObjectVerbGet)
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AssertVerb(0))
|
||||||
|
|
||||||
|
verb := v2session.ObjectSessionVerb(rand.Uint32())
|
||||||
|
|
||||||
|
c.SetVerb(verb)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AssertVerb(session.ObjectVerb(verb)))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("SEARCH", func(t *testing.T) {
|
t.Run("id", func(t *testing.T) {
|
||||||
assert(c.ForSearch, c.IsForSearch, v2session.ObjectVerbSearch)
|
id := uuid.New()
|
||||||
|
bID := id[:]
|
||||||
|
|
||||||
|
b.SetID(bID)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.Equal(t, id, x.ID())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("RANGE", func(t *testing.T) {
|
t.Run("lifetime", func(t *testing.T) {
|
||||||
assert(c.ForRange, c.IsForRange, v2session.ObjectVerbRange)
|
const nbf, iat, exp = 11, 22, 33
|
||||||
|
|
||||||
|
var lt v2session.TokenLifetime
|
||||||
|
lt.SetNbf(nbf)
|
||||||
|
lt.SetIat(iat)
|
||||||
|
lt.SetExp(exp)
|
||||||
|
|
||||||
|
b.SetLifetime(<)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.False(t, x.ExpiredAt(exp-1))
|
||||||
|
require.True(t, x.ExpiredAt(exp))
|
||||||
|
require.True(t, x.ExpiredAt(exp+1))
|
||||||
|
require.True(t, x.InvalidAt(nbf-1))
|
||||||
|
require.True(t, x.InvalidAt(iat-1))
|
||||||
|
require.False(t, x.InvalidAt(iat))
|
||||||
|
require.False(t, x.InvalidAt(exp-1))
|
||||||
|
require.True(t, x.InvalidAt(exp))
|
||||||
|
require.True(t, x.InvalidAt(exp+1))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("RANGEHASH", func(t *testing.T) {
|
t.Run("session key", func(t *testing.T) {
|
||||||
assert(c.ForRangeHash, c.IsForRangeHash, v2session.ObjectVerbRangeHash)
|
key := randPublicKey()
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("HEAD", func(t *testing.T) {
|
bKey := make([]byte, key.MaxEncodedSize())
|
||||||
assert(c.ForHead, c.IsForHead, v2session.ObjectVerbHead)
|
bKey = bKey[:key.Encode(bKey)]
|
||||||
|
|
||||||
|
b.SetSessionKey(bKey)
|
||||||
|
|
||||||
|
require.NoError(t, x.ReadFromV2(m))
|
||||||
|
require.True(t, x.AssertAuthKey(key))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObjectContext_ApplyTo(t *testing.T) {
|
func TestEncodingObject(t *testing.T) {
|
||||||
c := session.NewObjectContext()
|
tok := *sessiontest.ObjectSigned()
|
||||||
id := addresstest.Address()
|
|
||||||
|
|
||||||
t.Run("method", func(t *testing.T) {
|
|
||||||
c.ApplyTo(id)
|
|
||||||
|
|
||||||
require.Equal(t, id, c.Address())
|
|
||||||
|
|
||||||
c.ApplyTo(nil)
|
|
||||||
|
|
||||||
require.Nil(t, c.Address())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObjectFilter_ToV2(t *testing.T) {
|
|
||||||
t.Run("nil", func(t *testing.T) {
|
|
||||||
var x *session.ObjectContext
|
|
||||||
|
|
||||||
require.Nil(t, x.ToV2())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("default values", func(t *testing.T) {
|
|
||||||
c := session.NewObjectContext()
|
|
||||||
|
|
||||||
// check initial values
|
|
||||||
require.Nil(t, c.Address())
|
|
||||||
|
|
||||||
for _, op := range []func() bool{
|
|
||||||
c.IsForPut,
|
|
||||||
c.IsForDelete,
|
|
||||||
c.IsForGet,
|
|
||||||
c.IsForHead,
|
|
||||||
c.IsForRange,
|
|
||||||
c.IsForRangeHash,
|
|
||||||
c.IsForSearch,
|
|
||||||
} {
|
|
||||||
require.False(t, op())
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert to v2 message
|
|
||||||
cV2 := c.ToV2()
|
|
||||||
|
|
||||||
require.Equal(t, v2session.ObjectVerbUnknown, cV2.GetVerb())
|
|
||||||
require.Nil(t, cV2.GetAddress())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObjectContextEncoding(t *testing.T) {
|
|
||||||
c := sessiontest.ObjectContext()
|
|
||||||
|
|
||||||
t.Run("binary", func(t *testing.T) {
|
t.Run("binary", func(t *testing.T) {
|
||||||
data, err := c.Marshal()
|
data := tok.Marshal()
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
c2 := session.NewObjectContext()
|
var tok2 session.Object
|
||||||
require.NoError(t, c2.Unmarshal(data))
|
require.NoError(t, tok2.Unmarshal(data))
|
||||||
|
|
||||||
require.Equal(t, c, c2)
|
require.Equal(t, tok, tok2)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("json", func(t *testing.T) {
|
t.Run("json", func(t *testing.T) {
|
||||||
data, err := c.MarshalJSON()
|
data, err := tok.MarshalJSON()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
c2 := session.NewObjectContext()
|
var tok2 session.Object
|
||||||
require.NoError(t, c2.UnmarshalJSON(data))
|
require.NoError(t, tok2.UnmarshalJSON(data))
|
||||||
|
|
||||||
require.Equal(t, c, c2)
|
require.Equal(t, tok, tok2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestObjectAppliedTo(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
a := *addresstest.Address()
|
||||||
|
|
||||||
|
require.False(t, x.AppliedTo(a))
|
||||||
|
|
||||||
|
x.ApplyTo(a)
|
||||||
|
|
||||||
|
require.True(t, x.AppliedTo(a))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectExp(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
exp := rand.Uint64()
|
||||||
|
|
||||||
|
require.True(t, x.ExpiredAt(exp))
|
||||||
|
|
||||||
|
x.SetExp(exp)
|
||||||
|
|
||||||
|
require.False(t, x.ExpiredAt(exp-1))
|
||||||
|
require.True(t, x.ExpiredAt(exp))
|
||||||
|
require.True(t, x.ExpiredAt(exp+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectLifetime(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
nbf := rand.Uint64()
|
||||||
|
if nbf == math.MaxUint64 {
|
||||||
|
nbf--
|
||||||
|
}
|
||||||
|
|
||||||
|
iat := nbf
|
||||||
|
exp := iat + 1
|
||||||
|
|
||||||
|
x.SetNbf(nbf)
|
||||||
|
x.SetIat(iat)
|
||||||
|
x.SetExp(exp)
|
||||||
|
|
||||||
|
require.True(t, x.InvalidAt(nbf-1))
|
||||||
|
require.True(t, x.InvalidAt(iat-1))
|
||||||
|
require.False(t, x.InvalidAt(iat))
|
||||||
|
require.True(t, x.InvalidAt(exp))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectID(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
require.Zero(t, x.ID())
|
||||||
|
|
||||||
|
id := uuid.New()
|
||||||
|
|
||||||
|
x.SetID(id)
|
||||||
|
|
||||||
|
require.Equal(t, id, x.ID())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectAuthKey(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
key := randPublicKey()
|
||||||
|
|
||||||
|
require.False(t, x.AssertAuthKey(key))
|
||||||
|
|
||||||
|
x.SetAuthKey(key)
|
||||||
|
|
||||||
|
require.True(t, x.AssertAuthKey(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectVerb(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
const v1, v2 = session.VerbObjectGet, session.VerbObjectPut
|
||||||
|
|
||||||
|
require.False(t, x.AssertVerb(v1, v2))
|
||||||
|
|
||||||
|
x.ForVerb(v1)
|
||||||
|
require.True(t, x.AssertVerb(v1))
|
||||||
|
require.False(t, x.AssertVerb(v2))
|
||||||
|
require.True(t, x.AssertVerb(v1, v2))
|
||||||
|
require.True(t, x.AssertVerb(v2, v1))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectSignature(t *testing.T) {
|
||||||
|
var x session.Object
|
||||||
|
|
||||||
|
const nbf = 11
|
||||||
|
const iat = 22
|
||||||
|
const exp = 33
|
||||||
|
id := uuid.New()
|
||||||
|
key := randPublicKey()
|
||||||
|
obj := *addresstest.Address()
|
||||||
|
verb := session.VerbObjectDelete
|
||||||
|
|
||||||
|
signer := randSigner()
|
||||||
|
|
||||||
|
fs := []func(){
|
||||||
|
func() { x.SetNbf(nbf) },
|
||||||
|
func() { x.SetNbf(nbf + 1) },
|
||||||
|
|
||||||
|
func() { x.SetIat(iat) },
|
||||||
|
func() { x.SetIat(iat + 1) },
|
||||||
|
|
||||||
|
func() { x.SetExp(exp) },
|
||||||
|
func() { x.SetExp(exp + 1) },
|
||||||
|
|
||||||
|
func() { x.SetID(id) },
|
||||||
|
func() {
|
||||||
|
idcp := id
|
||||||
|
idcp[0]++
|
||||||
|
x.SetID(idcp)
|
||||||
|
},
|
||||||
|
|
||||||
|
func() { x.SetAuthKey(key) },
|
||||||
|
func() { x.SetAuthKey(randPublicKey()) },
|
||||||
|
|
||||||
|
func() { x.ApplyTo(obj) },
|
||||||
|
func() { x.ApplyTo(*addresstest.Address()) },
|
||||||
|
|
||||||
|
func() { x.ForVerb(verb) },
|
||||||
|
func() { x.ForVerb(verb + 1) },
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(fs); i += 2 {
|
||||||
|
fs[i]()
|
||||||
|
|
||||||
|
require.NoError(t, x.Sign(signer))
|
||||||
|
require.True(t, x.VerifySignature())
|
||||||
|
|
||||||
|
fs[i+1]()
|
||||||
|
require.False(t, x.VerifySignature())
|
||||||
|
|
||||||
|
fs[i]()
|
||||||
|
require.True(t, x.VerifySignature())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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
13
session/test/doc.go
Normal 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
|
|
@ -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
97
session/test/session.go
Normal 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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
})
|
|
||||||
}
|
|
Loading…
Reference in a new issue