256 lines
6.3 KiB
Go
256 lines
6.3 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Core struct contains core specific config value
|
|
type Config struct {
|
|
// SecretKey is used to encrypt opaque values such as
|
|
// the cursor. Auto-generated if not set
|
|
SecretKey string `mapstructure:"secret_key"`
|
|
|
|
// UseAllowList (aka production mode) when set to true ensures
|
|
// only queries lists in the allow.list file can be used. All
|
|
// queries are pre-prepared so no compiling happens and things are
|
|
// very fast.
|
|
UseAllowList bool `mapstructure:"use_allow_list"`
|
|
|
|
// AllowListFile if the path to allow list file if not set the
|
|
// path is assumed to tbe the same as the config path (allow.list)
|
|
AllowListFile string `mapstructure:"allow_list_file"`
|
|
|
|
// SetUserID forces the database session variable `user.id` to
|
|
// be set to the user id. This variables can be used by triggers
|
|
// or other database functions
|
|
SetUserID bool `mapstructure:"set_user_id"`
|
|
|
|
// DefaultBlock ensures that in anonymous mode (role 'anon') all tables
|
|
// are blocked from queries and mutations. To open access to tables in
|
|
// anonymous mode they have to be added to the 'anon' role config.
|
|
DefaultBlock bool `mapstructure:"default_block"`
|
|
|
|
// Vars is a map of hardcoded variables that can be leveraged in your
|
|
// queries (eg variable admin_id will be $admin_id in the query)
|
|
Vars map[string]string `mapstructure:"variables"`
|
|
|
|
// Blocklist is a list of tables and columns that should be filtered
|
|
// out from any and all queries
|
|
Blocklist []string
|
|
|
|
// Tables contains all table specific configuration such as aliased tables
|
|
// creating relationships between tables, etc
|
|
Tables []Table
|
|
|
|
// RolesQuery if set enabled attributed based access control. This query
|
|
// is use to fetch the user attributes that then dynamically define the users
|
|
// role.
|
|
RolesQuery string `mapstructure:"roles_query"`
|
|
|
|
// Roles contains all the configuration for all the roles you want to support
|
|
// `user` and `anon` are two default roles. User role is for when a user ID is
|
|
// available and Anon when it's not.
|
|
//
|
|
// If you're using the RolesQuery config to enable atribute based acess control then
|
|
// you can add more custom roles.
|
|
Roles []Role
|
|
|
|
// Inflections is to add additionally singular to plural mappings
|
|
// to the engine (eg. sheep: sheep)
|
|
Inflections map[string]string `mapstructure:"inflections"`
|
|
|
|
// Database schema name. Defaults to 'public'
|
|
DBSchema string `mapstructure:"db_schema"`
|
|
}
|
|
|
|
// Table struct defines a database table
|
|
type Table struct {
|
|
Name string
|
|
Table string
|
|
Blocklist []string
|
|
Remotes []Remote
|
|
Columns []Column
|
|
}
|
|
|
|
// Column struct defines a database column
|
|
type Column struct {
|
|
Name string
|
|
Type string
|
|
ForeignKey string `mapstructure:"related_to"`
|
|
}
|
|
|
|
// Remote struct defines a remote API endpoint
|
|
type Remote struct {
|
|
Name string
|
|
ID string
|
|
Path string
|
|
URL string
|
|
Debug bool
|
|
PassHeaders []string `mapstructure:"pass_headers"`
|
|
SetHeaders []struct {
|
|
Name string
|
|
Value string
|
|
} `mapstructure:"set_headers"`
|
|
}
|
|
|
|
// Role struct contains role specific access control values for for all database tables
|
|
type Role struct {
|
|
Name string
|
|
Match string
|
|
Tables []RoleTable
|
|
tm map[string]*RoleTable
|
|
}
|
|
|
|
// RoleTable struct contains role specific access control values for a database table
|
|
type RoleTable struct {
|
|
Name string
|
|
ReadOnly bool `mapstructure:"read_only"`
|
|
|
|
Query *Query
|
|
Insert *Insert
|
|
Update *Update
|
|
Delete *Delete
|
|
}
|
|
|
|
// Query struct contains access control values for query operations
|
|
type Query struct {
|
|
Limit int
|
|
Filters []string
|
|
Columns []string
|
|
DisableFunctions bool `mapstructure:"disable_functions"`
|
|
Block bool
|
|
}
|
|
|
|
// Insert struct contains access control values for insert operations
|
|
type Insert struct {
|
|
Filters []string
|
|
Columns []string
|
|
Presets map[string]string
|
|
Block bool
|
|
}
|
|
|
|
// Insert struct contains access control values for update operations
|
|
type Update struct {
|
|
Filters []string
|
|
Columns []string
|
|
Presets map[string]string
|
|
Block bool
|
|
}
|
|
|
|
// Delete struct contains access control values for delete operations
|
|
type Delete struct {
|
|
Filters []string
|
|
Columns []string
|
|
Block bool
|
|
}
|
|
|
|
// AddRoleTable function is a helper function to make it easy to add per-table
|
|
// row-level config
|
|
func (c *Config) AddRoleTable(role string, table string, conf interface{}) error {
|
|
var r *Role
|
|
|
|
for i := range c.Roles {
|
|
if strings.EqualFold(c.Roles[i].Name, role) {
|
|
r = &c.Roles[i]
|
|
break
|
|
}
|
|
}
|
|
if r == nil {
|
|
nr := Role{Name: role}
|
|
c.Roles = append(c.Roles, nr)
|
|
r = &nr
|
|
}
|
|
|
|
var t *RoleTable
|
|
for i := range r.Tables {
|
|
if strings.EqualFold(r.Tables[i].Name, table) {
|
|
t = &r.Tables[i]
|
|
break
|
|
}
|
|
}
|
|
if t == nil {
|
|
nt := RoleTable{Name: table}
|
|
r.Tables = append(r.Tables, nt)
|
|
t = &nt
|
|
}
|
|
|
|
switch v := conf.(type) {
|
|
case Query:
|
|
t.Query = &v
|
|
case Insert:
|
|
t.Insert = &v
|
|
case Update:
|
|
t.Update = &v
|
|
case Delete:
|
|
t.Delete = &v
|
|
default:
|
|
return fmt.Errorf("unsupported object type: %t", v)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReadInConfig function reads in the config file for the environment specified in the GO_ENV
|
|
// environment variable. This is the best way to create a new Super Graph config.
|
|
func ReadInConfig(configFile string) (*Config, error) {
|
|
cp := path.Dir(configFile)
|
|
vi := newViper(cp, path.Base(configFile))
|
|
|
|
if err := vi.ReadInConfig(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if pcf := vi.GetString("inherits"); pcf != "" {
|
|
cf := vi.ConfigFileUsed()
|
|
vi = newViper(cp, pcf)
|
|
|
|
if err := vi.ReadInConfig(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if v := vi.GetString("inherits"); v != "" {
|
|
return nil, fmt.Errorf("inherited config (%s) cannot itself inherit (%s)", pcf, v)
|
|
}
|
|
|
|
vi.SetConfigFile(cf)
|
|
|
|
if err := vi.MergeInConfig(); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
c := &Config{}
|
|
|
|
if err := vi.Unmarshal(&c); err != nil {
|
|
return nil, fmt.Errorf("failed to decode config, %v", err)
|
|
}
|
|
|
|
if c.AllowListFile == "" {
|
|
c.AllowListFile = path.Join(cp, "allow.list")
|
|
}
|
|
|
|
return c, nil
|
|
}
|
|
|
|
func newViper(configPath, configFile string) *viper.Viper {
|
|
vi := viper.New()
|
|
|
|
vi.SetEnvPrefix("SG")
|
|
vi.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
vi.AutomaticEnv()
|
|
|
|
if filepath.Ext(configFile) != "" {
|
|
vi.SetConfigFile(path.Join(configPath, configFile))
|
|
} else {
|
|
vi.SetConfigName(configFile)
|
|
vi.AddConfigPath(configPath)
|
|
vi.AddConfigPath("./config")
|
|
}
|
|
|
|
return vi
|
|
}
|