Make services GroupIDs must also be target of APE checks #1193
|
@ -34,3 +34,19 @@ func FormFrostfsIDRequestProperties(frostFSIDClient frostfsidcore.SubjectProvide
|
||||||
|
|
||||||
return reqProps, nil
|
return reqProps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Groups return the actor's group ids from frostfsid contract.
|
||||||
|
func Groups(frostFSIDClient frostfsidcore.SubjectProvider, pk *keys.PublicKey) ([]string, error) {
|
||||||
|
subj, err := frostFSIDClient.GetSubjectExtended(pk.GetScriptHash())
|
||||||
|
|||||||
|
if err != nil {
|
||||||
|
if !strings.Contains(err.Error(), frostfsidcore.SubjectNotFoundErrorMessage) {
|
||||||
|
return nil, fmt.Errorf("get subject error: %w", err)
|
||||||
|
}
|
||||||
|
return []string{}, nil
|
||||||
|
}
|
||||||
|
groups := make([]string, len(subj.Groups))
|
||||||
|
for i, group := range subj.Groups {
|
||||||
|
groups[i] = strconv.FormatInt(group.ID, 10)
|
||||||
|
}
|
||||||
|
return groups, nil
|
||||||
|
}
|
||||||
|
|
|
@ -164,11 +164,26 @@ func (ac *apeChecker) List(ctx context.Context, req *container.ListRequest) (*co
|
||||||
reqProps,
|
reqProps,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
groups, err := aperequest.Groups(ac.frostFSIDClient, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy contract keeps group related chains as namespace-group pair.
|
||||||
|
for i := range groups {
|
||||||
|
groups[i] = fmt.Sprintf("%s:%s", namespace, groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
rt := policyengine.NewRequestTargetWithNamespace(namespace)
|
rt := policyengine.NewRequestTargetWithNamespace(namespace)
|
||||||
rt.User = &policyengine.Target{
|
rt.User = &policyengine.Target{
|
||||||
Type: policyengine.User,
|
Type: policyengine.User,
|
||||||
dkirillov
commented
As I know, group target must be As I know, group target must be `<ns>:<gropuID>` rather than just `<groupID>`
aarifullin
commented
Didn't think about that - fixed Didn't think about that - fixed
|
|||||||
Name: fmt.Sprintf("%s:%s", namespace, pk.Address()),
|
Name: fmt.Sprintf("%s:%s", namespace, pk.Address()),
|
||||||
}
|
}
|
||||||
|
rt.Groups = make([]policyengine.Target, len(groups))
|
||||||
|
for i := range groups {
|
||||||
|
rt.Groups[i] = policyengine.GroupTarget(groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
s, found, err := ac.router.IsAllowed(apechain.Ingress, rt, request)
|
s, found, err := ac.router.IsAllowed(apechain.Ingress, rt, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -222,11 +237,26 @@ func (ac *apeChecker) Put(ctx context.Context, req *container.PutRequest) (*cont
|
||||||
reqProps,
|
reqProps,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
groups, err := aperequest.Groups(ac.frostFSIDClient, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy contract keeps group related chains as namespace-group pair.
|
||||||
|
for i := range groups {
|
||||||
|
groups[i] = fmt.Sprintf("%s:%s", namespace, groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
rt := policyengine.NewRequestTargetWithNamespace(namespace)
|
rt := policyengine.NewRequestTargetWithNamespace(namespace)
|
||||||
rt.User = &policyengine.Target{
|
rt.User = &policyengine.Target{
|
||||||
Type: policyengine.User,
|
Type: policyengine.User,
|
||||||
Name: fmt.Sprintf("%s:%s", namespace, pk.Address()),
|
Name: fmt.Sprintf("%s:%s", namespace, pk.Address()),
|
||||||
}
|
}
|
||||||
|
rt.Groups = make([]policyengine.Target, len(groups))
|
||||||
|
for i := range groups {
|
||||||
|
rt.Groups[i] = policyengine.GroupTarget(groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
s, found, err := ac.router.IsAllowed(apechain.Ingress, rt, request)
|
s, found, err := ac.router.IsAllowed(apechain.Ingress, rt, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -311,6 +341,16 @@ func (ac *apeChecker) validateContainerBoundedOperation(ctx context.Context, con
|
||||||
namespace = cntNamespace
|
namespace = cntNamespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groups, err := aperequest.Groups(ac.frostFSIDClient, pk)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy contract keeps group related chains as namespace-group pair.
|
||||||
|
for i := range groups {
|
||||||
|
groups[i] = fmt.Sprintf("%s:%s", namespace, groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
request := aperequest.NewRequest(
|
request := aperequest.NewRequest(
|
||||||
op,
|
op,
|
||||||
aperequest.NewResource(
|
aperequest.NewResource(
|
||||||
|
@ -321,7 +361,7 @@ func (ac *apeChecker) validateContainerBoundedOperation(ctx context.Context, con
|
||||||
)
|
)
|
||||||
|
|
||||||
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
s, found, err := ac.router.IsAllowed(apechain.Ingress,
|
||||||
policyengine.NewRequestTargetExtended(namespace, id.EncodeToString(), fmt.Sprintf("%s:%s", namespace, pk.Address()), nil),
|
policyengine.NewRequestTargetExtended(namespace, id.EncodeToString(), fmt.Sprintf("%s:%s", namespace, pk.Address()), groups),
|
||||||
request)
|
request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -44,6 +44,7 @@ const (
|
||||||
func TestAPE(t *testing.T) {
|
func TestAPE(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
t.Run("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
|
t.Run("allow then deny get container", testAllowThenDenyGetContainerRuleDefined)
|
||||||
|
t.Run("allow by group id", TestAllowByGroupIDs)
|
||||||
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
|
t.Run("deny get container no rule found", testDenyGetContainerNoRuleFound)
|
||||||
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
t.Run("deny get container for others", testDenyGetContainerForOthers)
|
||||||
t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag)
|
t.Run("deny get container by user claim tag", testDenyGetContainerByUserClaimTag)
|
||||||
|
@ -145,6 +146,104 @@ func testAllowThenDenyGetContainerRuleDefined(t *testing.T) {
|
||||||
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
|
require.Contains(t, errAccessDenied.Reason(), chain.AccessDenied.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllowByGroupIDs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
srv := &srvStub{
|
||||||
|
calls: map[string]int{},
|
||||||
|
}
|
||||||
|
router := inmemory.NewInMemory()
|
||||||
|
contRdr := &containerStub{
|
||||||
|
c: map[cid.ID]*containercore.Container{},
|
||||||
|
}
|
||||||
|
ir := &irStub{
|
||||||
|
keys: [][]byte{},
|
||||||
|
}
|
||||||
|
nm := &netmapStub{}
|
||||||
|
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
frostfsIDSubjectReader := &frostfsidStub{
|
||||||
|
subjects: map[util.Uint160]*client.Subject{
|
||||||
|
pk.PublicKey().GetScriptHash(): {
|
||||||
|
KV: map[string]string{
|
||||||
|
"tag-attr1": "value1",
|
||||||
|
"tag-attr2": "value2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subjectsExt: map[util.Uint160]*client.SubjectExtended{
|
||||||
|
pk.PublicKey().GetScriptHash(): {
|
||||||
|
KV: map[string]string{
|
||||||
|
"tag-attr1": "value1",
|
||||||
|
"tag-attr2": "value2",
|
||||||
|
},
|
||||||
|
Groups: []*client.Group{
|
||||||
|
{
|
||||||
|
ID: 1,
|
||||||
|
Name: "Group#1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apeSrv := NewAPEServer(router, contRdr, ir, nm, frostfsIDSubjectReader, srv)
|
||||||
|
|
||||||
|
contID := cidtest.ID()
|
||||||
|
testContainer := containertest.Container()
|
||||||
|
pp := netmap.PlacementPolicy{}
|
||||||
|
require.NoError(t, pp.DecodeString("REP 1"))
|
||||||
|
testContainer.SetPlacementPolicy(pp)
|
||||||
|
contRdr.c[contID] = &containercore.Container{Value: testContainer}
|
||||||
|
|
||||||
|
nm.currentEpoch = 100
|
||||||
|
nm.netmaps = map[uint64]*netmap.NetMap{}
|
||||||
|
var testNetmap netmap.NetMap
|
||||||
|
testNetmap.SetEpoch(nm.currentEpoch)
|
||||||
|
testNetmap.SetNodes([]netmap.NodeInfo{{}})
|
||||||
|
nm.netmaps[nm.currentEpoch] = &testNetmap
|
||||||
|
nm.netmaps[nm.currentEpoch-1] = &testNetmap
|
||||||
|
|
||||||
|
req := &container.GetRequest{}
|
||||||
|
req.SetBody(&container.GetRequestBody{})
|
||||||
|
var refContID refs.ContainerID
|
||||||
|
contID.WriteToV2(&refContID)
|
||||||
|
req.GetBody().SetContainerID(&refContID)
|
||||||
|
|
||||||
|
require.NoError(t, signature.SignServiceMessage(&pk.PrivateKey, req))
|
||||||
|
|
||||||
|
_, _, err = router.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.GroupTarget(":1"), &chain.Chain{
|
||||||
|
Rules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.Allow,
|
||||||
|
Actions: chain.Actions{
|
||||||
|
Names: []string{
|
||||||
|
nativeschema.MethodGetContainer,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{
|
||||||
|
fmt.Sprintf(nativeschema.ResourceFormatRootContainer, contID.EncodeToString()),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: commonschema.PropertyKeyFrostFSIDGroupID,
|
||||||
|
Value: "1",
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err := apeSrv.Get(context.Background(), req)
|
||||||
|
require.NotNil(t, resp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func testDenyGetContainerNoRuleFound(t *testing.T) {
|
func testDenyGetContainerNoRuleFound(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
srv := &srvStub{
|
srv := &srvStub{
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object"
|
||||||
|
aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/router"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/router"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
|
||||||
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid"
|
||||||
|
@ -159,11 +160,19 @@ func (c *checkerImpl) CheckAPE(ctx context.Context, prm Prm) error {
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub, err := keys.NewPublicKeyFromString(prm.SenderKey)
|
pub, err := keys.NewPublicKeyFromString(prm.SenderKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
groups, err := aperequest.Groups(c.frostFSIDClient, pub)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy contract keeps group related chains as namespace-group pair.
|
||||||
dkirillov
commented
I'm not sure the following question relates to this PR but still. I'm not sure the following question relates to this PR but still.
Why do we check only container target (`policyengine.NewRequestTargetWithContainer`) in case of APE chains from bearer token? As I understand user can set any target in `APEOverride`
aarifullin
commented
This is great that you've asked this. Because I was wondering can be other targets used in BT? In some sense I "inherited" this scheme from eACL tables in BT where eACL table's target was only container. Let's discuss This is great that you've asked this. Because I was wondering can be other targets used in BT? In some sense I "inherited" this scheme from eACL tables in BT where eACL table's target was only container. Let's discuss
dkirillov
commented
Probably we should ask @fyrchik @alexvanin Probably we should ask @fyrchik @alexvanin
fyrchik
commented
The current implementation was indeed intended to serve as a replacement for EACL.
Another thing is that overriding namespace rules in isolation is probably not that useful, we would like to probably combine that with other overrides. The current implementation was indeed intended to serve as a replacement for EACL.
For namespaces there are some security considerations:
1. Container **owner** should not be able to override rules set by the namespace **admin**. Or should it?
2. If namespace admin allowed user A to get an object with a bearer token, can user A allow user B to get the same object if namespace rules prohibit user B any operations?
Another thing is that overriding namespace rules in isolation is probably not that useful, we would like to probably combine that with other overrides.
dkirillov
commented
I've just meant that if we allow form APE chain with any target in bearer token we should be able process such token in node I've just meant that if we allow form APE chain with [any target](https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/commit/1a5886e776de79fc6598838242e4dc7ff21e7bea/bearer/bearer.go#L52) in bearer token we should be able process such token in node
aarifullin
commented
Yes, > if we allow form APE chain with
Yes, `sdk` freely sets a target for `APEOverride` within a bearer token but `sdk` doesn't care would a service that uses this token should allow or deny such target. The question is do we really or will we want to override other targets with BT
|
|||||||
|
for i := range groups {
|
||||||
|
groups[i] = fmt.Sprintf("%s:%s", prm.Namespace, groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
if prm.BearerToken != nil && !prm.BearerToken.Impersonate() {
|
if prm.BearerToken != nil && !prm.BearerToken.Impersonate() {
|
||||||
if err := isValidBearer(prm.BearerToken, prm.ContainerOwner, prm.Container, pub, c.st); err != nil {
|
if err := isValidBearer(prm.BearerToken, prm.ContainerOwner, prm.Container, pub, c.st); err != nil {
|
||||||
|
@ -185,7 +194,7 @@ func (c *checkerImpl) CheckAPE(ctx context.Context, prm Prm) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := policyengine.NewRequestTargetExtended(prm.Namespace, prm.Container.EncodeToString(), fmt.Sprintf("%s:%s", prm.Namespace, pub.Address()), nil)
|
rt := policyengine.NewRequestTargetExtended(prm.Namespace, prm.Container.EncodeToString(), fmt.Sprintf("%s:%s", prm.Namespace, pub.Address()), groups)
|
||||||
status, ruleFound, err := c.chainRouter.IsAllowed(apechain.Ingress, rt, r)
|
status, ruleFound, err := c.chainRouter.IsAllowed(apechain.Ingress, rt, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||||
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
||||||
|
commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
|
||||||
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
@ -159,6 +160,8 @@ var (
|
||||||
|
|
||||||
objectID = "BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R"
|
objectID = "BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R"
|
||||||
|
|
||||||
|
groupID = "1"
|
||||||
|
|
||||||
role = "Container"
|
role = "Container"
|
||||||
|
|
||||||
senderPrivateKey, _ = keys.NewPrivateKey()
|
senderPrivateKey, _ = keys.NewPrivateKey()
|
||||||
|
@ -238,6 +241,7 @@ var apeCheckTestCases = []struct {
|
||||||
methods []string
|
methods []string
|
||||||
header testHeader
|
header testHeader
|
||||||
containerRules []chain.Rule
|
containerRules []chain.Rule
|
||||||
|
groupidRules []chain.Rule
|
||||||
expectAPEErr bool
|
expectAPEErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -393,6 +397,36 @@ var apeCheckTestCases = []struct {
|
||||||
},
|
},
|
||||||
expectAPEErr: true,
|
expectAPEErr: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "optional oid requests reached quota limit by group-id",
|
||||||
|
container: containerID,
|
||||||
|
methods: methodsOptionalOID,
|
||||||
|
header: testHeader{
|
||||||
|
headerObjSDK: &headerObjectSDKParams{
|
||||||
|
payloadSize: 1000,
|
||||||
|
},
|
||||||
|
fromRequestResponseHeader: true,
|
||||||
|
},
|
||||||
|
groupidRules: []chain.Rule{
|
||||||
|
{
|
||||||
|
Status: chain.QuotaLimitReached,
|
||||||
|
Actions: chain.Actions{Names: methodsOptionalOID},
|
||||||
|
Resources: chain.Resources{
|
||||||
|
Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)},
|
||||||
|
},
|
||||||
|
Any: true,
|
||||||
|
Condition: []chain.Condition{
|
||||||
|
{
|
||||||
|
Op: chain.CondStringEquals,
|
||||||
|
Kind: chain.KindRequest,
|
||||||
|
Key: commonschema.PropertyKeyFrostFSIDGroupID,
|
||||||
|
Value: groupID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectAPEErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type stMock struct{}
|
type stMock struct{}
|
||||||
|
@ -486,10 +520,19 @@ func TestAPECheck(t *testing.T) {
|
||||||
ls := inmemory.NewInmemoryLocalStorage()
|
ls := inmemory.NewInmemoryLocalStorage()
|
||||||
ms := inmemory.NewInmemoryMorphRuleChainStorage()
|
ms := inmemory.NewInmemoryMorphRuleChainStorage()
|
||||||
|
|
||||||
ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(test.container), &chain.Chain{
|
if len(test.containerRules) > 0 {
|
||||||
Rules: test.containerRules,
|
ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(test.container), &chain.Chain{
|
||||||
MatchType: chain.MatchTypeFirstMatch,
|
Rules: test.containerRules,
|
||||||
})
|
MatchType: chain.MatchTypeFirstMatch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(test.groupidRules) > 0 {
|
||||||
|
ls.AddOverride(chain.Ingress, policyengine.GroupTarget(":"+groupID), &chain.Chain{
|
||||||
|
Rules: test.groupidRules,
|
||||||
|
MatchType: chain.MatchTypeFirstMatch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
|
router := policyengine.NewDefaultChainRouterWithLocalOverrides(ms, ls)
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,17 @@ func (s *Service) checkAPE(ctx context.Context, bt *bearer.Token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rt := engine.NewRequestTargetExtended(namespace, cid.EncodeToString(), fmt.Sprintf("%s:%s", namespace, publicKey.Address()), nil)
|
groups, err := aperequest.Groups(s.frostfsidSubjectProvider, publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get group ids: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy contract keeps group related chains as namespace-group pair.
|
||||||
|
for i := range groups {
|
||||||
|
groups[i] = fmt.Sprintf("%s:%s", namespace, groups[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
rt := engine.NewRequestTargetExtended(namespace, cid.EncodeToString(), fmt.Sprintf("%s:%s", namespace, publicKey.Address()), groups)
|
||||||
status, found, err := s.router.IsAllowed(apechain.Ingress, rt, request)
|
status, found, err := s.router.IsAllowed(apechain.Ingress, rt, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apeErr(err)
|
return apeErr(err)
|
||||||
|
|
We have cache in
frostfsIDClient
that's why we can freely invokeGroups
function along withFormFrostfsIDRequestProperties
?Sorry, I don't get your point. Do you mean we can retrieve groups after
FormFrostfsIDRequestProperties
? Yes, we can but this requires some hurtful refactor in all services. For me that was easier to perform some duplication as the cache is used