forked from TrueCloudLab/frostfs-node
[#1701] tree: Form $Tree:ID
resource property for APE
* Make `verifyClient`, `checkAPE` receive `treeID` from request body; * Make `newAPERequest` set `$Tree:ID` property * Add unit-test to check if a rule for `$Tree:ID` works Close #1701 Change-Id: I834fed366e8adfd4b5c07bf50aac09af6239991b Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
parent
fbc623f34e
commit
979d4bb2ae
5 changed files with 76 additions and 31 deletions
|
@ -22,7 +22,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) newAPERequest(ctx context.Context, namespace string,
|
func (s *Service) newAPERequest(ctx context.Context, namespace string,
|
||||||
cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
|
cid cid.ID, treeID string, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
|
||||||
) (aperequest.Request, error) {
|
) (aperequest.Request, error) {
|
||||||
schemaMethod, err := converter.SchemaMethodFromACLOperation(operation)
|
schemaMethod, err := converter.SchemaMethodFromACLOperation(operation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -53,15 +53,19 @@ func (s *Service) newAPERequest(ctx context.Context, namespace string,
|
||||||
resourceName = fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObjects, namespace, cid.EncodeToString())
|
resourceName = fmt.Sprintf(nativeschema.ResourceFormatNamespaceContainerObjects, namespace, cid.EncodeToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resProps := map[string]string{
|
||||||
|
nativeschema.ProperyKeyTreeID: treeID,
|
||||||
|
}
|
||||||
|
|
||||||
return aperequest.NewRequest(
|
return aperequest.NewRequest(
|
||||||
schemaMethod,
|
schemaMethod,
|
||||||
aperequest.NewResource(resourceName, make(map[string]string)),
|
aperequest.NewResource(resourceName, resProps),
|
||||||
reqProps,
|
reqProps,
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
|
func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
|
||||||
container *core.Container, cid cid.ID, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
|
container *core.Container, cid cid.ID, treeID string, operation acl.Op, role acl.Role, publicKey *keys.PublicKey,
|
||||||
) error {
|
) error {
|
||||||
namespace := ""
|
namespace := ""
|
||||||
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns")
|
cntNamespace, hasNamespace := strings.CutSuffix(cnrSDK.ReadDomain(container.Value).Zone(), ".ns")
|
||||||
|
@ -69,7 +73,7 @@ func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
|
||||||
namespace = cntNamespace
|
namespace = cntNamespace
|
||||||
}
|
}
|
||||||
|
|
||||||
request, err := s.newAPERequest(ctx, namespace, cid, operation, role, publicKey)
|
request, err := s.newAPERequest(ctx, namespace, cid, treeID, operation, role, publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create ape request: %w", err)
|
return fmt.Errorf("failed to create ape request: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,45 @@ func TestCheckAPE(t *testing.T) {
|
||||||
cid := cid.ID{}
|
cid := cid.ID{}
|
||||||
_ = cid.DecodeString(containerID)
|
_ = cid.DecodeString(containerID)
|
||||||
|
|
||||||
|
t.Run("treeID rule", func(t *testing.T) {
|
||||||
|
los := inmemory.NewInmemoryLocalStorage()
|
||||||
|
mcs := inmemory.NewInmemoryMorphRuleChainStorage()
|
||||||
|
fid := newFrostfsIDProviderMock(t)
|
||||||
|
s := Service{
|
||||||
|
cfg: cfg{
|
||||||
|
frostfsidSubjectProvider: fid,
|
||||||
|
},
|
||||||
|
apeChecker: checkercore.New(los, mcs, fid, &stMock{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
mcs.AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(containerID), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.QuotaLimitReached,
|
||||||
|
Actions: chain.Actions{Names: []string{nativeschema.MethodGetObject}},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
Kind: chain.KindResource,
|
||||||
|
Key: nativeschema.ProperyKeyTreeID,
|
||||||
|
Value: versionTreeID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
MatchType: chain.MatchTypeFirstMatch,
|
||||||
|
})
|
||||||
|
|
||||||
|
err := s.checkAPE(context.Background(), nil, rootCnr, cid, versionTreeID, acl.OpObjectGet, acl.RoleOwner, senderPrivateKey.PublicKey())
|
||||||
|
|
||||||
|
var chErr *checkercore.ChainRouterError
|
||||||
|
require.ErrorAs(t, err, &chErr)
|
||||||
|
require.Equal(t, chain.QuotaLimitReached, chErr.Status())
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("put non-tombstone rule won't affect tree remove", func(t *testing.T) {
|
t.Run("put non-tombstone rule won't affect tree remove", func(t *testing.T) {
|
||||||
los := inmemory.NewInmemoryLocalStorage()
|
los := inmemory.NewInmemoryLocalStorage()
|
||||||
mcs := inmemory.NewInmemoryMorphRuleChainStorage()
|
mcs := inmemory.NewInmemoryMorphRuleChainStorage()
|
||||||
|
@ -152,7 +191,7 @@ func TestCheckAPE(t *testing.T) {
|
||||||
MatchType: chain.MatchTypeFirstMatch,
|
MatchType: chain.MatchTypeFirstMatch,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := s.checkAPE(context.Background(), nil, rootCnr, cid, acl.OpObjectDelete, acl.RoleOwner, senderPrivateKey.PublicKey())
|
err := s.checkAPE(context.Background(), nil, rootCnr, cid, versionTreeID, acl.OpObjectDelete, acl.RoleOwner, senderPrivateKey.PublicKey())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -201,7 +240,7 @@ func TestCheckAPE(t *testing.T) {
|
||||||
MatchType: chain.MatchTypeFirstMatch,
|
MatchType: chain.MatchTypeFirstMatch,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := s.checkAPE(context.Background(), nil, rootCnr, cid, acl.OpObjectPut, acl.RoleOwner, senderPrivateKey.PublicKey())
|
err := s.checkAPE(context.Background(), nil, rootCnr, cid, versionTreeID, acl.OpObjectPut, acl.RoleOwner, senderPrivateKey.PublicKey())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ func (s *Service) Add(ctx context.Context, req *AddRequest) (*AddResponse, error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, req.GetBody().GetTreeId(), b.GetBearerToken(), acl.OpObjectPut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,7 @@ func (s *Service) AddByPath(ctx context.Context, req *AddByPathRequest) (*AddByP
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, req.GetBody().GetTreeId(), b.GetBearerToken(), acl.OpObjectPut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ func (s *Service) Remove(ctx context.Context, req *RemoveRequest) (*RemoveRespon
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectDelete)
|
err := s.verifyClient(ctx, req, cid, req.GetBody().GetTreeId(), b.GetBearerToken(), acl.OpObjectDelete)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ func (s *Service) Move(ctx context.Context, req *MoveRequest) (*MoveResponse, er
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectPut)
|
err := s.verifyClient(ctx, req, cid, req.GetBody().GetTreeId(), b.GetBearerToken(), acl.OpObjectPut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,7 @@ func (s *Service) GetNodeByPath(ctx context.Context, req *GetNodeByPathRequest)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(ctx, req, cid, b.GetBearerToken(), acl.OpObjectGet)
|
err := s.verifyClient(ctx, req, cid, req.GetBody().GetTreeId(), b.GetBearerToken(), acl.OpObjectGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -377,7 +377,7 @@ func (s *Service) GetSubTree(req *GetSubTreeRequest, srv TreeService_GetSubTreeS
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.verifyClient(srv.Context(), req, cid, b.GetBearerToken(), acl.OpObjectGet)
|
err := s.verifyClient(srv.Context(), req, cid, req.GetBody().GetTreeId(), b.GetBearerToken(), acl.OpObjectGet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ var (
|
||||||
// Operation must be one of:
|
// Operation must be one of:
|
||||||
// - 1. ObjectPut;
|
// - 1. ObjectPut;
|
||||||
// - 2. ObjectGet.
|
// - 2. ObjectGet.
|
||||||
func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID, rawBearer []byte, op acl.Op) error {
|
func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID, treeID string, rawBearer []byte, op acl.Op) error {
|
||||||
err := verifyMessage(req)
|
err := verifyMessage(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -64,7 +64,7 @@ func (s *Service) verifyClient(ctx context.Context, req message, cid cidSDK.ID,
|
||||||
return fmt.Errorf("can't get request role: %w", err)
|
return fmt.Errorf("can't get request role: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = s.checkAPE(ctx, bt, cnr, cid, op, role, pubKey); err != nil {
|
if err = s.checkAPE(ctx, bt, cnr, cid, treeID, op, role, pubKey); err != nil {
|
||||||
return apeErr(err)
|
return apeErr(err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -31,6 +31,8 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const versionTreeID = "version"
|
||||||
|
|
||||||
type dummyNetmapSource struct {
|
type dummyNetmapSource struct {
|
||||||
netmap.Source
|
netmap.Source
|
||||||
}
|
}
|
||||||
|
@ -168,26 +170,26 @@ func TestMessageSign(t *testing.T) {
|
||||||
cnr.Value.SetBasicACL(acl.PublicRW)
|
cnr.Value.SetBasicACL(acl.PublicRW)
|
||||||
|
|
||||||
t.Run("missing signature, no panic", func(t *testing.T) {
|
t.Run("missing signature, no panic", func(t *testing.T) {
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
|
require.Error(t, s.verifyClient(context.Background(), req, cid2, versionTreeID, nil, op))
|
||||||
})
|
})
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, nil, op))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, nil, op))
|
||||||
|
|
||||||
t.Run("invalid CID", func(t *testing.T) {
|
t.Run("invalid CID", func(t *testing.T) {
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
|
require.Error(t, s.verifyClient(context.Background(), req, cid2, versionTreeID, nil, op))
|
||||||
})
|
})
|
||||||
|
|
||||||
cnr.Value.SetBasicACL(acl.Private)
|
cnr.Value.SetBasicACL(acl.Private)
|
||||||
|
|
||||||
t.Run("extension disabled", func(t *testing.T) {
|
t.Run("extension disabled", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid2, nil, op))
|
require.Error(t, s.verifyClient(context.Background(), req, cid2, versionTreeID, nil, op))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid key", func(t *testing.T) {
|
t.Run("invalid key", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, nil, op))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, nil, op))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("bearer", func(t *testing.T) {
|
t.Run("bearer", func(t *testing.T) {
|
||||||
|
@ -200,7 +202,7 @@ func TestMessageSign(t *testing.T) {
|
||||||
t.Run("invalid bearer", func(t *testing.T) {
|
t.Run("invalid bearer", func(t *testing.T) {
|
||||||
req.Body.BearerToken = []byte{0xFF}
|
req.Body.BearerToken = []byte{0xFF}
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("invalid bearer CID", func(t *testing.T) {
|
t.Run("invalid bearer CID", func(t *testing.T) {
|
||||||
|
@ -209,7 +211,7 @@ func TestMessageSign(t *testing.T) {
|
||||||
req.Body.BearerToken = bt.Marshal()
|
req.Body.BearerToken = bt.Marshal()
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
})
|
})
|
||||||
t.Run("invalid bearer owner", func(t *testing.T) {
|
t.Run("invalid bearer owner", func(t *testing.T) {
|
||||||
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
|
@ -217,7 +219,7 @@ func TestMessageSign(t *testing.T) {
|
||||||
req.Body.BearerToken = bt.Marshal()
|
req.Body.BearerToken = bt.Marshal()
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
})
|
})
|
||||||
t.Run("invalid bearer signature", func(t *testing.T) {
|
t.Run("invalid bearer signature", func(t *testing.T) {
|
||||||
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
|
@ -229,7 +231,7 @@ func TestMessageSign(t *testing.T) {
|
||||||
req.Body.BearerToken = bv2.StableMarshal(nil)
|
req.Body.BearerToken = bv2.StableMarshal(nil)
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("impersonate", func(t *testing.T) {
|
t.Run("impersonate", func(t *testing.T) {
|
||||||
|
@ -241,8 +243,8 @@ func TestMessageSign(t *testing.T) {
|
||||||
req.Body.BearerToken = bt.Marshal()
|
req.Body.BearerToken = bt.Marshal()
|
||||||
|
|
||||||
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[0].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
||||||
})
|
})
|
||||||
|
|
||||||
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
bt := testBearerToken(cid1, privs[1].PublicKey(), privs[2].PublicKey())
|
||||||
|
@ -252,18 +254,18 @@ func TestMessageSign(t *testing.T) {
|
||||||
|
|
||||||
t.Run("put and get", func(t *testing.T) {
|
t.Run("put and get", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[1].PrivateKey))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
||||||
})
|
})
|
||||||
t.Run("only get", func(t *testing.T) {
|
t.Run("only get", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[2].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[2].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
require.NoError(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.NoError(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
||||||
})
|
})
|
||||||
t.Run("none", func(t *testing.T) {
|
t.Run("none", func(t *testing.T) {
|
||||||
require.NoError(t, SignMessage(req, &privs[3].PrivateKey))
|
require.NoError(t, SignMessage(req, &privs[3].PrivateKey))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectPut))
|
||||||
require.Error(t, s.verifyClient(context.Background(), req, cid1, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
require.Error(t, s.verifyClient(context.Background(), req, cid1, versionTreeID, req.GetBody().GetBearerToken(), acl.OpObjectGet))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue