From 03fe29b08802777484d623b31fd83d85951a39e3 Mon Sep 17 00:00:00 2001 From: Vikram Rangnekar Date: Thu, 23 Apr 2020 21:21:45 -0400 Subject: [PATCH] fix: improve documentation of the config object --- README.md | 7 +-- core/api.go | 7 +-- core/api_test.go | 4 +- core/config.go | 60 +++++++++++++++---- core/core.go | 3 +- core/init.go | 10 ++++ .../integration_tests/integration_tests.go | 2 +- core/internal/qcode/config.go | 3 +- core/internal/qcode/qcode.go | 7 ++- .../guide/.vuepress/components/HomeLayout.vue | 7 +-- internal/serv/init.go | 3 + 11 files changed, 77 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index cd4cd3b..b2b0c05 100644 --- a/README.md +++ b/README.md @@ -38,12 +38,7 @@ func main() { log.Fatalf(err) } - conf, err := core.ReadInConfig("./config/dev.yml") - if err != nil { - log.Fatalf(err) - } - - sg, err := core.NewSuperGraph(conf, db) + sg, err := core.NewSuperGraph(nil, db) if err != nil { log.Fatalf(err) } diff --git a/core/api.go b/core/api.go index 6e2a727..956c18f 100644 --- a/core/api.go +++ b/core/api.go @@ -19,12 +19,7 @@ log.Fatalf(err) } - conf, err := core.ReadInConfig("./config/dev.yml") - if err != nil { - log.Fatalf(err) - } - - sg, err := core.NewSuperGraph(conf, db) + sg, err := core.NewSuperGraph(nil, db) if err != nil { log.Fatalf(err) } diff --git a/core/api_test.go b/core/api_test.go index 2cf793a..e22a48d 100644 --- a/core/api_test.go +++ b/core/api_test.go @@ -19,8 +19,8 @@ func BenchmarkGraphQL(b *testing.B) { defer db.Close() // mock.ExpectQuery(`^SELECT jsonb_build_object`).WithArgs() - - sg, err := newSuperGraph(nil, db, psql.GetTestDBInfo()) + c := &Config{DefaultBlock: true} + sg, err := newSuperGraph(c, db, psql.GetTestDBInfo()) if err != nil { b.Fatal(err) } diff --git a/core/config.go b/core/config.go index 5b8790d..9c7f7ae 100644 --- a/core/config.go +++ b/core/config.go @@ -10,16 +10,56 @@ import ( // Core struct contains core specific config value type Config struct { - SecretKey string `mapstructure:"secret_key"` - UseAllowList bool `mapstructure:"use_allow_list"` - AllowListFile string `mapstructure:"allow_list_file"` - SetUserID bool `mapstructure:"set_user_id"` - Vars map[string]string `mapstructure:"variables"` - Blocklist []string - Tables []Table - RolesQuery string `mapstructure:"roles_query"` - Roles []Role - Inflections map[string]string + // 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 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 diff --git a/core/core.go b/core/core.go index 7c945c8..afe9553 100644 --- a/core/core.go +++ b/core/core.go @@ -75,7 +75,8 @@ func (sg *SuperGraph) initCompilers() error { } sg.qc, err = qcode.NewCompiler(qcode.Config{ - Blocklist: sg.conf.Blocklist, + DefaultBlock: sg.conf.DefaultBlock, + Blocklist: sg.conf.Blocklist, }) if err != nil { return err diff --git a/core/init.go b/core/init.go index ddd502b..f762e83 100644 --- a/core/init.go +++ b/core/init.go @@ -70,6 +70,16 @@ func (sg *SuperGraph) initConfig() error { 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 c.RolesQuery = sanitizeVars(c.RolesQuery) diff --git a/core/internal/integration_tests/integration_tests.go b/core/internal/integration_tests/integration_tests.go index a3659e4..cd41101 100644 --- a/core/internal/integration_tests/integration_tests.go +++ b/core/internal/integration_tests/integration_tests.go @@ -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)) { - config := core.Config{} + config := core.Config{DefaultBlock: true} config.UseAllowList = false config.AllowListFile = "./allow.list" config.RolesQuery = `SELECT * FROM users WHERE id = $user_id` diff --git a/core/internal/qcode/config.go b/core/internal/qcode/config.go index f692f44..df23899 100644 --- a/core/internal/qcode/config.go +++ b/core/internal/qcode/config.go @@ -7,7 +7,8 @@ import ( ) type Config struct { - Blocklist []string + Blocklist []string + DefaultBlock bool } type QueryConfig struct { diff --git a/core/internal/qcode/qcode.go b/core/internal/qcode/qcode.go index f7db7e5..c3d45ca 100644 --- a/core/internal/qcode/qcode.go +++ b/core/internal/qcode/qcode.go @@ -170,6 +170,7 @@ const ( ) type Compiler struct { + db bool // default block tables if not defined in anon role tr map[string]map[string]*trval bl map[string]struct{} } @@ -179,7 +180,7 @@ var expPool = sync.Pool{ } func NewCompiler(c Config) (*Compiler, error) { - co := &Compiler{} + co := &Compiler{db: c.DefaultBlock} co.tr = make(map[string]map[string]*trval) 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) { 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 { 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 sel.SkipRender = true } diff --git a/docs/guide/.vuepress/components/HomeLayout.vue b/docs/guide/.vuepress/components/HomeLayout.vue index 4f2ae0c..6430ab4 100644 --- a/docs/guide/.vuepress/components/HomeLayout.vue +++ b/docs/guide/.vuepress/components/HomeLayout.vue @@ -148,12 +148,7 @@ func main() { log.Fatalf(err) } - conf, err := config.NewConfig("./config") - if err != nil { - log.Fatalf(err) - } - - sg, err = core.NewSuperGraph(conf, db) + sg, err = core.NewSuperGraph(nil, db) if err != nil { log.Fatalf(err) } diff --git a/internal/serv/init.go b/internal/serv/init.go index fd4f89f..1d103c8 100644 --- a/internal/serv/init.go +++ b/internal/serv/init.go @@ -100,6 +100,9 @@ func initConf() (*Config, error) { c.UseAllowList = true } + // In anon role block all tables that are not defined in the role + c.DefaultBlock = true + return c, nil }