[#1052] object: Nuke out acl
middleware
Some checks failed
Tests and linters / Run gofumpt (push) Successful in 1m43s
Build / Build Components (push) Successful in 2m3s
Pre-commit hooks / Pre-commit (push) Successful in 2m12s
Tests and linters / Lint (push) Successful in 2m48s
Tests and linters / Staticcheck (push) Successful in 2m47s
Tests and linters / Tests (push) Successful in 1m36s
Tests and linters / gopls check (push) Successful in 4m43s
Tests and linters / Tests with -race (push) Successful in 5m16s
Vulncheck / Vulncheck (push) Failing after 11m48s
OCI image / Build container images (push) Successful in 4m32s
Some checks failed
Tests and linters / Run gofumpt (push) Successful in 1m43s
Build / Build Components (push) Successful in 2m3s
Pre-commit hooks / Pre-commit (push) Successful in 2m12s
Tests and linters / Lint (push) Successful in 2m48s
Tests and linters / Staticcheck (push) Successful in 2m47s
Tests and linters / Tests (push) Successful in 1m36s
Tests and linters / gopls check (push) Successful in 4m43s
Tests and linters / Tests with -race (push) Successful in 5m16s
Vulncheck / Vulncheck (push) Failing after 11m48s
OCI image / Build container images (push) Successful in 4m32s
* Remove `acl` package as it's no longer used; * Remove `RequestContext`; * Fix `cmd/frostfs-node`. Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
73e35bc885
commit
ccdd6cb767
15 changed files with 2 additions and 2115 deletions
|
@ -16,7 +16,6 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/cache"
|
||||||
objectTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/object/grpc"
|
objectTransportGRPC "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/network/transport/object/grpc"
|
||||||
objectService "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
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"
|
objectAPE "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/ape"
|
||||||
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
|
objectwriter "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/common/writer"
|
||||||
deletesvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/delete"
|
deletesvc "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object/delete"
|
||||||
|
@ -174,10 +173,8 @@ func initObjectService(c *cfg) {
|
||||||
|
|
||||||
apeSvc := createAPEService(c, &irFetcher, splitSvc)
|
apeSvc := createAPEService(c, &irFetcher, splitSvc)
|
||||||
|
|
||||||
aclSvc := createACLServiceV2(c, apeSvc, &irFetcher)
|
|
||||||
|
|
||||||
var commonSvc objectService.Common
|
var commonSvc objectService.Common
|
||||||
commonSvc.Init(&c.internals, aclSvc)
|
commonSvc.Init(&c.internals, apeSvc)
|
||||||
|
|
||||||
respSvc := objectService.NewResponseService(
|
respSvc := objectService.NewResponseService(
|
||||||
&commonSvc,
|
&commonSvc,
|
||||||
|
@ -284,7 +281,7 @@ func addPolicer(c *cfg, keyStorage *util.KeyStorage, clientConstructor *cache.Cl
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createInnerRingFetcher(c *cfg) v2.InnerRingFetcher {
|
func createInnerRingFetcher(c *cfg) objectAPE.InnerRingFetcher {
|
||||||
return &innerRingFetcherWithNotary{
|
return &innerRingFetcherWithNotary{
|
||||||
sidechain: c.cfgMorph.client,
|
sidechain: c.cfgMorph.client,
|
||||||
}
|
}
|
||||||
|
@ -429,16 +426,6 @@ func createSplitService(c *cfg, sPutV2 *putsvcV2.Service, sGetV2 *getsvcV2.Servi
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createACLServiceV2(c *cfg, apeSvc *objectAPE.Service, irFetcher *cachedIRFetcher) v2.Service {
|
|
||||||
return v2.New(
|
|
||||||
apeSvc,
|
|
||||||
c.netMapSource,
|
|
||||||
irFetcher,
|
|
||||||
c.cfgObject.cnrSource,
|
|
||||||
v2.WithLogger(c.log),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createAPEService(c *cfg, irFetcher *cachedIRFetcher, splitSvc *objectService.TransportSplitter) *objectAPE.Service {
|
func createAPEService(c *cfg, irFetcher *cachedIRFetcher, splitSvc *objectService.TransportSplitter) *objectAPE.Service {
|
||||||
return objectAPE.NewService(
|
return objectAPE.NewService(
|
||||||
objectAPE.NewChecker(
|
objectAPE.NewChecker(
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const invalidRequestMessage = "malformed request"
|
|
||||||
|
|
||||||
func malformedRequestError(reason string) error {
|
|
||||||
return fmt.Errorf("%s: %s", invalidRequestMessage, reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
errEmptyBody = malformedRequestError("empty body")
|
|
||||||
errEmptyVerificationHeader = malformedRequestError("empty verification header")
|
|
||||||
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")
|
|
||||||
)
|
|
|
@ -1,12 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WithLogger returns option to set logger.
|
|
||||||
func WithLogger(v *logger.Logger) Option {
|
|
||||||
return func(c *cfg) {
|
|
||||||
c.log = v
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
r.requestRole = requestRole
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bearer returns bearer token of the request.
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// MetaWithToken groups session and bearer tokens,
|
|
||||||
// verification header and raw API request.
|
|
||||||
type MetaWithToken struct {
|
|
||||||
vheader *sessionV2.RequestVerificationHeader
|
|
||||||
token *sessionSDK.Object
|
|
||||||
bearer *bearer.Token
|
|
||||||
src any
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestOwner returns ownerID and its public key
|
|
||||||
// according to internal meta information.
|
|
||||||
func (r MetaWithToken) RequestOwner() (*user.ID, *keys.PublicKey, error) {
|
|
||||||
if r.vheader == nil {
|
|
||||||
return nil, nil, errEmptyVerificationHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.bearer != nil && r.bearer.Impersonate() {
|
|
||||||
return unmarshalPublicKeyWithOwner(r.bearer.SigningKeyBytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
// if session token is presented, use it as truth source
|
|
||||||
if r.token != nil {
|
|
||||||
// verify signature of session token
|
|
||||||
return ownerFromToken(r.token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise get original body signature
|
|
||||||
bodySignature := originalBodySignature(r.vheader)
|
|
||||||
if bodySignature == nil {
|
|
||||||
return nil, nil, errEmptyBodySig
|
|
||||||
}
|
|
||||||
|
|
||||||
return unmarshalPublicKeyWithOwner(bodySignature.GetKey())
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalPublicKeyWithOwner(rawKey []byte) (*user.ID, *keys.PublicKey, error) {
|
|
||||||
key, err := unmarshalPublicKey(rawKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("invalid signature key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var idSender user.ID
|
|
||||||
user.IDFromKey(&idSender, (ecdsa.PublicKey)(*key))
|
|
||||||
|
|
||||||
return &idSender, key, nil
|
|
||||||
}
|
|
|
@ -1,164 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/refs"
|
|
||||||
sessionV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/session"
|
|
||||||
sigutilV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/util/signature"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
||||||
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
|
||||||
sessionSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRequestOwner(t *testing.T) {
|
|
||||||
containerOwner, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
userPk, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var userID user.ID
|
|
||||||
user.IDFromKey(&userID, userPk.PrivateKey.PublicKey)
|
|
||||||
|
|
||||||
var userSignature refs.Signature
|
|
||||||
userSignature.SetKey(userPk.PublicKey().Bytes())
|
|
||||||
|
|
||||||
vh := new(sessionV2.RequestVerificationHeader)
|
|
||||||
vh.SetBodySignature(&userSignature)
|
|
||||||
|
|
||||||
t.Run("empty verification header", func(t *testing.T) {
|
|
||||||
req := MetaWithToken{}
|
|
||||||
checkOwner(t, req, nil, errEmptyVerificationHeader)
|
|
||||||
})
|
|
||||||
t.Run("empty verification header signature", func(t *testing.T) {
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: new(sessionV2.RequestVerificationHeader),
|
|
||||||
}
|
|
||||||
checkOwner(t, req, nil, errEmptyBodySig)
|
|
||||||
})
|
|
||||||
t.Run("no tokens", func(t *testing.T) {
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: vh,
|
|
||||||
}
|
|
||||||
checkOwner(t, req, userPk.PublicKey(), nil)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("bearer without impersonate, no session", func(t *testing.T) {
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: vh,
|
|
||||||
bearer: newBearer(t, containerOwner, userID, false),
|
|
||||||
}
|
|
||||||
checkOwner(t, req, userPk.PublicKey(), nil)
|
|
||||||
})
|
|
||||||
t.Run("bearer with impersonate, no session", func(t *testing.T) {
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: vh,
|
|
||||||
bearer: newBearer(t, containerOwner, userID, true),
|
|
||||||
}
|
|
||||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
|
||||||
})
|
|
||||||
t.Run("bearer with impersonate, with session", func(t *testing.T) {
|
|
||||||
// To check that bearer token takes priority, use different key to sign session token.
|
|
||||||
pk, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: vh,
|
|
||||||
bearer: newBearer(t, containerOwner, userID, true),
|
|
||||||
token: newSession(t, pk),
|
|
||||||
}
|
|
||||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
|
||||||
})
|
|
||||||
t.Run("with session", func(t *testing.T) {
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: vh,
|
|
||||||
token: newSession(t, containerOwner),
|
|
||||||
}
|
|
||||||
checkOwner(t, req, containerOwner.PublicKey(), nil)
|
|
||||||
})
|
|
||||||
t.Run("malformed session token", func(t *testing.T) {
|
|
||||||
// This test is tricky: session token has issuer field and signature, which must correspond to each other.
|
|
||||||
// SDK prevents constructing such token in the first place, but it is still possible via API.
|
|
||||||
// Thus, construct v2 token, convert it to SDK one and pass to our function.
|
|
||||||
pk, err := keys.NewPrivateKey()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var user1 user.ID
|
|
||||||
user.IDFromKey(&user1, pk.PrivateKey.PublicKey)
|
|
||||||
|
|
||||||
var id refs.OwnerID
|
|
||||||
id.SetValue(user1.WalletBytes())
|
|
||||||
|
|
||||||
raw, err := uuid.New().MarshalBinary()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var cidV2 refs.ContainerID
|
|
||||||
cidtest.ID().WriteToV2(&cidV2)
|
|
||||||
|
|
||||||
sessionCtx := new(sessionV2.ObjectSessionContext)
|
|
||||||
sessionCtx.SetTarget(&cidV2)
|
|
||||||
|
|
||||||
var body sessionV2.TokenBody
|
|
||||||
body.SetOwnerID(&id)
|
|
||||||
body.SetID(raw)
|
|
||||||
body.SetLifetime(new(sessionV2.TokenLifetime))
|
|
||||||
body.SetSessionKey(pk.PublicKey().Bytes())
|
|
||||||
body.SetContext(sessionCtx)
|
|
||||||
|
|
||||||
var tokV2 sessionV2.Token
|
|
||||||
tokV2.SetBody(&body)
|
|
||||||
require.NoError(t, sigutilV2.SignData(&containerOwner.PrivateKey, smWrapper{Token: &tokV2}))
|
|
||||||
require.NoError(t, sigutilV2.VerifyData(smWrapper{Token: &tokV2}))
|
|
||||||
|
|
||||||
var tok sessionSDK.Object
|
|
||||||
require.NoError(t, tok.ReadFromV2(tokV2))
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: vh,
|
|
||||||
token: &tok,
|
|
||||||
}
|
|
||||||
checkOwner(t, req, nil, errInvalidSessionOwner)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type smWrapper struct {
|
|
||||||
*sessionV2.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s smWrapper) ReadSignedData(data []byte) ([]byte, error) {
|
|
||||||
return s.Token.GetBody().StableMarshal(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s smWrapper) SignedDataSize() int {
|
|
||||||
return s.Token.GetBody().StableSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newSession(t *testing.T, pk *keys.PrivateKey) *sessionSDK.Object {
|
|
||||||
var tok sessionSDK.Object
|
|
||||||
require.NoError(t, tok.Sign(pk.PrivateKey))
|
|
||||||
return &tok
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBearer(t *testing.T, pk *keys.PrivateKey, user user.ID, impersonate bool) *bearer.Token {
|
|
||||||
var tok bearer.Token
|
|
||||||
tok.SetImpersonate(impersonate)
|
|
||||||
tok.ForUser(user)
|
|
||||||
require.NoError(t, tok.Sign(pk.PrivateKey))
|
|
||||||
return &tok
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkOwner(t *testing.T, req MetaWithToken, expected *keys.PublicKey, expectedErr error) {
|
|
||||||
_, actual, err := req.RequestOwner()
|
|
||||||
if expectedErr != nil {
|
|
||||||
require.ErrorIs(t, err, expectedErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expected, actual)
|
|
||||||
}
|
|
|
@ -1,779 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
|
|
||||||
objectCore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/object"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
|
||||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
|
||||||
"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"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Service checks basic ACL rules.
|
|
||||||
type Service struct {
|
|
||||||
*cfg
|
|
||||||
|
|
||||||
c objectCore.SenderClassifier
|
|
||||||
}
|
|
||||||
|
|
||||||
type putStreamBasicChecker struct {
|
|
||||||
source *Service
|
|
||||||
next object.PutObjectStream
|
|
||||||
}
|
|
||||||
|
|
||||||
type patchStreamBasicChecker struct {
|
|
||||||
source *Service
|
|
||||||
next object.PatchObjectStream
|
|
||||||
nonFirstSend bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option represents Service constructor option.
|
|
||||||
type Option func(*cfg)
|
|
||||||
|
|
||||||
type cfg struct {
|
|
||||||
log *logger.Logger
|
|
||||||
|
|
||||||
containers container.Source
|
|
||||||
|
|
||||||
irFetcher InnerRingFetcher
|
|
||||||
|
|
||||||
nm netmap.Source
|
|
||||||
|
|
||||||
next object.ServiceServer
|
|
||||||
}
|
|
||||||
|
|
||||||
// New is a constructor for object ACL checking service.
|
|
||||||
func New(next object.ServiceServer,
|
|
||||||
nm netmap.Source,
|
|
||||||
irf InnerRingFetcher,
|
|
||||||
cs container.Source,
|
|
||||||
opts ...Option,
|
|
||||||
) Service {
|
|
||||||
cfg := &cfg{
|
|
||||||
log: logger.NewLoggerWrapper(zap.L()),
|
|
||||||
next: next,
|
|
||||||
nm: nm,
|
|
||||||
irFetcher: irf,
|
|
||||||
containers: cs,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range opts {
|
|
||||||
opts[i](cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Service{
|
|
||||||
cfg: cfg,
|
|
||||||
c: objectCore.NewSenderClassifier(cfg.irFetcher, cfg.nm, cfg.log),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrappedGetObjectStream propagates RequestContext into GetObjectStream's context.
|
|
||||||
// This allows to retrieve already calculated immutable request-specific values in next handler invocation.
|
|
||||||
type wrappedGetObjectStream struct {
|
|
||||||
object.GetObjectStream
|
|
||||||
|
|
||||||
requestInfo RequestInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrappedGetObjectStream) Context() context.Context {
|
|
||||||
return context.WithValue(w.GetObjectStream.Context(), object.RequestContextKey, &object.RequestContext{
|
|
||||||
Namespace: w.requestInfo.ContainerNamespace(),
|
|
||||||
ContainerOwner: w.requestInfo.ContainerOwner(),
|
|
||||||
SenderKey: w.requestInfo.SenderKey(),
|
|
||||||
Role: w.requestInfo.RequestRole(),
|
|
||||||
BearerToken: w.requestInfo.Bearer(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWrappedGetObjectStreamStream(getObjectStream object.GetObjectStream, reqInfo RequestInfo) object.GetObjectStream {
|
|
||||||
return &wrappedGetObjectStream{
|
|
||||||
GetObjectStream: getObjectStream,
|
|
||||||
requestInfo: reqInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrappedRangeStream propagates RequestContext into GetObjectRangeStream's context.
|
|
||||||
// This allows to retrieve already calculated immutable request-specific values in next handler invocation.
|
|
||||||
type wrappedRangeStream struct {
|
|
||||||
object.GetObjectRangeStream
|
|
||||||
|
|
||||||
requestInfo RequestInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrappedRangeStream) Context() context.Context {
|
|
||||||
return context.WithValue(w.GetObjectRangeStream.Context(), object.RequestContextKey, &object.RequestContext{
|
|
||||||
Namespace: w.requestInfo.ContainerNamespace(),
|
|
||||||
ContainerOwner: w.requestInfo.ContainerOwner(),
|
|
||||||
SenderKey: w.requestInfo.SenderKey(),
|
|
||||||
Role: w.requestInfo.RequestRole(),
|
|
||||||
BearerToken: w.requestInfo.Bearer(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWrappedRangeStream(rangeStream object.GetObjectRangeStream, reqInfo RequestInfo) object.GetObjectRangeStream {
|
|
||||||
return &wrappedRangeStream{
|
|
||||||
GetObjectRangeStream: rangeStream,
|
|
||||||
requestInfo: reqInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrappedSearchStream propagates RequestContext into SearchStream's context.
|
|
||||||
// This allows to retrieve already calculated immutable request-specific values in next handler invocation.
|
|
||||||
type wrappedSearchStream struct {
|
|
||||||
object.SearchStream
|
|
||||||
|
|
||||||
requestInfo RequestInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *wrappedSearchStream) Context() context.Context {
|
|
||||||
return context.WithValue(w.SearchStream.Context(), object.RequestContextKey, &object.RequestContext{
|
|
||||||
Namespace: w.requestInfo.ContainerNamespace(),
|
|
||||||
ContainerOwner: w.requestInfo.ContainerOwner(),
|
|
||||||
SenderKey: w.requestInfo.SenderKey(),
|
|
||||||
Role: w.requestInfo.RequestRole(),
|
|
||||||
BearerToken: w.requestInfo.Bearer(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWrappedSearchStream(searchStream object.SearchStream, reqInfo RequestInfo) object.SearchStream {
|
|
||||||
return &wrappedSearchStream{
|
|
||||||
SearchStream: searchStream,
|
|
||||||
requestInfo: reqInfo,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get implements ServiceServer interface, makes ACL checks and calls
|
|
||||||
// next Get method in the ServiceServer pipeline.
|
|
||||||
func (b Service) Get(request *objectV2.GetRequest, stream object.GetObjectStream) error {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok != nil {
|
|
||||||
err = assertSessionRelation(*sTok, cnr, obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr, acl.OpObjectGet)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
return b.next.Get(request, newWrappedGetObjectStreamStream(stream, reqInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Put(ctx context.Context) (object.PutObjectStream, error) {
|
|
||||||
streamer, err := b.next.Put(ctx)
|
|
||||||
|
|
||||||
return putStreamBasicChecker{
|
|
||||||
source: &b,
|
|
||||||
next: streamer,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Patch(ctx context.Context) (object.PatchObjectStream, error) {
|
|
||||||
streamer, err := b.next.Patch(ctx)
|
|
||||||
|
|
||||||
return &patchStreamBasicChecker{
|
|
||||||
source: &b,
|
|
||||||
next: streamer,
|
|
||||||
}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Head(
|
|
||||||
ctx context.Context,
|
|
||||||
request *objectV2.HeadRequest,
|
|
||||||
) (*objectV2.HeadResponse, error) {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok != nil {
|
|
||||||
err = assertSessionRelation(*sTok, cnr, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectHead)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
return b.next.Head(requestContext(ctx, reqInfo), request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Search(request *objectV2.SearchRequest, stream object.SearchStream) error {
|
|
||||||
id, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok != nil {
|
|
||||||
err = assertSessionRelation(*sTok, id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, id, acl.OpObjectSearch)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return b.next.Search(request, newWrappedSearchStream(stream, reqInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) Delete(
|
|
||||||
ctx context.Context,
|
|
||||||
request *objectV2.DeleteRequest,
|
|
||||||
) (*objectV2.DeleteResponse, error) {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok != nil {
|
|
||||||
err = assertSessionRelation(*sTok, cnr, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectDelete)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
return b.next.Delete(requestContext(ctx, reqInfo), request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) GetRange(request *objectV2.GetRangeRequest, stream object.GetObjectRangeStream) error {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok != nil {
|
|
||||||
err = assertSessionRelation(*sTok, cnr, obj)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(stream.Context(), req, cnr, acl.OpObjectRange)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
return b.next.GetRange(request, newWrappedRangeStream(stream, reqInfo))
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestContext(ctx context.Context, reqInfo RequestInfo) context.Context {
|
|
||||||
return context.WithValue(ctx, object.RequestContextKey, &object.RequestContext{
|
|
||||||
Namespace: reqInfo.ContainerNamespace(),
|
|
||||||
ContainerOwner: reqInfo.ContainerOwner(),
|
|
||||||
SenderKey: reqInfo.SenderKey(),
|
|
||||||
Role: reqInfo.RequestRole(),
|
|
||||||
BearerToken: reqInfo.Bearer(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) GetRangeHash(
|
|
||||||
ctx context.Context,
|
|
||||||
request *objectV2.GetRangeHashRequest,
|
|
||||||
) (*objectV2.GetRangeHashResponse, error) {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := getObjectIDFromRequestBody(request.GetBody())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sTok, err := originalSessionToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok != nil {
|
|
||||||
err = assertSessionRelation(*sTok, cnr, obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
return b.next.GetRangeHash(requestContext(ctx, reqInfo), request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b Service) PutSingle(ctx context.Context, request *objectV2.PutSingleRequest) (*objectV2.PutSingleResponse, error) {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
idV2 := request.GetBody().GetObject().GetHeader().GetOwnerID()
|
|
||||||
if idV2 == nil {
|
|
||||||
return nil, errors.New("missing object owner")
|
|
||||||
}
|
|
||||||
|
|
||||||
var idOwner user.ID
|
|
||||||
|
|
||||||
err = idOwner.ReadFromV2(*idV2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid object owner: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, err := getObjectIDFromRefObjectID(request.GetBody().GetObject().GetObjectID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var sTok *sessionSDK.Object
|
|
||||||
sTok, err = readSessionToken(cnr, obj, request.GetMetaHeader().GetSessionToken())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := b.findRequestInfo(ctx, req, cnr, acl.OpObjectPut)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
return b.next.PutSingle(requestContext(ctx, reqInfo), request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p putStreamBasicChecker) Send(ctx context.Context, request *objectV2.PutRequest) error {
|
|
||||||
body := request.GetBody()
|
|
||||||
if body == nil {
|
|
||||||
return errEmptyBody
|
|
||||||
}
|
|
||||||
|
|
||||||
part := body.GetObjectPart()
|
|
||||||
if part, ok := part.(*objectV2.PutObjectPartInit); ok {
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
idV2 := part.GetHeader().GetOwnerID()
|
|
||||||
if idV2 == nil {
|
|
||||||
return errors.New("missing object owner")
|
|
||||||
}
|
|
||||||
|
|
||||||
var idOwner user.ID
|
|
||||||
|
|
||||||
err = idOwner.ReadFromV2(*idV2)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid object owner: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
objV2 := part.GetObjectID()
|
|
||||||
var obj *oid.ID
|
|
||||||
|
|
||||||
if objV2 != nil {
|
|
||||||
obj = new(oid.ID)
|
|
||||||
|
|
||||||
err = obj.ReadFromV2(*objV2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var sTok *sessionSDK.Object
|
|
||||||
sTok, err = readSessionToken(cnr, obj, request.GetMetaHeader().GetSessionToken())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := p.source.findRequestInfo(ctx, req, cnr, acl.OpObjectPut)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
ctx = requestContext(ctx, reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.next.Send(ctx, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readSessionToken(cnr cid.ID, obj *oid.ID, tokV2 *session.Token) (*sessionSDK.Object, error) {
|
|
||||||
var sTok *sessionSDK.Object
|
|
||||||
|
|
||||||
if tokV2 != nil {
|
|
||||||
sTok = new(sessionSDK.Object)
|
|
||||||
|
|
||||||
err := sTok.ReadFromV2(*tokV2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid session token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if sTok.AssertVerb(sessionSDK.VerbObjectDelete) {
|
|
||||||
// if session relates to object's removal, we don't check
|
|
||||||
// relation of the tombstone to the session here since user
|
|
||||||
// can't predict tomb's ID.
|
|
||||||
err = assertSessionRelation(*sTok, cnr, nil)
|
|
||||||
} else {
|
|
||||||
err = assertSessionRelation(*sTok, cnr, obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sTok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p putStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.PutResponse, error) {
|
|
||||||
return p.next.CloseAndRecv(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *patchStreamBasicChecker) Send(ctx context.Context, request *objectV2.PatchRequest) error {
|
|
||||||
body := request.GetBody()
|
|
||||||
if body == nil {
|
|
||||||
return errEmptyBody
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.nonFirstSend {
|
|
||||||
p.nonFirstSend = true
|
|
||||||
|
|
||||||
cnr, err := getContainerIDFromRequest(request)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
objV2 := request.GetBody().GetAddress().GetObjectID()
|
|
||||||
if objV2 == nil {
|
|
||||||
return errors.New("missing oid")
|
|
||||||
}
|
|
||||||
obj := new(oid.ID)
|
|
||||||
err = obj.ReadFromV2(*objV2)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var sTok *sessionSDK.Object
|
|
||||||
sTok, err = readSessionToken(cnr, obj, request.GetMetaHeader().GetSessionToken())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(request.GetMetaHeader())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
req := MetaWithToken{
|
|
||||||
vheader: request.GetVerificationHeader(),
|
|
||||||
token: sTok,
|
|
||||||
bearer: bTok,
|
|
||||||
src: request,
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo, err := p.source.findRequestInfoWithoutACLOperationAssert(ctx, req, cnr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqInfo.obj = obj
|
|
||||||
|
|
||||||
ctx = requestContext(ctx, reqInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.next.Send(ctx, request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p patchStreamBasicChecker) CloseAndRecv(ctx context.Context) (*objectV2.PatchResponse, error) {
|
|
||||||
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) {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.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
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import "context"
|
|
||||||
|
|
||||||
// InnerRingFetcher is an interface that must provide
|
|
||||||
// Inner Ring information.
|
|
||||||
type InnerRingFetcher interface {
|
|
||||||
// InnerRingKeys must return list of public keys of
|
|
||||||
// the actual inner ring.
|
|
||||||
InnerRingKeys(ctx context.Context) ([][]byte, error)
|
|
||||||
}
|
|
|
@ -1,223 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
|
||||||
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"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errMissingContainerID = errors.New("missing container ID")
|
|
||||||
|
|
||||||
func getContainerIDFromRequest(req any) (cid.ID, error) {
|
|
||||||
var idV2 *refsV2.ContainerID
|
|
||||||
var id cid.ID
|
|
||||||
|
|
||||||
switch v := req.(type) {
|
|
||||||
case *objectV2.GetRequest:
|
|
||||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
|
||||||
case *objectV2.PutRequest:
|
|
||||||
part, ok := v.GetBody().GetObjectPart().(*objectV2.PutObjectPartInit)
|
|
||||||
if !ok {
|
|
||||||
return cid.ID{}, errors.New("can't get container ID in chunk")
|
|
||||||
}
|
|
||||||
|
|
||||||
idV2 = part.GetHeader().GetContainerID()
|
|
||||||
case *objectV2.HeadRequest:
|
|
||||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
|
||||||
case *objectV2.SearchRequest:
|
|
||||||
idV2 = v.GetBody().GetContainerID()
|
|
||||||
case *objectV2.DeleteRequest:
|
|
||||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
|
||||||
case *objectV2.GetRangeRequest:
|
|
||||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
|
||||||
case *objectV2.GetRangeHashRequest:
|
|
||||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
|
||||||
case *objectV2.PutSingleRequest:
|
|
||||||
idV2 = v.GetBody().GetObject().GetHeader().GetContainerID()
|
|
||||||
case *objectV2.PatchRequest:
|
|
||||||
idV2 = v.GetBody().GetAddress().GetContainerID()
|
|
||||||
default:
|
|
||||||
return cid.ID{}, errors.New("unknown request type")
|
|
||||||
}
|
|
||||||
|
|
||||||
if idV2 == nil {
|
|
||||||
return cid.ID{}, errMissingContainerID
|
|
||||||
}
|
|
||||||
|
|
||||||
return id, id.ReadFromV2(*idV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// originalBearerToken goes down to original request meta header and fetches
|
|
||||||
// bearer token from there.
|
|
||||||
func originalBearerToken(header *sessionV2.RequestMetaHeader) (*bearer.Token, error) {
|
|
||||||
for header.GetOrigin() != nil {
|
|
||||||
header = header.GetOrigin()
|
|
||||||
}
|
|
||||||
|
|
||||||
tokV2 := header.GetBearerToken()
|
|
||||||
if tokV2 == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var tok bearer.Token
|
|
||||||
return &tok, tok.ReadFromV2(*tokV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
// originalSessionToken goes down to original request meta header and fetches
|
|
||||||
// session token from there.
|
|
||||||
func originalSessionToken(header *sessionV2.RequestMetaHeader) (*sessionSDK.Object, error) {
|
|
||||||
for header.GetOrigin() != nil {
|
|
||||||
header = header.GetOrigin()
|
|
||||||
}
|
|
||||||
|
|
||||||
tokV2 := header.GetSessionToken()
|
|
||||||
if tokV2 == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var tok sessionSDK.Object
|
|
||||||
|
|
||||||
err := tok.ReadFromV2(*tokV2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid session token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tok, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getObjectIDFromRequestBody decodes oid.ID from the common interface of the
|
|
||||||
// object reference's holders. Returns an error if object ID is missing in the request.
|
|
||||||
func getObjectIDFromRequestBody(body interface{ GetAddress() *refsV2.Address }) (*oid.ID, error) {
|
|
||||||
idV2 := body.GetAddress().GetObjectID()
|
|
||||||
return getObjectIDFromRefObjectID(idV2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getObjectIDFromRefObjectID(idV2 *refsV2.ObjectID) (*oid.ID, error) {
|
|
||||||
if idV2 == nil {
|
|
||||||
return nil, errors.New("missing object ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
var id oid.ID
|
|
||||||
|
|
||||||
err := id.ReadFromV2(*idV2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ownerFromToken(token *sessionSDK.Object) (*user.ID, *keys.PublicKey, error) {
|
|
||||||
// 1. First check signature of session token.
|
|
||||||
if !token.VerifySignature() {
|
|
||||||
return nil, nil, errInvalidSessionSig
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Then check if session token owner issued the session token
|
|
||||||
// TODO(@cthulhu-rider): #468 implement and use another approach to avoid conversion
|
|
||||||
var tokV2 sessionV2.Token
|
|
||||||
token.WriteToV2(&tokV2)
|
|
||||||
|
|
||||||
tokenIssuerKey, err := unmarshalPublicKey(tokV2.GetSignature().GetKey())
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("invalid key in session token signature: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenIssuer := token.Issuer()
|
|
||||||
|
|
||||||
if !isOwnerFromKey(tokenIssuer, tokenIssuerKey) {
|
|
||||||
// TODO: #767 in this case we can issue all owner keys from frostfs.id and check once again
|
|
||||||
return nil, nil, errInvalidSessionOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tokenIssuer, tokenIssuerKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func originalBodySignature(v *sessionV2.RequestVerificationHeader) *refsV2.Signature {
|
|
||||||
if v == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for v.GetOrigin() != nil {
|
|
||||||
v = v.GetOrigin()
|
|
||||||
}
|
|
||||||
|
|
||||||
return v.GetBodySignature()
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalPublicKey(bs []byte) (*keys.PublicKey, error) {
|
|
||||||
return keys.NewPublicKeyFromBytes(bs, elliptic.P256())
|
|
||||||
}
|
|
||||||
|
|
||||||
func isOwnerFromKey(id user.ID, key *keys.PublicKey) bool {
|
|
||||||
if key == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var id2 user.ID
|
|
||||||
user.IDFromKey(&id2, (ecdsa.PublicKey)(*key))
|
|
||||||
|
|
||||||
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.
|
|
||||||
// Returns no error iff relation is correct. Criteria:
|
|
||||||
//
|
|
||||||
// session is bound to the given container
|
|
||||||
// object is not specified or session is bound to this object
|
|
||||||
//
|
|
||||||
// Session MUST be bound to the particular container, otherwise behavior is undefined.
|
|
||||||
func assertSessionRelation(tok sessionSDK.Object, cnr cid.ID, obj *oid.ID) error {
|
|
||||||
if !tok.AssertContainer(cnr) {
|
|
||||||
return errors.New("requested container is not related to the session")
|
|
||||||
}
|
|
||||||
|
|
||||||
if obj != nil && !tok.AssertObject(*obj) {
|
|
||||||
return errors.New("requested object is not related to the session")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
package v2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"slices"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
sessiontest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session/test"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOriginalTokens(t *testing.T) {
|
|
||||||
sToken := sessiontest.ObjectSigned()
|
|
||||||
bToken := bearertest.Token()
|
|
||||||
|
|
||||||
pk, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
||||||
require.NoError(t, bToken.Sign(*pk))
|
|
||||||
|
|
||||||
var bTokenV2 acl.BearerToken
|
|
||||||
bToken.WriteToV2(&bTokenV2)
|
|
||||||
// This line is needed because SDK uses some custom format for
|
|
||||||
// reserved filters, so `cid.ID` is not converted to string immediately.
|
|
||||||
require.NoError(t, bToken.ReadFromV2(bTokenV2))
|
|
||||||
|
|
||||||
var sTokenV2 session.Token
|
|
||||||
sToken.WriteToV2(&sTokenV2)
|
|
||||||
|
|
||||||
for i := range 10 {
|
|
||||||
metaHeaders := testGenerateMetaHeader(uint32(i), &bTokenV2, &sTokenV2)
|
|
||||||
res, err := originalSessionToken(metaHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, sToken, res, i)
|
|
||||||
|
|
||||||
bTok, err := originalBearerToken(metaHeaders)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, &bToken, bTok, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGenerateMetaHeader(depth uint32, b *acl.BearerToken, s *session.Token) *session.RequestMetaHeader {
|
|
||||||
metaHeader := new(session.RequestMetaHeader)
|
|
||||||
metaHeader.SetBearerToken(b)
|
|
||||||
metaHeader.SetSessionToken(s)
|
|
||||||
|
|
||||||
for range depth {
|
|
||||||
link := metaHeader
|
|
||||||
metaHeader = new(session.RequestMetaHeader)
|
|
||||||
metaHeader.SetOrigin(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
contains := slices.Contains(list, verb)
|
|
||||||
|
|
||||||
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()
|
|
||||||
cnrOther := cidtest.ID()
|
|
||||||
obj := oidtest.ID()
|
|
||||||
objOther := oidtest.ID()
|
|
||||||
|
|
||||||
// make sure ids differ, otherwise test won't work correctly
|
|
||||||
require.False(t, cnrOther.Equals(cnr))
|
|
||||||
require.False(t, objOther.Equals(obj))
|
|
||||||
|
|
||||||
// bind session to the container (required)
|
|
||||||
tok.BindContainer(cnr)
|
|
||||||
|
|
||||||
// test container-global session
|
|
||||||
require.NoError(t, assertSessionRelation(tok, cnr, nil))
|
|
||||||
require.NoError(t, assertSessionRelation(tok, cnr, &obj))
|
|
||||||
require.Error(t, assertSessionRelation(tok, cnrOther, nil))
|
|
||||||
require.Error(t, assertSessionRelation(tok, cnrOther, &obj))
|
|
||||||
|
|
||||||
// limit the session to the particular object
|
|
||||||
tok.LimitByObjects(obj)
|
|
||||||
|
|
||||||
// test fixed object session (here obj arg must be non-nil everywhere)
|
|
||||||
require.NoError(t, assertSessionRelation(tok, cnr, &obj))
|
|
||||||
require.Error(t, assertSessionRelation(tok, cnr, &objOther))
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
package object
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestContextKeyT struct{}
|
|
||||||
|
|
||||||
var RequestContextKey = RequestContextKeyT{}
|
|
||||||
|
|
||||||
// RequestContext is a context passed between middleware handlers.
|
|
||||||
type RequestContext struct {
|
|
||||||
Namespace string
|
|
||||||
|
|
||||||
SenderKey []byte
|
|
||||||
|
|
||||||
ContainerOwner user.ID
|
|
||||||
|
|
||||||
Role acl.Role
|
|
||||||
|
|
||||||
BearerToken *bearer.Token
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue