package tree import ( "context" "encoding/hex" "fmt" "testing" "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" core "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" frostfsidcore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/frostfsid" checkercore "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/common/ape" containerSDK "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" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory" 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" "github.com/stretchr/testify/require" ) var ( containerID = "73tQMTYyUkTgmvPR1HWib6pndbhSoBovbnMF7Pws8Rcy" senderPrivateKey, _ = keys.NewPrivateKey() senderKey = hex.EncodeToString(senderPrivateKey.PublicKey().Bytes()) rootCnr = &core.Container{Value: containerSDK.Container{}} ) type frostfsIDProviderMock struct { subjects map[util.Uint160]*client.Subject subjectsExtended map[util.Uint160]*client.SubjectExtended } func (f *frostfsIDProviderMock) GetSubject(key util.Uint160) (*client.Subject, error) { v, ok := f.subjects[key] if !ok { return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage) } return v, nil } func (f *frostfsIDProviderMock) GetSubjectExtended(key util.Uint160) (*client.SubjectExtended, error) { v, ok := f.subjectsExtended[key] if !ok { return nil, fmt.Errorf("%s", frostfsidcore.SubjectNotFoundErrorMessage) } return v, nil } var _ frostfsidcore.SubjectProvider = (*frostfsIDProviderMock)(nil) func newFrostfsIDProviderMock(t *testing.T) *frostfsIDProviderMock { return &frostfsIDProviderMock{ subjects: map[util.Uint160]*client.Subject{ scriptHashFromSenderKey(t, senderKey): { Namespace: "testnamespace", Name: "test", KV: map[string]string{ "tag-attr1": "value1", "tag-attr2": "value2", }, }, }, subjectsExtended: map[util.Uint160]*client.SubjectExtended{ scriptHashFromSenderKey(t, senderKey): { Namespace: "testnamespace", Name: "test", KV: map[string]string{ "tag-attr1": "value1", "tag-attr2": "value2", }, Groups: []*client.Group{ { ID: 1, Name: "test", Namespace: "testnamespace", KV: map[string]string{ "attr1": "value1", "attr2": "value2", }, }, }, }, }, } } func scriptHashFromSenderKey(t *testing.T, senderKey string) util.Uint160 { pk, err := keys.NewPublicKeyFromString(senderKey) require.NoError(t, err) return pk.GetScriptHash() } type stMock struct{} func (m *stMock) CurrentEpoch() uint64 { return 8 } func TestCheckAPE(t *testing.T) { cid := cid.ID{} _ = cid.DecodeString(containerID) t.Run("put non-tombstone rule won't affect tree remove", 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{}), } los.AddOverride(chain.Ingress, engine.ContainerTarget(containerID), &chain.Chain{ Rules: []chain.Rule{ { Status: chain.AccessDenied, Actions: chain.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: chain.Resources{ Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)}, }, Condition: []chain.Condition{ { Op: chain.CondStringNotEquals, Kind: chain.KindResource, Key: nativeschema.PropertyKeyObjectType, Value: "TOMBSTONE", }, }, }, }, MatchType: chain.MatchTypeFirstMatch, }) mcs.AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(containerID), &chain.Chain{ Rules: []chain.Rule{ { Status: chain.Allow, Actions: chain.Actions{Names: []string{nativeschema.MethodDeleteObject}}, Resources: chain.Resources{ Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)}, }, }, }, MatchType: chain.MatchTypeFirstMatch, }) err := s.checkAPE(context.Background(), nil, rootCnr, cid, acl.OpObjectDelete, acl.RoleOwner, senderPrivateKey.PublicKey()) require.NoError(t, err) }) t.Run("delete rule won't affect tree add", 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{}), } los.AddOverride(chain.Ingress, engine.ContainerTarget(containerID), &chain.Chain{ Rules: []chain.Rule{ { Status: chain.AccessDenied, Actions: chain.Actions{Names: []string{nativeschema.MethodDeleteObject}}, Resources: chain.Resources{ Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)}, }, }, }, MatchType: chain.MatchTypeFirstMatch, }) mcs.AddMorphRuleChain(chain.Ingress, engine.ContainerTarget(containerID), &chain.Chain{ Rules: []chain.Rule{ { Status: chain.Allow, Actions: chain.Actions{Names: []string{nativeschema.MethodPutObject}}, Resources: chain.Resources{ Names: []string{fmt.Sprintf(nativeschema.ResourceFormatRootContainerObjects, containerID)}, }, Condition: []chain.Condition{ { Op: chain.CondStringNotEquals, Kind: chain.KindResource, Key: nativeschema.PropertyKeyObjectType, Value: "TOMBSTONE", }, }, }, }, MatchType: chain.MatchTypeFirstMatch, }) err := s.checkAPE(context.Background(), nil, rootCnr, cid, acl.OpObjectPut, acl.RoleOwner, senderPrivateKey.PublicKey()) require.NoError(t, err) }) }