fix: improve documentation of the config object

This commit is contained in:
Vikram Rangnekar 2020-04-23 21:21:45 -04:00
parent 5857efdd70
commit 03fe29b088
11 changed files with 77 additions and 36 deletions

View File

@ -38,12 +38,7 @@ func main() {
log.Fatalf(err) log.Fatalf(err)
} }
conf, err := core.ReadInConfig("./config/dev.yml") sg, err := core.NewSuperGraph(nil, db)
if err != nil {
log.Fatalf(err)
}
sg, err := core.NewSuperGraph(conf, db)
if err != nil { if err != nil {
log.Fatalf(err) log.Fatalf(err)
} }

View File

@ -19,12 +19,7 @@
log.Fatalf(err) log.Fatalf(err)
} }
conf, err := core.ReadInConfig("./config/dev.yml") sg, err := core.NewSuperGraph(nil, db)
if err != nil {
log.Fatalf(err)
}
sg, err := core.NewSuperGraph(conf, db)
if err != nil { if err != nil {
log.Fatalf(err) log.Fatalf(err)
} }

View File

@ -19,8 +19,8 @@ func BenchmarkGraphQL(b *testing.B) {
defer db.Close() defer db.Close()
// mock.ExpectQuery(`^SELECT jsonb_build_object`).WithArgs() // mock.ExpectQuery(`^SELECT jsonb_build_object`).WithArgs()
c := &Config{DefaultBlock: true}
sg, err := newSuperGraph(nil, db, psql.GetTestDBInfo()) sg, err := newSuperGraph(c, db, psql.GetTestDBInfo())
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View File

@ -10,16 +10,56 @@ import (
// Core struct contains core specific config value // Core struct contains core specific config value
type Config struct { type Config struct {
SecretKey string `mapstructure:"secret_key"` // SecretKey is used to encrypt opaque values such as
UseAllowList bool `mapstructure:"use_allow_list"` // the cursor. Auto-generated if not set
AllowListFile string `mapstructure:"allow_list_file"` SecretKey string `mapstructure:"secret_key"`
SetUserID bool `mapstructure:"set_user_id"`
Vars map[string]string `mapstructure:"variables"` // UseAllowList (aka production mode) when set to true ensures
Blocklist []string // only queries lists in the allow.list file can be used. All
Tables []Table // queries are pre-prepared so no compiling happens and things are
RolesQuery string `mapstructure:"roles_query"` // very fast.
Roles []Role UseAllowList bool `mapstructure:"use_allow_list"`
Inflections map[string]string
// 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 only tables configured under the `anon` role
// config can be queries if the `anon` role. For example if the table
// `users` is not listed under the anon role then it will be filtered
// out of any unauthenticated queries that mention it.
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.
Roles []Role
// Inflections is to add additionally singular to plural mappings
// to the engine (eg. sheep: sheep)
Inflections map[string]string `mapstructure:"inflections"`
} }
// Table struct defines a database table // Table struct defines a database table

View File

@ -75,7 +75,8 @@ func (sg *SuperGraph) initCompilers() error {
} }
sg.qc, err = qcode.NewCompiler(qcode.Config{ sg.qc, err = qcode.NewCompiler(qcode.Config{
Blocklist: sg.conf.Blocklist, DefaultBlock: sg.conf.DefaultBlock,
Blocklist: sg.conf.Blocklist,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -70,6 +70,16 @@ func (sg *SuperGraph) initConfig() error {
sg.roles["user"] = &ur sg.roles["user"] = &ur
} }
// If anon role is not defined and DefaultBlock is not then then create it
if _, ok := sg.roles["anon"]; !ok && !c.DefaultBlock {
ur := Role{
Name: "anon",
tm: make(map[string]*RoleTable),
}
c.Roles = append(c.Roles, ur)
sg.roles["anon"] = &ur
}
// Roles: validate and sanitize // Roles: validate and sanitize
c.RolesQuery = sanitizeVars(c.RolesQuery) c.RolesQuery = sanitizeVars(c.RolesQuery)

View File

@ -50,7 +50,7 @@ func DropSchema(t *testing.T, db *sql.DB) {
} }
func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) { func TestSuperGraph(t *testing.T, db *sql.DB, before func(t *testing.T)) {
config := core.Config{} config := core.Config{DefaultBlock: true}
config.UseAllowList = false config.UseAllowList = false
config.AllowListFile = "./allow.list" config.AllowListFile = "./allow.list"
config.RolesQuery = `SELECT * FROM users WHERE id = $user_id` config.RolesQuery = `SELECT * FROM users WHERE id = $user_id`

View File

@ -7,7 +7,8 @@ import (
) )
type Config struct { type Config struct {
Blocklist []string Blocklist []string
DefaultBlock bool
} }
type QueryConfig struct { type QueryConfig struct {

View File

@ -170,6 +170,7 @@ const (
) )
type Compiler struct { type Compiler struct {
db bool // default block tables if not defined in anon role
tr map[string]map[string]*trval tr map[string]map[string]*trval
bl map[string]struct{} bl map[string]struct{}
} }
@ -179,7 +180,7 @@ var expPool = sync.Pool{
} }
func NewCompiler(c Config) (*Compiler, error) { func NewCompiler(c Config) (*Compiler, error) {
co := &Compiler{} co := &Compiler{db: c.DefaultBlock}
co.tr = make(map[string]map[string]*trval) co.tr = make(map[string]map[string]*trval)
co.bl = make(map[string]struct{}, len(c.Blocklist)) co.bl = make(map[string]struct{}, len(c.Blocklist))
@ -413,12 +414,12 @@ func (com *Compiler) compileQuery(qc *QCode, op *Operation, role string) error {
func (com *Compiler) AddFilters(qc *QCode, sel *Select, role string) { func (com *Compiler) AddFilters(qc *QCode, sel *Select, role string) {
var fil *Exp var fil *Exp
var nu bool var nu bool // user required (or not) in this filter
if trv, ok := com.tr[role][sel.Name]; ok { if trv, ok := com.tr[role][sel.Name]; ok {
fil, nu = trv.filter(qc.Type) fil, nu = trv.filter(qc.Type)
} else if role == "anon" { } else if com.db && role == "anon" {
// Tables not defined under the anon role will not be rendered // Tables not defined under the anon role will not be rendered
sel.SkipRender = true sel.SkipRender = true
} }

View File

@ -148,12 +148,7 @@ func main() {
log.Fatalf(err) log.Fatalf(err)
} }
conf, err := config.NewConfig("./config") sg, err = core.NewSuperGraph(nil, db)
if err != nil {
log.Fatalf(err)
}
sg, err = core.NewSuperGraph(conf, db)
if err != nil { if err != nil {
log.Fatalf(err) log.Fatalf(err)
} }

View File

@ -100,6 +100,9 @@ func initConf() (*Config, error) {
c.UseAllowList = true c.UseAllowList = true
} }
// In anon role block all tables that are not defined in the role
c.DefaultBlock = true
return c, nil return c, nil
} }