From 0b87388c183618882b8a1bf8d446c69ceb4df8a6 Mon Sep 17 00:00:00 2001
From: Airat Arifullin <a.arifullin@yadro.com>
Date: Thu, 20 Jun 2024 15:39:39 +0300
Subject: [PATCH] [#1190] object: GroupIDs must also be target of APE checks

* Also add new test case for ape middleware in container service.

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
---
 pkg/services/object/ape/checker.go      | 13 ++++++-
 pkg/services/object/ape/checker_test.go | 51 +++++++++++++++++++++++--
 2 files changed, 58 insertions(+), 6 deletions(-)

diff --git a/pkg/services/object/ape/checker.go b/pkg/services/object/ape/checker.go
index ee71e6e1dd..ee12d7b973 100644
--- a/pkg/services/object/ape/checker.go
+++ b/pkg/services/object/ape/checker.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 
 	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/core/container"
 	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 {
 		return fmt.Errorf("failed to create ape request: %w", err)
 	}
-
 	pub, err := keys.NewPublicKeyFromString(prm.SenderKey)
 	if err != nil {
 		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.
+	for i := range groups {
+		groups[i] = fmt.Sprintf("%s:%s", prm.Namespace, groups[i])
+	}
 
 	if prm.BearerToken != nil && !prm.BearerToken.Impersonate() {
 		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)
 	if err != nil {
 		return err
diff --git a/pkg/services/object/ape/checker_test.go b/pkg/services/object/ape/checker_test.go
index 09954602c4..5efd3669b5 100644
--- a/pkg/services/object/ape/checker_test.go
+++ b/pkg/services/object/ape/checker_test.go
@@ -27,6 +27,7 @@ import (
 	"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
 	policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
 	"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"
 	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
 	"github.com/nspcc-dev/neo-go/pkg/util"
@@ -159,6 +160,8 @@ var (
 
 	objectID = "BzQw5HH3feoxFDD5tCT87Y1726qzgLfxEE7wgtoRzB3R"
 
+	groupID = "1"
+
 	role = "Container"
 
 	senderPrivateKey, _ = keys.NewPrivateKey()
@@ -238,6 +241,7 @@ var apeCheckTestCases = []struct {
 	methods        []string
 	header         testHeader
 	containerRules []chain.Rule
+	groupidRules   []chain.Rule
 	expectAPEErr   bool
 }{
 	{
@@ -393,6 +397,36 @@ var apeCheckTestCases = []struct {
 		},
 		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{}
@@ -486,10 +520,19 @@ func TestAPECheck(t *testing.T) {
 					ls := inmemory.NewInmemoryLocalStorage()
 					ms := inmemory.NewInmemoryMorphRuleChainStorage()
 
-					ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(test.container), &chain.Chain{
-						Rules:     test.containerRules,
-						MatchType: chain.MatchTypeFirstMatch,
-					})
+					if len(test.containerRules) > 0 {
+						ls.AddOverride(chain.Ingress, policyengine.ContainerTarget(test.container), &chain.Chain{
+							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)