WIP: Reuse acl/v2
middleware as a new middleware context_enricher
#1670
14 changed files with 20 additions and 813 deletions
|
@ -16,9 +16,9 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache"
|
||||
objectTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/object/grpc"
|
||||
objectService "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
||||
v2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/acl/v2"
|
||||
objectAPE "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/ape"
|
||||
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
|
||||
ctxEnricher "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/context_enricher"
|
||||
deletesvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/delete"
|
||||
deletesvcV2 "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/delete/v2"
|
||||
getsvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/get"
|
||||
|
@ -174,10 +174,10 @@ func initObjectService(c *cfg) {
|
|||
|
||||
apeSvc := createAPEService(c, splitSvc)
|
||||
|
||||
aclSvc := createACLServiceV2(c, apeSvc, &irFetcher)
|
||||
contextEnricher := createContextEnricher(c, apeSvc, &irFetcher)
|
||||
|
||||
var commonSvc objectService.Common
|
||||
commonSvc.Init(&c.internals, aclSvc)
|
||||
commonSvc.Init(&c.internals, contextEnricher)
|
||||
|
||||
respSvc := objectService.NewResponseService(
|
||||
&commonSvc,
|
||||
|
@ -284,7 +284,7 @@ func addPolicer(c *cfg, keyStorage *util.KeyStorage, clientConstructor *cache.Cl
|
|||
})
|
||||
}
|
||||
|
||||
func createInnerRingFetcher(c *cfg) v2.InnerRingFetcher {
|
||||
func createInnerRingFetcher(c *cfg) ctxEnricher.InnerRingFetcher {
|
||||
return &innerRingFetcherWithNotary{
|
||||
sidechain: c.cfgMorph.client,
|
||||
}
|
||||
|
@ -429,13 +429,13 @@ func createSplitService(c *cfg, sPutV2 *putsvcV2.Service, sGetV2 *getsvcV2.Servi
|
|||
)
|
||||
}
|
||||
|
||||
func createACLServiceV2(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFetcher) v2.Service {
|
||||
return v2.New(
|
||||
func createContextEnricher(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFetcher) ctxEnricher.Service {
|
||||
return ctxEnricher.New(
|
||||
apeSvc,
|
||||
c.netMapSource,
|
||||
irFetcher,
|
||||
c.cfgObject.cnrSource,
|
||||
v2.WithLogger(c.log),
|
||||
ctxEnricher.WithLogger(c.log),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testLocalStorage struct {
|
||||
t *testing.T
|
||||
|
||||
expAddr oid.Address
|
||||
|
||||
obj *objectSDK.Object
|
||||
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *testLocalStorage) Head(ctx context.Context, addr oid.Address) (*objectSDK.Object, error) {
|
||||
require.True(s.t, addr.Container().Equals(s.expAddr.Container()))
|
||||
require.True(s.t, addr.Object().Equals(s.expAddr.Object()))
|
||||
|
||||
return s.obj, s.err
|
||||
}
|
||||
|
||||
func testXHeaders(strs ...string) []session.XHeader {
|
||||
res := make([]session.XHeader, len(strs)/2)
|
||||
|
||||
for i := 0; i < len(strs); i += 2 {
|
||||
res[i/2].SetKey(strs[i])
|
||||
res[i/2].SetValue(strs[i+1])
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func TestHeadRequest(t *testing.T) {
|
||||
req := new(objectV2.HeadRequest)
|
||||
|
||||
meta := new(session.RequestMetaHeader)
|
||||
req.SetMetaHeader(meta)
|
||||
|
||||
body := new(objectV2.HeadRequestBody)
|
||||
req.SetBody(body)
|
||||
|
||||
addr := oidtest.Address()
|
||||
|
||||
var addrV2 refs.Address
|
||||
addr.WriteToV2(&addrV2)
|
||||
|
||||
body.SetAddress(&addrV2)
|
||||
|
||||
xKey := "x-key"
|
||||
xVal := "x-val"
|
||||
xHdrs := testXHeaders(
|
||||
xKey, xVal,
|
||||
)
|
||||
|
||||
meta.SetXHeaders(xHdrs)
|
||||
|
||||
obj := objectSDK.New()
|
||||
|
||||
attrKey := "attr_key"
|
||||
attrVal := "attr_val"
|
||||
var attr objectSDK.Attribute
|
||||
attr.SetKey(attrKey)
|
||||
attr.SetValue(attrVal)
|
||||
obj.SetAttributes(attr)
|
||||
|
||||
table := new(eaclSDK.Table)
|
||||
|
||||
priv, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
senderKey := priv.PublicKey()
|
||||
|
||||
r := eaclSDK.NewRecord()
|
||||
r.SetOperation(eaclSDK.OperationHead)
|
||||
r.SetAction(eaclSDK.ActionDeny)
|
||||
r.AddFilter(eaclSDK.HeaderFromObject, eaclSDK.MatchStringEqual, attrKey, attrVal)
|
||||
r.AddFilter(eaclSDK.HeaderFromRequest, eaclSDK.MatchStringEqual, xKey, xVal)
|
||||
eaclSDK.AddFormedTarget(r, eaclSDK.RoleUnknown, (ecdsa.PublicKey)(*senderKey))
|
||||
|
||||
table.AddRecord(r)
|
||||
|
||||
lStorage := &testLocalStorage{
|
||||
t: t,
|
||||
expAddr: addr,
|
||||
obj: obj,
|
||||
}
|
||||
|
||||
id := addr.Object()
|
||||
|
||||
newSource := func(t *testing.T) eaclSDK.TypedHeaderSource {
|
||||
hdrSrc, err := NewMessageHeaderSource(
|
||||
lStorage,
|
||||
NewRequestXHeaderSource(req),
|
||||
addr.Container(),
|
||||
WithOID(&id))
|
||||
require.NoError(t, err)
|
||||
return hdrSrc
|
||||
}
|
||||
|
||||
cnr := addr.Container()
|
||||
|
||||
unit := new(eaclSDK.ValidationUnit).
|
||||
WithContainerID(&cnr).
|
||||
WithOperation(eaclSDK.OperationHead).
|
||||
WithSenderKey(senderKey.Bytes()).
|
||||
WithEACLTable(table)
|
||||
|
||||
validator := eaclSDK.NewValidator()
|
||||
|
||||
checkAction(t, eaclSDK.ActionDeny, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
meta.SetXHeaders(nil)
|
||||
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
meta.SetXHeaders(xHdrs)
|
||||
|
||||
obj.SetAttributes()
|
||||
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
lStorage.err = errors.New("any error")
|
||||
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
|
||||
r.SetAction(eaclSDK.ActionAllow)
|
||||
|
||||
rID := eaclSDK.NewRecord()
|
||||
rID.SetOperation(eaclSDK.OperationHead)
|
||||
rID.SetAction(eaclSDK.ActionDeny)
|
||||
rID.AddObjectIDFilter(eaclSDK.MatchStringEqual, addr.Object())
|
||||
eaclSDK.AddFormedTarget(rID, eaclSDK.RoleUnknown, (ecdsa.PublicKey)(*senderKey))
|
||||
|
||||
table = eaclSDK.NewTable()
|
||||
table.AddRecord(r)
|
||||
table.AddRecord(rID)
|
||||
|
||||
unit.WithEACLTable(table)
|
||||
checkDefaultAction(t, validator, unit.WithHeaderSource(newSource(t)))
|
||||
}
|
||||
|
||||
func checkAction(t *testing.T, expected eaclSDK.Action, v *eaclSDK.Validator, u *eaclSDK.ValidationUnit) {
|
||||
actual, fromRule := v.CalculateAction(u)
|
||||
require.True(t, fromRule)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func checkDefaultAction(t *testing.T, v *eaclSDK.Validator, u *eaclSDK.ValidationUnit) {
|
||||
actual, fromRule := v.CalculateAction(u)
|
||||
require.False(t, fromRule)
|
||||
require.Equal(t, eaclSDK.ActionAllow, actual)
|
||||
}
|
|
@ -1,246 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
||||
refsV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
)
|
||||
|
||||
type Option func(*cfg)
|
||||
|
||||
type cfg struct {
|
||||
storage ObjectStorage
|
||||
|
||||
msg XHeaderSource
|
||||
|
||||
cnr cid.ID
|
||||
obj *oid.ID
|
||||
}
|
||||
|
||||
type ObjectStorage interface {
|
||||
Head(context.Context, oid.Address) (*objectSDK.Object, error)
|
||||
}
|
||||
|
||||
type Request interface {
|
||||
GetMetaHeader() *session.RequestMetaHeader
|
||||
}
|
||||
|
||||
type Response interface {
|
||||
GetMetaHeader() *session.ResponseMetaHeader
|
||||
}
|
||||
|
||||
type headerSource struct {
|
||||
requestHeaders []eaclSDK.Header
|
||||
objectHeaders []eaclSDK.Header
|
||||
|
||||
incompleteObjectHeaders bool
|
||||
}
|
||||
|
||||
func NewMessageHeaderSource(os ObjectStorage, xhs XHeaderSource, cnrID cid.ID, opts ...Option) (eaclSDK.TypedHeaderSource, error) {
|
||||
cfg := &cfg{
|
||||
storage: os,
|
||||
cnr: cnrID,
|
||||
msg: xhs,
|
||||
}
|
||||
|
||||
for i := range opts {
|
||||
opts[i](cfg)
|
||||
}
|
||||
|
||||
if cfg.msg == nil {
|
||||
return nil, errors.New("message is not provided")
|
||||
}
|
||||
|
||||
var res headerSource
|
||||
|
||||
err := cfg.readObjectHeaders(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.requestHeaders = cfg.msg.GetXHeaders()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h headerSource) HeadersOfType(typ eaclSDK.FilterHeaderType) ([]eaclSDK.Header, bool) {
|
||||
switch typ {
|
||||
default:
|
||||
return nil, true
|
||||
case eaclSDK.HeaderFromRequest:
|
||||
return h.requestHeaders, true
|
||||
case eaclSDK.HeaderFromObject:
|
||||
return h.objectHeaders, !h.incompleteObjectHeaders
|
||||
}
|
||||
}
|
||||
|
||||
type xHeader session.XHeader
|
||||
|
||||
func (x xHeader) Key() string {
|
||||
return (*session.XHeader)(&x).GetKey()
|
||||
}
|
||||
|
||||
func (x xHeader) Value() string {
|
||||
return (*session.XHeader)(&x).GetValue()
|
||||
}
|
||||
|
||||
var errMissingOID = errors.New("object ID is missing")
|
||||
|
||||
func (h *cfg) readObjectHeaders(dst *headerSource) error {
|
||||
switch m := h.msg.(type) {
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected message type %T", h.msg))
|
||||
case requestXHeaderSource:
|
||||
return h.readObjectHeadersFromRequestXHeaderSource(m, dst)
|
||||
case responseXHeaderSource:
|
||||
return h.readObjectHeadersResponseXHeaderSource(m, dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *cfg) readObjectHeadersFromRequestXHeaderSource(m requestXHeaderSource, dst *headerSource) error {
|
||||
switch req := m.req.(type) {
|
||||
case
|
||||
*objectV2.GetRequest,
|
||||
*objectV2.HeadRequest:
|
||||
if h.obj == nil {
|
||||
return errMissingOID
|
||||
}
|
||||
|
||||
objHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
||||
|
||||
dst.objectHeaders = objHeaders
|
||||
dst.incompleteObjectHeaders = !completed
|
||||
case
|
||||
*objectV2.GetRangeRequest,
|
||||
*objectV2.GetRangeHashRequest,
|
||||
*objectV2.DeleteRequest:
|
||||
if h.obj == nil {
|
||||
return errMissingOID
|
||||
}
|
||||
|
||||
dst.objectHeaders = addressHeaders(h.cnr, h.obj)
|
||||
case *objectV2.PutRequest:
|
||||
if v, ok := req.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit); ok {
|
||||
oV2 := new(objectV2.Object)
|
||||
oV2.SetObjectID(v.GetObjectID())
|
||||
oV2.SetHeader(v.GetHeader())
|
||||
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
case *objectV2.PutSingleRequest:
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(req.GetBody().GetObject()), h.cnr, h.obj)
|
||||
case *objectV2.SearchRequest:
|
||||
cnrV2 := req.GetBody().GetContainerID()
|
||||
var cnr cid.ID
|
||||
|
||||
if cnrV2 != nil {
|
||||
if err := cnr.ReadFromV2(*cnrV2); err != nil {
|
||||
return fmt.Errorf("can't parse container ID: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
dst.objectHeaders = []eaclSDK.Header{cidHeader(cnr)}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *cfg) readObjectHeadersResponseXHeaderSource(m responseXHeaderSource, dst *headerSource) error {
|
||||
switch resp := m.resp.(type) {
|
||||
default:
|
||||
objectHeaders, completed := h.localObjectHeaders(h.cnr, h.obj)
|
||||
|
||||
dst.objectHeaders = objectHeaders
|
||||
dst.incompleteObjectHeaders = !completed
|
||||
case *objectV2.GetResponse:
|
||||
if v, ok := resp.GetBody().GetObjectPart().(*objectV2.GetObjectPartInit); ok {
|
||||
oV2 := new(objectV2.Object)
|
||||
oV2.SetObjectID(v.GetObjectID())
|
||||
oV2.SetHeader(v.GetHeader())
|
||||
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
case *objectV2.HeadResponse:
|
||||
oV2 := new(objectV2.Object)
|
||||
|
||||
var hdr *objectV2.Header
|
||||
|
||||
switch v := resp.GetBody().GetHeaderPart().(type) {
|
||||
case *objectV2.ShortHeader:
|
||||
hdr = new(objectV2.Header)
|
||||
|
||||
var idV2 refsV2.ContainerID
|
||||
h.cnr.WriteToV2(&idV2)
|
||||
|
||||
hdr.SetContainerID(&idV2)
|
||||
hdr.SetVersion(v.GetVersion())
|
||||
hdr.SetCreationEpoch(v.GetCreationEpoch())
|
||||
hdr.SetOwnerID(v.GetOwnerID())
|
||||
hdr.SetObjectType(v.GetObjectType())
|
||||
hdr.SetPayloadLength(v.GetPayloadLength())
|
||||
case *objectV2.HeaderWithSignature:
|
||||
hdr = v.GetHeader()
|
||||
}
|
||||
|
||||
oV2.SetHeader(hdr)
|
||||
|
||||
dst.objectHeaders = headersFromObject(objectSDK.NewFromV2(oV2), h.cnr, h.obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *cfg) localObjectHeaders(cnr cid.ID, idObj *oid.ID) ([]eaclSDK.Header, bool) {
|
||||
if idObj != nil {
|
||||
var addr oid.Address
|
||||
addr.SetContainer(cnr)
|
||||
addr.SetObject(*idObj)
|
||||
|
||||
obj, err := h.storage.Head(context.TODO(), addr)
|
||||
if err == nil {
|
||||
return headersFromObject(obj, cnr, idObj), true
|
||||
}
|
||||
}
|
||||
|
||||
return addressHeaders(cnr, idObj), false
|
||||
}
|
||||
|
||||
func cidHeader(idCnr cid.ID) sysObjHdr {
|
||||
return sysObjHdr{
|
||||
k: acl.FilterObjectContainerID,
|
||||
v: idCnr.EncodeToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func oidHeader(obj oid.ID) sysObjHdr {
|
||||
return sysObjHdr{
|
||||
k: acl.FilterObjectID,
|
||||
v: obj.EncodeToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func ownerIDHeader(ownerID user.ID) sysObjHdr {
|
||||
return sysObjHdr{
|
||||
k: acl.FilterObjectOwnerID,
|
||||
v: ownerID.EncodeToString(),
|
||||
}
|
||||
}
|
||||
|
||||
func addressHeaders(cnr cid.ID, oid *oid.ID) []eaclSDK.Header {
|
||||
hh := make([]eaclSDK.Header, 0, 2)
|
||||
hh = append(hh, cidHeader(cnr))
|
||||
|
||||
if oid != nil {
|
||||
hh = append(hh, oidHeader(*oid))
|
||||
}
|
||||
|
||||
return hh
|
||||
}
|
|
@ -1,92 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
)
|
||||
|
||||
type sysObjHdr struct {
|
||||
k, v string
|
||||
}
|
||||
|
||||
func (s sysObjHdr) Key() string {
|
||||
return s.k
|
||||
}
|
||||
|
||||
func (s sysObjHdr) Value() string {
|
||||
return s.v
|
||||
}
|
||||
|
||||
func u64Value(v uint64) string {
|
||||
return strconv.FormatUint(v, 10)
|
||||
}
|
||||
|
||||
func headersFromObject(obj *objectSDK.Object, cnr cid.ID, oid *oid.ID) []eaclSDK.Header {
|
||||
var count int
|
||||
for obj := obj; obj != nil; obj = obj.Parent() {
|
||||
count += 9 + len(obj.Attributes())
|
||||
}
|
||||
|
||||
res := make([]eaclSDK.Header, 0, count)
|
||||
for ; obj != nil; obj = obj.Parent() {
|
||||
res = append(res,
|
||||
cidHeader(cnr),
|
||||
// creation epoch
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectCreationEpoch,
|
||||
v: u64Value(obj.CreationEpoch()),
|
||||
},
|
||||
// payload size
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectPayloadLength,
|
||||
v: u64Value(obj.PayloadSize()),
|
||||
},
|
||||
// object version
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectVersion,
|
||||
v: obj.Version().String(),
|
||||
},
|
||||
// object type
|
||||
sysObjHdr{
|
||||
k: acl.FilterObjectType,
|
||||
v: obj.Type().String(),
|
||||
},
|
||||
)
|
||||
|
||||
if oid != nil {
|
||||
res = append(res, oidHeader(*oid))
|
||||
}
|
||||
|
||||
if idOwner := obj.OwnerID(); !idOwner.IsEmpty() {
|
||||
res = append(res, ownerIDHeader(idOwner))
|
||||
}
|
||||
|
||||
cs, ok := obj.PayloadChecksum()
|
||||
if ok {
|
||||
res = append(res, sysObjHdr{
|
||||
k: acl.FilterObjectPayloadHash,
|
||||
v: cs.String(),
|
||||
})
|
||||
}
|
||||
|
||||
cs, ok = obj.PayloadHomomorphicHash()
|
||||
if ok {
|
||||
res = append(res, sysObjHdr{
|
||||
k: acl.FilterObjectHomomorphicHash,
|
||||
v: cs.String(),
|
||||
})
|
||||
}
|
||||
|
||||
attrs := obj.Attributes()
|
||||
for i := range attrs {
|
||||
res = append(res, &attrs[i]) // only pointer attrs can implement eaclSDK.Header interface
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
)
|
||||
|
||||
func WithOID(v *oid.ID) Option {
|
||||
return func(c *cfg) {
|
||||
c.obj = v
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
package v2
|
||||
|
||||
import (
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
eaclSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl"
|
||||
)
|
||||
|
||||
type XHeaderSource interface {
|
||||
GetXHeaders() []eaclSDK.Header
|
||||
}
|
||||
|
||||
type requestXHeaderSource struct {
|
||||
req Request
|
||||
}
|
||||
|
||||
func NewRequestXHeaderSource(req Request) XHeaderSource {
|
||||
return requestXHeaderSource{req: req}
|
||||
}
|
||||
|
||||
type responseXHeaderSource struct {
|
||||
resp Response
|
||||
|
||||
req Request
|
||||
}
|
||||
|
||||
func NewResponseXHeaderSource(resp Response, req Request) XHeaderSource {
|
||||
return responseXHeaderSource{resp: resp, req: req}
|
||||
}
|
||||
|
||||
func (s requestXHeaderSource) GetXHeaders() []eaclSDK.Header {
|
||||
ln := 0
|
||||
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
ln += len(meta.GetXHeaders())
|
||||
}
|
||||
|
||||
res := make([]eaclSDK.Header, 0, ln)
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
x := meta.GetXHeaders()
|
||||
for i := range x {
|
||||
res = append(res, (xHeader)(x[i]))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s responseXHeaderSource) GetXHeaders() []eaclSDK.Header {
|
||||
ln := 0
|
||||
xHdrs := make([][]session.XHeader, 0)
|
||||
|
||||
for meta := s.req.GetMetaHeader(); meta != nil; meta = meta.GetOrigin() {
|
||||
x := meta.GetXHeaders()
|
||||
|
||||
ln += len(x)
|
||||
|
||||
xHdrs = append(xHdrs, x)
|
||||
}
|
||||
|
||||
res := make([]eaclSDK.Header, 0, ln)
|
||||
|
||||
for i := range xHdrs {
|
||||
for j := range xHdrs[i] {
|
||||
res = append(res, xHeader(xHdrs[i][j]))
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
|
@ -16,5 +16,4 @@ var (
|
|||
errEmptyBodySig = malformedRequestError("empty at body signature")
|
||||
errInvalidSessionSig = malformedRequestError("invalid session token signature")
|
||||
errInvalidSessionOwner = malformedRequestError("invalid session token owner")
|
||||
errInvalidVerb = malformedRequestError("session token verb is invalid")
|
||||
)
|
|
@ -7,8 +7,6 @@ import (
|
|||
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
|
@ -17,29 +15,15 @@ import (
|
|||
// RequestInfo groups parsed version-independent (from SDK library)
|
||||
// request information and raw API request.
|
||||
type RequestInfo struct {
|
||||
basicACL acl.Basic
|
||||
requestRole acl.Role
|
||||
operation acl.Op // put, get, head, etc.
|
||||
cnrOwner user.ID // container owner
|
||||
|
||||
// cnrNamespace defined to which namespace a container is belonged.
|
||||
cnrNamespace string
|
||||
|
||||
idCnr cid.ID
|
||||
|
||||
// optional for some request
|
||||
// e.g. Put, Search
|
||||
obj *oid.ID
|
||||
|
||||
senderKey []byte
|
||||
|
||||
bearer *bearer.Token // bearer token of request
|
||||
|
||||
srcRequest any
|
||||
}
|
||||
|
||||
func (r *RequestInfo) SetBasicACL(basicACL acl.Basic) {
|
||||
r.basicACL = basicACL
|
||||
}
|
||||
|
||||
func (r *RequestInfo) SetRequestRole(requestRole acl.Role) {
|
||||
|
@ -50,11 +34,6 @@ func (r *RequestInfo) SetSenderKey(senderKey []byte) {
|
|||
r.senderKey = senderKey
|
||||
}
|
||||
|
||||
// Request returns raw API request.
|
||||
func (r RequestInfo) Request() any {
|
||||
return r.srcRequest
|
||||
}
|
||||
|
||||
// ContainerOwner returns owner if the container.
|
||||
func (r RequestInfo) ContainerOwner() user.ID {
|
||||
return r.cnrOwner
|
||||
|
@ -64,16 +43,6 @@ func (r RequestInfo) ContainerNamespace() string {
|
|||
return r.cnrNamespace
|
||||
}
|
||||
|
||||
// ObjectID return object ID.
|
||||
func (r RequestInfo) ObjectID() *oid.ID {
|
||||
return r.obj
|
||||
}
|
||||
|
||||
// ContainerID return container ID.
|
||||
func (r RequestInfo) ContainerID() cid.ID {
|
||||
return r.idCnr
|
||||
}
|
||||
|
||||
// CleanBearer forces cleaning bearer token information.
|
||||
func (r *RequestInfo) CleanBearer() {
|
||||
r.bearer = nil
|
||||
|
@ -84,21 +53,11 @@ func (r RequestInfo) Bearer() *bearer.Token {
|
|||
return r.bearer
|
||||
}
|
||||
|
||||
// BasicACL returns basic ACL of the container.
|
||||
func (r RequestInfo) BasicACL() acl.Basic {
|
||||
return r.basicACL
|
||||
}
|
||||
|
||||
// SenderKey returns public key of the request's sender.
|
||||
func (r RequestInfo) SenderKey() []byte {
|
||||
return r.senderKey
|
||||
}
|
||||
|
||||
// Operation returns request's operation.
|
||||
func (r RequestInfo) Operation() acl.Op {
|
||||
return r.operation
|
||||
}
|
||||
|
||||
// RequestRole returns request sender's role.
|
||||
func (r RequestInfo) RequestRole() acl.Role {
|
||||
return r.requestRole
|
||||
|
@ -110,7 +69,6 @@ type MetaWithToken struct {
|
|||
vheader *sessionV2.RequestVerificationHeader
|
||||
token *sessionSDK.Object
|
||||
bearer *bearer.Token
|
||||
src any
|
||||
}
|
||||
|
||||
// RequestOwner returns ownerID and its public key
|
|
@ -15,7 +15,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
||||
cnrSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
|
@ -23,7 +22,8 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Service checks basic ACL rules.
|
||||
// Service enriches a request context with values that are retrieved only once,
|
||||
// allowing these values to be used in subsequent middlewares.
|
||||
type Service struct {
|
||||
*cfg
|
||||
|
||||
|
@ -56,7 +56,7 @@ type cfg struct {
|
|||
next object.ServiceServer
|
||||
}
|
||||
|
||||
// New is a constructor for object ACL checking service.
|
||||
// New is a constructor for context enricher service.
|
||||
func New(next object.ServiceServer,
|
||||
nm netmap.Source,
|
||||
irf InnerRingFetcher,
|
||||
|
@ -190,16 +190,13 @@ func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr, acl.OpObjectGet)
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.Get(request, newWrappedGetObjectStreamStream(stream, reqInfo))
|
||||
}
|
||||
|
||||
|
@ -256,16 +253,13 @@ func (b Service) Head(
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectHead)
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.Head(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
|
@ -296,10 +290,9 @@ func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStr
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, id, acl.OpObjectSearch)
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -342,16 +335,13 @@ func (b Service) Delete(
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectDelete)
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.Delete(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
|
@ -387,16 +377,13 @@ func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetOb
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr, acl.OpObjectRange)
|
||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.GetRange(request, newWrappedRangeStream(stream, reqInfo))
|
||||
}
|
||||
|
||||
|
@ -445,16 +432,13 @@ func (b Service) GetRangeHash(
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectHash)
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.GetRangeHash(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
|
@ -496,16 +480,13 @@ func (b Service) PutSingle(ctx context.Context, request *objectV2.PutSingleReque
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectPut)
|
||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
return b.next.PutSingle(requestContext(ctx, reqInfo), request)
|
||||
}
|
||||
|
||||
|
@ -561,16 +542,13 @@ func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRe
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := p.source.findRequestInfo(ctx, req, cnr, acl.OpObjectPut)
|
||||
reqInfo, err := p.source.findRequestInfo(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
ctx = requestContext(ctx, reqInfo)
|
||||
}
|
||||
|
||||
|
@ -648,16 +626,13 @@ func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.Pa
|
|||
vheader: request.GetVerificationHeader(),
|
||||
token: sTok,
|
||||
bearer: bTok,
|
||||
src: request,
|
||||
}
|
||||
|
||||
reqInfo, err := p.source.findRequestInfoWithoutACLOperationAssert(ctx, req, cnr)
|
||||
reqInfo, err := p.source.findRequestInfo(ctx, req, cnr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reqInfo.obj = obj
|
||||
|
||||
ctx = requestContext(ctx, reqInfo)
|
||||
}
|
||||
|
||||
|
@ -668,65 +643,7 @@ func (p patchStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.Pa
|
|||
return p.next.CloseAndRecv(ctx)
|
||||
}
|
||||
|
||||
func (b Service) findRequestInfo(ctx context.Context, req MetaWithToken, idCnr cid.ID, op acl.Op) (info RequestInfo, err error) {
|
||||
cnr, err := b.containers.Get(ctx, idCnr) // fetch actual container
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if req.token != nil {
|
||||
currentEpoch, err := b.nm.Epoch(ctx)
|
||||
if err != nil {
|
||||
return info, errors.New("can't fetch current epoch")
|
||||
}
|
||||
if req.token.ExpiredAt(currentEpoch) {
|
||||
return info, new(apistatus.SessionTokenExpired)
|
||||
}
|
||||
if req.token.InvalidAt(currentEpoch) {
|
||||
return info, fmt.Errorf("%s: token is invalid at %d epoch)",
|
||||
invalidRequestMessage, currentEpoch)
|
||||
}
|
||||
|
||||
if !assertVerb(*req.token, op) {
|
||||
return info, errInvalidVerb
|
||||
}
|
||||
}
|
||||
|
||||
// find request role and key
|
||||
ownerID, ownerKey, err := req.RequestOwner()
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
res, err := b.c.Classify(ctx, ownerID, ownerKey, idCnr, cnr.Value)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
info.basicACL = cnr.Value.BasicACL()
|
||||
info.requestRole = res.Role
|
||||
info.operation = op
|
||||
info.cnrOwner = cnr.Value.Owner()
|
||||
info.idCnr = idCnr
|
||||
|
||||
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
|
||||
if hasNamespace {
|
||||
info.cnrNamespace = cnrNamespace
|
||||
}
|
||||
|
||||
// it is assumed that at the moment the key will be valid,
|
||||
// otherwise the request would not pass validation
|
||||
info.senderKey = res.Key
|
||||
|
||||
// add bearer token if it is present in request
|
||||
info.bearer = req.bearer
|
||||
|
||||
info.srcRequest = req.src
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// findRequestInfoWithoutACLOperationAssert is findRequestInfo without session token verb assert.
|
||||
func (b Service) findRequestInfoWithoutACLOperationAssert(ctx context.Context, req MetaWithToken, idCnr cid.ID) (info RequestInfo, err error) {
|
||||
func (b Service) findRequestInfo(ctx context.Context, req MetaWithToken, idCnr cid.ID) (info RequestInfo, err error) {
|
||||
cnr, err := b.containers.Get(ctx, idCnr) // fetch actual container
|
||||
if err != nil {
|
||||
return info, err
|
||||
|
@ -756,10 +673,8 @@ func (b Service) findRequestInfoWithoutACLOperationAssert(ctx context.Context, r
|
|||
return info, err
|
||||
}
|
||||
|
||||
info.basicACL = cnr.Value.BasicACL()
|
||||
info.requestRole = res.Role
|
||||
info.cnrOwner = cnr.Value.Owner()
|
||||
info.idCnr = idCnr
|
||||
|
||||
cnrNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(cnr.Value).Zone(), ".ns")
|
||||
if hasNamespace {
|
||||
|
@ -773,7 +688,5 @@ func (b Service) findRequestInfoWithoutACLOperationAssert(ctx context.Context, r
|
|||
// add bearer token if it is present in request
|
||||
info.bearer = req.bearer
|
||||
|
||||
info.srcRequest = req.src
|
||||
|
||||
return info, nil
|
||||
}
|
|
@ -10,7 +10,6 @@ import (
|
|||
refsV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
||||
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
|
@ -172,35 +171,6 @@ func isOwnerFromKey(id user.ID, key *keys.PublicKey) bool {
|
|||
return id2.Equals(id)
|
||||
}
|
||||
|
||||
// assertVerb checks that token verb corresponds to op.
|
||||
func assertVerb(tok sessionSDK.Object, op acl.Op) bool {
|
||||
switch op {
|
||||
case acl.OpObjectPut:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete, sessionSDK.VerbObjectPatch)
|
||||
case acl.OpObjectDelete:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectDelete)
|
||||
case acl.OpObjectGet:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectGet)
|
||||
case acl.OpObjectHead:
|
||||
return tok.AssertVerb(
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectPatch,
|
||||
)
|
||||
case acl.OpObjectSearch:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete)
|
||||
case acl.OpObjectRange:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash, sessionSDK.VerbObjectPatch)
|
||||
case acl.OpObjectHash:
|
||||
return tok.AssertVerb(sessionSDK.VerbObjectRangeHash)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// assertSessionRelation checks if given token describing the FrostFS session
|
||||
// relates to the given container and optional object. Missing object
|
||||
// means that the context isn't bound to any FrostFS object in the container.
|
|
@ -9,7 +9,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/acl"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
||||
bearertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer/test"
|
||||
aclsdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
||||
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
|
@ -59,54 +58,6 @@ func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.Token)
|
|||
return metaHeader
|
||||
}
|
||||
|
||||
func TestIsVerbCompatible(t *testing.T) {
|
||||
// Source: https://nspcc.ru/upload/frostfs-spec-latest.pdf#page=28
|
||||
table := map[aclsdk.Op][]sessionSDK.ObjectVerb{
|
||||
aclsdk.OpObjectPut: {sessionSDK.VerbObjectPut, sessionSDK.VerbObjectDelete},
|
||||
aclsdk.OpObjectDelete: {sessionSDK.VerbObjectDelete},
|
||||
aclsdk.OpObjectGet: {sessionSDK.VerbObjectGet},
|
||||
aclsdk.OpObjectHead: {
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
},
|
||||
aclsdk.OpObjectRange: {sessionSDK.VerbObjectRange, sessionSDK.VerbObjectRangeHash},
|
||||
aclsdk.OpObjectHash: {sessionSDK.VerbObjectRangeHash},
|
||||
aclsdk.OpObjectSearch: {sessionSDK.VerbObjectSearch, sessionSDK.VerbObjectDelete},
|
||||
}
|
||||
|
||||
verbs := []sessionSDK.ObjectVerb{
|
||||
sessionSDK.VerbObjectPut,
|
||||
sessionSDK.VerbObjectDelete,
|
||||
sessionSDK.VerbObjectHead,
|
||||
sessionSDK.VerbObjectRange,
|
||||
sessionSDK.VerbObjectRangeHash,
|
||||
sessionSDK.VerbObjectGet,
|
||||
sessionSDK.VerbObjectSearch,
|
||||
}
|
||||
|
||||
var tok sessionSDK.Object
|
||||
|
||||
for op, list := range table {
|
||||
for _, verb := range verbs {
|
||||
var contains bool
|
||||
for _, v := range list {
|
||||
if v == verb {
|
||||
contains = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tok.ForVerb(verb)
|
||||
|
||||
require.Equal(t, contains, assertVerb(tok, op),
|
||||
"%v in token, %s executing", verb, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssertSessionRelation(t *testing.T) {
|
||||
var tok sessionSDK.Object
|
||||
cnr := cidtest.ID()
|
Loading…
Add table
Reference in a new issue