268 lines
10 KiB
Go
268 lines
10 KiB
Go
package tests
|
|
|
|
import (
|
|
"bytes"
|
|
"path"
|
|
"testing"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/nns"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const policyPath = "../policy"
|
|
|
|
type policyContracts struct {
|
|
policy *neotest.ContractInvoker
|
|
frostfsid *neotest.ContractInvoker
|
|
}
|
|
|
|
func newPolicyInvokers(t *testing.T) *policyContracts {
|
|
e := newExecutor(t)
|
|
|
|
n := deployNNSContract(t, e)
|
|
ffid := deployFrostfsid(t, e)
|
|
polic := deployPolicyContract(t, e)
|
|
|
|
n.Invoke(t, true, "register",
|
|
nns.FrostfsIDNNSName, n.CommitteeHash,
|
|
"myemail@frostfs.info", defaultRefresh, defaultRetry, defaultExpire, defaultTTL)
|
|
n.Invoke(t, stackitem.Null{}, "addRecord",
|
|
nns.FrostfsIDNNSName, int64(nns.TXT), ffid.Hash.StringLE())
|
|
n.Invoke(t, stackitem.Null{}, "addRecord",
|
|
nns.FrostfsIDNNSName, int64(nns.TXT), address.Uint160ToString(ffid.Hash))
|
|
|
|
return &policyContracts{
|
|
policy: polic,
|
|
frostfsid: ffid,
|
|
}
|
|
}
|
|
|
|
func deployNNSContract(t *testing.T, e *neotest.Executor) *neotest.ContractInvoker {
|
|
ctrNNS := neotest.CompileFile(t, e.CommitteeHash, nnsPath, path.Join(nnsPath, "config.yml"))
|
|
e.DeployContract(t, ctrNNS, nil)
|
|
|
|
n := e.CommitteeInvoker(ctrNNS.Hash)
|
|
|
|
return n
|
|
}
|
|
|
|
func deployFrostfsid(t *testing.T, e *neotest.Executor) *neotest.ContractInvoker {
|
|
acc, err := wallet.NewAccount()
|
|
require.NoError(t, err)
|
|
args := make([]any, 5)
|
|
args[0] = acc.ScriptHash()
|
|
frostfsID := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml"))
|
|
e.DeployContract(t, frostfsID, args)
|
|
return e.CommitteeInvoker(frostfsID.Hash)
|
|
}
|
|
|
|
func deployPolicyContract(t *testing.T, e *neotest.Executor) *neotest.ContractInvoker {
|
|
cfgPath := path.Join(policyPath, "config.yml")
|
|
c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath)
|
|
e.DeployContract(t, c, []any{nil})
|
|
return e.CommitteeInvoker(c.Hash)
|
|
}
|
|
|
|
func TestPolicy(t *testing.T) {
|
|
c := newPolicyInvokers(t)
|
|
|
|
checkChainsIteratorByPrefix(t, c.policy, policy.Namespace, "mynamespace", "ingress", [][]byte{})
|
|
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress", [][]byte{})
|
|
|
|
// Policies are opaque to the contract and are just raw bytes to store.
|
|
p1 := []byte("chain1")
|
|
p2 := []byte("chain2")
|
|
p3 := []byte("chain3")
|
|
p33 := []byte("chain33")
|
|
|
|
c.frostfsid.Invoke(t, stackitem.Null{}, "createNamespace", "mynamespace")
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
|
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1})
|
|
checkChains(t, c.policy, "mynamespace", "", "all", nil)
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule2", p2)
|
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
|
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1}) // Only namespace chains.
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2})
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "all", nil) // No chains attached to 'all'.
|
|
checkChains(t, c.policy, "mynamespace", "cnr2", "ingress", [][]byte{p1}) // Only namespace, no chains for the container.
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p3)
|
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p3})
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
|
checkChain(t, c.policy, policy.Container, "cnr1", "ingress:myrule3", p33)
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p1, p2, p33}) // Override chain.
|
|
checkChainsByPrefix(t, c.policy, policy.Container, "cnr1", "", [][]byte{p2, p33})
|
|
checkChainsByPrefix(t, c.policy, policy.IAM, "", "", nil)
|
|
checkChainKeys(t, c.policy, policy.Container, "cnr1", []string{"ingress:myrule2", "ingress:myrule3"})
|
|
|
|
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress:myrule3", [][]byte{p33})
|
|
checkChainsIteratorByPrefix(t, c.policy, policy.Container, "cnr1", "ingress", [][]byte{p2, p33})
|
|
|
|
t.Run("removal", func(t *testing.T) {
|
|
t.Run("wrong name", func(t *testing.T) {
|
|
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress")
|
|
checkChains(t, c.policy, "mynamespace", "", "ingress", [][]byte{p1})
|
|
})
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "removeChain", policy.Namespace, "mynamespace", "ingress:123")
|
|
checkChains(t, c.policy, "mynamespace", "", "ingress", nil)
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", [][]byte{p2, p33}) // Container chains still exist.
|
|
|
|
// Remove by prefix.
|
|
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil)
|
|
|
|
// Remove by prefix.
|
|
c.policy.Invoke(t, stackitem.Null{}, "removeChainsByPrefix", policy.Container, "cnr1", "ingress")
|
|
checkChains(t, c.policy, "mynamespace", "cnr1", "ingress", nil)
|
|
|
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{})
|
|
checkTargets(t, c.policy, policy.Container, [][]byte{})
|
|
})
|
|
|
|
t.Run("add again after removal", func(t *testing.T) {
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "mynamespace", "ingress:123", p1)
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Container, "cnr1", "ingress:myrule3", p33)
|
|
|
|
checkTargets(t, c.policy, policy.Namespace, [][]byte{[]byte("mynamespace")})
|
|
checkTargets(t, c.policy, policy.Container, [][]byte{[]byte("cnr1")})
|
|
})
|
|
|
|
t.Run("add chain for non-active namespace", func(t *testing.T) {
|
|
c.frostfsid.Invoke(t, stackitem.Null{}, "createNamespace", "nsdisabled")
|
|
|
|
c.frostfsid.Invoke(t, stackitem.Null{}, "updateNamespace", "nsdisabled", "frozen")
|
|
c.policy.InvokeFail(t, "namespace is non-active", "addChain", policy.Namespace, "nsdisabled", "ingress:3", p1)
|
|
|
|
c.frostfsid.Invoke(t, stackitem.Null{}, "updateNamespace", "nsdisabled", "purge")
|
|
c.policy.InvokeFail(t, "namespace is non-active", "addChain", policy.Namespace, "nsdisabled", "ingress:3", p1)
|
|
|
|
c.frostfsid.Invoke(t, stackitem.Null{}, "updateNamespace", "nsdisabled", "active")
|
|
c.policy.Invoke(t, stackitem.Null{}, "addChain", policy.Namespace, "nsdisabled", "ingress:3", p1)
|
|
})
|
|
}
|
|
|
|
func TestAutorization(t *testing.T) {
|
|
c := newPolicyInvokers(t)
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "getAdmin")
|
|
|
|
s := c.policy.NewAccount(t, 1_0000_0000)
|
|
cs := c.policy.WithSigners(s)
|
|
|
|
args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")}
|
|
cs.InvokeFail(t, policy.ErrNotAuthorized, "addChain", args...)
|
|
|
|
c.policy.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
|
|
c.policy.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
|
|
|
|
cs.Invoke(t, stackitem.Null{}, "addChain", args...)
|
|
}
|
|
|
|
func checkChains(t *testing.T, e *neotest.ContractInvoker, namespace, container, name string, expected [][]byte) {
|
|
s, err := e.TestInvoke(t, "listChains", namespace, container, name)
|
|
require.NoError(t, err)
|
|
|
|
checksChainsOnStack(t, s, expected)
|
|
}
|
|
|
|
func checkChainsByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) {
|
|
s, err := e.TestInvoke(t, "listChainsByPrefix", kind, entityName, prefix)
|
|
require.NoError(t, err)
|
|
|
|
checksChainsOnStack(t, s, expected)
|
|
}
|
|
|
|
func checkChainsIteratorByPrefix(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, prefix string, expected [][]byte) {
|
|
s, err := e.TestInvoke(t, "iteratorChainsByPrefix", kind, entityName, prefix)
|
|
require.NoError(t, err)
|
|
|
|
if s.Len() == 0 {
|
|
t.Fatal("Stack is empty")
|
|
}
|
|
|
|
iteratorItem := s.Pop().Value().(*storage.Iterator)
|
|
policys := iteratorToArray(iteratorItem)
|
|
require.Equal(t, len(expected), len(policys))
|
|
|
|
for i := range expected {
|
|
bytesPolicy, err := policys[i].TryBytes()
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected[i], bytesPolicy)
|
|
}
|
|
}
|
|
|
|
func checkChainKeys(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName string, expected []string) {
|
|
s, err := e.TestInvoke(t, "listChainNames", kind, entityName)
|
|
require.NoError(t, err)
|
|
|
|
if s.Len() == 0 {
|
|
t.Fatal("Stack is empty")
|
|
}
|
|
|
|
iteratorItem := s.Pop().Value().(*storage.Iterator)
|
|
policys := iteratorToArray(iteratorItem)
|
|
require.Equal(t, len(expected), len(policys))
|
|
|
|
for i := range expected {
|
|
bytesPolicy, err := policys[i].TryBytes()
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected[i], string(bytesPolicy))
|
|
}
|
|
}
|
|
|
|
func checksChainsOnStack(t *testing.T, s *vm.Stack, expected [][]byte) {
|
|
require.Equal(t, 1, s.Len())
|
|
|
|
var actual [][]byte
|
|
arr := s.Pop().Array()
|
|
for i := range arr {
|
|
bs, err := arr[i].TryBytes()
|
|
|
|
require.NoError(t, err)
|
|
actual = append(actual, bs)
|
|
}
|
|
|
|
require.ElementsMatch(t, expected, actual)
|
|
}
|
|
|
|
func checkChain(t *testing.T, e *neotest.ContractInvoker, kind byte, entityName, name string, expected []byte) {
|
|
s, err := e.TestInvoke(t, "getChain", kind, entityName, name)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, s.Len())
|
|
|
|
require.True(t, bytes.Equal(expected, s.Pop().Bytes()))
|
|
}
|
|
|
|
func checkTargets(t *testing.T, e *neotest.ContractInvoker, kind byte, expected [][]byte) {
|
|
s, err := e.TestInvoke(t, "listTargets", kind)
|
|
require.NoError(t, err)
|
|
|
|
require.NotEqual(t, 0, s.Len(), "stack is empty")
|
|
|
|
iteratorItem := s.Pop().Value().(*storage.Iterator)
|
|
targets := iteratorToArray(iteratorItem)
|
|
require.Equal(t, len(expected), len(targets))
|
|
|
|
for i := range expected {
|
|
bytesTargets, err := targets[i].TryBytes()
|
|
require.NoError(t, err)
|
|
require.Equal(t, expected[i], bytesTargets)
|
|
}
|
|
}
|