diff --git a/policy/config.yml b/policy/config.yml index b9a62f8..27ca4bd 100644 --- a/policy/config.yml +++ b/policy/config.yml @@ -1,2 +1,6 @@ name: "APE" -safemethods: ["listChains","getChain","listChainsByPrefix"] +safemethods: + - "getAdmin" + - "listChains" + - "getChain" + - "listChainsByPrefix" diff --git a/policy/policy_contract.go b/policy/policy_contract.go index 398468b..ea5d742 100644 --- a/policy/policy_contract.go +++ b/policy/policy_contract.go @@ -2,7 +2,9 @@ package policy import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/common" + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) @@ -16,8 +18,65 @@ const ( IAM = 'i' ) +const ( + ownerKeyPrefix = 'o' +) + +const ( + // ErrNotAutorized is returned when the none of the transaction signers + // belongs to the list of autorized keys. + ErrNotAutorized = "none of the signers is not autorized to change the contract" +) + // _deploy function sets up initial list of inner ring public keys. func _deploy(data any, isUpdate bool) { + if isUpdate { + return + } + + args := data.(struct { + Admin interop.Hash160 + }) + ctx := storage.GetContext() + if args.Admin != nil { + if len(args.Admin) != 20 { + panic("invaliad admin hash length") + } + storage.Put(ctx, ownerKey(args.Admin), []byte{1}) + } +} + +func ownerKey(sender interop.Hash160) []byte { + return append([]byte{ownerKeyPrefix}, sender...) +} + +func checkAuthorization(ctx storage.Context) { + if runtime.CheckWitness(common.AlphabetAddress()) { + return + } + + admin := getAdmin(ctx) + if admin != nil && runtime.CheckWitness(admin) { + return + } + + panic(ErrNotAutorized) +} + +func SetAdmin(addr interop.Hash160) { + common.CheckAlphabetWitness() + + ctx := storage.GetContext() + storage.Put(ctx, []byte{ownerKeyPrefix}, addr) +} + +func GetAdmin() interop.Hash160 { + ctx := storage.GetReadOnlyContext() + return getAdmin(ctx) +} + +func getAdmin(ctx storage.Context) interop.Hash160 { + return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160) } func storageKey(prefix Kind, entityName, name string) []byte { @@ -28,9 +87,9 @@ func storageKey(prefix Kind, entityName, name string) []byte { } func AddChain(entity Kind, entityName, name string, chain []byte) { - common.CheckAlphabetWitness() // TODO: Allow to work with chain directly for everyone? - ctx := storage.GetContext() + checkAuthorization(ctx) + key := storageKey(entity, entityName, name) storage.Put(ctx, key, chain) } @@ -47,17 +106,17 @@ func GetChain(entity Kind, entityName, name string) []byte { } func RemoveChain(entity Kind, entityName string, name string) { - common.CheckAlphabetWitness() - ctx := storage.GetContext() + checkAuthorization(ctx) + key := storageKey(entity, entityName, name) storage.Delete(ctx, key) } func RemoveChainsByPrefix(entity Kind, entityName string, name string) { - common.CheckAlphabetWitness() - ctx := storage.GetContext() + checkAuthorization(ctx) + key := storageKey(entity, entityName, name) it := storage.Find(ctx, key, storage.KeysOnly) for iterator.Next(it) { diff --git a/tests/policy_test.go b/tests/policy_test.go index d484d5a..117a42c 100644 --- a/tests/policy_test.go +++ b/tests/policy_test.go @@ -18,7 +18,7 @@ const policyPath = "../policy" func deployPolicyContract(t *testing.T, e *neotest.Executor) util.Uint160 { cfgPath := path.Join(policyPath, "config.yml") c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath) - e.DeployContract(t, c, nil) + e.DeployContract(t, c, []any{nil}) return c.Hash } @@ -72,6 +72,23 @@ func TestPolicy(t *testing.T) { }) } +func TestAutorization(t *testing.T) { + e := newPolicyInvoker(t) + + e.Invoke(t, stackitem.Null{}, "getAdmin") + + s := e.NewAccount(t, 1_0000_0000) + c := e.WithSigners(s) + + args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")} + c.InvokeFail(t, policy.ErrNotAutorized, "addChain", args...) + + e.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash()) + e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin") + + c.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)