Merge pull request #974 from CosmWasm/945_access_config_anyof

Introduce new AccessType to allow a set of addresses
This commit is contained in:
Alexander Peters
2022-09-08 15:21:28 +02:00
committed by GitHub
13 changed files with 1172 additions and 204 deletions

View File

@@ -119,7 +119,8 @@ AccessConfig access control type.
| Field | Type | Label | Description | | Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- | | ----- | ---- | ----- | ----------- |
| `permission` | [AccessType](#cosmwasm.wasm.v1.AccessType) | | | | `permission` | [AccessType](#cosmwasm.wasm.v1.AccessType) | | |
| `address` | [string](#string) | | | | `address` | [string](#string) | | Address Deprecated: replaced by addresses |
| `addresses` | [string](#string) | repeated | |
@@ -240,8 +241,9 @@ AccessType permission types
| ---- | ------ | ----------- | | ---- | ------ | ----------- |
| ACCESS_TYPE_UNSPECIFIED | 0 | AccessTypeUnspecified placeholder for empty value | | ACCESS_TYPE_UNSPECIFIED | 0 | AccessTypeUnspecified placeholder for empty value |
| ACCESS_TYPE_NOBODY | 1 | AccessTypeNobody forbidden | | ACCESS_TYPE_NOBODY | 1 | AccessTypeNobody forbidden |
| ACCESS_TYPE_ONLY_ADDRESS | 2 | AccessTypeOnlyAddress restricted to an address | | ACCESS_TYPE_ONLY_ADDRESS | 2 | AccessTypeOnlyAddress restricted to a single address Deprecated: use AccessTypeAnyOfAddresses instead |
| ACCESS_TYPE_EVERYBODY | 3 | AccessTypeEverybody unrestricted | | ACCESS_TYPE_EVERYBODY | 3 | AccessTypeEverybody unrestricted |
| ACCESS_TYPE_ANY_OF_ADDRESSES | 4 | AccessTypeAnyOfAddresses allow any of the addresses |

View File

@@ -19,12 +19,16 @@ enum AccessType {
// AccessTypeNobody forbidden // AccessTypeNobody forbidden
ACCESS_TYPE_NOBODY = 1 ACCESS_TYPE_NOBODY = 1
[ (gogoproto.enumvalue_customname) = "AccessTypeNobody" ]; [ (gogoproto.enumvalue_customname) = "AccessTypeNobody" ];
// AccessTypeOnlyAddress restricted to an address // AccessTypeOnlyAddress restricted to a single address
// Deprecated: use AccessTypeAnyOfAddresses instead
ACCESS_TYPE_ONLY_ADDRESS = 2 ACCESS_TYPE_ONLY_ADDRESS = 2
[ (gogoproto.enumvalue_customname) = "AccessTypeOnlyAddress" ]; [ (gogoproto.enumvalue_customname) = "AccessTypeOnlyAddress" ];
// AccessTypeEverybody unrestricted // AccessTypeEverybody unrestricted
ACCESS_TYPE_EVERYBODY = 3 ACCESS_TYPE_EVERYBODY = 3
[ (gogoproto.enumvalue_customname) = "AccessTypeEverybody" ]; [ (gogoproto.enumvalue_customname) = "AccessTypeEverybody" ];
// AccessTypeAnyOfAddresses allow any of the addresses
ACCESS_TYPE_ANY_OF_ADDRESSES = 4
[ (gogoproto.enumvalue_customname) = "AccessTypeAnyOfAddresses" ];
} }
// AccessTypeParam // AccessTypeParam
@@ -37,7 +41,11 @@ message AccessTypeParam {
message AccessConfig { message AccessConfig {
option (gogoproto.goproto_stringer) = true; option (gogoproto.goproto_stringer) = true;
AccessType permission = 1 [ (gogoproto.moretags) = "yaml:\"permission\"" ]; AccessType permission = 1 [ (gogoproto.moretags) = "yaml:\"permission\"" ];
// Address
// Deprecated: replaced by addresses
string address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ]; string address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ];
repeated string addresses = 3 [ (gogoproto.moretags) = "yaml:\"addresses\"" ];
} }
// Params defines the set of wasm parameters. // Params defines the set of wasm parameters.

View File

@@ -73,6 +73,7 @@ func GenesisStoreCodeCmd(defaultNodeHome string, genesisMutator GenesisMutator)
cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional") cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional") cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional")
cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional") cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional")
cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)") cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")

View File

