[#48] frostfsid: Support empty namespaces

Require ns to create subject.
Since we don't allow move subject from one ns to another -
drop add/remove subject to/from namespace

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2023-12-07 12:23:18 +03:00
parent a1b61d3949
commit 12a34fa777
5 changed files with 202 additions and 364 deletions

View file

@ -101,13 +101,11 @@ const (
deleteSubjectKVMethod = "deleteSubjectKV"
deleteSubjectMethod = "deleteSubject"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
addSubjectToNamespaceMethod = "addSubjectToNamespace"
removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createGroupMethod = "createGroup"
getGroupMethod = "getGroup"
@ -199,16 +197,16 @@ func (c Client) GetAdmin() (util.Uint160, bool, error) {
return u, true, err
}
// CreateSubject creates new subject using public key.
// CreateSubject creates new subject using public key and namespace.
// Must be invoked by contract owner.
func (c Client) CreateSubject(key *keys.PublicKey) (tx util.Uint256, vub uint32, err error) {
method, args := c.CreateSubjectCall(key)
func (c Client) CreateSubject(ns string, key *keys.PublicKey) (tx util.Uint256, vub uint32, err error) {
method, args := c.CreateSubjectCall(ns, key)
return c.act.SendCall(c.contract, method, args...)
}
// CreateSubjectCall provides args for CreateSubject to use in commonclient.Transaction.
func (c Client) CreateSubjectCall(key *keys.PublicKey) (method string, args []any) {
return createSubjectMethod, []any{key.Bytes()}
func (c Client) CreateSubjectCall(ns string, key *keys.PublicKey) (method string, args []any) {
return createSubjectMethod, []any{ns, key.Bytes()}
}
// GetSubject gets subject by address.
@ -366,30 +364,6 @@ func (c Client) ListNamespaces() ([]*Namespace, error) {
return parseNamespaces(items)
}
// AddSubjectToNamespace adds a subject to namespace.
// Must be invoked by contract owner.
func (c Client) AddSubjectToNamespace(addr util.Uint160, namespace string) (tx util.Uint256, vub uint32, err error) {
method, args := c.AddSubjectToNamespaceCall(addr, namespace)
return c.act.SendCall(c.contract, method, args...)
}
// AddSubjectToNamespaceCall provides args for AddSubjectToNamespace to use in commonclient.Transaction.
func (c Client) AddSubjectToNamespaceCall(addr util.Uint160, namespace string) (method string, args []any) {
return addSubjectToNamespaceMethod, []any{addr, namespace}
}
// RemoveSubjectFromNamespace removes a subject from namespace.
// Must be invoked by contract owner.
func (c Client) RemoveSubjectFromNamespace(addr util.Uint160) (tx util.Uint256, vub uint32, err error) {
method, args := c.RemoveSubjectFromNamespaceCall(addr)
return c.act.SendCall(c.contract, method, args...)
}
// RemoveSubjectFromNamespaceCall provides args for RemoveSubjectFromNamespace to use in commonclient.Transaction.
func (c Client) RemoveSubjectFromNamespaceCall(addr util.Uint160) (method string, args []any) {
return removeSubjectFromNamespaceMethod, []any{addr}
}
// ListNamespaceSubjects gets all subjects from namespace.
func (c Client) ListNamespaceSubjects(namespace string) ([]util.Uint160, error) {
return unwrapArrayOfUint160(commonclient.ReadIteratorItems(c.act, iteratorBatchSize, c.contract, listNamespaceSubjectsMethod, namespace))

View file

@ -85,6 +85,7 @@ func _deploy(data any, isUpdate bool) {
}
storage.Put(ctx, groupCounterKey, 0)
storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{}))
runtime.Log("frostfsid contract initialized")
}
@ -128,7 +129,7 @@ func Version() int {
return common.Version
}
func CreateSubject(key interop.PublicKey) {
func CreateSubject(ns string, key interop.PublicKey) {
ctx := storage.GetContext()
checkContractOwner(ctx)
@ -149,11 +150,22 @@ func CreateSubject(key interop.PublicKey) {
panic("key is occupied")
}
nsKey := namespaceKey(ns)
data = storage.Get(ctx, nsKey).([]byte)
if data == nil {
panic("namespace not found")
}
subj := Subject{
PrimaryKey: key,
Namespace: ns,
}
storage.Put(ctx, sKey, std.Serialize(subj))
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1})
runtime.Notify("CreateSubject", interop.Hash160(addr))
}
@ -315,9 +327,7 @@ func DeleteSubject(addr interop.Hash160) {
}
storage.Delete(ctx, sKey)
if subj.Namespace != "" {
removeSubjectFromNamespace(ctx, subj.Namespace, addr)
}
removeSubjectFromNamespace(ctx, subj.Namespace, addr)
deleteNamespaceSubjectName(ctx, subj.Namespace, subj.Name)
runtime.Notify("DeleteSubject", addr)
@ -339,33 +349,21 @@ func GetSubject(addr interop.Hash160) Subject {
}
func GetSubjectExtended(addr interop.Hash160) SubjectExtended {
if len(addr) != interop.Hash160Len {
panic("incorrect address length")
}
subj := GetSubject(addr)
ctx := storage.GetReadOnlyContext()
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subj := std.Deserialize(data).(Subject)
var groups []Group
if subj.Namespace != "" {
nsHash := ripemd160Hash(subj.Namespace)
it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
groupHash := iterator.Value(it).([]byte)
data = storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte)
if data != nil {
data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte)
if data == nil {
panic("group not found")
}
groups = append(groups, std.Deserialize(data).(Group))
nsHash := ripemd160Hash(subj.Namespace)
it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix)
for iterator.Next(it) {
groupHash := iterator.Value(it).([]byte)
data := storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte)
if data != nil {
data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte)
if data == nil {
panic("group not found")
}
groups = append(groups, std.Deserialize(data).(Group))
}
}
@ -410,8 +408,8 @@ func GetSubjectByKey(key interop.PublicKey) Subject {
}
func GetSubjectKeyByName(ns, name string) interop.PublicKey {
if ns == "" || name == "" {
panic("invalid namespace or name")
if name == "" {
panic("invalid or name")
}
ctx := storage.GetReadOnlyContext()
@ -434,10 +432,6 @@ func CreateNamespace(ns string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
nsKey := namespaceKey(ns)
data := storage.Get(ctx, nsKey).([]byte)
if data != nil {
@ -494,76 +488,6 @@ func ListNamespaces() iterator.Iterator {
return storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.ValuesOnly|storage.DeserializeValues)
}
func AddSubjectToNamespace(addr interop.Hash160, ns string) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == ns {
panic("subject already added")
}
if subject.Namespace != "" {
panic("subject cannot be moved to another namespace")
}
subject.Namespace = ns
storage.Put(ctx, sKey, std.Serialize(subject))
data = storage.Get(ctx, namespaceKey(ns)).([]byte)
if data == nil {
panic("namespace not found")
}
nsSubjKey := namespaceSubjectKey(ns, addr)
storage.Put(ctx, nsSubjKey, []byte{1})
setNamespaceSubjectName(ctx, subject)
runtime.Notify("AddSubjectToNamespace", addr, ns)
}
func RemoveSubjectFromNamespace(addr interop.Hash160) {
ctx := storage.GetContext()
checkContractOwner(ctx)
if len(addr) != interop.Hash160Len {
panic("invalid address length")
}
sKey := subjectKeyFromAddr(addr)
data := storage.Get(ctx, sKey).([]byte)
if data == nil {
panic("subject not found")
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject does not belong to any namespace")
}
removeSubjectFromNamespace(ctx, subject.Namespace, addr)
deleteNamespaceSubjectName(ctx, subject.Namespace, subject.Name)
subject.Namespace = ""
storage.Put(ctx, sKey, std.Serialize(subject))
runtime.Notify("RemoveSubjectFromNamespace", addr, subject.Namespace)
}
func ListNamespaceSubjects(ns string) iterator.Iterator {
ctx := storage.GetReadOnlyContext()
return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix)
@ -573,9 +497,6 @@ func CreateGroup(ns, group string) int {
ctx := storage.GetContext()
checkContractOwner(ctx)
if ns == "" {
panic("invalid namespace name")
}
if group == "" {
panic("invalid group name")
}
@ -643,8 +564,8 @@ func GetGroupExtended(ns string, groupID int) GroupExtended {
}
func GetGroupIDByName(ns, name string) int {
if ns == "" || name == "" {
panic("invalid namespace or name")
if name == "" {
panic("invalid name")
}
ctx := storage.GetReadOnlyContext()
@ -735,18 +656,16 @@ func AddSubjectToGroup(addr interop.Hash160, groupID int) {
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject does not belong to any namespace")
}
nSubjKey := namespaceSubjectKey(subject.Namespace, addr)
storage.Put(ctx, nSubjKey, []byte{1})
gKey := groupKey(subject.Namespace, groupID)
data = storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
group := std.Deserialize(data).(Group)
if group.Namespace != subject.Namespace {
panic("subject and group must be in the same namespace")
}
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Put(ctx, gsKey, []byte{1})
@ -769,15 +688,17 @@ func RemoveSubjectFromGroup(addr interop.Hash160, groupID int) {
}
subject := std.Deserialize(data).(Subject)
if subject.Namespace == "" {
panic("subject doesn't belong to any namespace")
}
gKey := groupKey(subject.Namespace, groupID)
data = storage.Get(ctx, gKey).([]byte)
if data == nil {
panic("group not found")
}
group := std.Deserialize(data).(Group)
if group.Namespace != subject.Namespace {
panic("subject and group must be in the same namespace")
}
gsKey := groupSubjectKey(subject.Namespace, groupID, addr)
storage.Delete(ctx, gsKey)

View file

@ -336,28 +336,6 @@ func (c *Contract) AddSubjectToGroupUnsigned(addr util.Uint160, groupID *big.Int
return c.actor.MakeUnsignedCall(c.hash, "addSubjectToGroup", nil, addr, groupID)
}
// AddSubjectToNamespace creates a transaction invoking `addSubjectToNamespace` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) AddSubjectToNamespace(addr util.Uint160, ns string) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "addSubjectToNamespace", addr, ns)
}
// AddSubjectToNamespaceTransaction creates a transaction invoking `addSubjectToNamespace` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) AddSubjectToNamespaceTransaction(addr util.Uint160, ns string) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "addSubjectToNamespace", addr, ns)
}
// AddSubjectToNamespaceUnsigned creates a transaction invoking `addSubjectToNamespace` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) AddSubjectToNamespaceUnsigned(addr util.Uint160, ns string) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "addSubjectToNamespace", nil, addr, ns)
}
// ClearAdmin creates a transaction invoking `clearAdmin` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
@ -427,23 +405,23 @@ func (c *Contract) CreateNamespaceUnsigned(ns string) (*transaction.Transaction,
// CreateSubject creates a transaction invoking `createSubject` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) CreateSubject(key *keys.PublicKey) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "createSubject", key)
func (c *Contract) CreateSubject(ns string, key *keys.PublicKey) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "createSubject", ns, key)
}
// CreateSubjectTransaction creates a transaction invoking `createSubject` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) CreateSubjectTransaction(key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "createSubject", key)
func (c *Contract) CreateSubjectTransaction(ns string, key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "createSubject", ns, key)
}
// CreateSubjectUnsigned creates a transaction invoking `createSubject` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) CreateSubjectUnsigned(key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "createSubject", nil, key)
func (c *Contract) CreateSubjectUnsigned(ns string, key *keys.PublicKey) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "createSubject", nil, ns, key)
}
// DeleteGroup creates a transaction invoking `deleteGroup` method of the contract.
@ -556,28 +534,6 @@ func (c *Contract) RemoveSubjectFromGroupUnsigned(addr util.Uint160, groupID *bi
return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromGroup", nil, addr, groupID)
}
// RemoveSubjectFromNamespace creates a transaction invoking `removeSubjectFromNamespace` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.
func (c *Contract) RemoveSubjectFromNamespace(addr util.Uint160) (util.Uint256, uint32, error) {
return c.actor.SendCall(c.hash, "removeSubjectFromNamespace", addr)
}
// RemoveSubjectFromNamespaceTransaction creates a transaction invoking `removeSubjectFromNamespace` method of the contract.
// This transaction is signed, but not sent to the network, instead it's
// returned to the caller.
func (c *Contract) RemoveSubjectFromNamespaceTransaction(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeCall(c.hash, "removeSubjectFromNamespace", addr)
}
// RemoveSubjectFromNamespaceUnsigned creates a transaction invoking `removeSubjectFromNamespace` method of the contract.
// This transaction is not signed, it's simply returned to the caller.
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
func (c *Contract) RemoveSubjectFromNamespaceUnsigned(addr util.Uint160) (*transaction.Transaction, error) {
return c.actor.MakeUnsignedCall(c.hash, "removeSubjectFromNamespace", nil, addr)
}
// RemoveSubjectKey creates a transaction invoking `removeSubjectKey` method of the contract.
// This transaction is signed and immediately sent to the network.
// The values returned are its hash, ValidUntilBlock value and error if any.

