frostfs-contract/tests/frostfsid_test.go
Alexander Chuprov 7a8c64b966
All checks were successful
DCO action / DCO (pull_request) Successful in 40s
Code generation / Generate wrappers (pull_request) Successful in 1m26s
Tests / Tests (pull_request) Successful in 1m40s
[#118] frostfsid: Restrict keys to a single subject
Signed-off-by: Alexander Chuprov <a.chuprov@yadro.com>
2024-11-13 21:35:15 +03:00

885 lines
30 KiB
Go

package tests
import (
"errors"
"path"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/require"
)
const frostfsidPath = "../frostfsid"
const defaultNamespace = ""
const (
setAdminMethod = "setAdmin"
getAdminMethod = "getAdmin"
clearAdminMethod = "clearAdmin"
createSubjectMethod = "createSubject"
getSubjectMethod = "getSubject"
listSubjectsMethod = "listSubjects"
addSubjectKeyMethod = "addSubjectKey"
removeSubjectKeyMethod = "removeSubjectKey"
getSubjectByKeyMethod = "getSubjectByKey"
getSubjectKeyByNameMethod = "getSubjectKeyByName"
setSubjectNameMethod = "setSubjectName"
setSubjectKVMethod = "setSubjectKV"
deleteSubjectKVMethod = "deleteSubjectKV"
deleteSubjectMethod = "deleteSubject"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createGroupMethod = "createGroup"
getGroupMethod = "getGroup"
getGroupExtendedMethod = "getGroupExtended"
getGroupIDByNameMethod = "getGroupIDByName"
setGroupNameMethod = "setGroupName"
setGroupKVMethod = "setGroupKV"
deleteGroupKVMethod = "deleteGroupKV"
listGroupsMethod = "listGroups"
addSubjectToGroupMethod = "addSubjectToGroup"
removeSubjectFromGroupMethod = "removeSubjectFromGroup"
listGroupSubjectsMethod = "listGroupSubjects"
deleteGroupMethod = "deleteGroup"
)
const notWitnessedError = "not witnessed"
type testFrostFSIDInvoker struct {
e *neotest.Executor
contractHash util.Uint160
owner *wallet.Account
}
func (f *testFrostFSIDInvoker) OwnerInvoker() *neotest.ContractInvoker {
return f.e.NewInvoker(f.contractHash, neotest.NewSingleSigner(f.owner))
}
func (f *testFrostFSIDInvoker) CommitteeInvoker() *neotest.ContractInvoker {
return f.e.CommitteeInvoker(f.contractHash)
}
func (f *testFrostFSIDInvoker) AnonInvoker(t *testing.T) *neotest.ContractInvoker {
acc, err := wallet.NewAccount()
require.NoError(t, err)
return f.e.NewInvoker(f.contractHash, newSigner(t, f.CommitteeInvoker(), acc))
}
func newSigner(t *testing.T, c *neotest.ContractInvoker, acc *wallet.Account) neotest.Signer {
amount := int64(100_0000_0000)
tx := c.NewTx(t, []neotest.Signer{c.Validator},
c.NativeHash(t, nativenames.Gas), "transfer",
c.Validator.ScriptHash(), acc.Contract.ScriptHash(), amount, nil)
c.AddNewBlock(t, tx)
c.CheckHalt(t, tx.Hash())
return neotest.NewSingleSigner(acc)
}
func deployFrostFSIDContract(t *testing.T, e *neotest.Executor, contractOwner util.Uint160) util.Uint160 {
args := make([]any, 5)
args[0] = contractOwner
c := neotest.CompileFile(t, e.CommitteeHash, frostfsidPath, path.Join(frostfsidPath, "config.yml"))
e.DeployContract(t, c, args)
return c.Hash
}
func newFrostFSIDInvoker(t *testing.T) *testFrostFSIDInvoker {
e := newExecutor(t)
acc, err := wallet.NewAccount()
require.NoError(t, err)
h := deployFrostFSIDContract(t, e, acc.ScriptHash())
newSigner(t, e.CommitteeInvoker(h), acc)
return &testFrostFSIDInvoker{
e: e,
contractHash: h,
owner: acc,
}
}
func TestFrostFSID_ContractOwnersManagement(t *testing.T) {
f := newFrostFSIDInvoker(t)
anonInvoker := f.AnonInvoker(t)
anonInvokerHash := anonInvoker.Signers[0].ScriptHash()
invoker := f.OwnerInvoker()
invokerHash := invoker.Signers[0].ScriptHash()
committeeInvoker := f.CommitteeInvoker()
checkOwner(t, anonInvoker, invokerHash)
anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace")
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, "namespace")
t.Run("setAdmin is only allowed for committee", func(t *testing.T) {
invoker.InvokeFail(t, notWitnessedError, setAdminMethod, anonInvokerHash)
})
t.Run("replace owner", func(t *testing.T) {
committeeInvoker.Invoke(t, stackitem.Null{}, setAdminMethod, anonInvokerHash)
checkOwner(t, anonInvoker, anonInvokerHash)
invoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace2")
anonInvoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, "namespace2")
})
t.Run("remove owner", func(t *testing.T) {
committeeInvoker.Invoke(t, stackitem.Null{}, clearAdminMethod)
checkOwner(t, anonInvoker)
invoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace3")
anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, "namespace3")
})
}
func checkOwner(t *testing.T, invoker *neotest.ContractInvoker, owner ...util.Uint160) {
if len(owner) > 1 {
require.Fail(t, "invalid testcase")
}
s, err := invoker.TestInvoke(t, getAdminMethod)
require.NoError(t, err)
require.Equal(t, 1, s.Len(), "unexpected number items on stack")
if len(owner) == 0 {
_, isMissing := s.Pop().Item().(stackitem.Null)
require.True(t, isMissing)
return
}
bs, err := s.Pop().Item().TryBytes()
require.NoError(t, err)
require.Equal(t, bs, owner[0].BytesBE())
}
func TestFrostFSID_DefaultNamespace(t *testing.T) {
f := newFrostFSIDInvoker(t)
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr := subjKey.PublicKey().GetScriptHash()
invoker := f.OwnerInvoker()
groupID := int64(1)
groupName := "group"
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, defaultNamespace, groupName)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKeyAddr, groupID)
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjKeyAddr, groupID)
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, defaultNamespace, groupID)
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
}
func TestFrostFSID_SubjectManagement(t *testing.T) {
f := newFrostFSIDInvoker(t)
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr := subjKey.PublicKey().GetScriptHash()
anonInvoker := f.AnonInvoker(t)
invoker := f.OwnerInvoker()
anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
invoker.InvokeFail(t, "already exists", createSubjectMethod, defaultNamespace, subjKey.PublicKey().Bytes())
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err)
subj := parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey))
t.Run("add subject key", func(t *testing.T) {
subjNewKey, err := keys.NewPrivateKey()
require.NoError(t, err)
anonInvoker.InvokeFail(t, notWitnessedError, addSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err)
subj := parseSubject(t, s)
require.Len(t, subj.AdditionalKeys, 1)
require.True(t, subjNewKey.PublicKey().Equal(subj.AdditionalKeys[0]))
t.Run("get subject by additional key", func(t *testing.T) {
s, err = anonInvoker.TestInvoke(t, getSubjectByKeyMethod, subjNewKey.PublicKey().Bytes())
require.NoError(t, err)
subj := parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
s, err = anonInvoker.TestInvoke(t, getSubjectByKeyMethod, subjKey.PublicKey().Bytes())
require.NoError(t, err)
subj = parseSubject(t, s)
require.True(t, subjKey.PublicKey().Equal(&subj.PrimaryKey), "keys must be the same")
t.Run("remove subject key", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjKeyAddr, subjNewKey.PublicKey().Bytes())
anonInvoker.InvokeFail(t, "not found", getSubjectByKeyMethod, subjNewKey.PublicKey().Bytes())
})
})
})
t.Run("set subject name", func(t *testing.T) {
login := "some-login"
anonInvoker.InvokeFail(t, notWitnessedError, setSubjectNameMethod, subjKeyAddr, login)
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr, login)
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err)
subj = parseSubject(t, s)
require.Equal(t, login, subj.Name)
})
t.Run("set subject KVs", func(t *testing.T) {
iamPath := "iam/path"
anonInvoker.InvokeFail(t, notWitnessedError, setSubjectKVMethod, subjKeyAddr, client.IAMPathKey, iamPath)
invoker.Invoke(t, stackitem.Null{}, setSubjectKVMethod, subjKeyAddr, client.IAMPathKey, iamPath)
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err)
subj = parseSubject(t, s)
require.Equal(t, iamPath, subj.KV[client.IAMPathKey])
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectKVMethod, subjKeyAddr, client.IAMPathKey)
invoker.Invoke(t, stackitem.Null{}, deleteSubjectKVMethod, subjKeyAddr, client.IAMPathKey)
s, err = anonInvoker.TestInvoke(t, getSubjectMethod, subjKeyAddr)
require.NoError(t, err)
subj = parseSubject(t, s)
require.Empty(t, subj.KV)
})
t.Run("list subjects", func(t *testing.T) {
newSubjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, newSubjKey.PublicKey().Bytes())
s, err = anonInvoker.TestInvoke(t, listSubjectsMethod)
require.NoError(t, err)
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
require.NoError(t, err)
require.Len(t, addresses, 2)
require.ElementsMatch(t, addresses, []util.Uint160{subjKeyAddr, newSubjKey.PublicKey().GetScriptHash()})
})
anonInvoker.InvokeFail(t, notWitnessedError, deleteSubjectMethod, subjKeyAddr)
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr)
anonInvoker.InvokeFail(t, "subject not found", getSubjectMethod, subjKeyAddr)
}
func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
f := newFrostFSIDInvoker(t)
subjName1 := "subj1"
subjKey1, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr1 := subjKey1.PublicKey().GetScriptHash()
subjName2 := "subj2"
subjKey2, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr2 := subjKey2.PublicKey().GetScriptHash()
subjName3 := "subj3"
subjKey3, err := keys.NewPrivateKey()
require.NoError(t, err)
subjKeyAddr3 := subjKey3.PublicKey().GetScriptHash()
invoker := f.OwnerInvoker()
ns1, ns2 := "ns1", "ns2"
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1)
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey1.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns1, subjKey2.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns2, subjKey3.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName3)
// Check that we can find public key by name for subj1 (with name)
// and cannot find key for subj2 (without name)
s, err := invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName1)
checkPublicKeyResult(t, s, err, subjKey1)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2)
checkPublicKeyResult(t, s, err, nil)
// Check that we can find public key for by name for subj2 when we set its name
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr2, subjName2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2)
checkPublicKeyResult(t, s, err, subjKey2)
// Check that we cannot set for second subject name that the first subject has already taken
invoker.InvokeFail(t, "not available", setSubjectNameMethod, subjKeyAddr2, subjName1)
// Check that we cannot find public key by name for subject that was removed
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjKeyAddr2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns1, subjName2)
checkPublicKeyResult(t, s, err, nil)
// Check that subj3 can have the same name as subj1 if they belong to different namespaces
// Also check that after subject renaming its key cannot be found by old name
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName1)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName1)
checkPublicKeyResult(t, s, err, subjKey3)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName3)
checkPublicKeyResult(t, s, err, nil)
}
func TestFrostFSIS_GroupNameRelatedInvariants(t *testing.T) {
f := newFrostFSIDInvoker(t)
groupName1, groupName2 := "group1", "group2"
groupID1, groupID2 := int64(1), int64(2)
invoker := f.OwnerInvoker()
ns1, ns2 := "ns1", "ns2"
// Create two group
// Create two namespace.
// Add these groups to ns1
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1)
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2)
invoker.Invoke(t, stackitem.Make(groupID1), createGroupMethod, ns1, groupName1)
invoker.Invoke(t, stackitem.Make(groupID2), createGroupMethod, ns1, groupName2)
// Check that we can find group id by name for group1 in ns1 and not in ns2
s, err := invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName1)
checkGroupIDResult(t, s, err, groupID1)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns2, groupName1)
checkGroupIDResult(t, s, err, -1)
// Check that we cannot set for second group name that the first subject has already taken
invoker.InvokeFail(t, "not available", setGroupNameMethod, ns1, groupID1, groupName2)
// Check that we cannot create group with the same name in namespace, but can in another
invoker.InvokeFail(t, "not available", createGroupMethod, ns1, groupName2)
invoker.Invoke(t, stackitem.Make(3), createGroupMethod, ns2, groupName2)
// Check that we cannot find group id by name for group that was removed
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, ns1, groupID2)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName2)
checkGroupIDResult(t, s, err, -1)
// Check that we can create group with the name that was freed after deleting
invoker.Invoke(t, stackitem.Make(4), createGroupMethod, ns1, groupName2)
// Check that after group renaming its id cannot be found by old name
newGroupName := "new"
invoker.Invoke(t, stackitem.Null{}, setGroupNameMethod, ns1, groupID1, newGroupName)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, groupName1)
checkGroupIDResult(t, s, err, -1)
s, err = invoker.TestInvoke(t, getGroupIDByNameMethod, ns1, newGroupName)
checkGroupIDResult(t, s, err, groupID1)
}
func TestFrostFSID_NamespaceManagement(t *testing.T) {
f := newFrostFSIDInvoker(t)
anonInvoker := f.AnonInvoker(t)
invoker := f.OwnerInvoker()
namespace := "some-namespace"
anonInvoker.InvokeFail(t, notWitnessedError, createNamespaceMethod, namespace)
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace)
invoker.InvokeFail(t, "already exists", createNamespaceMethod, namespace)
s, err := anonInvoker.TestInvoke(t, getNamespaceMethod, namespace)
require.NoError(t, err)
ns := parseNamespace(t, s.Pop().Item())
require.Equal(t, namespace, ns.Name)
t.Run("add user to namespace", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, ns.Name, subjKey.PublicKey().Bytes())
subjName := "name"
subjAddress := subjKey.PublicKey().GetScriptHash()
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjAddress, subjName)
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress)
require.NoError(t, err)
subj := parseSubject(t, s)
require.Equal(t, namespace, subj.Namespace)
t.Run("list namespace subjects", func(t *testing.T) {
s, err := anonInvoker.TestInvoke(t, listNamespaceSubjectsMethod, namespace)
require.NoError(t, err)
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
require.NoError(t, err)
require.ElementsMatch(t, addresses, []util.Uint160{subjAddress})
})
t.Run("get subject key by name", func(t *testing.T) {
s, err := anonInvoker.TestInvoke(t, getSubjectKeyByNameMethod, namespace, subjName)
require.NoError(t, err)
foundKey, err := unwrap.PublicKey(makeValidRes(s.Pop().Item()), nil)
require.NoError(t, err)
require.Equal(t, subjKey.PublicKey(), foundKey)
})
t.Run("list namespaces", func(t *testing.T) {
namespace2 := "some-namespace2"
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, namespace2)
s, err := anonInvoker.TestInvoke(t, listNamespacesMethod)
require.NoError(t, err)
namespaces := parseNamespaces(t, readIteratorAll(s))
require.NoError(t, err)
require.ElementsMatch(t, namespaces, []Namespace{{Name: defaultNamespace}, {Name: namespace}, {Name: namespace2}})
t.Run("find namespaces with some subjects", func(t *testing.T) {
for _, ns := range namespaces {
s, err := anonInvoker.TestInvoke(t, getNamespaceExtendedMethod, ns.Name)
require.NoError(t, err)
nsExt := parseNamespaceExtended(t, s.Pop().Item())
if nsExt.SubjectsCount > 0 {
require.Equal(t, namespace, nsExt.Name)
}
}
})
})
})
}
func TestFrostFSID_GroupManagement(t *testing.T) {
f := newFrostFSIDInvoker(t)
anonInvoker := f.AnonInvoker(t)
invoker := f.OwnerInvoker()
nsName := "namespace"
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, nsName)
groupID := int64(1)
groupName := "group"
anonInvoker.InvokeFail(t, notWitnessedError, createGroupMethod, nsName, groupName)
invoker.Invoke(t, stackitem.Make(groupID), createGroupMethod, nsName, groupName)
s, err := anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group := parseGroup(t, s.Pop().Item())
require.Equal(t, groupID, group.ID)
require.Equal(t, groupName, group.Name)
s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName)
require.NoError(t, err)
groups := parseGroups(t, readIteratorAll(s))
require.ElementsMatch(t, groups, []Group{{ID: groupID, Name: groupName, Namespace: nsName}})
t.Run("add subjects to group", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes())
subjAddress := subjKey.PublicKey().GetScriptHash()
anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupID)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupID)
t.Run("list group subjects", func(t *testing.T) {
s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupID)
require.NoError(t, err)
addresses, err := unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
require.NoError(t, err)
require.ElementsMatch(t, addresses, []util.Uint160{subjAddress})
anonInvoker.InvokeFail(t, "not witnessed", removeSubjectFromGroupMethod, subjAddress, groupID)
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromGroupMethod, subjAddress, groupID)
s, err = anonInvoker.TestInvoke(t, listGroupSubjectsMethod, nsName, groupID)
require.NoError(t, err)
addresses, err = unwrap.ArrayOfUint160(makeValidRes(stackitem.NewArray(readIteratorAll(s))), nil)
require.NoError(t, err)
require.Empty(t, addresses)
t.Run("get group extended", func(t *testing.T) {
subjectsCount := 10
for i := 0; i < subjectsCount; i++ {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupID)
}
s, err = anonInvoker.TestInvoke(t, getGroupExtendedMethod, nsName, groupID)
require.NoError(t, err)
groupExt := parseGroupExtended(t, s.Pop().Item())
require.Equal(t, groupID, groupExt.ID)
require.Equal(t, groupName, groupExt.Name)
require.Empty(t, groupExt.KV)
require.EqualValues(t, subjectsCount, groupExt.SubjectsCount)
})
})
})
t.Run("set group name", func(t *testing.T) {
newGroupName := "new-name"
anonInvoker.InvokeFail(t, notWitnessedError, setGroupNameMethod, nsName, groupID, newGroupName)
invoker.Invoke(t, stackitem.Null{}, setGroupNameMethod, nsName, groupID, newGroupName)
s, err = anonInvoker.TestInvoke(t, getGroupIDByNameMethod, nsName, newGroupName)
require.NoError(t, err)
require.Equal(t, groupID, s.Pop().BigInt().Int64())
s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group = parseGroup(t, s.Pop().Item())
require.Equal(t, newGroupName, group.Name)
})
t.Run("set group KVs", func(t *testing.T) {
iamARN := "arn"
anonInvoker.InvokeFail(t, notWitnessedError, setGroupKVMethod, nsName, groupID, client.IAMARNKey, iamARN)
invoker.Invoke(t, stackitem.Null{}, setGroupKVMethod, nsName, groupID, client.IAMARNKey, iamARN)
s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group = parseGroup(t, s.Pop().Item())
require.Equal(t, iamARN, group.KV[client.IAMARNKey])
anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupKVMethod, nsName, groupID, client.IAMARNKey)
invoker.Invoke(t, stackitem.Null{}, deleteGroupKVMethod, nsName, groupID, client.IAMARNKey)
s, err = anonInvoker.TestInvoke(t, getGroupMethod, nsName, groupID)
require.NoError(t, err)
group = parseGroup(t, s.Pop().Item())
require.Empty(t, group.KV)
})
t.Run("delete group", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, deleteGroupMethod, nsName, groupID)
invoker.Invoke(t, stackitem.Null{}, deleteGroupMethod, nsName, groupID)
anonInvoker.InvokeFail(t, "group not found", getGroupMethod, nsName, groupID)
s, err = anonInvoker.TestInvoke(t, listGroupsMethod, nsName)
require.NoError(t, err)
groups := parseGroups(t, readIteratorAll(s))
require.Empty(t, groups)
})
}
func TestAdditionalKeyFromPrimarySubject(t *testing.T) {
f := newFrostFSIDInvoker(t)
invoker := f.OwnerInvoker()
subjAPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjAKeyAddr := subjAPrimaryKey.PublicKey().GetScriptHash()
subjBPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjBKeyAddr := subjBPrimaryKey.PublicKey().GetScriptHash()
subjCPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
subjDPrimaryKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjAPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjBPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjAPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjCPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjCPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjBKeyAddr)
invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes())
}
func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) {
if key == nil {
require.ErrorContains(t, err, "not found")
return
}
require.NoError(t, err)
foundKey, err := unwrap.PublicKey(makeValidRes(s.Pop().Item()), nil)
require.NoError(t, err)
require.Equal(t, key.PublicKey(), foundKey)
}
func checkGroupIDResult(t *testing.T, s *vm.Stack, err error, groupID int64) {
if groupID == -1 {
require.ErrorContains(t, err, "not found")
return
}
require.NoError(t, err)
foundGroupID, err := unwrap.Int64(makeValidRes(s.Pop().Item()), nil)
require.NoError(t, err)
require.Equal(t, groupID, foundGroupID)
}
func readIteratorAll(s *vm.Stack) []stackitem.Item {
iter := s.Pop().Value().(*storage.Iterator)
stackItems := make([]stackitem.Item, 0)
for iter.Next() {
stackItems = append(stackItems, iter.Value())
}
return stackItems
}
type Subject struct {
PrimaryKey keys.PublicKey
AdditionalKeys keys.PublicKeys
Namespace string
Name string
KV map[string]string
}
func parseSubject(t *testing.T, s *vm.Stack) Subject {
var subj Subject
subjStruct := s.Pop().Array()
require.Len(t, subjStruct, 5)
pkBytes, err := subjStruct[0].TryBytes()
require.NoError(t, err)
err = subj.PrimaryKey.DecodeBytes(pkBytes)
require.NoError(t, err)
if !subjStruct[1].Equals(stackitem.Null{}) {
subj.AdditionalKeys, err = unwrap.ArrayOfPublicKeys(makeValidRes(subjStruct[1]), nil)
require.NoError(t, err)
}
nsBytes, err := subjStruct[2].TryBytes()
require.NoError(t, err)
subj.Namespace = string(nsBytes)
nameBytes, err := subjStruct[3].TryBytes()
require.NoError(t, err)
subj.Name = string(nameBytes)
subj.KV, err = parseMap(subjStruct[4])
require.NoError(t, err)
return subj
}
func parseMap(item stackitem.Item) (map[string]string, error) {
if item.Equals(stackitem.Null{}) {
return nil, nil
}
metaMap, err := unwrap.Map(makeValidRes(item), nil)
if err != nil {
return nil, err
}
meta, ok := metaMap.Value().([]stackitem.MapElement)
if !ok {
return nil, errors.New("invalid map type")
}
res := make(map[string]string, len(meta))
for _, element := range meta {
key, err := element.Key.TryBytes()
if err != nil {
return nil, err
}
val, err := element.Value.TryBytes()
if err != nil {
return nil, err
}
res[string(key)] = string(val)
}
return res, nil
}
type Namespace struct {
Name string
}
type NamespaceExtended struct {
Name string
SubjectsCount int64
GroupsCount int64
}
func parseNamespace(t *testing.T, item stackitem.Item) Namespace {
var ns Namespace
subjStruct := item.Value().([]stackitem.Item)
require.Len(t, subjStruct, 1)
nameBytes, err := subjStruct[0].TryBytes()
require.NoError(t, err)
ns.Name = string(nameBytes)
return ns
}
func parseNamespaceExtended(t *testing.T, item stackitem.Item) NamespaceExtended {
var ns NamespaceExtended
subjStruct := item.Value().([]stackitem.Item)
require.Len(t, subjStruct, 3)
nameBytes, err := subjStruct[0].TryBytes()
require.NoError(t, err)
ns.Name = string(nameBytes)
groupCountInt, err := subjStruct[1].TryInteger()
require.NoError(t, err)
ns.GroupsCount = groupCountInt.Int64()
subjectsCountInt, err := subjStruct[2].TryInteger()
require.NoError(t, err)
ns.SubjectsCount = subjectsCountInt.Int64()
return ns
}
func parseNamespaces(t *testing.T, items []stackitem.Item) []Namespace {
res := make([]Namespace, len(items))
for i := 0; i < len(items); i++ {
res[i] = parseNamespace(t, items[i])
}
return res
}
type Group struct {
ID int64
Name string
Namespace string
KV map[string]string
}
type GroupExtended struct {
ID int64
Name string
Namespace string
KV map[string]string
SubjectsCount int64
}
func parseGroup(t *testing.T, item stackitem.Item) Group {
var group Group
subjStruct := item.Value().([]stackitem.Item)
require.Len(t, subjStruct, 4)
groupID, err := subjStruct[0].TryInteger()
require.NoError(t, err)
group.ID = groupID.Int64()
nameBytes, err := subjStruct[1].TryBytes()
require.NoError(t, err)
group.Name = string(nameBytes)
namespaceBytes, err := subjStruct[2].TryBytes()
require.NoError(t, err)
group.Namespace = string(namespaceBytes)
group.KV, err = parseMap(subjStruct[3])
require.NoError(t, err)
return group
}
func parseGroupExtended(t *testing.T, item stackitem.Item) GroupExtended {
var gr GroupExtended
subjStruct := item.Value().([]stackitem.Item)
require.Len(t, subjStruct, 5)
groupID, err := subjStruct[0].TryInteger()
require.NoError(t, err)
gr.ID = groupID.Int64()
nameBytes, err := subjStruct[1].TryBytes()
require.NoError(t, err)
gr.Name = string(nameBytes)
namespaceBytes, err := subjStruct[2].TryBytes()
require.NoError(t, err)
gr.Namespace = string(namespaceBytes)
gr.KV, err = parseMap(subjStruct[3])
require.NoError(t, err)
subjectsCountInt, err := subjStruct[4].TryInteger()
require.NoError(t, err)
gr.SubjectsCount = subjectsCountInt.Int64()
return gr
}
func parseGroups(t *testing.T, items []stackitem.Item) []Group {
res := make([]Group, len(items))
for i := 0; i < len(items); i++ {
res[i] = parseGroup(t, items[i])
}
return res
}
func makeValidRes(item stackitem.Item) *result.Invoke {
return &result.Invoke{
Stack: []stackitem.Item{item},
State: vmstate.Halt.String(),
}
}