@@ -597,27 +597,38 @@ func ProposalUnpinCodesCmd() *cobra.Command {
return cmd return cmd
} }
func parseAccessConfig(config string) (types.AccessConfig, error) { func parseAccessConfig(raw string) (c types.AccessConfig, err error) {
switch config { switch raw {
case "nobody": case "nobody":
return types.AllowNobody, nil return types.AllowNobody, nil
case "everybody": case "everybody":
return types.AllowEverybody, nil return types.AllowEverybody, nil
default: default:
address, err := sdk.AccAddressFromBech32(config) parts := strings.Split(raw, ",")
if err != nil { addrs := make([]sdk.AccAddress, len(parts))
return types.AccessConfig{}, fmt.Errorf("unable to parse address %s", config) for i, v := range parts {
addr, err := sdk.AccAddressFromBech32(v)
if err != nil {
return types.AccessConfig{}, fmt.Errorf("unable to parse address %q: %s", v, err)
}
addrs[i] = addr
} }
return types.AccessTypeOnlyAddress.With(address), nil defer func() { // convert panic in ".With" to error for better output
if r := recover(); r != nil {
err = r.(error)
}
}()
cfg := types.AccessTypeAnyOfAddresses.With(addrs...)
return cfg, cfg.ValidateBasic()
} }
} }
func parseAccessConfigUpdates(args []string) ([]types.AccessConfigUpdate, error) { func parseAccessConfigUpdates(args []string) ([]types.AccessConfigUpdate, error) {
updates := make([]types.AccessConfigUpdate, len(args)) updates := make([]types.AccessConfigUpdate, len(args))
for i, c := range args { for i, c := range args {
// format: code_id,access_config // format: code_id:access_config
// access_config: nobody|everybody|address // access_config: nobody|everybody|address(es)
parts := strings.Split(c, ",") parts := strings.Split(c, ":")
if len(parts) != 2 { if len(parts) != 2 {
return nil, fmt.Errorf("invalid format") return nil, fmt.Errorf("invalid format")
} }
@@ -642,15 +653,15 @@ func parseAccessConfigUpdates(args []string) ([]types.AccessConfigUpdate, error)
func ProposalUpdateInstantiateConfigCmd() *cobra.Command { func ProposalUpdateInstantiateConfigCmd() *cobra.Command {
bech32Prefix := sdk.GetConfig().GetBech32AccountAddrPrefix() bech32Prefix := sdk.GetConfig().GetBech32AccountAddrPrefix()
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "update-instantiate-config [code-id,permission]...", Use: "update-instantiate-config [code-id:permission]...",
Short: "Submit an update instantiate config proposal.", Short: "Submit an update instantiate config proposal.",
Args: cobra.MinimumNArgs(1), Args: cobra.MinimumNArgs(1),
Long: strings.TrimSpace( Long: strings.TrimSpace(
fmt.Sprintf(`Submit an update instantiate config proposal for multiple code ids. fmt.Sprintf(`Submit an update instantiate config proposal for multiple code ids.
Example: Example:
$ %s tx gov submit-proposal update-instantiate-config 1,nobody 2,everybody 3,%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm $ %s tx gov submit-proposal update-instantiate-config 1:nobody 2:everybody 3:%s1l2rsakp388kuv9k8qzq6lrm9taddae7fpx59wm,%s1vx8knpllrj7n963p9ttd80w47kpacrhuts497x
`, version.AppName, bech32Prefix)), `, version.AppName, bech32Prefix, bech32Prefix)),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd) clientCtx, err := client.GetClientTxContext(cmd)
if err != nil { if err != nil {

View File

@@ -0,0 +1,97 @@
package cli
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
func TestParseAccessConfigUpdates(t *testing.T) {
specs := map[string]struct {
src []string
exp []types.AccessConfigUpdate
expErr bool
}{
"nobody": {
src: []string{"1:nobody"},
exp: []types.AccessConfigUpdate{{
CodeID: 1,
InstantiatePermission: types.AccessConfig{Permission: types.AccessTypeNobody},
}},
},
"everybody": {
src: []string{"1:everybody"},
exp: []types.AccessConfigUpdate{{
CodeID: 1,
InstantiatePermission: types.AccessConfig{Permission: types.AccessTypeEverybody},
}},
},
"any of addresses - single": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
exp: []types.AccessConfigUpdate{
{
CodeID: 1,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeAnyOfAddresses,
Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
},
},
},
},
"any of addresses - multiple": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
exp: []types.AccessConfigUpdate{
{
CodeID: 1,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeAnyOfAddresses,
Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
},
},
},
},
"multiple code ids with different permissions": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr", "2:nobody"},
exp: []types.AccessConfigUpdate{
{
CodeID: 1,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeAnyOfAddresses,
Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
},
}, {
CodeID: 2,
InstantiatePermission: types.AccessConfig{
Permission: types.AccessTypeNobody,
},
},
},
},
"any of addresses - empty list": {
src: []string{"1:"},
expErr: true,
},
"any of addresses - invalid address": {
src: []string{"1:foo"},
expErr: true,
},
"any of addresses - duplicate address": {
src: []string{"1:cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
expErr: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
got, gotErr := parseAccessConfigUpdates(spec.src)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.exp, got)
})
}
}

View File

@@ -19,15 +19,16 @@ import (
) )
const ( const (
flagAmount = "amount" flagAmount = "amount"
flagLabel = "label" flagLabel = "label"
flagAdmin = "admin" flagAdmin = "admin"
flagNoAdmin = "no-admin" flagNoAdmin = "no-admin"
flagRunAs = "run-as" flagRunAs = "run-as"
flagInstantiateByEverybody = "instantiate-everybody" flagInstantiateByEverybody = "instantiate-everybody"
flagInstantiateNobody = "instantiate-nobody" flagInstantiateNobody = "instantiate-nobody"
flagInstantiateByAddress = "instantiate-only-address" flagInstantiateByAddress = "instantiate-only-address"
flagUnpinCode = "unpin-code" flagInstantiateByAnyOfAddress = "instantiate-anyof-addresses"
flagUnpinCode = "unpin-code"
) )
// GetTxCmd returns the transaction commands for this module // GetTxCmd returns the transaction commands for this module
@@ -75,7 +76,8 @@ func StoreCodeCmd() *cobra.Command {
cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional") cmd.Flags().String(flagInstantiateByEverybody, "", "Everybody can instantiate a contract from the code, optional")
cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional") cmd.Flags().String(flagInstantiateNobody, "", "Nobody except the governance process can instantiate a contract from the code, optional")
cmd.Flags().String(flagInstantiateByAddress, "", "Only this address can instantiate a contract instance from the code, optional") cmd.Flags().String(flagInstantiateByAddress, "", "Deprecated: Only this address can instantiate a contract from the code, optional")
cmd.Flags().StringSlice(flagInstantiateByAnyOfAddress, []string{}, "Any of the addresses can instantiate a contract from the code, optional")
flags.AddTxFlagsToCmd(cmd) flags.AddTxFlagsToCmd(cmd)
return cmd return cmd
} }
@@ -97,47 +99,9 @@ func parseStoreCodeArgs(file string, sender sdk.AccAddress, flags *flag.FlagSet)
return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip") return types.MsgStoreCode{}, fmt.Errorf("invalid input file. Use wasm binary or gzip")
} }
var perm *types.AccessConfig perm, err := parseAccessConfigFlags(flags)
onlyAddrStr, err := flags.GetString(flagInstantiateByAddress)
if err != nil { if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("instantiate by address: %s", err) return types.MsgStoreCode{}, err
}
if onlyAddrStr != "" {
allowedAddr, err := sdk.AccAddressFromBech32(onlyAddrStr)
if err != nil {
return types.MsgStoreCode{}, sdkerrors.Wrap(err, flagInstantiateByAddress)
}
x := types.AccessTypeOnlyAddress.With(allowedAddr)
perm = &x
} else {
everybodyStr, err := flags.GetString(flagInstantiateByEverybody)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("instantiate by everybody: %s", err)
}
if everybodyStr != "" {
ok, err := strconv.ParseBool(everybodyStr)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err)
}
if ok {
perm = &types.AllowEverybody
}
}
nobodyStr, err := flags.GetString(flagInstantiateNobody)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("instantiate by nobody: %s", err)
}
if nobodyStr != "" {
ok, err := strconv.ParseBool(nobodyStr)
if err != nil {
return types.MsgStoreCode{}, fmt.Errorf("boolean value expected for instantiate by nobody: %s", err)
}
if ok {
perm = &types.AllowNobody
}
}
} }
msg := types.MsgStoreCode{ msg := types.MsgStoreCode{
@@ -148,6 +112,65 @@ func parseStoreCodeArgs(file string, sender sdk.AccAddress, flags *flag.FlagSet)
return msg, nil return msg, nil
} }
func parseAccessConfigFlags(flags *flag.FlagSet) (*types.AccessConfig, error) {
addrs, err := flags.GetStringSlice(flagInstantiateByAnyOfAddress)
if err != nil {
return nil, fmt.Errorf("flag any of: %s", err)
}
if len(addrs) != 0 {
acceptedAddrs := make([]sdk.AccAddress, len(addrs))
for i, v := range addrs {
acceptedAddrs[i], err = sdk.AccAddressFromBech32(v)
if err != nil {
return nil, fmt.Errorf("parse %q: %w", v, err)
}
}
x := types.AccessTypeAnyOfAddresses.With(acceptedAddrs...)
return &x, nil
}
onlyAddrStr, err := flags.GetString(flagInstantiateByAddress)
if err != nil {
return nil, fmt.Errorf("instantiate by address: %s", err)
}
if onlyAddrStr != "" {
allowedAddr, err := sdk.AccAddressFromBech32(onlyAddrStr)
if err != nil {
return nil, sdkerrors.Wrap(err, flagInstantiateByAddress)
}
x := types.AccessTypeOnlyAddress.With(allowedAddr)
return &x, nil
}
everybodyStr, err := flags.GetString(flagInstantiateByEverybody)
if err != nil {
return nil, fmt.Errorf("instantiate by everybody: %s", err)
}
if everybodyStr != "" {
ok, err := strconv.ParseBool(everybodyStr)
if err != nil {
return nil, fmt.Errorf("boolean value expected for instantiate by everybody: %s", err)
}
if ok {
return &types.AllowEverybody, nil
}
}
nobodyStr, err := flags.GetString(flagInstantiateNobody)
if err != nil {
return nil, fmt.Errorf("instantiate by nobody: %s", err)
}
if nobodyStr != "" {
ok, err := strconv.ParseBool(nobodyStr)
if err != nil {
return nil, fmt.Errorf("boolean value expected for instantiate by nobody: %s", err)
}
if ok {
return &types.AllowNobody, nil
}
}
return nil, nil
}
// InstantiateContractCmd will instantiate a contract from previously uploaded code. // InstantiateContractCmd will instantiate a contract from previously uploaded code.
func InstantiateContractCmd() *cobra.Command { func InstantiateContractCmd() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{

View File

@@ -0,0 +1,59 @@
package cli
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
func TestParseAccessConfigFlags(t *testing.T) {
specs := map[string]struct {
args []string
expCfg *types.AccessConfig
expErr bool
}{
"nobody": {
args: []string{"--instantiate-nobody=true"},
expCfg: &types.AccessConfig{Permission: types.AccessTypeNobody},
},
"everybody": {
args: []string{"--instantiate-everybody=true"},
expCfg: &types.AccessConfig{Permission: types.AccessTypeEverybody},
},
"only address": {
args: []string{"--instantiate-only-address=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
expCfg: &types.AccessConfig{Permission: types.AccessTypeOnlyAddress, Address: "cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x"},
},
"only address - invalid": {
args: []string{"--instantiate-only-address=foo"},
expErr: true,
},
"any of address": {
args: []string{"--instantiate-anyof-addresses=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"},
expCfg: &types.AccessConfig{Permission: types.AccessTypeAnyOfAddresses, Addresses: []string{"cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x", "cosmos14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s4hmalr"}},
},
"any of address - invalid": {
args: []string{"--instantiate-anyof-addresses=cosmos1vx8knpllrj7n963p9ttd80w47kpacrhuts497x,foo"},
expErr: true,
},
"not set": {
args: []string{},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
flags := StoreCodeCmd().Flags()
require.NoError(t, flags.Parse(spec.args))
gotCfg, gotErr := parseAccessConfigFlags(flags)
if spec.expErr {
require.Error(t, gotErr)
return
}
require.NoError(t, gotErr)
assert.Equal(t, spec.expCfg, gotCfg)
})
}
}

View File

@@ -0,0 +1,311 @@
package keeper
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/CosmWasm/wasmd/x/wasm/types"
)
func TestDefaultAuthzPolicyCanCreateCode(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
config types.AccessConfig
actor sdk.AccAddress
exp bool
panics bool
}{
"nobody": {
config: types.AllowNobody,
exp: false,
},
"everybody": {
config: types.AllowEverybody,
exp: true,
},
"only address - same": {
config: types.AccessTypeOnlyAddress.With(myActorAddress),
exp: true,
},
"only address - different": {
config: types.AccessTypeOnlyAddress.With(otherAddress),
exp: false,
},
"any address - included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress),
exp: true,
},
"any address - not included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress),
exp: false,
},
"undefined config - panics": {
config: types.AccessConfig{},
panics: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := DefaultAuthorizationPolicy{}
if !spec.panics {
got := policy.CanCreateCode(spec.config, myActorAddress)
assert.Equal(t, spec.exp, got)
return
}
assert.Panics(t, func() {
policy.CanCreateCode(spec.config, myActorAddress)
})
})
}
}
func TestDefaultAuthzPolicyCanInstantiateContract(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
config types.AccessConfig
actor sdk.AccAddress
exp bool
panics bool
}{
"nobody": {
config: types.AllowNobody,
exp: false,
},
"everybody": {
config: types.AllowEverybody,
exp: true,
},
"only address - same": {
config: types.AccessTypeOnlyAddress.With(myActorAddress),
exp: true,
},
"only address - different": {
config: types.AccessTypeOnlyAddress.With(otherAddress),
exp: false,
},
"any address - included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress),
exp: true,
},
"any address - not included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress),
exp: false,
},
"undefined config - panics": {
config: types.AccessConfig{},
panics: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := DefaultAuthorizationPolicy{}
if !spec.panics {
got := policy.CanInstantiateContract(spec.config, myActorAddress)
assert.Equal(t, spec.exp, got)
return
}
assert.Panics(t, func() {
policy.CanInstantiateContract(spec.config, myActorAddress)
})
})
}
}
func TestDefaultAuthzPolicyCanModifyContract(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
admin sdk.AccAddress
exp bool
}{
"same as actor": {
admin: myActorAddress,
exp: true,
},
"different admin": {
admin: otherAddress,
exp: false,
},
"no admin": {
exp: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := DefaultAuthorizationPolicy{}
got := policy.CanModifyContract(spec.admin, myActorAddress)
assert.Equal(t, spec.exp, got)
})
}
}
func TestDefaultAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
admin sdk.AccAddress
subset bool
exp bool
}{
"same as actor - subset": {
admin: myActorAddress,
subset: true,
exp: true,
},
"same as actor - not subset": {
admin: myActorAddress,
subset: false,
exp: false,
},
"different admin": {
admin: otherAddress,
exp: false,
},
"no admin": {
exp: false,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := DefaultAuthorizationPolicy{}
got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset)
assert.Equal(t, spec.exp, got)
})
}
}
func TestGovAuthzPolicyCanCreateCode(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
config types.AccessConfig
actor sdk.AccAddress
}{
"nobody": {
config: types.AllowNobody,
},
"everybody": {
config: types.AllowEverybody,
},
"only address - same": {
config: types.AccessTypeOnlyAddress.With(myActorAddress),
},
"only address - different": {
config: types.AccessTypeOnlyAddress.With(otherAddress),
},
"any address - included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress),
},
"any address - not included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress),
},
"undefined config - panics": {
config: types.AccessConfig{},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := GovAuthorizationPolicy{}
got := policy.CanCreateCode(spec.config, myActorAddress)
assert.True(t, got)
})
}
}
func TestGovAuthzPolicyCanInstantiateContract(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
config types.AccessConfig
actor sdk.AccAddress
}{
"nobody": {
config: types.AllowNobody,
},
"everybody": {
config: types.AllowEverybody,
},
"only address - same": {
config: types.AccessTypeOnlyAddress.With(myActorAddress),
},
"only address - different": {
config: types.AccessTypeOnlyAddress.With(otherAddress),
},
"any address - included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress, myActorAddress),
},
"any address - not included": {
config: types.AccessTypeAnyOfAddresses.With(otherAddress),
},
"undefined config - panics": {
config: types.AccessConfig{},
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := GovAuthorizationPolicy{}
got := policy.CanInstantiateContract(spec.config, myActorAddress)
assert.True(t, got)
})
}
}
func TestGovAuthzPolicyCanModifyContract(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
admin sdk.AccAddress
}{
"same as actor": {
admin: myActorAddress,
},
"different admin": {
admin: otherAddress,
},
"no admin": {},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := GovAuthorizationPolicy{}
got := policy.CanModifyContract(spec.admin, myActorAddress)
assert.True(t, got)
})
}
}
func TestGovAuthzPolicyCanModifyCodeAccessConfig(t *testing.T) {
myActorAddress := RandomAccountAddress(t)
otherAddress := RandomAccountAddress(t)
specs := map[string]struct {
admin sdk.AccAddress
subset bool
}{
"same as actor - subset": {
admin: myActorAddress,
subset: true,
},
"same as actor - not subset": {
admin: myActorAddress,
subset: false,
},
"different admin": {
admin: otherAddress,
},
"no admin": {},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
policy := GovAuthorizationPolicy{}
got := policy.CanModifyCodeAccessConfig(spec.admin, myActorAddress, spec.subset)
assert.True(t, got)
})
}
}

