add validation on config (#3350)
This commit is contained in:
parent
8dd8b1ae45
commit
bb6139aef1
|
@ -239,6 +239,10 @@ func setupSendingClient(backend *api.GethStatusBackend, cs, configJSON string) (
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = validateAndVerifyPassword(conf, conf.SenderConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.SenderConfig.DB = backend.GetMultiaccountDB()
|
||||
|
||||
|
@ -437,6 +441,10 @@ func setupReceivingClient(backend *api.GethStatusBackend, cs, configJSON string)
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = validateAndVerifyNodeConfig(conf, conf.ReceiverConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf.ReceiverConfig.DB = backend.GetMultiaccountDB()
|
||||
|
||||
|
|
|
@ -1,13 +1,51 @@
|
|||
package pairing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
|
||||
"github.com/status-im/status-go/account/generator"
|
||||
"github.com/status-im/status-go/eth-node/keystore"
|
||||
)
|
||||
|
||||
func newValidate() (*validator.Validate, error) {
|
||||
var validate = validator.New()
|
||||
var keyUIDPattern = regexp.MustCompile(`^0x[0-9a-fA-F]{64}$`)
|
||||
if err := validate.RegisterValidation("keyuid", func(fl validator.FieldLevel) bool {
|
||||
return keyUIDPattern.MatchString(fl.Field().String())
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validate.RegisterValidation("keystorepath", func(fl validator.FieldLevel) bool {
|
||||
keyUIDField := fl.Parent()
|
||||
if keyUIDField.Kind() == reflect.Ptr {
|
||||
keyUIDField = keyUIDField.Elem()
|
||||
}
|
||||
|
||||
keyUID := keyUIDField.FieldByName("KeyUID").String()
|
||||
return strings.HasSuffix(fl.Field().String(), keyUID)
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := validate.RegisterValidation("not_end_keyuid", func(fl validator.FieldLevel) bool {
|
||||
keystorePath := fl.Field().String()
|
||||
return len(keystorePath) <= 66 || !keyUIDPattern.MatchString(keystorePath[len(keystorePath)-66:])
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return validate, nil
|
||||
}
|
||||
|
||||
func validateKeys(keys map[string][]byte, password string) error {
|
||||
for _, key := range keys {
|
||||
k, err := keystore.DecryptKey(key, password)
|
||||
|
@ -24,6 +62,83 @@ func validateKeys(keys map[string][]byte, password string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func loadKeys(keys map[string][]byte, keyStorePath string) error {
|
||||
fileWalker := func(path string, dirEntry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dirEntry.IsDir() || filepath.Dir(path) != keyStorePath {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawKeyFile, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid account key file: %v", err)
|
||||
}
|
||||
|
||||
keys[dirEntry.Name()] = rawKeyFile
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.WalkDir(keyStorePath, fileWalker)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot traverse key store folder: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validate(s interface{}) error {
|
||||
v, err := newValidate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return v.Struct(s)
|
||||
}
|
||||
|
||||
func validateAndVerifyPassword(s interface{}, senderConfig *SenderConfig) error {
|
||||
err := validate(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keys := make(map[string][]byte)
|
||||
err = loadKeys(keys, senderConfig.KeystorePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateKeys(keys, senderConfig.Password)
|
||||
}
|
||||
|
||||
func validateAndVerifyNodeConfig(s interface{}, receiverConfig *ReceiverConfig) error {
|
||||
if receiverConfig.NodeConfig != nil {
|
||||
// we should update with defaults before validation
|
||||
err := receiverConfig.NodeConfig.UpdateWithDefaults()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := validate(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if receiverConfig.NodeConfig == nil {
|
||||
return fmt.Errorf("node config is required for receiver config")
|
||||
}
|
||||
|
||||
if receiverConfig.NodeConfig.RootDataDir == "" {
|
||||
return fmt.Errorf("root data dir is required for node config")
|
||||
}
|
||||
|
||||
return receiverConfig.NodeConfig.Validate()
|
||||
}
|
||||
|
||||
func emptyDir(dir string) error {
|
||||
// Open the directory
|
||||
d, err := os.Open(dir)
|
||||
|
|
|
@ -10,26 +10,29 @@ import (
|
|||
|
||||
type SenderConfig struct {
|
||||
// SenderConfig.KeystorePath must end with keyUID
|
||||
KeystorePath string `json:"keystorePath"`
|
||||
KeystorePath string `json:"keystorePath" validate:"required,keystorepath"`
|
||||
// DeviceType SendPairInstallation need this information
|
||||
DeviceType string `json:"deviceType"`
|
||||
DeviceType string `json:"deviceType" validate:"required"`
|
||||
|
||||
KeyUID string `json:"keyUID"`
|
||||
Password string `json:"password"`
|
||||
KeyUID string `json:"keyUID" validate:"required,keyuid"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
|
||||
DB *multiaccounts.Database `json:"-"`
|
||||
}
|
||||
|
||||
type ReceiverConfig struct {
|
||||
// nodeConfig is required, but we'll validate it separately
|
||||
NodeConfig *params.NodeConfig `json:"nodeConfig"`
|
||||
|
||||
// ReceiverConfig.KeystorePath must not end with keyUID (because keyUID is not known yet)
|
||||
KeystorePath string `json:"keystorePath"`
|
||||
KeystorePath string `json:"keystorePath" validate:"required,not_end_keyuid"`
|
||||
|
||||
// DeviceType SendPairInstallation need this information
|
||||
DeviceType string `json:"deviceType"`
|
||||
KDFIterations int `json:"kdfIterations"`
|
||||
DeviceType string `json:"deviceType" validate:"required"`
|
||||
KDFIterations int `json:"kdfIterations" validate:"gte=0"`
|
||||
|
||||
// SettingCurrentNetwork corresponding to field current_network from table settings, so that we can override current network from sender
|
||||
SettingCurrentNetwork string `json:"settingCurrentNetwork"`
|
||||
SettingCurrentNetwork string `json:"settingCurrentNetwork" validate:"required"`
|
||||
|
||||
DB *multiaccounts.Database `json:"-"`
|
||||
LoggedInKeyUID string `json:"-"`
|
||||
|
@ -37,7 +40,7 @@ type ReceiverConfig struct {
|
|||
|
||||
type ServerConfig struct {
|
||||
// Timeout the number of milliseconds after which the pairing server will automatically terminate
|
||||
Timeout uint `json:"timeout"`
|
||||
Timeout uint `json:"timeout" validate:"omitempty,gte=0"`
|
||||
|
||||
// Connection fields, not json (un)marshalled
|
||||
// Required for the server, but MUST NOT come from client
|
||||
|
@ -51,23 +54,23 @@ type ServerConfig struct {
|
|||
type ClientConfig struct{}
|
||||
|
||||
type SenderServerConfig struct {
|
||||
SenderConfig *SenderConfig `json:"senderConfig"`
|
||||
ServerConfig *ServerConfig `json:"serverConfig"`
|
||||
SenderConfig *SenderConfig `json:"senderConfig" validate:"required"`
|
||||
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
type SenderClientConfig struct {
|
||||
SenderConfig *SenderConfig `json:"senderConfig"`
|
||||
SenderConfig *SenderConfig `json:"senderConfig" validate:"required"`
|
||||
ClientConfig *ClientConfig `json:"clientConfig"`
|
||||
}
|
||||
|
||||
type ReceiverClientConfig struct {
|
||||
ReceiverConfig *ReceiverConfig `json:"receiverConfig"`
|
||||
ReceiverConfig *ReceiverConfig `json:"receiverConfig" validate:"required"`
|
||||
ClientConfig *ClientConfig `json:"clientConfig"`
|
||||
}
|
||||
|
||||
type ReceiverServerConfig struct {
|
||||
ReceiverConfig *ReceiverConfig `json:"receiverConfig"`
|
||||
ServerConfig *ServerConfig `json:"serverConfig"`
|
||||
ReceiverConfig *ReceiverConfig `json:"receiverConfig" validate:"required"`
|
||||
ServerConfig *ServerConfig `json:"serverConfig" validate:"omitempty,dive"`
|
||||
}
|
||||
|
||||
func NewSenderServerConfig() *SenderServerConfig {
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package pairing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConfigTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ConfigTestSuite))
|
||||
}
|
||||
|
||||
type ConfigTestSuite struct {
|
||||
suite.Suite
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
func (s *ConfigTestSuite) SetupTest() {
|
||||
var err error
|
||||
s.validate, err = newValidate()
|
||||
require.NoError(s.T(), err, "newValidate should not return error")
|
||||
}
|
||||
|
||||
func (s *ConfigTestSuite) TestValidationKeystorePath() {
|
||||
s.T().Run("Valid keystore path with keyUID", func(t *testing.T) {
|
||||
sc := &SenderConfig{
|
||||
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
DeviceType: "phone",
|
||||
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
assert.NoError(t, s.validate.Struct(sc), "SenderConfig validation should pass")
|
||||
})
|
||||
|
||||
s.T().Run("Invalid keystore path without keyUID", func(t *testing.T) {
|
||||
sc := &SenderConfig{
|
||||
KeystorePath: "some/path/",
|
||||
DeviceType: "phone",
|
||||
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
assert.Error(t, s.validate.Struct(sc), "SenderConfig validation should fail")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ConfigTestSuite) TestValidationKeyUID() {
|
||||
s.T().Run("Valid keyUID", func(t *testing.T) {
|
||||
sc := &SenderConfig{
|
||||
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
DeviceType: "phone",
|
||||
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
assert.NoError(t, s.validate.Struct(sc), "SenderConfig validation should pass")
|
||||
})
|
||||
|
||||
s.T().Run("Invalid keyUID", func(t *testing.T) {
|
||||
sc := &SenderConfig{
|
||||
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
DeviceType: "phone",
|
||||
KeyUID: "0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54",
|
||||
Password: "password",
|
||||
}
|
||||
|
||||
assert.Error(t, s.validate.Struct(sc), "SenderConfig validation should fail")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ConfigTestSuite) TestValidationNotEndKeyUID() {
|
||||
nodeConfig, err := defaultNodeConfig(uuid.New().String(), "")
|
||||
nodeConfig.RootDataDir = "/tmp"
|
||||
require.NoError(s.T(), err, "defaultNodeConfig should not return error")
|
||||
s.T().Run("Valid keystore path without keyUID", func(t *testing.T) {
|
||||
r := &ReceiverConfig{
|
||||
NodeConfig: nodeConfig,
|
||||
KeystorePath: "some/path/",
|
||||
DeviceType: "phone",
|
||||
KDFIterations: 1,
|
||||
SettingCurrentNetwork: "mainnet",
|
||||
}
|
||||
|
||||
assert.NoError(t, validateAndVerifyNodeConfig(r, r), "ReceiverConfig validation should pass")
|
||||
})
|
||||
|
||||
s.T().Run("Invalid keystore path with keyUID", func(t *testing.T) {
|
||||
r := &ReceiverConfig{
|
||||
NodeConfig: nodeConfig,
|
||||
KeystorePath: "some/path/0x130cc0ebdaecd220c1d6dea0ef01d575ef5364506785745049eb98ddf49cb54e",
|
||||
DeviceType: "phone",
|
||||
KDFIterations: 1,
|
||||
SettingCurrentNetwork: "mainnet",
|
||||
}
|
||||
assert.Error(t, validateAndVerifyNodeConfig(r, r), "ReceiverConfig validation should fail")
|
||||
})
|
||||
}
|
|
@ -1,11 +1,6 @@
|
|||
package pairing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
|
||||
|
@ -126,7 +121,8 @@ func NewAccountPayloadLoader(p *AccountPayload, config *SenderConfig) (*AccountP
|
|||
}
|
||||
|
||||
func (apl *AccountPayloadLoader) Load() error {
|
||||
err := apl.loadKeys(apl.keystorePath)
|
||||
apl.keys = make(map[string][]byte)
|
||||
err := loadKeys(apl.keys, apl.keystorePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -144,36 +140,6 @@ func (apl *AccountPayloadLoader) Load() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (apl *AccountPayloadLoader) loadKeys(keyStorePath string) error {
|
||||
apl.keys = make(map[string][]byte)
|
||||
|
||||
fileWalker := func(path string, dirEntry fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if dirEntry.IsDir() || filepath.Dir(path) != keyStorePath {
|
||||
return nil
|
||||
}
|
||||
|
||||
rawKeyFile, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid account key file: %v", err)
|
||||
}
|
||||
|
||||
apl.keys[dirEntry.Name()] = rawKeyFile
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err := filepath.WalkDir(keyStorePath, fileWalker)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot traverse key store folder: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| RawMessagePayload
|
||||
|
|
|
@ -172,6 +172,10 @@ func StartUpSenderServer(backend *api.GethStatusBackend, configJSON string) (str
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = validateAndVerifyPassword(conf, conf.SenderConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ps, err := MakeFullSenderServer(backend, conf)
|
||||
if err != nil {
|
||||
|
@ -268,6 +272,10 @@ func StartUpReceiverServer(backend *api.GethStatusBackend, configJSON string) (s
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = validateAndVerifyNodeConfig(conf, conf.ReceiverConfig)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ps, err := MakeFullReceiverServer(backend, conf)
|
||||
if err != nil {
|
||||
|
|
|
@ -363,13 +363,9 @@ func defaultSettings(generatedAccountInfo generator.GeneratedAccountInfo, derive
|
|||
visibleTokenJSONRaw := json.RawMessage(visibleTokensJSON)
|
||||
settings.WalletVisibleTokens = &visibleTokenJSONRaw
|
||||
|
||||
networks := make([]map[string]string, 0)
|
||||
networksJSON, err := json.Marshal(networks)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
networkRawMessage := json.RawMessage(networksJSON)
|
||||
settings.Networks = &networkRawMessage
|
||||
networks := `[{"id":"goerli_rpc","chain-explorer-link":"https://goerli.etherscan.io/address/","name":"Goerli with upstream RPC","config":{"NetworkId":5,"DataDir":"/ethereum/goerli_rpc","UpstreamConfig":{"Enabled":true,"URL":"https://goerli-archival.gateway.pokt.network/v1/lb/3ef2018191814b7e1009b8d9"}}},{"id":"mainnet_rpc","chain-explorer-link":"https://etherscan.io/address/","name":"Mainnet with upstream RPC","config":{"NetworkId":1,"DataDir":"/ethereum/mainnet_rpc","UpstreamConfig":{"Enabled":true,"URL":"https://eth-archival.gateway.pokt.network/v1/lb/3ef2018191814b7e1009b8d9"}}}]`
|
||||
var networksRawMessage json.RawMessage = []byte(networks)
|
||||
settings.Networks = &networksRawMessage
|
||||
settings.CurrentNetwork = currentNetwork
|
||||
|
||||
return settings, nil
|
||||
|
@ -407,7 +403,7 @@ func defaultNodeConfig(installationID, keyUID string) (*params.NodeConfig, error
|
|||
}
|
||||
|
||||
nodeConfig.ShhextConfig = params.ShhextConfig{
|
||||
BackupDisabledDataDir: "",
|
||||
BackupDisabledDataDir: nodeConfig.DataDir,
|
||||
InstallationID: installationID,
|
||||
MaxMessageDeliveryAttempts: 6,
|
||||
MailServerConfirmations: true,
|
||||
|
|
Loading…
Reference in New Issue