WIP: Reuse acl/v2 middleware as a new middleware context_enricher #1670

Draft
aarifullin wants to merge 6 commits from aarifullin/frostfs-node:feat/1052_factorout_acl into master
14 changed files with 20 additions and 813 deletions

View file

@ -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),
)
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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")
)

View file

@ -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

View file

@ -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
}

View file

@ -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.

View file

@ -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()