View file

@ -11,6 +11,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"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/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -115,7 +116,7 @@ func TestFrostFSID_Client_SubjectManagement(t *testing.T) {
subjLogin := "subj-login"
iamPathKV := "iam/path"
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
ffsid.a.await(ffsid.cli.CreateSubject(defaultNamespace, subjKey.PublicKey()))
ffsid.a.await(ffsid.cli.SetSubjectName(subjAddr, subjLogin))
ffsid.a.await(ffsid.cli.SetSubjectKV(subjAddr, client.IAMPathKey, iamPathKV))
ffsid.a.await(ffsid.cli.AddSubjectKey(subjAddr, extraKey.PublicKey()))
@ -157,25 +158,22 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
namespace := "namespace"
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateNamespace(namespace))
_, _, err := ffsid.cli.CreateNamespace(namespace)
require.ErrorContains(t, err, "already exists")
ffsid.a.await(ffsid.cli.CreateSubject(namespace, subjKey.PublicKey()))
ns, err := ffsid.cli.GetNamespace(namespace)
require.NoError(t, err)
require.Equal(t, namespace, ns.Name)
namespaces, err := ffsid.cli.ListNamespaces()
require.NoError(t, err)
require.ElementsMatch(t, []*client.Namespace{ns}, namespaces)
ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace))
_, _, err = ffsid.cli.AddSubjectToNamespace(subjAddr, namespace)
require.ErrorContains(t, err, "already added")
require.ElementsMatch(t, []*client.Namespace{{}, ns}, namespaces)
subj, err := ffsid.cli.GetSubject(subjAddr)
require.NoError(t, err)
@ -185,26 +183,57 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
require.NoError(t, err)
require.ElementsMatch(t, []util.Uint160{subjAddr}, subjects)
ffsid.a.await(ffsid.cli.RemoveSubjectFromNamespace(subjAddr))
subj, err = ffsid.cli.GetSubject(subjAddr)
require.NoError(t, err)
require.Empty(t, subj.Namespace)
ffsid.a.await(ffsid.cli.DeleteSubject(subjAddr))
subjects, err = ffsid.cli.ListNamespaceSubjects(namespace)
require.NoError(t, err)
require.Empty(t, subjects)
}
func TestFrostFSID_Client_DefaultNamespace(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
group := "group"
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateSubject(defaultNamespace, subjKey.PublicKey()))
groupID, err := ffsid.cli.ParseGroupID(ffsid.cli.Wait(ffsid.cli.CreateGroup(defaultNamespace, group)))
require.NoError(t, err)
ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID))
namespaces, err := ffsid.cli.ListNamespaces()
require.NoError(t, err)
require.EqualValues(t, []*client.Namespace{{}}, namespaces)
groups, err := ffsid.cli.ListGroups(defaultNamespace)
require.NoError(t, err)
require.EqualValues(t, []*client.Group{{ID: groupID, Name: group, Namespace: defaultNamespace}}, groups)
subjects, err := ffsid.cli.ListNamespaceSubjects(defaultNamespace)
require.NoError(t, err)
require.EqualValues(t, []util.Uint160{subjAddr}, subjects)
subjects, err = ffsid.cli.ListGroupSubjects(defaultNamespace, groupID)
require.NoError(t, err)
require.EqualValues(t, []util.Uint160{subjAddr}, subjects)
subject, err := ffsid.cli.GetSubjectExtended(subjAddr)
require.NoError(t, err)
require.True(t, subjKey.PublicKey().Equal(subject.PrimaryKey))
require.Equal(t, defaultNamespace, subject.Namespace)
require.EqualValues(t, []*client.Group{{ID: groupID, Name: group, Namespace: defaultNamespace}}, subject.Groups)
}
func TestFrostFSID_Client_GroupManagement(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateSubject(subjKey.PublicKey()))
namespace := "namespace"
subjKey, subjAddr := newKey(t)
ffsid.a.await(ffsid.cli.CreateNamespace(namespace))
ffsid.a.await(ffsid.cli.CreateSubject(namespace, subjKey.PublicKey()))
groupName := "group"
groupID := int64(1)
@ -229,7 +258,6 @@ func TestFrostFSID_Client_GroupManagement(t *testing.T) {
require.NoError(t, err)
require.Zero(t, groupExt.SubjectsCount)
ffsid.a.await(ffsid.cli.AddSubjectToNamespace(subjAddr, namespace))
ffsid.a.await(ffsid.cli.AddSubjectToGroup(subjAddr, groupID))
subjExt, err := ffsid.cli.GetSubjectExtended(subjAddr)
@ -276,73 +304,17 @@ func TestFrostFSID_Client_Lists(t *testing.T) {
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
namespaces := []string{"empty-ns0", "ns1", "ns2", "ns3"}
namespaces := append([]string{defaultNamespace}, createNamespaces(t, ffsid, 3)...)
tx := ffsid.cli.StartTx()
for _, namespace := range namespaces {
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
subjects := createSubjectsInNS(t, ffsid, namespaces[1], 3)
subjects = append(subjects, createSubjectsInNS(t, ffsid, namespaces[2], 2)...)
subjects = append(subjects, createSubjectsInNS(t, ffsid, namespaces[3], 5)...)
subjects := make([]testSubject, 10)
tx = ffsid.cli.StartTx()
for i := range subjects {
subjects[i].key, subjects[i].addr = newKey(t)
err := tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey()))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
groups := createGroupsInNS(t, ffsid, namespaces[0], 1)
groups = append(groups, createGroupsInNS(t, ffsid, namespaces[1], 2)...)
groups = append(groups, createGroupsInNS(t, ffsid, namespaces[2], 1)...)
groups = append(groups, createGroupsInNS(t, ffsid, namespaces[3], 2)...)
groups := []client.Group{
{
ID: 1,
Name: "empty-group0",
Namespace: namespaces[0],
},
{
ID: 2,
Name: "empty-group1",
Namespace: namespaces[1],
},
{
ID: 3,
Name: "group2",
Namespace: namespaces[1],
},
{
ID: 4,
Name: "group3",
Namespace: namespaces[2],
},
{
ID: 5,
Name: "group4",
Namespace: namespaces[3],
},
{
ID: 6,
Name: "group5",
Namespace: namespaces[3],
},
}
tx = ffsid.cli.StartTx()
for _, group := range groups[:3] {
err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
// we split into two tx because of gas limit exceeded error
tx = ffsid.cli.StartTx()
for _, group := range groups[3:] {
err := tx.WrapCall(ffsid.cli.CreateGroupCall(group.Namespace, group.Name))
require.NoError(t, err)
}
ffsid.a.await(ffsid.cli.SendTx(tx))
addSubjectsToNamespaces(t, ffsid, namespaces, subjects)
addSubjectsToGroups(t, ffsid, groups, subjects)
nsList, err := ffsid.cli.ListNamespaces()
@ -370,29 +342,55 @@ func TestFrostFSID_Client_Lists(t *testing.T) {
checkGroupSubjects(t, ffsid.cli, groups[5], subjects, 8, 10)
}
func addSubjectsToNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, namespaces []string, subjects []testSubject) {
cli := ffsid.cli
func createSubjectsInNS(t *testing.T, ffsid *testFrostFSIDClientInvoker, ns string, count int) []testSubject {
subjects := make([]testSubject, count)
tx := cli.StartTx()
for _, subject := range subjects[:3] {
err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[1]))
tx := ffsid.cli.StartTx()
for i := range subjects {
subjects[i].key, subjects[i].addr = newKey(t)
err := tx.WrapCall(ffsid.cli.CreateSubjectCall(ns, subjects[i].key.PublicKey()))
require.NoError(t, err)
}
ffsid.a.await(cli.SendTx(tx))
ffsid.a.await(ffsid.cli.SendTx(tx))
tx = cli.StartTx()
for _, subject := range subjects[3:5] {
err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[2]))
return subjects
}
func createGroupsInNS(t *testing.T, ffsid *testFrostFSIDClientInvoker, ns string, count int) []client.Group {
groups := make([]client.Group, count)
tx := ffsid.cli.StartTx()
for i := range groups {
groups[i].Namespace = ns
groups[i].Name = ns + "/group" + strconv.Itoa(i+1)
err := tx.WrapCall(ffsid.cli.CreateGroupCall(ns, groups[i].Name))
require.NoError(t, err)
}
ffsid.a.await(cli.SendTx(tx))
res := ffsid.a.await(ffsid.cli.SendTx(tx))
tx = cli.StartTx()
for _, subject := range subjects[5:] {
err := tx.WrapCall(cli.AddSubjectToNamespaceCall(subject.addr, namespaces[3]))
ids, err := unwrap.ArrayOfBigInts(makeValidRes(stackitem.NewArray(res.Stack)), nil)
require.NoError(t, err)
for i, id := range ids {
groups[i].ID = id.Int64()
}
return groups
}
func createNamespaces(t *testing.T, ffsid *testFrostFSIDClientInvoker, count int) []string {
namespaces := make([]string, count)
tx := ffsid.cli.StartTx()
for i := range namespaces {
namespaces[i] = "ns" + strconv.Itoa(i+1)
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespaces[i]))
require.NoError(t, err)
}
ffsid.a.await(cli.SendTx(tx))
ffsid.a.await(ffsid.cli.SendTx(tx))
return namespaces
}
func addSubjectsToGroups(t *testing.T, ffsid *testFrostFSIDClientInvoker, groups []client.Group, subjects []testSubject) {
@ -457,14 +455,12 @@ func TestFrostFSID_Client_UseCaseWithS3GW(t *testing.T) {
tx := ffsid.cli.StartTx()
err := tx.WrapCall(ffsid.cli.CreateNamespaceCall(namespace))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(dataUserKey.PublicKey()))
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(namespace, dataUserKey.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(dataUserAddr, login))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.AddSubjectKeyCall(dataUserAddr, extraDataUserKey.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(dataUserAddr, namespace))
require.NoError(t, err)
ffsid.a.await(ffsid.cli.SendTx(tx))
// s3-gw
@ -497,12 +493,10 @@ func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) {
for i := range subjects {
tx = ffsid.cli.StartTx()
subjects[i].key, subjects[i].addr = newKey(t)
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(subjects[i].key.PublicKey()))
err = tx.WrapCall(ffsid.cli.CreateSubjectCall(namespace, subjects[i].key.PublicKey()))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.SetSubjectNameCall(subjects[i].addr, "login"+strconv.Itoa(i)))
require.NoError(t, err)
err = tx.WrapCall(ffsid.cli.AddSubjectToNamespaceCall(subjects[i].addr, namespace))
require.NoError(t, err)
if i > len(subjects)/2 {
err = tx.WrapCall(ffsid.cli.AddSubjectToGroupCall(subjects[i].addr, groupID))

View file

@ -22,6 +22,8 @@ import (
const frostfsidPath = "../frostfsid"
const defaultNamespace = ""
const (
setAdminMethod = "setAdmin"
getAdminMethod = "getAdmin"
@ -39,13 +41,11 @@ const (
deleteSubjectKVMethod = "deleteSubjectKV"
deleteSubjectMethod = "deleteSubject"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
addSubjectToNamespaceMethod = "addSubjectToNamespace"
removeSubjectFromNamespaceMethod = "removeSubjectFromNamespace"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createNamespaceMethod = "createNamespace"
getNamespaceMethod = "getNamespace"
getNamespaceExtendedMethod = "getNamespaceExtended"
listNamespacesMethod = "listNamespaces"
listNamespaceSubjectsMethod = "listNamespaceSubjects"
createGroupMethod = "createGroup"
getGroupMethod = "getGroup"
@ -174,6 +174,26 @@ func checkOwner(t *testing.T, invoker *neotest.ContractInvoker, owner ...util.Ui
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)
@ -184,9 +204,9 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
anonInvoker := f.AnonInvoker(t)
invoker := f.OwnerInvoker()
anonInvoker.InvokeFail(t, notWitnessedError, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.InvokeFail(t, "already exists", createSubjectMethod, subjKey.PublicKey().Bytes())
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)
@ -263,7 +283,7 @@ func TestFrostFSID_SubjectManagement(t *testing.T) {
newSubjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, newSubjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, newSubjKey.PublicKey().Bytes())
s, err = anonInvoker.TestInvoke(t, listSubjectsMethod)
require.NoError(t, err)
@ -293,20 +313,22 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
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"
// Create two subject (one of them with name)
// Create two namespace.
// Add these subjects to ns1
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey1.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr1, subjName1)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey2.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns1)
invoker.Invoke(t, stackitem.Null{}, createNamespaceMethod, ns2)
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr1, ns1)
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr2, ns1)
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)
@ -323,25 +345,17 @@ func TestFrostFSIS_SubjectNameRelatedInvariants(t *testing.T) {
// 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 move subject from one namespace to another
invoker.InvokeFail(t, "cannot be moved", addSubjectToNamespaceMethod, subjKeyAddr2, ns2)
// Check that we cannot find public key by name for subject that was removed from namespace
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjKeyAddr2)
// 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 we can find public key by name for subject in new namespace
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKeyAddr2, ns2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName2)
checkPublicKeyResult(t, s, err, subjKey2)
// Check that subj2 can have the same name as subj1 if they belong to different namespaces
// 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, subjKeyAddr2, subjName1)
invoker.Invoke(t, stackitem.Null{}, setSubjectNameMethod, subjKeyAddr3, subjName1)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName1)
checkPublicKeyResult(t, s, err, subjKey2)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName2)
checkPublicKeyResult(t, s, err, subjKey3)
s, err = invoker.TestInvoke(t, getSubjectKeyByNameMethod, ns2, subjName3)
checkPublicKeyResult(t, s, err, nil)
}
@ -414,16 +428,12 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
t.Run("add user to namespace", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
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)
anonInvoker.InvokeFail(t, notWitnessedError, addSubjectToNamespaceMethod, subjAddress, namespace)
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, namespace)
invoker.InvokeFail(t, "already added", addSubjectToNamespaceMethod, subjAddress, namespace)
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress)
require.NoError(t, err)
subj := parseSubject(t, s)
@ -456,7 +466,7 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
namespaces := parseNamespaces(t, readIteratorAll(s))
require.NoError(t, err)
require.ElementsMatch(t, namespaces, []Namespace{{Name: namespace}, {Name: namespace2}})
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 {
@ -468,21 +478,6 @@ func TestFrostFSID_NamespaceManagement(t *testing.T) {
require.Equal(t, namespace, nsExt.Name)
}
}
t.Run("remove subject from namespace", func(t *testing.T) {
anonInvoker.InvokeFail(t, notWitnessedError, removeSubjectFromNamespaceMethod, subjAddress)
invoker.Invoke(t, stackitem.Null{}, removeSubjectFromNamespaceMethod, subjAddress)
s, err := anonInvoker.TestInvoke(t, getSubjectMethod, subjAddress)
require.NoError(t, err)
subj := parseSubject(t, s)
require.Empty(t, subj.Namespace)
s, err = anonInvoker.TestInvoke(t, getNamespaceExtendedMethod, namespace)
require.NoError(t, err)
nsExt := parseNamespaceExtended(t, s.Pop().Item())
require.Zero(t, nsExt.SubjectsCount)
})
})
})
})
@ -516,10 +511,9 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
t.Run("add subjects to group", func(t *testing.T) {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes())
subjAddress := subjKey.PublicKey().GetScriptHash()
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjAddress, nsName)
anonInvoker.InvokeFail(t, "not witnessed", addSubjectToGroupMethod, subjAddress, groupID)
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjAddress, groupID)
@ -546,8 +540,7 @@ func TestFrostFSID_GroupManagement(t *testing.T) {
for i := 0; i < subjectsCount; i++ {
subjKey, err := keys.NewPrivateKey()
require.NoError(t, err)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectToNamespaceMethod, subjKey.PublicKey().GetScriptHash(), nsName)
invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, nsName, subjKey.PublicKey().Bytes())
invoker.Invoke(t, stackitem.Null{}, addSubjectToGroupMethod, subjKey.PublicKey().GetScriptHash(), groupID)
}