super-graph/serv/config.go

347 lines
7.2 KiB
Go
Raw Normal View History

2019-05-13 01:27:26 +02:00
package serv
2019-06-04 16:54:51 +02:00
import (
2019-10-31 06:14:51 +01:00
"fmt"
"os"
"regexp"
"strings"
"time"
"unicode"
2019-10-31 06:14:51 +01:00
"github.com/gobuffalo/flect"
"github.com/spf13/viper"
2019-06-04 16:54:51 +02:00
)
2019-05-13 01:27:26 +02:00
type config struct {
*viper.Viper
2019-09-08 20:56:32 +02:00
AppName string `mapstructure:"app_name"`
Env string
HostPort string `mapstructure:"host_port"`
Host string
Port string
WebUI bool `mapstructure:"web_ui"`
LogLevel string `mapstructure:"log_level"`
EnableTracing bool `mapstructure:"enable_tracing"`
UseAllowList bool `mapstructure:"use_allow_list"`
2019-11-07 08:37:24 +01:00
Production bool
2019-09-08 20:56:32 +02:00
WatchAndReload bool `mapstructure:"reload_on_config_change"`
AuthFailBlock bool `mapstructure:"auth_fail_block"`
2019-09-20 06:19:11 +02:00
SeedFile string `mapstructure:"seed_file"`
2019-09-26 06:35:31 +02:00
MigrationsPath string `mapstructure:"migrations_path"`
Inflections map[string]string
2019-05-13 01:27:26 +02:00
Auth struct {
Type string
Cookie string
CredsInHeader bool `mapstructure:"creds_in_header"`
2019-05-13 01:27:26 +02:00
Rails struct {
Version string
SecretKeyBase string `mapstructure:"secret_key_base"`
URL string
Password string
MaxIdle int `mapstructure:"max_idle"`
MaxActive int `mapstructure:"max_active"`
Salt string
SignSalt string `mapstructure:"sign_salt"`
AuthSalt string `mapstructure:"auth_salt"`
}
JWT struct {
Provider string
Secret string
PubKeyFile string `mapstructure:"public_key_file"`
PubKeyType string `mapstructure:"public_key_type"`
}
}
DB struct {
Type string
Host string
Port uint16
DBName string
User string
Password string
Schema string
PoolSize int32 `mapstructure:"pool_size"`
MaxRetries int `mapstructure:"max_retries"`
SetUserID bool `mapstructure:"set_user_id"`
PingTimeout time.Duration `mapstructure:"ping_timeout"`
2019-05-13 01:27:26 +02:00
2019-11-25 08:22:33 +01:00
Vars map[string]string `mapstructure:"variables"`
Blocklist []string
2019-05-13 01:27:26 +02:00
Tables []configTable
} `mapstructure:"database"`
Tables []configTable
RolesQuery string `mapstructure:"roles_query"`
Roles []configRole
2019-11-25 08:22:33 +01:00
roles map[string]*configRole
2019-05-13 01:27:26 +02:00
}
type configColumn struct {
Name string
ForeignKey string `mapstructure:"related_to"`
}
2019-05-13 01:27:26 +02:00
type configTable struct {
2019-10-14 08:51:36 +02:00
Name string
Table string
Blocklist []string
Remotes []configRemote
Columns []configColumn
2019-05-13 01:27:26 +02:00
}
type configRemote struct {
Name string
ID string
Path string
URL string
Debug bool
2019-05-13 01:27:26 +02:00
PassHeaders []string `mapstructure:"pass_headers"`
SetHeaders []struct {
Name string
Value string
} `mapstructure:"set_headers"`
}
type configQuery struct {
Limit int
Filters []string
Columns []string
DisableFunctions bool `mapstructure:"disable_functions"`
Block bool
}
2019-10-14 08:51:36 +02:00
type configInsert struct {
Filters []string
Columns []string
Presets map[string]string
Block bool
}
2019-10-14 08:51:36 +02:00
type configUpdate struct {
Filters []string
Columns []string
Presets map[string]string
Block bool
}
2019-10-14 08:51:36 +02:00
type configDelete struct {
Filters []string
Columns []string
Block bool
}
2019-10-31 06:14:51 +01:00
type configRoleTable struct {
Name string
Query configQuery
Insert configInsert
Update configUpdate
Delete configDelete
2019-10-14 08:51:36 +02:00
}
2019-10-31 06:14:51 +01:00
type configRole struct {
2019-11-07 08:37:24 +01:00
Name string
Match string
Tables []configRoleTable
tablesMap map[string]*configRoleTable
2019-10-31 06:14:51 +01:00
}
func newConfig(name string) *viper.Viper {
vi := viper.New()
vi.SetEnvPrefix("SG")
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
vi.AutomaticEnv()
vi.SetConfigName(name)
vi.AddConfigPath(confPath)
vi.AddConfigPath("./config")
2019-10-31 06:14:51 +01:00
if dir, _ := os.Getwd(); strings.HasSuffix(dir, "/serv") {
vi.AddConfigPath("../config")
}
vi.SetDefault("host_port", "0.0.0.0:8080")
vi.SetDefault("web_ui", false)
vi.SetDefault("enable_tracing", false)
vi.SetDefault("auth_fail_block", "always")
vi.SetDefault("seed_file", "seed.js")
vi.SetDefault("database.type", "postgres")
vi.SetDefault("database.host", "localhost")
vi.SetDefault("database.port", 5432)
vi.SetDefault("database.user", "postgres")
vi.SetDefault("database.schema", "public")
vi.SetDefault("env", "development")
vi.BindEnv("env", "GO_ENV") //nolint: errcheck
vi.BindEnv("HOST", "HOST") //nolint: errcheck
vi.BindEnv("PORT", "PORT") //nolint: errcheck
vi.SetDefault("auth.rails.max_idle", 80)
vi.SetDefault("auth.rails.max_active", 12000)
return vi
}
2019-10-31 06:14:51 +01:00
func (c *config) Init(vi *viper.Viper) error {
if err := vi.Unmarshal(c); err != nil {
return fmt.Errorf("unable to decode config, %v", err)
}
c.Viper = vi
if len(c.Tables) == 0 {
c.Tables = c.DB.Tables
}
2019-11-07 08:37:24 +01:00
if c.UseAllowList {
c.Production = true
}
2019-10-31 06:14:51 +01:00
for k, v := range c.Inflections {
flect.AddPlural(k, v)
}
for i := range c.Tables {
t := c.Tables[i]
t.Name = flect.Pluralize(strings.ToLower(t.Name))
t.Table = flect.Pluralize(strings.ToLower(t.Table))
}
for i := range c.Roles {
r := c.Roles[i]
r.Name = strings.ToLower(r.Name)
}
for k, v := range c.DB.Vars {
c.DB.Vars[k] = sanitize(v)
}
c.RolesQuery = sanitize(c.RolesQuery)
2019-11-25 08:22:33 +01:00
c.roles = make(map[string]*configRole)
2019-10-31 06:14:51 +01:00
for i := range c.Roles {
2019-11-07 08:37:24 +01:00
role := &c.Roles[i]
2019-10-31 06:14:51 +01:00
2019-11-25 08:22:33 +01:00
if _, ok := c.roles[role.Name]; ok {
errlog.Fatal().Msgf("duplicate role '%s' found", role.Name)
2019-10-31 06:14:51 +01:00
}
2019-11-25 08:22:33 +01:00
role.Name = strings.ToLower(role.Name)
2019-10-31 06:14:51 +01:00
role.Match = sanitize(role.Match)
2019-11-07 08:37:24 +01:00
role.tablesMap = make(map[string]*configRoleTable)
for n, table := range role.Tables {
role.tablesMap[table.Name] = &role.Tables[n]
}
2019-11-25 08:22:33 +01:00
c.roles[role.Name] = role
2019-10-31 06:14:51 +01:00
}
2019-11-25 08:22:33 +01:00
if _, ok := c.roles["user"]; !ok {
u := configRole{Name: "user"}
c.Roles = append(c.Roles, u)
c.roles["user"] = &u
2019-10-31 06:14:51 +01:00
}
2019-11-25 08:22:33 +01:00
if _, ok := c.roles["anon"]; !ok {
logger.Warn().Msg("unauthenticated requests will be blocked. no role 'anon' defined")
c.AuthFailBlock = true
2019-10-31 06:14:51 +01:00
}
c.validate()
return nil
}
func (c *config) validate() {
rm := make(map[string]struct{})
for i := range c.Roles {
2019-10-31 06:14:51 +01:00
name := c.Roles[i].Name
if _, ok := rm[name]; ok {
2019-11-25 08:22:33 +01:00
errlog.Fatal().Msgf("duplicate config for role '%s'", c.Roles[i].Name)
}
rm[name] = struct{}{}
}
tm := make(map[string]struct{})
for i := range c.Tables {
2019-10-31 06:14:51 +01:00
name := c.Tables[i].Name
if _, ok := tm[name]; ok {
2019-11-25 08:22:33 +01:00
errlog.Fatal().Msgf("duplicate config for table '%s'", c.Tables[i].Name)
}
tm[name] = struct{}{}
}
if len(c.RolesQuery) == 0 {
logger.Warn().Msgf("no 'roles_query' defined.")
}
}
func (c *config) getAliasMap() map[string][]string {
m := make(map[string][]string, len(c.Tables))
2019-05-13 01:27:26 +02:00
for i := range c.Tables {
t := c.Tables[i]
2019-05-13 01:27:26 +02:00
if len(t.Table) == 0 {
continue
}
2019-10-31 06:14:51 +01:00
m[t.Table] = append(m[t.Table], t.Name)
2019-05-13 01:27:26 +02:00
}
return m
}
func (c *config) isABCLEnabled() bool {
if len(c.RolesQuery) == 0 {
return false
}
switch len(c.Roles) {
case 0, 1:
return false
case 2:
_, ok1 := c.roles["anon"]
_, ok2 := c.roles["user"]
return !(ok1 && ok2)
}
return true
}
func (c *config) isAnonRoleDefined() bool {
_, ok := c.roles["anon"]
return ok
}
var varRe1 = regexp.MustCompile(`(?mi)\$([a-zA-Z0-9_.]+)`)
var varRe2 = regexp.MustCompile(`\{\{([a-zA-Z0-9_.]+)\}\}`)
func sanitize(s string) string {
s0 := varRe1.ReplaceAllString(s, `{{$1}}`)
s1 := strings.Map(func(r rune) rune {
if unicode.IsSpace(r) {
return ' '
}
return r
}, s0)
return varRe2.ReplaceAllStringFunc(s1, func(m string) string {
return strings.ToLower(m)
})
}