View File

@@ -20,20 +20,33 @@ var (
var AllAccessTypes = []AccessType{ var AllAccessTypes = []AccessType{
AccessTypeNobody, AccessTypeNobody,
AccessTypeOnlyAddress, AccessTypeOnlyAddress,
AccessTypeAnyOfAddresses,
AccessTypeEverybody, AccessTypeEverybody,
} }
func (a AccessType) With(addr sdk.AccAddress) AccessConfig { func (a AccessType) With(addrs ...sdk.AccAddress) AccessConfig {
switch a { switch a {
case AccessTypeNobody: case AccessTypeNobody:
return AllowNobody return AllowNobody
case AccessTypeOnlyAddress: case AccessTypeOnlyAddress:
if err := sdk.VerifyAddressFormat(addr); err != nil { if n := len(addrs); n != 1 {
panic(fmt.Sprintf("expected exactly 1 address but got %d", n))
}
if err := sdk.VerifyAddressFormat(addrs[0]); err != nil {
panic(err) panic(err)
} }
return AccessConfig{Permission: AccessTypeOnlyAddress, Address: addr.String()} return AccessConfig{Permission: AccessTypeOnlyAddress, Address: addrs[0].String()}
case AccessTypeEverybody: case AccessTypeEverybody:
return AllowEverybody return AllowEverybody
case AccessTypeAnyOfAddresses:
bech32Addrs := make([]string, len(addrs))
for i, v := range addrs {
bech32Addrs[i] = v.String()
}
if err := assertValidAddresses(bech32Addrs); err != nil {
panic(sdkerrors.Wrap(err, "addresses"))
}
return AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: bech32Addrs}
} }
panic("unsupported access type") panic("unsupported access type")
} }
@@ -46,6 +59,8 @@ func (a AccessType) String() string {
return "OnlyAddress" return "OnlyAddress"
case AccessTypeEverybody: case AccessTypeEverybody:
return "Everybody" return "Everybody"
case AccessTypeAnyOfAddresses:
return "AnyOfAddresses"
} }
return "Unspecified" return "Unspecified"
} }
@@ -147,6 +162,7 @@ func validateAccessType(i interface{}) error {
return sdkerrors.Wrapf(ErrInvalid, "unknown type: %q", a) return sdkerrors.Wrapf(ErrInvalid, "unknown type: %q", a)
} }
// ValidateBasic performs basic validation
func (a AccessConfig) ValidateBasic() error { func (a AccessConfig) ValidateBasic() error {
switch a.Permission { switch a.Permission {
case AccessTypeUnspecified: case AccessTypeUnspecified:
@@ -157,12 +173,39 @@ func (a AccessConfig) ValidateBasic() error {
} }
return nil return nil
case AccessTypeOnlyAddress: case AccessTypeOnlyAddress:
if len(a.Addresses) != 0 {
return ErrInvalid.Wrap("addresses field set")
}
_, err := sdk.AccAddressFromBech32(a.Address) _, err := sdk.AccAddressFromBech32(a.Address)
return err return err
case AccessTypeAnyOfAddresses:
if a.Address != "" {
return ErrInvalid.Wrap("address field set")
}
return sdkerrors.Wrap(assertValidAddresses(a.Addresses), "addresses")
} }
return sdkerrors.Wrapf(ErrInvalid, "unknown type: %q", a.Permission) return sdkerrors.Wrapf(ErrInvalid, "unknown type: %q", a.Permission)
} }
func assertValidAddresses(addrs []string) error {
if len(addrs) == 0 {
return ErrEmpty
}
idx := make(map[string]struct{}, len(addrs))
for _, a := range addrs {
if _, err := sdk.AccAddressFromBech32(a); err != nil {
return sdkerrors.Wrapf(err, "address: %s", a)
}
if _, exists := idx[a]; exists {
return ErrDuplicate.Wrapf("address: %s", a)
}
idx[a] = struct{}{}
}
return nil
}
// Allowed returns if permission includes the actor.
// Actor address must be valid and not nil
func (a AccessConfig) Allowed(actor sdk.AccAddress) bool { func (a AccessConfig) Allowed(actor sdk.AccAddress) bool {
switch a.Permission { switch a.Permission {
case AccessTypeNobody: case AccessTypeNobody:
@@ -171,6 +214,13 @@ func (a AccessConfig) Allowed(actor sdk.AccAddress) bool {
return true return true
case AccessTypeOnlyAddress: case AccessTypeOnlyAddress:
return a.Address == actor.String() return a.Address == actor.String()
case AccessTypeAnyOfAddresses:
for _, v := range a.Addresses {
if v == actor.String() {
return true
}
}
return false
default: default:
panic("unknown type") panic("unknown type")
} }

View File

@@ -1,6 +1,7 @@
package types package types
import ( import (
"bytes"
"encoding/json" "encoding/json"
"testing" "testing"
@@ -14,6 +15,7 @@ import (
func TestValidateParams(t *testing.T) { func TestValidateParams(t *testing.T) {
var ( var (
anyAddress sdk.AccAddress = make([]byte, ContractAddrLen) anyAddress sdk.AccAddress = make([]byte, ContractAddrLen)
otherAddress sdk.AccAddress = bytes.Repeat([]byte{1}, ContractAddrLen)
invalidAddress = "invalid address" invalidAddress = "invalid address"
) )
@@ -42,6 +44,18 @@ func TestValidateParams(t *testing.T) {
InstantiateDefaultPermission: AccessTypeOnlyAddress, InstantiateDefaultPermission: AccessTypeOnlyAddress,
}, },
}, },
"all good with anyOf address": {
src: Params{
CodeUploadAccess: AccessTypeAnyOfAddresses.With(anyAddress),
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
},
"all good with anyOf addresses": {
src: Params{
CodeUploadAccess: AccessTypeAnyOfAddresses.With(anyAddress, otherAddress),
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
},
"reject empty type in instantiate permission": { "reject empty type in instantiate permission": {
src: Params{ src: Params{
CodeUploadAccess: AllowNobody, CodeUploadAccess: AllowNobody,
@@ -62,6 +76,13 @@ func TestValidateParams(t *testing.T) {
}, },
expErr: true, expErr: true,
}, },
"reject wrong field addresses in only address": {
src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeOnlyAddress, Address: anyAddress.String(), Addresses: []string{anyAddress.String()}},
InstantiateDefaultPermission: AccessTypeOnlyAddress,
},
expErr: true,
},
"reject CodeUploadAccess Everybody with obsolete address": { "reject CodeUploadAccess Everybody with obsolete address": {
src: Params{ src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeEverybody, Address: anyAddress.String()}, CodeUploadAccess: AccessConfig{Permission: AccessTypeEverybody, Address: anyAddress.String()},
@@ -89,6 +110,41 @@ func TestValidateParams(t *testing.T) {
}, },
expErr: true, expErr: true,
}, },
"reject empty addresses in any of addresses": {
src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{}},
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
expErr: true,
},
"reject addresses not set in any of addresses": {
src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeAnyOfAddresses},
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
expErr: true,
},
"reject invalid address in any of addresses": {
src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{invalidAddress}},
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
expErr: true,
},
"reject duplicate address in any of addresses": {
src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{anyAddress.String(), anyAddress.String()}},
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
expErr: true,
},
"reject wrong field address in any of addresses": {
src: Params{
CodeUploadAccess: AccessConfig{Permission: AccessTypeAnyOfAddresses, Address: anyAddress.String(), Addresses: []string{anyAddress.String()}},
InstantiateDefaultPermission: AccessTypeAnyOfAddresses,
},
expErr: true,
},
} }
for msg, spec := range specs { for msg, spec := range specs {
t.Run(msg, func(t *testing.T) { t.Run(msg, func(t *testing.T) {
@@ -107,11 +163,12 @@ func TestAccessTypeMarshalJson(t *testing.T) {
src AccessType src AccessType
exp string exp string
}{ }{
"Unspecified": {src: AccessTypeUnspecified, exp: `"Unspecified"`}, "Unspecified": {src: AccessTypeUnspecified, exp: `"Unspecified"`},
"Nobody": {src: AccessTypeNobody, exp: `"Nobody"`}, "Nobody": {src: AccessTypeNobody, exp: `"Nobody"`},
"OnlyAddress": {src: AccessTypeOnlyAddress, exp: `"OnlyAddress"`}, "OnlyAddress": {src: AccessTypeOnlyAddress, exp: `"OnlyAddress"`},
"Everybody": {src: AccessTypeEverybody, exp: `"Everybody"`}, "AccessTypeAnyOfAddresses": {src: AccessTypeAnyOfAddresses, exp: `"AnyOfAddresses"`},
"unknown": {src: 999, exp: `"Unspecified"`}, "Everybody": {src: AccessTypeEverybody, exp: `"Everybody"`},
"unknown": {src: 999, exp: `"Unspecified"`},
} }
for msg, spec := range specs { for msg, spec := range specs {
t.Run(msg, func(t *testing.T) { t.Run(msg, func(t *testing.T) {
@@ -127,11 +184,12 @@ func TestAccessTypeUnmarshalJson(t *testing.T) {
src string src string
exp AccessType exp AccessType
}{ }{
"Unspecified": {src: `"Unspecified"`, exp: AccessTypeUnspecified}, "Unspecified": {src: `"Unspecified"`, exp: AccessTypeUnspecified},
"Nobody": {src: `"Nobody"`, exp: AccessTypeNobody}, "Nobody": {src: `"Nobody"`, exp: AccessTypeNobody},
"OnlyAddress": {src: `"OnlyAddress"`, exp: AccessTypeOnlyAddress}, "OnlyAddress": {src: `"OnlyAddress"`, exp: AccessTypeOnlyAddress},
"Everybody": {src: `"Everybody"`, exp: AccessTypeEverybody}, "AnyOfAddresses": {src: `"AnyOfAddresses"`, exp: AccessTypeAnyOfAddresses},
"unknown": {src: `""`, exp: AccessTypeUnspecified}, "Everybody": {src: `"Everybody"`, exp: AccessTypeEverybody},
"unknown": {src: `""`, exp: AccessTypeUnspecified},
} }
for msg, spec := range specs { for msg, spec := range specs {
t.Run(msg, func(t *testing.T) { t.Run(msg, func(t *testing.T) {
@@ -166,3 +224,83 @@ func TestParamsUnmarshalJson(t *testing.T) {
}) })
} }
} }
func TestAccessTypeWith(t *testing.T) {
myAddress := sdk.AccAddress(randBytes(SDKAddrLen))
myOtherAddress := sdk.AccAddress(randBytes(SDKAddrLen))
specs := map[string]struct {
src AccessType
addrs []sdk.AccAddress
exp AccessConfig
expPanic bool
}{
"nobody": {
src: AccessTypeNobody,
exp: AccessConfig{Permission: AccessTypeNobody},
},
"nobody with address": {
src: AccessTypeNobody,
addrs: []sdk.AccAddress{myAddress},
exp: AccessConfig{Permission: AccessTypeNobody},
},
"everybody": {
src: AccessTypeEverybody,
exp: AccessConfig{Permission: AccessTypeEverybody},
},
"everybody with address": {
src: AccessTypeEverybody,
addrs: []sdk.AccAddress{myAddress},
exp: AccessConfig{Permission: AccessTypeEverybody},
},
"only address without address": {
src: AccessTypeOnlyAddress,
expPanic: true,
},
"only address with address": {
src: AccessTypeOnlyAddress,
addrs: []sdk.AccAddress{myAddress},
exp: AccessConfig{Permission: AccessTypeOnlyAddress, Address: myAddress.String()},
},
"only address with invalid address": {
src: AccessTypeOnlyAddress,
addrs: []sdk.AccAddress{nil},
expPanic: true,
},
"any of address without address": {
src: AccessTypeAnyOfAddresses,
expPanic: true,
},
"any of address with single address": {
src: AccessTypeAnyOfAddresses,
addrs: []sdk.AccAddress{myAddress},
exp: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{myAddress.String()}},
},
"any of address with multiple addresses": {
src: AccessTypeAnyOfAddresses,
addrs: []sdk.AccAddress{myAddress, myOtherAddress},
exp: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{myAddress.String(), myOtherAddress.String()}},
},
"any of address with duplicate addresses": {
src: AccessTypeAnyOfAddresses,
addrs: []sdk.AccAddress{myAddress, myAddress},
expPanic: true,
},
"any of address with invalid address": {
src: AccessTypeAnyOfAddresses,
addrs: []sdk.AccAddress{nil},
expPanic: true,
},
}
for name, spec := range specs {
t.Run(name, func(t *testing.T) {
if !spec.expPanic {
got := spec.src.With(spec.addrs...)
assert.Equal(t, spec.exp, got)
return
}
assert.Panics(t, func() {
spec.src.With(spec.addrs...)
})
})
}
}

View File

@@ -342,9 +342,9 @@ func (a AccessType) IsSubset(superSet AccessType) bool {
case AccessTypeNobody: case AccessTypeNobody:
// Only an exact match is a subset of this // Only an exact match is a subset of this
return a == AccessTypeNobody return a == AccessTypeNobody
case AccessTypeOnlyAddress: case AccessTypeOnlyAddress, AccessTypeAnyOfAddresses:
// An exact match or nobody // Nobody or address(es)
return a == AccessTypeNobody || a == AccessTypeOnlyAddress return a == AccessTypeNobody || a == AccessTypeOnlyAddress || a == AccessTypeAnyOfAddresses
default: default:
return false return false
} }
@@ -356,8 +356,32 @@ func (a AccessConfig) IsSubset(superSet AccessConfig) bool {
switch superSet.Permission { switch superSet.Permission {
case AccessTypeOnlyAddress: case AccessTypeOnlyAddress:
// An exact match or nobody // An exact match or nobody
return a.Permission == AccessTypeNobody || (a.Permission == AccessTypeOnlyAddress && a.Address == superSet.Address) return a.Permission == AccessTypeNobody || (a.Permission == AccessTypeOnlyAddress && a.Address == superSet.Address) ||
(a.Permission == AccessTypeAnyOfAddresses && isSubset([]string{superSet.Address}, a.Addresses))
case AccessTypeAnyOfAddresses:
// An exact match or nobody
return a.Permission == AccessTypeNobody || (a.Permission == AccessTypeOnlyAddress && isSubset(superSet.Addresses, []string{a.Address})) ||
a.Permission == AccessTypeAnyOfAddresses && isSubset(superSet.Addresses, a.Addresses)
case AccessTypeUnspecified:
return false
default: default:
return a.Permission.IsSubset(superSet.Permission) return a.Permission.IsSubset(superSet.Permission)
} }
} }
// return true when all elements in sub are also part of super
func isSubset(super, sub []string) bool {
if len(sub) == 0 {
return true
}
var matches int
for _, o := range sub {
for _, s := range super {
if o == s {
matches++
break
}
}
}
return matches == len(sub)
}

View File

@@ -39,10 +39,13 @@ const (
AccessTypeUnspecified AccessType = 0 AccessTypeUnspecified AccessType = 0
// AccessTypeNobody forbidden // AccessTypeNobody forbidden
AccessTypeNobody AccessType = 1 AccessTypeNobody AccessType = 1
// AccessTypeOnlyAddress restricted to an address // AccessTypeOnlyAddress restricted to a single address
// Deprecated: use AccessTypeAnyOfAddresses instead
AccessTypeOnlyAddress AccessType = 2 AccessTypeOnlyAddress AccessType = 2
// AccessTypeEverybody unrestricted // AccessTypeEverybody unrestricted
AccessTypeEverybody AccessType = 3 AccessTypeEverybody AccessType = 3
// AccessTypeAnyOfAddresses allow any of the addresses
AccessTypeAnyOfAddresses AccessType = 4
) )
var AccessType_name = map[int32]string{ var AccessType_name = map[int32]string{
@@ -50,13 +53,15 @@ var AccessType_name = map[int32]string{
1: "ACCESS_TYPE_NOBODY", 1: "ACCESS_TYPE_NOBODY",
2: "ACCESS_TYPE_ONLY_ADDRESS", 2: "ACCESS_TYPE_ONLY_ADDRESS",
3: "ACCESS_TYPE_EVERYBODY", 3: "ACCESS_TYPE_EVERYBODY",
4: "ACCESS_TYPE_ANY_OF_ADDRESSES",
} }
var AccessType_value = map[string]int32{ var AccessType_value = map[string]int32{
"ACCESS_TYPE_UNSPECIFIED": 0, "ACCESS_TYPE_UNSPECIFIED": 0,
"ACCESS_TYPE_NOBODY": 1, "ACCESS_TYPE_NOBODY": 1,
"ACCESS_TYPE_ONLY_ADDRESS": 2, "ACCESS_TYPE_ONLY_ADDRESS": 2,
"ACCESS_TYPE_EVERYBODY": 3, "ACCESS_TYPE_EVERYBODY": 3,
"ACCESS_TYPE_ANY_OF_ADDRESSES": 4,
} }
func (AccessType) EnumDescriptor() ([]byte, []int) { func (AccessType) EnumDescriptor() ([]byte, []int) {
@@ -145,7 +150,10 @@ var xxx_messageInfo_AccessTypeParam proto.InternalMessageInfo
// AccessConfig access control type. // AccessConfig access control type.
type AccessConfig struct { type AccessConfig struct {
Permission AccessType `protobuf:"varint,1,opt,name=permission,proto3,enum=cosmwasm.wasm.v1.AccessType" json:"permission,omitempty" yaml:"permission"` Permission AccessType `protobuf:"varint,1,opt,name=permission,proto3,enum=cosmwasm.wasm.v1.AccessType" json:"permission,omitempty" yaml:"permission"`
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty" yaml:"address"` // Address
// Deprecated: replaced by addresses
Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty" yaml:"address"`
Addresses []string `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses,omitempty" yaml:"addresses"`
} }
func (m *AccessConfig) Reset() { *m = AccessConfig{} } func (m *AccessConfig) Reset() { *m = AccessConfig{} }
@@ -493,78 +501,81 @@ func init() {
func init() { proto.RegisterFile("cosmwasm/wasm/v1/types.proto", fileDescriptor_e6155d98fa173e02) } func init() { proto.RegisterFile("cosmwasm/wasm/v1/types.proto", fileDescriptor_e6155d98fa173e02) }
var fileDescriptor_e6155d98fa173e02 = []byte{ var fileDescriptor_e6155d98fa173e02 = []byte{
// 1123 bytes of a gzipped FileDescriptorProto // 1178 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xcd, 0x8f, 0xdb, 0x44, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xcf, 0x8f, 0xdb, 0xc4,
0x14, 0x8f, 0x93, 0xec, 0xd7, 0x34, 0x14, 0x77, 0xd8, 0xa5, 0x49, 0xa8, 0x9c, 0xd4, 0x14, 0xd8, 0x17, 0x8f, 0x93, 0xec, 0x8f, 0x4c, 0xf7, 0xdb, 0xaf, 0x3b, 0xec, 0xd2, 0x6c, 0x58, 0x25, 0xa9,
0x7e, 0x25, 0x74, 0x41, 0x80, 0x7a, 0xa8, 0x94, 0x0f, 0xd3, 0xb8, 0x62, 0x93, 0x68, 0x92, 0x52, 0x29, 0xb0, 0xfd, 0x95, 0xd0, 0x05, 0x01, 0xea, 0xa1, 0x52, 0x7e, 0xb8, 0x5d, 0x57, 0x6c, 0x1c,
0x2d, 0x52, 0x65, 0x39, 0xf6, 0x6c, 0x62, 0xd5, 0xf1, 0x44, 0x9e, 0xc9, 0x36, 0xfe, 0x0f, 0x50, 0x4d, 0x52, 0xaa, 0x45, 0xaa, 0x2c, 0xc7, 0x9e, 0xcd, 0x5a, 0x75, 0x3c, 0x91, 0x67, 0xb2, 0x8d,
0x24, 0x04, 0x37, 0xb8, 0x44, 0x42, 0x80, 0x50, 0xff, 0x00, 0xae, 0xdc, 0x2b, 0x4e, 0x3d, 0x72, 0xff, 0x03, 0x14, 0x09, 0xc1, 0x91, 0x4b, 0x24, 0x04, 0x08, 0x95, 0x3b, 0x57, 0xee, 0x15, 0x5c,
0x8a, 0x20, 0xbd, 0xc0, 0x75, 0x8f, 0xe5, 0x82, 0x3c, 0x93, 0x10, 0xab, 0xdd, 0x76, 0xc3, 0xc5, 0x7a, 0xe4, 0x14, 0xc1, 0xf6, 0xc2, 0x39, 0xc7, 0x72, 0x41, 0x9e, 0x89, 0x1b, 0xd3, 0x6e, 0xbb,
0xf2, 0xbc, 0xf7, 0x7e, 0xbf, 0xf7, 0xde, 0x6f, 0xde, 0xb3, 0x0c, 0x2e, 0x58, 0x84, 0xf6, 0x1f, 0xe1, 0x62, 0xcd, 0xbc, 0xf7, 0x3e, 0x9f, 0xf7, 0xde, 0x67, 0xe6, 0x8d, 0x0c, 0xb6, 0x2c, 0x42,
0x9a, 0xb4, 0x5f, 0xe4, 0x8f, 0xa3, 0x1b, 0x45, 0x16, 0x0c, 0x30, 0x2d, 0x0c, 0x7c, 0xc2, 0x08, 0x7b, 0x0f, 0x4d, 0xda, 0x2b, 0xf3, 0xcf, 0xd1, 0xf5, 0x32, 0x0b, 0xfa, 0x98, 0x96, 0xfa, 0x3e,
0x94, 0x17, 0xde, 0x02, 0x7f, 0x1c, 0xdd, 0xc8, 0x66, 0x42, 0x0b, 0xa1, 0x06, 0xf7, 0x17, 0xc5, 0x61, 0x04, 0xca, 0x91, 0xb7, 0xc4, 0x3f, 0x47, 0xd7, 0x73, 0x9b, 0xa1, 0x85, 0x50, 0x83, 0xfb,
0x41, 0x04, 0x67, 0xb7, 0xbb, 0xa4, 0x4b, 0x84, 0x3d, 0x7c, 0x9b, 0x5b, 0x33, 0x5d, 0x42, 0xba, 0xcb, 0x62, 0x23, 0x82, 0x73, 0xeb, 0x5d, 0xd2, 0x25, 0xc2, 0x1e, 0xae, 0x66, 0xd6, 0xcd, 0x2e,
0x2e, 0x2e, 0xf2, 0x53, 0x67, 0x78, 0x58, 0x34, 0xbd, 0x40, 0xb8, 0xd4, 0xfb, 0xe0, 0xf5, 0x92, 0x21, 0x5d, 0x17, 0x97, 0xf9, 0xae, 0x33, 0x38, 0x28, 0x9b, 0x5e, 0x20, 0x5c, 0xca, 0x7d, 0xf0,
0x65, 0x61, 0x4a, 0xdb, 0xc1, 0x00, 0x37, 0x4d, 0xdf, 0xec, 0xc3, 0x2a, 0x58, 0x3b, 0x32, 0xdd, 0xff, 0x8a, 0x65, 0x61, 0x4a, 0xdb, 0x41, 0x1f, 0x37, 0x4d, 0xdf, 0xec, 0xc1, 0x3a, 0x58, 0x3a,
0x21, 0x4e, 0x4b, 0x79, 0x69, 0xf7, 0xec, 0xde, 0x85, 0xc2, 0xf3, 0x05, 0x14, 0x96, 0x88, 0xb2, 0x32, 0xdd, 0x01, 0xce, 0x4a, 0x45, 0x69, 0xfb, 0xec, 0xce, 0x56, 0xe9, 0xc5, 0x02, 0x4a, 0x73,
0x7c, 0x3c, 0xcd, 0xa5, 0x02, 0xb3, 0xef, 0xde, 0x54, 0x39, 0x48, 0x45, 0x02, 0x7c, 0x33, 0xf9, 0x44, 0x55, 0x9e, 0x4e, 0x0a, 0x6b, 0x81, 0xd9, 0x73, 0x6f, 0x28, 0x1c, 0xa4, 0x20, 0x01, 0xbe,
0xdd, 0xf7, 0x39, 0x49, 0xfd, 0x56, 0x02, 0x29, 0x11, 0x5d, 0x21, 0xde, 0xa1, 0xd3, 0x85, 0x2d, 0x91, 0xfe, 0xe6, 0xdb, 0x82, 0xa4, 0xfc, 0x26, 0x81, 0x35, 0x11, 0x5d, 0x23, 0xde, 0x81, 0xd3,
0x00, 0x06, 0xd8, 0xef, 0x3b, 0x94, 0x3a, 0xc4, 0x5b, 0x29, 0xc3, 0xce, 0xf1, 0x34, 0x77, 0x4e, 0x85, 0x2d, 0x00, 0xfa, 0xd8, 0xef, 0x39, 0x94, 0x3a, 0xc4, 0x5b, 0x28, 0xc3, 0xc6, 0x74, 0x52,
0x64, 0x58, 0x22, 0x55, 0x14, 0xa1, 0x81, 0xd7, 0xc0, 0x86, 0x69, 0xdb, 0x3e, 0xa6, 0x34, 0x1d, 0x38, 0x27, 0x32, 0xcc, 0x91, 0x0a, 0x8a, 0xd1, 0xc0, 0xab, 0x60, 0xc5, 0xb4, 0x6d, 0x1f, 0x53,
0xcf, 0x4b, 0xbb, 0x5b, 0x65, 0x78, 0x3c, 0xcd, 0x9d, 0x15, 0x98, 0xb9, 0x43, 0x45, 0x8b, 0x90, 0x9a, 0x4d, 0x16, 0xa5, 0xed, 0x4c, 0x15, 0x4e, 0x27, 0x85, 0xb3, 0x02, 0x33, 0x73, 0x28, 0x28,
0x79, 0x65, 0x5f, 0xc7, 0xc1, 0x3a, 0xef, 0x97, 0x42, 0x02, 0xa0, 0x45, 0x6c, 0x6c, 0x0c, 0x07, 0x0a, 0x81, 0x3b, 0x20, 0x33, 0x5b, 0x62, 0x9a, 0x4d, 0x15, 0x53, 0xdb, 0x99, 0xea, 0xfa, 0x74,
0x2e, 0x31, 0x6d, 0xc3, 0xe4, 0xb9, 0x79, 0x6d, 0x67, 0xf6, 0x94, 0x97, 0xd5, 0x26, 0xfa, 0x29, 0x52, 0x90, 0xff, 0x15, 0x8f, 0xa9, 0x82, 0xe6, 0x61, 0xb3, 0x6e, 0xbe, 0x4a, 0x82, 0x65, 0xae,
0x5f, 0x7c, 0x3c, 0xcd, 0xc5, 0x8e, 0xa7, 0xb9, 0x8c, 0xc8, 0xf6, 0x22, 0x8f, 0x8a, 0xe4, 0xd0, 0x11, 0x85, 0x04, 0x40, 0x8b, 0xd8, 0xd8, 0x18, 0xf4, 0x5d, 0x62, 0xda, 0x86, 0xc9, 0xeb, 0xe5,
0x78, 0x97, 0xdb, 0x04, 0x14, 0x7e, 0x25, 0x01, 0xc5, 0xf1, 0x28, 0x33, 0x3d, 0xe6, 0x98, 0x0c, 0xfd, 0x9c, 0xd9, 0xc9, 0xbf, 0xaa, 0x1f, 0xa1, 0x41, 0xf5, 0xc2, 0xe3, 0x49, 0x21, 0x31, 0x9d,
0x1b, 0x36, 0x3e, 0x34, 0x87, 0x2e, 0x33, 0x22, 0xca, 0xc4, 0x57, 0x50, 0xe6, 0xf2, 0xf1, 0x34, 0x14, 0x36, 0x45, 0xc6, 0x97, 0x79, 0x14, 0x24, 0x87, 0xc6, 0xbb, 0xdc, 0x26, 0xa0, 0xf0, 0x4b,
0xf7, 0x8e, 0xc8, 0xfb, 0x6a, 0x36, 0x15, 0x5d, 0x88, 0x04, 0x54, 0x85, 0xbf, 0xf9, 0x9f, 0x9b, 0x09, 0xe4, 0x1d, 0x8f, 0x32, 0xd3, 0x63, 0x8e, 0xc9, 0xb0, 0x61, 0xe3, 0x03, 0x73, 0xe0, 0x32,
0x2b, 0x12, 0x53, 0x7f, 0x90, 0xc0, 0x66, 0x85, 0xd8, 0x58, 0xf7, 0x0e, 0x09, 0x7c, 0x0b, 0x6c, 0x23, 0xa6, 0x66, 0x72, 0x01, 0x35, 0x2f, 0x4d, 0x27, 0x85, 0x77, 0x44, 0xde, 0xd7, 0xb3, 0x29,
0xf1, 0x5e, 0x7a, 0x26, 0xed, 0x71, 0x29, 0x52, 0x68, 0x33, 0x34, 0xd4, 0x4c, 0xda, 0x83, 0x69, 0x68, 0x2b, 0x16, 0x50, 0x17, 0xfe, 0xe6, 0x73, 0x37, 0x57, 0x24, 0xa1, 0x7c, 0x27, 0x81, 0xd5,
0xb0, 0x61, 0xf9, 0xd8, 0x64, 0xc4, 0x17, 0x7a, 0xa3, 0xc5, 0x11, 0xb6, 0x00, 0x8c, 0x96, 0x62, 0x1a, 0xb1, 0xb1, 0xe6, 0x1d, 0x10, 0xf8, 0x16, 0xc8, 0xf0, 0x5e, 0x0e, 0x4d, 0x7a, 0xc8, 0xa5,
0x71, 0x91, 0xd2, 0x6b, 0x2b, 0x49, 0x99, 0x0c, 0xa5, 0x44, 0xe7, 0x22, 0x78, 0xe1, 0xb8, 0x93, 0x58, 0x43, 0xab, 0xa1, 0x61, 0xd7, 0xa4, 0x87, 0x30, 0x0b, 0x56, 0x2c, 0x1f, 0x9b, 0x8c, 0xf8,
0xdc, 0x4c, 0xc8, 0xc9, 0x3b, 0xc9, 0xcd, 0xa4, 0xbc, 0xa6, 0xfe, 0x1a, 0x07, 0xa9, 0x0a, 0xf1, 0xe2, 0x8c, 0x50, 0xb4, 0x85, 0x2d, 0x00, 0xe3, 0xa5, 0x58, 0x5c, 0xa4, 0xec, 0xd2, 0x42, 0x52,
0x98, 0x6f, 0x5a, 0x8c, 0x17, 0xfa, 0x36, 0xd8, 0xe0, 0x85, 0x3a, 0x36, 0x2f, 0x33, 0x59, 0x06, 0xa6, 0x43, 0x29, 0xd1, 0xb9, 0x18, 0x5e, 0x38, 0xee, 0xa4, 0x57, 0x53, 0x72, 0xfa, 0x4e, 0x7a,
0xb3, 0x69, 0x6e, 0x9d, 0xf7, 0x51, 0x45, 0xeb, 0xa1, 0x4b, 0xb7, 0x5f, 0x51, 0xf0, 0x36, 0x58, 0x35, 0x2d, 0x2f, 0x29, 0xbf, 0x24, 0xc1, 0x5a, 0x8d, 0x78, 0xcc, 0x37, 0x2d, 0xc6, 0x0b, 0x7d,
0x33, 0xed, 0xbe, 0xe3, 0xa5, 0x13, 0xdc, 0x2e, 0x0e, 0xa1, 0xd5, 0x35, 0x3b, 0xd8, 0x4d, 0x27, 0x1b, 0xac, 0xf0, 0x42, 0x1d, 0x9b, 0x97, 0x99, 0xae, 0x82, 0xe3, 0x49, 0x61, 0x99, 0xf7, 0x51,
0x85, 0x95, 0x1f, 0xe0, 0xad, 0x39, 0x0b, 0xb6, 0xe7, 0x1d, 0x5d, 0x3a, 0xa1, 0xa3, 0x0e, 0x25, 0x47, 0xcb, 0xa1, 0x4b, 0xb3, 0x5f, 0x53, 0xf0, 0x3a, 0x58, 0x32, 0xed, 0x9e, 0xe3, 0x65, 0x53,
0xee, 0x90, 0xe1, 0xf6, 0xa8, 0x49, 0xa8, 0xc3, 0x1c, 0xe2, 0xa1, 0x05, 0x08, 0x5e, 0x07, 0x67, 0xdc, 0x2e, 0x36, 0xa1, 0xd5, 0x35, 0x3b, 0xd8, 0xcd, 0xa6, 0x85, 0x95, 0x6f, 0xe0, 0xcd, 0x19,
0x9c, 0x8e, 0x65, 0x0c, 0x88, 0xcf, 0xc2, 0x72, 0xd7, 0xf9, 0xa8, 0xbe, 0x36, 0x9b, 0xe6, 0xb6, 0x0b, 0xb6, 0x67, 0x1d, 0x5d, 0x3c, 0xa1, 0xa3, 0x0e, 0x25, 0xee, 0x80, 0xe1, 0xf6, 0xb0, 0x49,
0xf4, 0x72, 0xa5, 0x49, 0x7c, 0xa6, 0x57, 0xd1, 0x96, 0xd3, 0xb1, 0xf8, 0xab, 0x0d, 0xf7, 0xc1, 0xa8, 0xc3, 0x1c, 0xe2, 0xa1, 0x08, 0x04, 0xaf, 0x81, 0x33, 0x4e, 0xc7, 0x32, 0xfa, 0xc4, 0x67,
0x16, 0x1e, 0x31, 0xec, 0xf1, 0x79, 0xd8, 0xe0, 0x09, 0xb7, 0x0b, 0x62, 0x93, 0x0b, 0x8b, 0x4d, 0x61, 0xb9, 0xcb, 0xfc, 0x7a, 0xff, 0xef, 0x78, 0x52, 0xc8, 0x68, 0xd5, 0x5a, 0x93, 0xf8, 0x4c,
0x2e, 0x94, 0xbc, 0xa0, 0x9c, 0xf9, 0xed, 0x97, 0xeb, 0x3b, 0x51, 0x51, 0xb4, 0x05, 0x0c, 0x2d, 0xab, 0xa3, 0x8c, 0xd3, 0xb1, 0xf8, 0xd2, 0x86, 0x7b, 0x20, 0x83, 0x87, 0x0c, 0x7b, 0xfc, 0x3e,
0x19, 0x6e, 0x26, 0xff, 0x0a, 0xc7, 0xfe, 0x1f, 0x09, 0xa4, 0x17, 0xa1, 0xa1, 0x48, 0x35, 0x87, 0xac, 0xf0, 0x84, 0xeb, 0x25, 0x31, 0xfd, 0xa5, 0x68, 0xfa, 0x4b, 0x15, 0x2f, 0xa8, 0x6e, 0xfe,
0x32, 0xe2, 0x07, 0x9a, 0xc7, 0xfc, 0x00, 0x36, 0xc1, 0x16, 0x19, 0x60, 0xdf, 0x64, 0xcb, 0xdd, 0xfa, 0xf3, 0xb5, 0x8d, 0xb8, 0x28, 0x6a, 0x04, 0x43, 0x73, 0x86, 0x1b, 0xe9, 0xbf, 0xc2, 0x6b,
0xdc, 0x7b, 0xb1, 0xc5, 0x13, 0xe0, 0x8d, 0x05, 0x2a, 0x9c, 0x4b, 0xb4, 0x24, 0x89, 0xde, 0x4e, 0xff, 0xb7, 0x04, 0xb2, 0x51, 0x68, 0x28, 0xd2, 0xae, 0x43, 0x19, 0xf1, 0x03, 0xd5, 0x63, 0x7e,
0xfc, 0xa5, 0xb7, 0x73, 0x0b, 0x6c, 0x0c, 0x07, 0x36, 0xd7, 0x35, 0xf1, 0x7f, 0x74, 0x9d, 0x83, 0x00, 0x9b, 0x20, 0x43, 0xfa, 0xd8, 0x37, 0xd9, 0x7c, 0x9e, 0x77, 0x5e, 0x6e, 0xf1, 0x04, 0xb8,
0xe0, 0x2e, 0x48, 0xf4, 0x69, 0x97, 0xdf, 0x55, 0xaa, 0xfc, 0xe6, 0xb3, 0x69, 0x0e, 0x22, 0xf3, 0x1e, 0xa1, 0xc2, 0x7b, 0x89, 0xe6, 0x24, 0xf1, 0xd3, 0x49, 0xbe, 0xf2, 0x74, 0x6e, 0x82, 0x95,
0xe1, 0xa2, 0xca, 0x7d, 0x4c, 0xa9, 0xd9, 0xc5, 0x28, 0x0c, 0x51, 0x11, 0x80, 0x2f, 0x12, 0xc1, 0x41, 0xdf, 0xe6, 0xba, 0xa6, 0xfe, 0x8b, 0xae, 0x33, 0x10, 0xdc, 0x06, 0xa9, 0x1e, 0xed, 0xf2,
0x8b, 0x20, 0xd5, 0x71, 0x89, 0xf5, 0xc0, 0xe8, 0x61, 0xa7, 0xdb, 0x63, 0x62, 0x8e, 0xd0, 0x19, 0xb3, 0x5a, 0xab, 0xbe, 0xf9, 0x6c, 0x52, 0x80, 0xc8, 0x7c, 0x18, 0x55, 0xb9, 0x87, 0x29, 0x35,
0x6e, 0xab, 0x71, 0x13, 0xcc, 0x80, 0x4d, 0x36, 0x32, 0x1c, 0xcf, 0xc6, 0x23, 0xd1, 0x08, 0xda, 0xbb, 0x18, 0x85, 0x21, 0x0a, 0x02, 0xf0, 0x65, 0x22, 0x78, 0x01, 0xac, 0x75, 0x5c, 0x62, 0x3d,
0x60, 0x23, 0x3d, 0x3c, 0xaa, 0x0e, 0x58, 0xdb, 0x27, 0x36, 0x76, 0xe1, 0x1d, 0x90, 0x78, 0x80, 0x30, 0x0e, 0xb1, 0xd3, 0x3d, 0x64, 0xe2, 0x1e, 0xa1, 0x33, 0xdc, 0xb6, 0xcb, 0x4d, 0x70, 0x13,
0x03, 0xb1, 0x2c, 0xe5, 0x4f, 0x9e, 0x4d, 0x73, 0x1f, 0x76, 0x1d, 0xd6, 0x1b, 0x76, 0x0a, 0x16, 0xac, 0xb2, 0xa1, 0xe1, 0x78, 0x36, 0x1e, 0x8a, 0x46, 0xd0, 0x0a, 0x1b, 0x6a, 0xe1, 0x56, 0x71,
0xe9, 0x17, 0x19, 0xf6, 0xec, 0x70, 0xe1, 0x3c, 0x16, 0x7d, 0x75, 0x9d, 0x0e, 0x2d, 0x76, 0x02, 0xc0, 0xd2, 0x1e, 0xb1, 0xb1, 0x0b, 0xef, 0x80, 0xd4, 0x03, 0x1c, 0x88, 0x61, 0xa9, 0x7e, 0xf2,
0x86, 0x69, 0xa1, 0x86, 0x47, 0xe5, 0xf0, 0x05, 0x85, 0x24, 0xe1, 0x00, 0x8a, 0x6f, 0x70, 0x9c, 0x6c, 0x52, 0xf8, 0xb0, 0xeb, 0xb0, 0xc3, 0x41, 0xa7, 0x64, 0x91, 0x5e, 0x99, 0x61, 0xcf, 0x0e,
0xaf, 0x9e, 0x38, 0x5c, 0xf9, 0x5b, 0x02, 0x60, 0xb9, 0xff, 0xf0, 0x23, 0x70, 0xbe, 0x54, 0xa9, 0x07, 0xce, 0x63, 0xf1, 0xa5, 0xeb, 0x74, 0x68, 0xb9, 0x13, 0x30, 0x4c, 0x4b, 0xbb, 0x78, 0x58,
0x68, 0xad, 0x96, 0xd1, 0x3e, 0x68, 0x6a, 0xc6, 0xdd, 0x7a, 0xab, 0xa9, 0x55, 0xf4, 0x4f, 0x75, 0x0d, 0x17, 0x28, 0x24, 0x09, 0x2f, 0xa0, 0x78, 0xb7, 0x93, 0x7c, 0xf4, 0xc4, 0xe6, 0xf2, 0x4f,
0xad, 0x2a, 0xc7, 0xb2, 0x99, 0xf1, 0x24, 0xbf, 0xb3, 0x0c, 0xbe, 0xeb, 0xd1, 0x01, 0xb6, 0x9c, 0x49, 0x00, 0xe6, 0xf3, 0x0f, 0x3f, 0x02, 0xe7, 0x2b, 0xb5, 0x9a, 0xda, 0x6a, 0x19, 0xed, 0xfd,
0x43, 0x07, 0xdb, 0xf0, 0x1a, 0x80, 0x51, 0x5c, 0xbd, 0x51, 0x6e, 0x54, 0x0f, 0x64, 0x29, 0xbb, 0xa6, 0x6a, 0xdc, 0x6d, 0xb4, 0x9a, 0x6a, 0x4d, 0xbb, 0xa5, 0xa9, 0x75, 0x39, 0x91, 0xdb, 0x1c,
0x3d, 0x9e, 0xe4, 0xe5, 0x25, 0xa4, 0x4e, 0x3a, 0xc4, 0x0e, 0xe0, 0xc7, 0x20, 0x1d, 0x8d, 0x6e, 0x8d, 0x8b, 0x1b, 0xf3, 0xe0, 0xbb, 0x1e, 0xed, 0x63, 0xcb, 0x39, 0x70, 0xb0, 0x0d, 0xaf, 0x02,
0xd4, 0x3f, 0x3b, 0x30, 0x4a, 0xd5, 0x2a, 0xd2, 0x5a, 0x2d, 0x39, 0xfe, 0x7c, 0x9a, 0x86, 0xe7, 0x18, 0xc7, 0x35, 0xf4, 0xaa, 0x5e, 0xdf, 0x97, 0xa5, 0xdc, 0xfa, 0x68, 0x5c, 0x94, 0xe7, 0x90,
0x06, 0x25, 0xf1, 0x9d, 0x85, 0x7b, 0x60, 0x27, 0x0a, 0xd4, 0x3e, 0xd7, 0xd0, 0x01, 0xcf, 0x94, 0x06, 0xe9, 0x10, 0x3b, 0x80, 0x1f, 0x83, 0x6c, 0x3c, 0x5a, 0x6f, 0x7c, 0xba, 0x6f, 0x54, 0xea,
0xc8, 0x9e, 0x1f, 0x4f, 0xf2, 0x6f, 0x2c, 0x51, 0xda, 0x11, 0xf6, 0x83, 0x30, 0x59, 0x76, 0xf3, 0x75, 0xa4, 0xb6, 0x5a, 0x72, 0xf2, 0xc5, 0x34, 0xba, 0xe7, 0x06, 0x95, 0xe7, 0x6f, 0xf3, 0x46,
0xcb, 0x1f, 0x95, 0xd8, 0xa3, 0x9f, 0x94, 0xd8, 0x95, 0x9f, 0x13, 0x20, 0x7f, 0xda, 0xa4, 0x41, 0x1c, 0xa8, 0x7e, 0xa6, 0xa2, 0x7d, 0x9e, 0x29, 0x95, 0x3b, 0x3f, 0x1a, 0x17, 0xdf, 0x98, 0xa3,
0x0c, 0xde, 0xaf, 0x34, 0xea, 0x6d, 0x54, 0xaa, 0xb4, 0x8d, 0x4a, 0xa3, 0xaa, 0x19, 0x35, 0xbd, 0xd4, 0x23, 0xec, 0x07, 0x3c, 0xd9, 0x4d, 0xb0, 0x15, 0xc7, 0x54, 0x1a, 0xfb, 0x86, 0x7e, 0x2b,
0xd5, 0x6e, 0xa0, 0x03, 0xa3, 0xd1, 0xd4, 0x50, 0xa9, 0xad, 0x37, 0xea, 0x27, 0x49, 0x53, 0x1c, 0x4a, 0xa7, 0xb6, 0xe4, 0x74, 0x6e, 0x6b, 0x34, 0x2e, 0x66, 0xe7, 0xd0, 0x8a, 0x17, 0xe8, 0x07,
0x4f, 0xf2, 0x57, 0x4f, 0xe3, 0x8e, 0x0a, 0x76, 0x0f, 0x5c, 0x5e, 0x29, 0x8d, 0x5e, 0xd7, 0xdb, 0x95, 0xe8, 0x6d, 0xcf, 0xad, 0x7e, 0xf1, 0x7d, 0x3e, 0xf1, 0xe8, 0x87, 0x7c, 0xe2, 0xf2, 0x8f,
0xb2, 0x94, 0xdd, 0x1d, 0x4f, 0xf2, 0x97, 0x4e, 0xe3, 0xd7, 0x3d, 0x87, 0xc1, 0xfb, 0xe0, 0xda, 0x29, 0x50, 0x3c, 0xed, 0xa6, 0x42, 0x0c, 0xde, 0xaf, 0xe9, 0x8d, 0x36, 0xaa, 0xd4, 0xda, 0x46,
0x4a, 0xc4, 0xfb, 0xfa, 0x6d, 0x54, 0x6a, 0x6b, 0x72, 0x3c, 0x7b, 0x75, 0x3c, 0xc9, 0xbf, 0x77, 0x4d, 0xaf, 0xab, 0xc6, 0xae, 0xd6, 0x6a, 0xeb, 0x68, 0xdf, 0xd0, 0x9b, 0x2a, 0xaa, 0xb4, 0x35,
0x1a, 0xf7, 0xbe, 0xd3, 0xf5, 0x4d, 0x86, 0x57, 0xa6, 0xbf, 0xad, 0xd5, 0xb5, 0x96, 0xde, 0x92, 0xbd, 0x71, 0x92, 0xb4, 0xe5, 0xd1, 0xb8, 0x78, 0xe5, 0x34, 0xee, 0xb8, 0xe0, 0xf7, 0xc0, 0xa5,
0x13, 0xab, 0xd1, 0xdf, 0xc6, 0x1e, 0xa6, 0x0e, 0xcd, 0x26, 0xc3, 0xcb, 0x2a, 0xd7, 0x1e, 0xff, 0x85, 0xd2, 0x68, 0x0d, 0xad, 0x2d, 0x4b, 0xb9, 0xed, 0xd1, 0xb8, 0x78, 0xf1, 0x34, 0x7e, 0xcd,
0xa9, 0xc4, 0x1e, 0xcd, 0x14, 0xe9, 0xf1, 0x4c, 0x91, 0x9e, 0xcc, 0x14, 0xe9, 0x8f, 0x99, 0x22, 0x73, 0x18, 0xbc, 0x0f, 0xae, 0x2e, 0x44, 0xbc, 0xa7, 0xdd, 0x46, 0x95, 0xb6, 0x2a, 0x27, 0x73,
0x7d, 0xf3, 0x54, 0x89, 0x3d, 0x79, 0xaa, 0xc4, 0x7e, 0x7f, 0xaa, 0xc4, 0xbe, 0x78, 0x37, 0xb2, 0x57, 0x46, 0xe3, 0xe2, 0x7b, 0xa7, 0x71, 0xef, 0x39, 0x5d, 0xdf, 0x64, 0x78, 0x61, 0xfa, 0xdb,
0x07, 0x15, 0x42, 0xfb, 0xf7, 0x16, 0xbf, 0x3a, 0x76, 0x71, 0x24, 0x7e, 0x79, 0xf8, 0xff, 0x4e, 0x6a, 0x43, 0x6d, 0x69, 0x2d, 0x39, 0xb5, 0x18, 0xfd, 0x6d, 0xec, 0x61, 0xea, 0xd0, 0x5c, 0x3a,
0x67, 0x9d, 0x7f, 0xd5, 0x3e, 0xf8, 0x37, 0x00, 0x00, 0xff, 0xff, 0x21, 0x8c, 0xa0, 0x70, 0x10, 0x3c, 0xac, 0xea, 0xee, 0xe3, 0x3f, 0xf3, 0x89, 0x47, 0xc7, 0x79, 0xe9, 0xf1, 0x71, 0x5e, 0x7a,
0x09, 0x00, 0x00, 0x72, 0x9c, 0x97, 0xfe, 0x38, 0xce, 0x4b, 0x5f, 0x3f, 0xcd, 0x27, 0x9e, 0x3c, 0xcd, 0x27, 0x7e,
0x7f, 0x9a, 0x4f, 0x7c, 0xfe, 0x6e, 0x6c, 0x8e, 0x6a, 0x84, 0xf6, 0xee, 0x45, 0xbf, 0x57, 0x76,
0x79, 0x28, 0x7e, 0xb3, 0xf8, 0x3f, 0x56, 0x67, 0x99, 0xbf, 0x8a, 0x1f, 0xfc, 0x13, 0x00, 0x00,
0xff, 0xff, 0x7a, 0x16, 0x8c, 0xd9, 0x84, 0x09, 0x00, 0x00,
} }
func (this *AccessTypeParam) Equal(that interface{}) bool { func (this *AccessTypeParam) Equal(that interface{}) bool {
@@ -617,6 +628,14 @@ func (this *AccessConfig) Equal(that interface{}) bool {
if this.Address != that1.Address { if this.Address != that1.Address {
return false return false
} }
if len(this.Addresses) != len(that1.Addresses) {
return false
}
for i := range this.Addresses {
if this.Addresses[i] != that1.Addresses[i] {
return false
}
}
return true return true
} }
@@ -860,6 +879,15 @@ func (m *AccessConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i _ = i
var l int var l int
_ = l _ = l
if len(m.Addresses) > 0 {
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
i -= len(m.Addresses[iNdEx])
copy(dAtA[i:], m.Addresses[iNdEx])
i = encodeVarintTypes(dAtA, i, uint64(len(m.Addresses[iNdEx])))
i--
dAtA[i] = 0x1a
}
}
if len(m.Address) > 0 { if len(m.Address) > 0 {
i -= len(m.Address) i -= len(m.Address)
copy(dAtA[i:], m.Address) copy(dAtA[i:], m.Address)
@@ -1199,6 +1227,12 @@ func (m *AccessConfig) Size() (n int) {
if l > 0 { if l > 0 {
n += 1 + l + sovTypes(uint64(l)) n += 1 + l + sovTypes(uint64(l))
} }
if len(m.Addresses) > 0 {
for _, s := range m.Addresses {
l = len(s)
n += 1 + l + sovTypes(uint64(l))
}
}
return n return n
} }
@@ -1484,6 +1518,38 @@ func (m *AccessConfig) Unmarshal(dAtA []byte) error {
} }
m.Address = string(dAtA[iNdEx:postIndex]) m.Address = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowTypes
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthTypes
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthTypes
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex]))
iNdEx = postIndex
default: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipTypes(dAtA[iNdEx:]) skippy, err := skipTypes(dAtA[iNdEx:])

View File

@@ -374,52 +374,42 @@ func TestVerifyAddressLen(t *testing.T) {
} }
func TestAccessConfigSubset(t *testing.T) { func TestAccessConfigSubset(t *testing.T) {
// read
// <, <= is subset of
// !< is not subset of
specs := map[string]struct { specs := map[string]struct {
check AccessConfig check AccessConfig
superSet AccessConfig superSet AccessConfig
isSubSet bool isSubSet bool
}{ }{
// nobody
"nobody <= nobody": { "nobody <= nobody": {
superSet: AccessConfig{Permission: AccessTypeNobody}, superSet: AccessConfig{Permission: AccessTypeNobody},
check: AccessConfig{Permission: AccessTypeNobody}, check: AccessConfig{Permission: AccessTypeNobody},
isSubSet: true, isSubSet: true,
}, },
"only > nobody": { "only !< nobody": {
superSet: AccessConfig{Permission: AccessTypeNobody}, superSet: AccessConfig{Permission: AccessTypeNobody},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "foobar"}, check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "foobar"},
isSubSet: false, isSubSet: false,
}, },
"everybody > nobody": { "anyOf !< nobody": {
superSet: AccessConfig{Permission: AccessTypeNobody},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"foobar"}},
isSubSet: false,
},
"everybody !< nobody ": {
superSet: AccessConfig{Permission: AccessTypeNobody}, superSet: AccessConfig{Permission: AccessTypeNobody},
check: AccessConfig{Permission: AccessTypeEverybody}, check: AccessConfig{Permission: AccessTypeEverybody},
isSubSet: false, isSubSet: false,
}, },
"unspecified > nobody": { "unspecified !< nobody": {
superSet: AccessConfig{Permission: AccessTypeNobody}, superSet: AccessConfig{Permission: AccessTypeNobody},
check: AccessConfig{Permission: AccessTypeUnspecified}, check: AccessConfig{Permission: AccessTypeUnspecified},
isSubSet: false, isSubSet: false,
}, },
"nobody <= everybody": { // only
superSet: AccessConfig{Permission: AccessTypeEverybody}, "nobody < only": {
check: AccessConfig{Permission: AccessTypeNobody},
isSubSet: true,
},
"only <= everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "foobar"},
isSubSet: true,
},
"everybody <= everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeEverybody},
isSubSet: true,
},
"unspecified > everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeUnspecified},
isSubSet: false,
},
"nobody <= only": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"}, superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeNobody}, check: AccessConfig{Permission: AccessTypeNobody},
isSubSet: true, isSubSet: true,
@@ -429,23 +419,141 @@ func TestAccessConfigSubset(t *testing.T) {
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"}, check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
isSubSet: true, isSubSet: true,
}, },
"only > only(other)": { "only !< only(other)": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"}, superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "other"}, check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "other"},
isSubSet: false, isSubSet: false,
}, },
"everybody > only": { "anyOf(same) <= only": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
isSubSet: true,
},
"anyOf(other) !< only": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"foobar"}},
isSubSet: false,
},
"anyOf(same, other) !< only": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "foobar"}},
isSubSet: false,
},
"everybody !< only": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"}, superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeEverybody}, check: AccessConfig{Permission: AccessTypeEverybody},
isSubSet: false, isSubSet: false,
}, },
"nobody > unspecified": { "unspecified !<= only": {
superSet: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
check: AccessConfig{Permission: AccessTypeUnspecified},
isSubSet: false,
},
// any of
"nobody < anyOf": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeNobody},
isSubSet: true,
},
"only(same) < anyOf(same)": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
isSubSet: true,
},
"only(same) < anyOf(same, other)": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "other"}},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "owner"},
isSubSet: true,
},
"only(other) !< anyOf": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "other"},
isSubSet: false,
},
"anyOf < anyOf": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
isSubSet: true,
},
"anyOf(multiple) < anyOf(multiple)": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "other"}},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "other"}},
isSubSet: true,
},
"anyOf(multiple, other) !< anyOf(multiple)": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "other", "foo"}},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "other", "bar"}},
isSubSet: false,
},
"anyOf(multiple) !< anyOf": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner", "other"}},
isSubSet: false,
},
"everybody !< anyOf": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeEverybody},
isSubSet: false,
},
"unspecified !< anyOf ": {
superSet: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"owner"}},
check: AccessConfig{Permission: AccessTypeUnspecified},
isSubSet: false,
},
// everybody
"nobody < everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeNobody},
isSubSet: true,
},
"only < everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "foobar"},
isSubSet: true,
},
"anyOf < everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"foobar"}},
isSubSet: true,
},
"everybody <= everybody": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeEverybody},
isSubSet: true,
},
"unspecified !< everybody ": {
superSet: AccessConfig{Permission: AccessTypeEverybody},
check: AccessConfig{Permission: AccessTypeUnspecified},
isSubSet: false,
},
// unspecified
"nobody !< unspecified": {
superSet: AccessConfig{Permission: AccessTypeUnspecified}, superSet: AccessConfig{Permission: AccessTypeUnspecified},
check: AccessConfig{Permission: AccessTypeNobody}, check: AccessConfig{Permission: AccessTypeNobody},
isSubSet: false, isSubSet: false,
}, },
"only !< unspecified": {
superSet: AccessConfig{Permission: AccessTypeUnspecified},
check: AccessConfig{Permission: AccessTypeOnlyAddress, Address: "foobar"},
isSubSet: false,
},
"anyOf !< unspecified": {
superSet: AccessConfig{Permission: AccessTypeUnspecified},
check: AccessConfig{Permission: AccessTypeAnyOfAddresses, Addresses: []string{"foobar"}},
isSubSet: false,
},
"everybody !< unspecified": {
superSet: AccessConfig{Permission: AccessTypeUnspecified},
check: AccessConfig{Permission: AccessTypeEverybody},
isSubSet: false,
},
"unspecified !< unspecified ": {
superSet: AccessConfig{Permission: AccessTypeUnspecified},
check: AccessConfig{Permission: AccessTypeUnspecified},
isSubSet: false,
},
} }
for name, spec := range specs { for name, spec := range specs {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
subset := spec.check.IsSubset(spec.superSet) subset := spec.check.IsSubset(spec.superSet)
@@ -460,64 +568,134 @@ func TestAccessTypeSubset(t *testing.T) {
superSet AccessType superSet AccessType
isSubSet bool isSubSet bool
}{ }{
// nobody
"nobody <= nobody": { "nobody <= nobody": {
superSet: AccessTypeNobody, superSet: AccessTypeNobody,
check: AccessTypeNobody, check: AccessTypeNobody,
isSubSet: true, isSubSet: true,
}, },
"only > nobody": { "only !< nobody": {
superSet: AccessTypeNobody, superSet: AccessTypeNobody,
check: AccessTypeOnlyAddress, check: AccessTypeOnlyAddress,
isSubSet: false, isSubSet: false,
}, },
"everybody > nobody": { "any !< nobody": {
superSet: AccessTypeNobody,
check: AccessTypeAnyOfAddresses,
isSubSet: false,
},
"everybody !< nobody": {
superSet: AccessTypeNobody, superSet: AccessTypeNobody,
check: AccessTypeEverybody, check: AccessTypeEverybody,
isSubSet: false, isSubSet: false,
}, },
"unspecified > nobody": { "unspecified !< nobody": {
superSet: AccessTypeNobody, superSet: AccessTypeNobody,
check: AccessTypeUnspecified, check: AccessTypeUnspecified,
isSubSet: false, isSubSet: false,
}, },
"nobody <= everybody": { // only
"nobody < only": {
superSet: AccessTypeOnlyAddress,
check: AccessTypeNobody,
isSubSet: true,
},
"only <= only": {
superSet: AccessTypeOnlyAddress,
check: AccessTypeOnlyAddress,
isSubSet: true,
},
"anyOf !< only": {
superSet: AccessTypeOnlyAddress,
check: AccessTypeAnyOfAddresses,
isSubSet: true,
},
"everybody !< only": {
superSet: AccessTypeOnlyAddress,
check: AccessTypeEverybody,
isSubSet: false,
},
"unspecified !< only": {
superSet: AccessTypeOnlyAddress,
check: AccessTypeUnspecified,
isSubSet: false,
},
// any of
"nobody < anyOf": {
superSet: AccessTypeAnyOfAddresses,
check: AccessTypeNobody,
isSubSet: true,
},
"only <= anyOf": {
superSet: AccessTypeAnyOfAddresses,
check: AccessTypeOnlyAddress,
isSubSet: true,
},
"anyOf <= anyOf": {
superSet: AccessTypeAnyOfAddresses,
check: AccessTypeAnyOfAddresses,
isSubSet: true,
},
"everybody !< anyOf": {
superSet: AccessTypeAnyOfAddresses,
check: AccessTypeEverybody,
isSubSet: false,
},
"unspecified !< anyOf": {
superSet: AccessTypeAnyOfAddresses,
check: AccessTypeUnspecified,
isSubSet: false,
},
// everybody
"nobody < everybody": {
superSet: AccessTypeEverybody, superSet: AccessTypeEverybody,
check: AccessTypeNobody, check: AccessTypeNobody,
isSubSet: true, isSubSet: true,
}, },
"only <= everybody": { "only < everybody": {
superSet: AccessTypeEverybody, superSet: AccessTypeEverybody,
check: AccessTypeOnlyAddress, check: AccessTypeOnlyAddress,
isSubSet: true, isSubSet: true,
}, },
"anyOf < everybody": {
superSet: AccessTypeEverybody,
check: AccessTypeAnyOfAddresses,
isSubSet: true,
},
"everybody <= everybody": { "everybody <= everybody": {
superSet: AccessTypeEverybody, superSet: AccessTypeEverybody,
check: AccessTypeEverybody, check: AccessTypeEverybody,
isSubSet: true, isSubSet: true,
}, },
"unspecified > everybody": { "unspecified !< everybody": {
superSet: AccessTypeEverybody, superSet: AccessTypeEverybody,
check: AccessTypeUnspecified, check: AccessTypeUnspecified,
isSubSet: false, isSubSet: false,
}, },
"nobody <= only": { // unspecified
superSet: AccessTypeOnlyAddress, "nobody !< unspecified": {
superSet: AccessTypeUnspecified,
check: AccessTypeNobody, check: AccessTypeNobody,
isSubSet: true, isSubSet: false,
}, },
"only <= only(same)": { "only !< unspecified": {
superSet: AccessTypeOnlyAddress, superSet: AccessTypeUnspecified,
check: AccessTypeOnlyAddress, check: AccessTypeOnlyAddress,
isSubSet: true, isSubSet: false,
}, },
"everybody > only": { "anyOf !< unspecified": {
superSet: AccessTypeOnlyAddress, superSet: AccessTypeUnspecified,
check: AccessTypeAnyOfAddresses,
isSubSet: false,
},
"everybody !< unspecified": {
superSet: AccessTypeUnspecified,
check: AccessTypeEverybody, check: AccessTypeEverybody,
isSubSet: false, isSubSet: false,
}, },
"nobody > unspecified": { "unspecified !< unspecified": {
superSet: AccessTypeUnspecified, superSet: AccessTypeUnspecified,
check: AccessTypeNobody, check: AccessTypeUnspecified,
isSubSet: false, isSubSet: false,
}, },
} }